Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix: Use virtual bytes to calculate correct tx fees for Segwit UTXO generate_transaction #1156

Merged
merged 1 commit into from
Nov 29, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
},
};

Expand Down Expand Up @@ -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)
}