test: repeat block malleability test with relayable block over P2P
What changed, and why it matters
This commit only adds a new functional test to Bitcoin Core. It does not change any production code, consensus rules, or network behavior. The test verifies that an invalid-but-relayable block with a malformed witness nonce is rejected over the peer-to-peer network, and that a corrected version is accepted. There is no security vulnerability being fixed here.
No action required; this is a test-only change. Reviewers may optionally verify the new test passes in CI.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff extends test/functional/p2p_segwit.py to add a second block malleability test case. The existing test used an oversized block that could not be relayed over P2P; this new test constructs a block under MAX_BLOCK_WEIGHT by appending 100,000 bytes of extra witness data to the coinbase input, then asserts it is rejected with ‘bad-witness-nonce-size’ via P2P, and that removing the extra data makes it valid. No C++ node code, validation logic, or RPC behavior is modified.
Changed components
test/functional/p2p_segwit.pyInspect captured patch +31 / −1
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 8193ff7c..a2cd9790 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()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.