Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/txtools.py
Show All 10 Lines | |||||
VOUT_VALUE_SIZE = 8 | VOUT_VALUE_SIZE = 8 | ||||
def get_random_bytes(size: int) -> bytes: | def get_random_bytes(size: int) -> bytes: | ||||
if sys.version_info >= (3, 9, 0): | if sys.version_info >= (3, 9, 0): | ||||
return random.randbytes(size) # type: ignore[attr-defined] | return random.randbytes(size) # type: ignore[attr-defined] | ||||
# slower workaround | # slower workaround | ||||
if not size: | if not size: | ||||
return b'' | return b"" | ||||
return bytes.fromhex(f"{random.randrange(2**(8*size)):0{2*size}x}") | return bytes.fromhex(f"{random.randrange(2**(8*size)):0{2*size}x}") | ||||
def pad_tx(tx: CTransaction, pad_to_size: int = MIN_TX_SIZE): | def pad_tx(tx: CTransaction, pad_to_size: int = MIN_TX_SIZE): | ||||
""" | """ | ||||
Pad a transaction with op_return junk data until it is at least pad_to_size, | Pad a transaction with op_return junk data until it is at least pad_to_size, | ||||
or leave it alone if it's already bigger than that. | or leave it alone if it's already bigger than that. | ||||
Show All 15 Lines | while required_padding > 0: | ||||
break | break | ||||
# The total padding size, for a payload < 0x4c, is: | # The total padding size, for a payload < 0x4c, is: | ||||
# vout.value (8 bytes) + script_length (1) + OP_RETURN (1) + | # vout.value (8 bytes) + script_length (1) + OP_RETURN (1) + | ||||
# + data length (1) + data | # + data length (1) + data | ||||
data_size = required_padding - VOUT_VALUE_SIZE - 3 | data_size = required_padding - VOUT_VALUE_SIZE - 3 | ||||
was_op_pushdata1_used = True | was_op_pushdata1_used = True | ||||
if data_size <= 0x4c: | if data_size <= 0x4C: | ||||
was_op_pushdata1_used = False | was_op_pushdata1_used = False | ||||
if data_size == 0x4c: | if data_size == 0x4C: | ||||
# Adding one more byte to the data causes two more bytes to be | # Adding one more byte to the data causes two more bytes to be | ||||
# added to the tx size, because of the need for OP_PUSHDATA1. | # added to the tx size, because of the need for OP_PUSHDATA1. | ||||
# So remove 10 bytes to add an empty OP_RETURN vout instead in | # So remove 10 bytes to add an empty OP_RETURN vout instead in | ||||
# the next iteration. | # the next iteration. | ||||
data_size -= 10 | data_size -= 10 | ||||
elif MAX_OP_RETURN_PAYLOAD < data_size <= MAX_OP_RETURN_PAYLOAD + 10: | elif MAX_OP_RETURN_PAYLOAD < data_size <= MAX_OP_RETURN_PAYLOAD + 10: | ||||
# We require more than one VOUT, but the extra space needed is | # We require more than one VOUT, but the extra space needed is | ||||
# less than the VOUT footprint. Remove 10 bytes from the current | # less than the VOUT footprint. Remove 10 bytes from the current | ||||
# data to avoid overpadding in next iteration. | # data to avoid overpadding in next iteration. | ||||
data_size -= 10 | data_size -= 10 | ||||
elif data_size > MAX_OP_RETURN_PAYLOAD + 10: | elif data_size > MAX_OP_RETURN_PAYLOAD + 10: | ||||
# Use a full OP_RETURN. | # Use a full OP_RETURN. | ||||
data_size = MAX_OP_RETURN_PAYLOAD + 1 | data_size = MAX_OP_RETURN_PAYLOAD + 1 | ||||
if was_op_pushdata1_used: | if was_op_pushdata1_used: | ||||
# OP_PUSHDATA1 adds 1 extra byte to the transaction size. | # OP_PUSHDATA1 adds 1 extra byte to the transaction size. | ||||
data_size -= 1 | data_size -= 1 | ||||
required_padding -= 1 | required_padding -= 1 | ||||
required_padding -= data_size + VOUT_VALUE_SIZE + 3 | required_padding -= data_size + VOUT_VALUE_SIZE + 3 | ||||
tx.vout.append( | tx.vout.append(CTxOut(0, CScript([OP_RETURN, get_random_bytes(data_size)]))) | ||||
CTxOut(0, CScript([OP_RETURN, get_random_bytes(data_size)])) | |||||
) | |||||
tx.rehash() | tx.rehash() | ||||
def pad_raw_tx(rawtx_hex, min_size=MIN_TX_SIZE): | def pad_raw_tx(rawtx_hex, min_size=MIN_TX_SIZE): | ||||
""" | """ | ||||
Pad a raw transaction with OP_RETURN data until it reaches at least min_size | Pad a raw transaction with OP_RETURN data until it reaches at least min_size | ||||
""" | """ | ||||
Show All 12 Lines | def test_pad_raw_tx(self): | ||||
) | ) | ||||
# Helper functions | # Helper functions | ||||
def rawtx_length(rawtx): | def rawtx_length(rawtx): | ||||
return len(bytes.fromhex(rawtx)) | return len(bytes.fromhex(rawtx)) | ||||
def test_size(requested_size, expected_size): | def test_size(requested_size, expected_size): | ||||
self.assertEqual( | self.assertEqual( | ||||
rawtx_length(pad_raw_tx(raw_tx, requested_size)), | rawtx_length(pad_raw_tx(raw_tx, requested_size)), expected_size | ||||
expected_size) | ) | ||||
self.assertEqual(rawtx_length(raw_tx), 85) | self.assertEqual(rawtx_length(raw_tx), 85) | ||||
# The tx size is never reduced. | # The tx size is never reduced. | ||||
for size in [-1, 0, 1, 83, 84, 85]: | for size in [-1, 0, 1, 83, 84, 85]: | ||||
test_size(size, expected_size=85) | test_size(size, expected_size=85) | ||||
# The first new VOUT is added as soon as the requested size is more | # The first new VOUT is added as soon as the requested size is more | ||||
# than the initial size. The next 9 sizes are overpadded to 95 bytes, | # than the initial size. The next 9 sizes are overpadded to 95 bytes, | ||||
# because a VOUT with an empty OP_RETURN is the minimum data we can | # because a VOUT with an empty OP_RETURN is the minimum data we can | ||||
# add. | # add. | ||||
for size in [86, 87, 88, 89, 90, 91, 92, 93, 94]: | for size in [86, 87, 88, 89, 90, 91, 92, 93, 94]: | ||||
test_size(requested_size=size, | test_size(requested_size=size, expected_size=95) | ||||
expected_size=95) | |||||
# After that, the size is exactly as expected. | # After that, the size is exactly as expected. | ||||
for size in range(95, 1000): | for size in range(95, 1000): | ||||
test_size(requested_size=size, | test_size(requested_size=size, expected_size=size) | ||||
expected_size=size) |