mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge bitcoin/bitcoin#29777: test: refactor: introduce and use calculate_input_weight
helper
6d91cb781c
test: add unit tests for `calculate_input_weight` (Sebastian Falbesoner)f81fad5e0f
test: introduce and use `calculate_input_weight` helper (Sebastian Falbesoner) Pull request description: Rather than manually estimating an input's weight by adding up all the involved components (fixed-size skeleton, compact-serialized lengths, and the actual scriptSig / witness stack items) we can simply take use of the serialization classes `CTxIn` / `CTxInWitness` instead, to achieve the same with significantly less code. The new helper is used in the functional tests rpc_psbt.py and wallet_send.py, where the previous manual estimation code was duplicated. Unit tests are added in the second commit. ACKs for top commit: kevkevinpal: tACK [6d91cb7
](6d91cb781c
) QureshiFaisal: tACK [6d91cb7
](6d91cb781c
) achow101: ACK6d91cb781c
AngusP: tACK6d91cb781c
rkrux: tACK [6d91cb7
](6d91cb781c
) Tree-SHA512: 04424e4d94d0e13745a9c11df2dd3697c98552bbb0e792c4af67ecbb66060adc3cc0cefc202cdee2d9db0baf85b8bedf2eb339ac4b316d986b5f10f6b70c5a33
This commit is contained in:
commit
256e170319
4 changed files with 70 additions and 29 deletions
|
@ -16,8 +16,6 @@ from test_framework.messages import (
|
||||||
CTxIn,
|
CTxIn,
|
||||||
CTxOut,
|
CTxOut,
|
||||||
MAX_BIP125_RBF_SEQUENCE,
|
MAX_BIP125_RBF_SEQUENCE,
|
||||||
WITNESS_SCALE_FACTOR,
|
|
||||||
ser_compact_size,
|
|
||||||
)
|
)
|
||||||
from test_framework.psbt import (
|
from test_framework.psbt import (
|
||||||
PSBT,
|
PSBT,
|
||||||
|
@ -42,6 +40,7 @@ from test_framework.util import (
|
||||||
find_vout_for_address,
|
find_vout_for_address,
|
||||||
)
|
)
|
||||||
from test_framework.wallet_util import (
|
from test_framework.wallet_util import (
|
||||||
|
calculate_input_weight,
|
||||||
generate_keypair,
|
generate_keypair,
|
||||||
get_generate_key,
|
get_generate_key,
|
||||||
)
|
)
|
||||||
|
@ -752,17 +751,9 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
input_idx = i
|
input_idx = i
|
||||||
break
|
break
|
||||||
psbt_in = dec["inputs"][input_idx]
|
psbt_in = dec["inputs"][input_idx]
|
||||||
# Calculate the input weight
|
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
|
||||||
# (prevout + sequence + length of scriptSig + scriptsig) * WITNESS_SCALE_FACTOR + len of num scriptWitness stack items + (length of stack item + stack item) * N stack items
|
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
|
||||||
# Note that occasionally this weight estimate may be slightly larger or smaller than the real weight
|
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
|
||||||
# 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))
|
|
||||||
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
|
low_input_weight = input_weight // 2
|
||||||
high_input_weight = input_weight * 2
|
high_input_weight = input_weight * 2
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Useful util functions for testing the wallet"""
|
"""Useful util functions for testing the wallet"""
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
import unittest
|
||||||
|
|
||||||
from test_framework.address import (
|
from test_framework.address import (
|
||||||
byte_to_base58,
|
byte_to_base58,
|
||||||
|
@ -15,6 +16,11 @@ from test_framework.address import (
|
||||||
script_to_p2wsh,
|
script_to_p2wsh,
|
||||||
)
|
)
|
||||||
from test_framework.key import ECKey
|
from test_framework.key import ECKey
|
||||||
|
from test_framework.messages import (
|
||||||
|
CTxIn,
|
||||||
|
CTxInWitness,
|
||||||
|
WITNESS_SCALE_FACTOR,
|
||||||
|
)
|
||||||
from test_framework.script_util import (
|
from test_framework.script_util import (
|
||||||
key_to_p2pkh_script,
|
key_to_p2pkh_script,
|
||||||
key_to_p2wpkh_script,
|
key_to_p2wpkh_script,
|
||||||
|
@ -123,6 +129,19 @@ def generate_keypair(compressed=True, wif=False):
|
||||||
privkey = bytes_to_wif(privkey.get_bytes(), compressed)
|
privkey = bytes_to_wif(privkey.get_bytes(), compressed)
|
||||||
return privkey, pubkey
|
return privkey, pubkey
|
||||||
|
|
||||||
|
def calculate_input_weight(scriptsig_hex, witness_stack_hex=None):
|
||||||
|
"""Given a scriptSig and a list of witness stack items for an input in hex format,
|
||||||
|
calculate the total input weight. If the input has no witness data,
|
||||||
|
`witness_stack_hex` can be set to None."""
|
||||||
|
tx_in = CTxIn(scriptSig=bytes.fromhex(scriptsig_hex))
|
||||||
|
witness_size = 0
|
||||||
|
if witness_stack_hex is not None:
|
||||||
|
tx_inwit = CTxInWitness()
|
||||||
|
for witness_item_hex in witness_stack_hex:
|
||||||
|
tx_inwit.scriptWitness.stack.append(bytes.fromhex(witness_item_hex))
|
||||||
|
witness_size = len(tx_inwit.serialize())
|
||||||
|
return len(tx_in.serialize()) * WITNESS_SCALE_FACTOR + witness_size
|
||||||
|
|
||||||
class WalletUnlock():
|
class WalletUnlock():
|
||||||
"""
|
"""
|
||||||
A context manager for unlocking a wallet with a passphrase and automatically locking it afterward.
|
A context manager for unlocking a wallet with a passphrase and automatically locking it afterward.
|
||||||
|
@ -141,3 +160,42 @@ class WalletUnlock():
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
_ = args
|
_ = args
|
||||||
self.wallet.walletlock()
|
self.wallet.walletlock()
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrameworkWalletUtil(unittest.TestCase):
|
||||||
|
def test_calculate_input_weight(self):
|
||||||
|
SKELETON_BYTES = 32 + 4 + 4 # prevout-txid, prevout-index, sequence
|
||||||
|
SMALL_LEN_BYTES = 1 # bytes needed for encoding scriptSig / witness item lenghts < 253
|
||||||
|
LARGE_LEN_BYTES = 3 # bytes needed for encoding scriptSig / witness item lengths >= 253
|
||||||
|
|
||||||
|
# empty scriptSig, no witness
|
||||||
|
self.assertEqual(calculate_input_weight(""),
|
||||||
|
(SKELETON_BYTES + SMALL_LEN_BYTES) * WITNESS_SCALE_FACTOR)
|
||||||
|
self.assertEqual(calculate_input_weight("", None),
|
||||||
|
(SKELETON_BYTES + SMALL_LEN_BYTES) * WITNESS_SCALE_FACTOR)
|
||||||
|
# small scriptSig, no witness
|
||||||
|
scriptSig_small = "00"*252
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_small, None),
|
||||||
|
(SKELETON_BYTES + SMALL_LEN_BYTES + 252) * WITNESS_SCALE_FACTOR)
|
||||||
|
# small scriptSig, empty witness stack
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_small, []),
|
||||||
|
(SKELETON_BYTES + SMALL_LEN_BYTES + 252) * WITNESS_SCALE_FACTOR + SMALL_LEN_BYTES)
|
||||||
|
# large scriptSig, no witness
|
||||||
|
scriptSig_large = "00"*253
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_large, None),
|
||||||
|
(SKELETON_BYTES + LARGE_LEN_BYTES + 253) * WITNESS_SCALE_FACTOR)
|
||||||
|
# large scriptSig, empty witness stack
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_large, []),
|
||||||
|
(SKELETON_BYTES + LARGE_LEN_BYTES + 253) * WITNESS_SCALE_FACTOR + SMALL_LEN_BYTES)
|
||||||
|
# empty scriptSig, 5 small witness stack items
|
||||||
|
self.assertEqual(calculate_input_weight("", ["00", "11", "22", "33", "44"]),
|
||||||
|
((SKELETON_BYTES + SMALL_LEN_BYTES) * WITNESS_SCALE_FACTOR) + SMALL_LEN_BYTES + 5 * SMALL_LEN_BYTES + 5)
|
||||||
|
# empty scriptSig, 253 small witness stack items
|
||||||
|
self.assertEqual(calculate_input_weight("", ["00"]*253),
|
||||||
|
((SKELETON_BYTES + SMALL_LEN_BYTES) * WITNESS_SCALE_FACTOR) + LARGE_LEN_BYTES + 253 * SMALL_LEN_BYTES + 253)
|
||||||
|
# small scriptSig, 3 large witness stack items
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_small, ["00"*253]*3),
|
||||||
|
((SKELETON_BYTES + SMALL_LEN_BYTES + 252) * WITNESS_SCALE_FACTOR) + SMALL_LEN_BYTES + 3 * LARGE_LEN_BYTES + 3*253)
|
||||||
|
# large scriptSig, 3 large witness stack items
|
||||||
|
self.assertEqual(calculate_input_weight(scriptSig_large, ["00"*253]*3),
|
||||||
|
((SKELETON_BYTES + LARGE_LEN_BYTES + 253) * WITNESS_SCALE_FACTOR) + SMALL_LEN_BYTES + 3 * LARGE_LEN_BYTES + 3*253)
|
||||||
|
|
|
@ -85,6 +85,7 @@ TEST_FRAMEWORK_MODULES = [
|
||||||
"crypto.ripemd160",
|
"crypto.ripemd160",
|
||||||
"script",
|
"script",
|
||||||
"segwit_addr",
|
"segwit_addr",
|
||||||
|
"wallet_util",
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTENDED_SCRIPTS = [
|
EXTENDED_SCRIPTS = [
|
||||||
|
|
|
@ -9,10 +9,6 @@ from itertools import product
|
||||||
|
|
||||||
from test_framework.authproxy import JSONRPCException
|
from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.descriptors import descsum_create
|
from test_framework.descriptors import descsum_create
|
||||||
from test_framework.messages import (
|
|
||||||
ser_compact_size,
|
|
||||||
WITNESS_SCALE_FACTOR,
|
|
||||||
)
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
@ -21,7 +17,10 @@ from test_framework.util import (
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
count_bytes,
|
count_bytes,
|
||||||
)
|
)
|
||||||
from test_framework.wallet_util import generate_keypair
|
from test_framework.wallet_util import (
|
||||||
|
calculate_input_weight,
|
||||||
|
generate_keypair,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WalletSendTest(BitcoinTestFramework):
|
class WalletSendTest(BitcoinTestFramework):
|
||||||
|
@ -543,17 +542,9 @@ class WalletSendTest(BitcoinTestFramework):
|
||||||
input_idx = i
|
input_idx = i
|
||||||
break
|
break
|
||||||
psbt_in = dec["inputs"][input_idx]
|
psbt_in = dec["inputs"][input_idx]
|
||||||
# Calculate the input weight
|
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
|
||||||
# (prevout + sequence + length of scriptSig + scriptsig) * WITNESS_SCALE_FACTOR + len of num scriptWitness stack items + (length of stack item + stack item) * N stack items
|
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
|
||||||
# Note that occasionally this weight estimate may be slightly larger or smaller than the real weight
|
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
|
||||||
# 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))
|
|
||||||
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
|
# Input weight error conditions
|
||||||
assert_raises_rpc_error(
|
assert_raises_rpc_error(
|
||||||
|
|
Loading…
Add table
Reference in a new issue