Skip to content

Commit 90d7d28

Browse files
committed
added extreme tx tests
1 parent 6ee8e08 commit 90d7d28

3 files changed

Lines changed: 91 additions & 7 deletions

File tree

TODO

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ OTHERLIBS-related
5454

5555
OTHER
5656
- create script: clean-less-than-sats 100000 bc1q86gsg4t32...
57-
- create test with 260 outputs (inputs too much trouble) - file: test_extreme_txs.py
58-
- create test with script of 260 and another with 66000 commands
5957
- maybe make RBF the default from now on (like Bitcoin Core)
6058
. will need to change all the tests, unless I add minor hack that uses RBF by default only for taproot
6159

bitcoinutils/script.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,15 @@ def _op_push_data(self, data: str) -> bytes:
293293
Also note that according to standarardness rules (BIP-62) the minimum
294294
possible PUSHDATA operator must be used!
295295
"""
296-
297-
data_bytes = h_to_b(data) # Assuming string is hexadecimal
296+
data_bytes = h_to_b(data)
298297

299298
if len(data_bytes) < 0x4C:
300299
return bytes([len(data_bytes)]) + data_bytes
301-
elif len(data_bytes) < 0xFF:
300+
elif len(data_bytes) <= 0xFF:
302301
return b"\x4c" + bytes([len(data_bytes)]) + data_bytes
303-
elif len(data_bytes) < 0xFFFF:
302+
elif len(data_bytes) <= 0xFFFF:
304303
return b"\x4d" + struct.pack("<H", len(data_bytes)) + data_bytes
305-
elif len(data_bytes) < 0xFFFFFFFF:
304+
elif len(data_bytes) <= 0xFFFFFFFF:
306305
return b"\x4e" + struct.pack("<I", len(data_bytes)) + data_bytes
307306
else:
308307
raise ValueError("Data too large. Cannot push into script")

tests/test_extreme_txs.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright (C) 2018-2025 The python-bitcoin-utils developers
2+
#
3+
# This file is part of python-bitcoin-utils
4+
#
5+
# It is subject to the license terms in the LICENSE file found in the top-level
6+
# directory of this distribution.
7+
#
8+
# No part of python-bitcoin-utils, including this file, may be copied,
9+
# modified, propagated, or distributed except according to the terms contained
10+
# in the LICENSE file.
11+
12+
import unittest
13+
14+
from bitcoinutils.keys import P2pkhAddress
15+
from bitcoinutils.script import Script
16+
from bitcoinutils.setup import setup
17+
from bitcoinutils.transactions import Transaction, TxInput, TxOutput
18+
19+
20+
class TestExtremeTransactions(unittest.TestCase):
21+
def setUp(self):
22+
setup("testnet")
23+
self.txin = TxInput(
24+
"11" * 32,
25+
0,
26+
sequence=b"\xff\xff\xff\xff",
27+
)
28+
self.script_pubkey = P2pkhAddress(
29+
"mytmhndz4UbEMeoSZorXXrLpPfeoFUDzEp"
30+
).to_script_pub_key()
31+
32+
def _assert_script_length_prefix(self, commands, expected_prefix):
33+
script = Script(commands)
34+
tx = Transaction([self.txin], [TxOutput(1000, script)])
35+
36+
raw = tx.serialize()
37+
38+
# version + input count + one input with empty scriptSig + output count
39+
# + output amount
40+
script_length_offset = (4 + 1 + 32 + 4 + 1 + 4 + 1 + 8) * 2
41+
self.assertEqual(
42+
raw[script_length_offset : script_length_offset + len(expected_prefix)],
43+
expected_prefix,
44+
)
45+
46+
parsed_tx = Transaction.from_raw(raw)
47+
self.assertEqual(len(parsed_tx.outputs[0].script_pubkey.script), len(commands))
48+
self.assertEqual(parsed_tx.serialize(), raw)
49+
50+
def test_transaction_with_260_outputs_uses_compact_size(self):
51+
outputs = [TxOutput(1000 + i, self.script_pubkey) for i in range(260)]
52+
tx = Transaction([self.txin], outputs)
53+
54+
raw = tx.serialize()
55+
56+
# version + input count + one input with empty scriptSig
57+
output_count_offset = (4 + 1 + 32 + 4 + 1 + 4) * 2
58+
self.assertEqual(raw[output_count_offset : output_count_offset + 6], "fd0401")
59+
60+
parsed_tx = Transaction.from_raw(raw)
61+
self.assertEqual(len(parsed_tx.outputs), 260)
62+
self.assertEqual(parsed_tx.serialize(), raw)
63+
64+
def test_script_with_260_commands_uses_compact_size_uint16(self):
65+
self._assert_script_length_prefix(["OP_1"] * 260, "fd0401")
66+
67+
def test_script_with_66000_commands_uses_compact_size_uint32(self):
68+
self._assert_script_length_prefix(["OP_1"] * 66000, "fed0010100")
69+
70+
def test_pushdata_boundaries_use_minimal_opcode(self):
71+
cases = [
72+
(75, "4b"),
73+
(76, "4c4c"),
74+
(255, "4cff"),
75+
(256, "4d0001"),
76+
(65535, "4dffff"),
77+
(65536, "4e00000100"),
78+
]
79+
80+
for size, expected_prefix in cases:
81+
with self.subTest(size=size):
82+
raw = Script(["aa" * size]).to_hex()
83+
self.assertEqual(raw[: len(expected_prefix)], expected_prefix)
84+
85+
86+
if __name__ == "__main__":
87+
unittest.main()

0 commit comments

Comments
 (0)