From d0e1bbad016cc4949094daea2934712f92dfeecd Mon Sep 17 00:00:00 2001 From: Musa Haruna Date: Mon, 11 Aug 2025 16:25:16 +0100 Subject: [PATCH] test: repeat block malleability test with relayable block over P2P Adds a functional test that repeats the existing witness nonce size malleability check using a block under MAX_BLOCK_WEIGHT so it can be relayed over the P2P network, addressing the TODO in test_block_malleability. Includes rejection check for 'bad-witness-nonce-size' and confirmation that a corrected block is accepted. --- test/functional/p2p_segwit.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 8193ff7cd69..a2cd9790fe0 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -82,6 +82,7 @@ from test_framework.script_util import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_not_equal, + assert_greater_than_or_equal, assert_equal, assert_raises_rpc_error, ensure_for, @@ -822,7 +823,6 @@ class SegWitTest(BitcoinTestFramework): assert block.get_weight() > MAX_BLOCK_WEIGHT # We can't send over the p2p network, because this is too big to relay - # TODO: repeat this test with a block that can be relayed assert_equal('bad-witness-nonce-size', self.nodes[0].submitblock(block.serialize().hex())) assert_not_equal(self.nodes[0].getbestblockhash(), block.hash_hex) @@ -833,6 +833,36 @@ class SegWitTest(BitcoinTestFramework): assert self.nodes[0].getbestblockhash() == block.hash_hex + # Build a relayable-but-invalid block + relayable_block = self.build_next_block() + add_witness_commitment(relayable_block) + relayable_block.solve() + + # Append extra witness data to the coinbase input that triggers the + # same validation rejection but keeps the block under the weight limit + # so it can be sent via P2P. + relayable_block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.append(b'a' * 100_000) + + # Ensure it's relayable by weight + assert_greater_than_or_equal(MAX_BLOCK_WEIGHT, relayable_block.get_weight()) + + # Send over P2P and expect rejection for the same reason + test_witness_block(self.nodes[0], self.test_node, relayable_block, + accepted=False, reason='bad-witness-nonce-size') + + # Node should still be on the previous tip + assert_not_equal(self.nodes[0].getbestblockhash(), relayable_block.hash_hex) + + # Now fix the block by removing the extra witness data + relayable_block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.pop() + + # Confirm the block is still relayable by weight + assert relayable_block.get_weight() <= MAX_BLOCK_WEIGHT + + # Send the corrected block and expect acceptance + test_witness_block(self.nodes[0], self.test_node, relayable_block, accepted=True) + assert_equal(self.nodes[0].getbestblockhash(), relayable_block.hash_hex) + # Now make sure that malleating the witness reserved value doesn't # result in a block permanently marked bad. block = self.build_next_block()