test: add coverage for UB caused by FindMostWorkChain
What changed, and why it matters
This commit only changes a test file. It adjusts which block height is invalidated in an existing functional test so that the test exercises a specific edge case involving duplicate block processing in pruned nodes. There is no change to production code, so it does not introduce or fix a live vulnerability by itself. It is a test-coverage improvement.
No immediate action is required for production systems. Developers should ensure the underlying undefined-behavior issue in FindMostWorkChain is tracked and fixed separately if it has not already been addressed. Reviewers may run the updated functional test to confirm it reproduces the intended UB coverage.
Security signals we found
test-only change
coverage for undefined behavior in FindMostWorkChain
duplicate entries in m_blocks_unlinked / setBlockIndexCandidates
pruned node block re-download / reorg scenario
Evidence from the diff
The diff modifies test/functional/feature_pruning.py. Previously the test invalidated the block at mainchainheight (height 1320), making the chain tip 1319. With this change it invalidates height 1295, making the chain tip 1294. The commit message explains that this causes node2 to process duplicate entries in ReceivedBlockTransactions twice and to insert into setBlockIndexCandidates a duplicate entry at height 1294 from m_blocks_unlinked, thereby covering an undefined-behavior scenario in FindMostWorkChain. No consensus, networking, or wallet code is changed.
Changed components
test/functional/feature_pruning.pyInspect captured patch +3 / −3
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index c935ea95..0487a138 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -236,8 +236,8 @@ class PruneTest(BitcoinTestFramework):
self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight))
first_reorg_height = self.nodes[2].getblockcount()
- curchainhash = self.nodes[2].getblockhash(self.mainchainheight)
- self.nodes[2].invalidateblock(curchainhash)
+ block_hash_1295 = self.nodes[2].getblockhash(1295)
+ self.nodes[2].invalidateblock(block_hash_1295)
goalbestheight = self.mainchainheight
goalbesthash = self.mainchainhash2
@@ -252,7 +252,7 @@ class PruneTest(BitcoinTestFramework):
if self.nodes[2].getblockcount() < self.mainchainheight:
blocks_to_mine = first_reorg_height + 1 - self.mainchainheight
self.log.info(f"Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: {blocks_to_mine}")
- self.nodes[0].invalidateblock(curchainhash)
+ self.nodes[0].invalidateblock(block_hash_1295)
assert_equal(self.nodes[0].getblockcount(), self.mainchainheight)
assert_equal(self.nodes[0].getbestblockhash(), self.mainchainhash2)
goalbesthash = self.generate(self.nodes[0], blocks_to_mine, sync_fun=self.no_op)[-1]
Why this scored 12/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.