mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-16 08:22:46 +00:00
Extend functional test for setBlockIndexCandidates UB
Fix the from-disk subtest to use a separate node so it builds on a clean genesis block, rather than the leftover chain from the in-memory subtest. Change from a two-way to a three-way block race. The UB in the old LoadChainTip (mutating nSequenceId, a sort key, while the block is in setBlockIndexCandidates) corrupts the internal tree structure, resulting in a failed erase that leaves stale blocks in the set alongside the tip. With only two competing blocks, this is caught by libstdc++ but not by libc++. A three-way split triggers the bug on both implementations. To trigger CheckBlockIndex (where the crashing assertion is), replace the restart loop with sending a new block after a single restart.
This commit is contained in:
parent
854a6d5a9a
commit
20ae9b98ea
@ -15,6 +15,9 @@ class ChainTiebreaksTest(BitcoinTestFramework):
|
||||
self.num_nodes = 2
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def setup_network(self):
|
||||
self.setup_nodes()
|
||||
|
||||
@staticmethod
|
||||
def send_headers(node, blocks):
|
||||
"""Submit headers for blocks to node."""
|
||||
@ -103,27 +106,29 @@ class ChainTiebreaksTest(BitcoinTestFramework):
|
||||
node.invalidateblock(blocks[0].hash_hex)
|
||||
|
||||
def test_chain_split_from_disk(self):
|
||||
node = self.nodes[0]
|
||||
node = self.nodes[1]
|
||||
peer = node.add_p2p_connection(P2PDataStore())
|
||||
|
||||
self.generate(node, 1, sync_fun=self.no_op)
|
||||
|
||||
self.log.info('Precomputing blocks')
|
||||
#
|
||||
# A1
|
||||
# /
|
||||
# G
|
||||
# \
|
||||
# A2
|
||||
# /- A1
|
||||
# /
|
||||
# G -- B1 --- A2
|
||||
# \
|
||||
# \- A3
|
||||
#
|
||||
blocks = []
|
||||
|
||||
# Construct two blocks building from genesis.
|
||||
# Construct three equal-work blocks building from the tip.
|
||||
start_height = node.getblockcount()
|
||||
genesis_block = node.getblock(node.getblockhash(start_height))
|
||||
prev_time = genesis_block["time"]
|
||||
tip_block = node.getblock(node.getbestblockhash())
|
||||
prev_time = tip_block["time"]
|
||||
|
||||
for i in range(0, 2):
|
||||
for i in range(0, 3):
|
||||
blocks.append(create_block(
|
||||
hashprev=int(genesis_block["hash"], 16),
|
||||
hashprev=int(tip_block["hash"], 16),
|
||||
tmpl={"height": start_height + 1,
|
||||
# Make sure each block has a different hash.
|
||||
"curtime": prev_time + i + 1,
|
||||
@ -131,16 +136,24 @@ class ChainTiebreaksTest(BitcoinTestFramework):
|
||||
))
|
||||
blocks[-1].solve()
|
||||
|
||||
# Send blocks and test the last one is not connected
|
||||
self.log.info('Send A1 and A2. Make sure that only the former connects')
|
||||
# Send blocks and test that only the first one connects
|
||||
self.log.info('Send A1, A2, and A3. Make sure that only the former connects')
|
||||
peer.send_blocks_and_test([blocks[0]], node, success=True)
|
||||
peer.send_blocks_and_test([blocks[1]], node, success=False)
|
||||
peer.send_blocks_and_test([blocks[2]], node, success=False)
|
||||
|
||||
self.log.info('Restart the node and check that the best tip before restarting matched the ones afterwards')
|
||||
# Restart and check enough times for this to eventually fail if the logic is broken
|
||||
for _ in range(10):
|
||||
self.restart_node(0)
|
||||
assert_equal(blocks[0].hash_hex, node.getbestblockhash())
|
||||
# Restart and send a new block
|
||||
self.restart_node(1)
|
||||
assert_equal(blocks[0].hash_hex, node.getbestblockhash())
|
||||
peer = node.add_p2p_connection(P2PDataStore())
|
||||
next_block = create_block(
|
||||
hashprev=blocks[0].hash_int,
|
||||
tmpl={"height": start_height + 2,
|
||||
"curtime": prev_time + 10,
|
||||
}
|
||||
)
|
||||
next_block.solve()
|
||||
peer.send_blocks_and_test([next_block], node, success=True)
|
||||
|
||||
def run_test(self):
|
||||
self.test_chain_split_in_memory()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user