From 9e5692edca0a13bc20cbfbcccc65c81c8e4c3c17 Mon Sep 17 00:00:00 2001 From: Omar Shamardy Date: Mon, 29 Nov 2021 13:00:17 +0200 Subject: [PATCH] use virtual bytes to calculate tx fees --- mm2src/coins/utxo/utxo_common.rs | 74 +++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 2692997be2..156fc95202 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -280,6 +280,24 @@ where Box::new(fut.boxed().compat().map(|tx| tx.into())) } +pub fn tx_size_in_v_bytes(from_addr_format: &UtxoAddressFormat, tx: &UtxoTx) -> usize { + let transaction_bytes = serialize(tx); + // 2 bytes are used to indicate the length of signature and pubkey + // total is 107 + let additional_len = 2 + MAX_DER_SIGNATURE_LEN + COMPRESSED_PUBKEY_LEN; + // Virtual size of the transaction + // https://bitcoin.stackexchange.com/questions/87275/how-to-calculate-segwit-transaction-fee-in-bytes/87276#87276 + match from_addr_format { + UtxoAddressFormat::Segwit => { + let base_size = transaction_bytes.len(); + // 4 additional bytes (2 for the marker and 2 for the flag) and 1 additional byte for every input in the witness for the SIGHASH flag + let total_size = transaction_bytes.len() + 4 + tx.inputs().len() * (additional_len + 1); + ((0.75 * base_size as f64) + (0.25 * total_size as f64)) as usize + }, + _ => transaction_bytes.len() + tx.inputs().len() * additional_len, + } +} + /// Generates unsigned transaction (TransactionInputSigner) from specified utxos and outputs. /// This function expects that utxos are sorted by amounts in ascending order /// Consider sorting before calling this function @@ -384,26 +402,18 @@ where ActualTxFee::Fixed(f) => *f, ActualTxFee::Dynamic(f) => { let transaction = UtxoTx::from(tx.clone()); - let transaction_bytes = serialize(&transaction); - // 2 bytes are used to indicate the length of signature and pubkey - // total is 107 - let additional_len = 2 + MAX_DER_SIGNATURE_LEN + COMPRESSED_PUBKEY_LEN; - let tx_size = transaction_bytes.len() + transaction.inputs().len() * additional_len; - (f * tx_size as u64) / KILO_BYTE + let v_size = tx_size_in_v_bytes(&coin.as_ref().my_address.addr_format, &transaction); + (f * v_size as u64) / KILO_BYTE }, ActualTxFee::FixedPerKb(f) => { let transaction = UtxoTx::from(tx.clone()); - let transaction_bytes = serialize(&transaction); - // 2 bytes are used to indicate the length of signature and pubkey - // total is 107 - let additional_len = 2 + MAX_DER_SIGNATURE_LEN + COMPRESSED_PUBKEY_LEN; - let tx_size_bytes = (transaction_bytes.len() + transaction.inputs().len() * additional_len) as u64; - let tx_size_kb = if tx_size_bytes % KILO_BYTE == 0 { - tx_size_bytes / KILO_BYTE + let v_size = tx_size_in_v_bytes(&coin.as_ref().my_address.addr_format, &transaction) as u64; + let v_size_kb = if v_size % KILO_BYTE == 0 { + v_size / KILO_BYTE } else { - tx_size_bytes / KILO_BYTE + 1 + v_size / KILO_BYTE + 1 }; - f * tx_size_kb + f * v_size_kb }, }; @@ -2972,3 +2982,37 @@ fn test_pubkey_from_script_sig() { let script_sig_err = Script::from("493044022071edae37cf518e98db3f7637b9073a7a980b957b0c7b871415dbb4898ec3ebdc022031b402a6b98e64ffdf752266449ca979a9f70144dba77ed7a6a25bfab11648f6012103ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36fa"); pubkey_from_script_sig(&script_sig_err).unwrap_err(); } + +#[test] +fn test_tx_v_size() { + // Multiple legacy inputs with P2SH and P2PKH output + // https://live.blockcypher.com/btc-testnet/tx/ac6218b33d02e069c4055af709bbb6ca92ce11e55450cde96bc17411e281e5e7/ + let mut tx: UtxoTx = "0100000002440f1a2929eb08c350cc8d2385c77c40411560c3b43b65efb5b06f997fc67672020000006b483045022100f82e88af256d2487afe0c30a166c9ecf6b7013e764e1407317c712d47f7731bd0220358a4d7987bfde2271599b5c4376d26f9ce9f1df2e04f5de8f89593352607110012103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edfffffffffb9c2fd7a19b55a4ffbda2ce5065d988a4f4efcf1ae567b4ddb6d97529c8fb0c000000006b483045022100dd75291db32dc859657a5eead13b85c340b4d508e57d2450ebfad76484f254130220727fcd65dda046ea62b449ab217da264dbf7c7ca7e63b39c8835973a152752c1012103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edffffffff03102700000000000017a9148d0ad41545dea44e914c419d33d422148c35a274870000000000000000166a149c0a919d4e9a23f0234df916a7dd21f9e2fdaa8f931d0000000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88acbd8ff160".into(); + // Removing inputs script_sig as it's not included in UnsignedTransactionInput when fees are calculated + tx.inputs[0].script_sig = Bytes::new(); + tx.inputs[1].script_sig = Bytes::new(); + let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Standard, &tx); + assert_eq!(v_size, 403); + // Segwit input with 2 P2WPKH outputs + // https://live.blockcypher.com/btc-testnet/tx/8a32e794b2a8a0356bb3b2717279d118b4010bf8bb3229abb5a2b4fb86541bb2/ + // the transaction is deserialized without the witnesses which makes the calculation of v_size similar to how + // it's calculated in generate_transaction + let tx: UtxoTx = "0200000000010192a4497268107d7999e9551be733f5e0eab479be7d995a061a7bbdc43ef0e5ed0000000000feffffff02cd857a00000000001600145cb39bfcd68d520e29cadc990bceb5cd1562c507a0860100000000001600149a85cc05e9a722575feb770a217c73fd6145cf01024730440220030e0fb58889ab939c701f12d950f00b64836a1a33ec0d6697fd3053d469d244022053e33d72ef53b37b86eea8dfebbafffb0f919ef952dcb6ea6058b81576d8dc86012102225de6aed071dc29d0ca10b9f64a4b502e33e55b3c0759eedd8e333834c6a7d07a1f2000".into(); + let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); + assert_eq!(v_size, 141); + // Segwit input with 1 P2WSH output + // https://live.blockcypher.com/btc-testnet/tx/f8c1fed6f307eb131040965bd11018787567413e6437c907b1fd15de6517ad16/ + let tx: UtxoTx = "010000000001017996e77b2b1f4e66da606cfc2f16e3f52e1eac4a294168985bd4dbd54442e61f0100000000ffffffff01ab36010000000000220020693090c0e291752d448826a9dc72c9045b34ed4f7bd77e6e8e62645c23d69ac502483045022100d0800719239d646e69171ede7f02af916ac778ffe384fa0a5928645b23826c9f022044072622de2b47cfc81ac5172b646160b0c48d69d881a0ce77be06dbd6f6e5ac0121031ac6d25833a5961e2a8822b2e8b0ac1fd55d90cbbbb18a780552cbd66fc02bb3735a9e61".into(); + let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); + assert_eq!(v_size, 122); + // Multipl segwit inputs with P2PKH output + // https://live.blockcypher.com/btc-testnet/tx/649d514d76702a0925a917d830e407f4f1b52d78832520e486c140ce8d0b879f/ + let tx: UtxoTx = "0100000000010250c434acbad252481564d56b41990577c55d247aedf4bb853dca3567c4404c8f0000000000ffffffff55baf016f0628ecf0f0ec228e24d8029879b0491ab18bac61865afaa9d16e8bb0000000000ffffffff01e8030000000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac0247304402202611c05dd0e748f7c9955ed94a172af7ed56a0cdf773e8c919bef6e70b13ec1c02202fd7407891c857d95cdad1038dcc333186815f50da2fc9a334f814dd8d0a2d63012103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed02483045022100bb9d483f6b2b46f8e70d62d65b33b6de056e1878c9c2a1beed69005daef2f89502201690cd44cf6b114fa0d494258f427e1ed11a21d897e407d8a1ff3b7e09b9a426012103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed9cf7bd60".into(); + let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); + assert_eq!(v_size, 181); + // Multiple segwit inputs + // https://live.blockcypher.com/btc-testnet/tx/a7bb128703b57058955d555ed48b65c2c9bdefab6d3acbb4243c56e430533def/ + let tx: UtxoTx = "010000000001023b7308e5ca5d02000b743441f7653c1110e07275b7ab0e983f489e92bfdd2b360100000000ffffffffd6c4f22e9b1090b2584a82cf4cb6f85595dd13c16ad065711a7585cc373ae2e50000000000ffffffff02947b2a00000000001600148474e72f396d44504cd30b1e7b992b65344240c609050700000000001600141b891309c8fe1338786fa3476d5d1a9718d43a0202483045022100bfae465fcd8d2636b2513f68618eb4996334c94d47e285cb538e3416eaf4521b02201b953f46ff21c8715a0997888445ca814dfdb834ef373a29e304bee8b32454d901210226bde3bca3fe7c91e4afb22c4bc58951c60b9bd73514081b6bd35f5c09b8c9a602483045022100ba48839f7becbf8f91266140f9727edd08974fcc18017661477af1d19603ed31022042fd35af1b393eeb818b420e3a5922079776cc73f006d26dd67be932e1b4f9000121034b6a54040ad2175e4c198370ac36b70d0b0ab515b59becf100c4cd310afbfd0c00000000".into(); + let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); + assert_eq!(v_size, 209) +}