|
| 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