From 3c49e6967050cfc367b3c826c9eac86a48528af5 Mon Sep 17 00:00:00 2001 From: Max Edwards Date: Fri, 1 Mar 2024 11:43:36 +0000 Subject: [PATCH 1/2] test: fix weight estimates in functional tests Updates the weight estimate to be more accurate by removing byte buffers and calculating the length of the count of scriptWitnesses rather than just using the count itself. --- test/functional/rpc_psbt.py | 13 +++++++++---- test/functional/wallet_send.py | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 1fd938d18a9..016aa3ba119 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -753,11 +753,16 @@ class PSBTTest(BitcoinTestFramework): break psbt_in = dec["inputs"][input_idx] # Calculate the input weight - # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer + # (prevout + sequence + length of scriptSig + scriptsig) * WITNESS_SCALE_FACTOR + len of num scriptWitness stack items + (length of stack item + stack item) * N stack items + # Note that occasionally this weight estimate may be slightly larger or smaller than the real weight + # as sometimes ECDSA signatures are one byte shorter than expected with a probability of 1/128 len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 - len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1 - len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0 - input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness + len_scriptsig += len(ser_compact_size(len_scriptsig)) + len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(ser_compact_size(len(psbt_in["final_scriptwitness"])))) if "final_scriptwitness" in psbt_in else 0 + len_prevout_txid = 32 + len_prevout_index = 4 + len_sequence = 4 + input_weight = ((len_prevout_txid + len_prevout_index + len_sequence + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness low_input_weight = input_weight // 2 high_input_weight = input_weight * 2 diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 6ce2a56bfcb..c5942578a44 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -543,11 +543,16 @@ class WalletSendTest(BitcoinTestFramework): break psbt_in = dec["inputs"][input_idx] # Calculate the input weight - # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer + # (prevout + sequence + length of scriptSig + scriptsig) * WITNESS_SCALE_FACTOR + len of num scriptWitness stack items + (length of stack item + stack item) * N stack items + # Note that occasionally this weight estimate may be slightly larger or smaller than the real weight + # as sometimes ECDSA signatures are one byte shorter than expected with a probability of 1/128 len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 - len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1 - len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0 - input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness + len_scriptsig += len(ser_compact_size(len_scriptsig)) + len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(ser_compact_size(len(psbt_in["final_scriptwitness"])))) if "final_scriptwitness" in psbt_in else 0 + len_prevout_txid = 32 + len_prevout_index = 4 + len_sequence = 4 + input_weight = ((len_prevout_txid + len_prevout_index + len_sequence + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness # Input weight error conditions assert_raises_rpc_error( From e67ab174c9c04bba7a10724b7e694dd57f732177 Mon Sep 17 00:00:00 2001 From: Max Edwards Date: Fri, 1 Mar 2024 11:44:21 +0000 Subject: [PATCH 2/2] test: fix flaky wallet_send functional test Rather than asserting that the exact fees are the same, check the fee rate rounded to nearest interger. This will account for small differences in fees caused by variability in ECDSA signature lengths. --- test/functional/wallet_send.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index c5942578a44..580a9d2b22d 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -563,6 +563,7 @@ class WalletSendTest(BitcoinTestFramework): options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]} ) + target_fee_rate_sat_vb = 10 # Funding should also work when input weights are provided res = self.test_send( from_wallet=ext_wallet, @@ -572,14 +573,17 @@ class WalletSendTest(BitcoinTestFramework): add_inputs=True, psbt=True, include_watching=True, - fee_rate=10 + fee_rate=target_fee_rate_sat_vb ) signed = ext_wallet.walletprocesspsbt(res["psbt"]) signed = ext_fund.walletprocesspsbt(res["psbt"]) assert signed["complete"] testres = self.nodes[0].testmempoolaccept([signed["hex"]])[0] assert_equal(testres["allowed"], True) - assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001)) + actual_fee_rate_sat_vb = Decimal(testres["fees"]["base"]) * Decimal(1e8) / Decimal(testres["vsize"]) + # Due to ECDSA signatures not always being the same length, the actual fee rate may be slightly different + # but rounded to nearest integer, it should be the same as the target fee rate + assert_equal(round(actual_fee_rate_sat_vb), target_fee_rate_sat_vb) if __name__ == '__main__': WalletSendTest().main()