Skip to content

Commit

Permalink
test: add functional test for complex reorgs
Browse files Browse the repository at this point in the history
  • Loading branch information
sipa committed Jan 19, 2024
1 parent c61c2c4 commit e80c315
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
120 changes: 120 additions & 0 deletions test/functional/feature_chain_tiebreaks.py
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that the correct active block is chosen in complex reorgs."""

import json
import time

from test_framework.blocktools import (
create_block,
)
from test_framework.messages import (
CBlock,
CBlockHeader,
msg_headers,
)
from test_framework.p2p import (
P2PDataStore,
p2p_lock,
)
from test_framework.test_framework import (
BitcoinTestFramework,
)
from test_framework.util import (
assert_equal,
)

class ChainTiebreaksTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True

@staticmethod
def send_headers(node, blocks):
"""Submit headers for blocks to node."""
for block in blocks:
# Use RPC rather than P2P, to prevent the message from being interpreted as a block
# announcement.
node.submitheader(hexdata=CBlockHeader(block).serialize().hex())

def run_test(self):
node = self.nodes[0]
# Add P2P connection to bitcoind
peer = node.add_p2p_connection(P2PDataStore())

self.log.info('Precomputing blocks')
#
# /- B3 -- B7
# B1 \- B8
# / \
# / \ B4 -- B9
# B0 \- B10
# \
# \ /- B5
# B2
# \- B6
#
blocks = []

# Construct B0, building off genesis.
start_height = node.getblockcount()
blocks.append(create_block(
hashprev=int(node.getbestblockhash(), 16),
tmpl={"height": start_height + 1}
))
blocks[-1].solve()

# Construct B1-B10.
for i in range(1, 11):
blocks.append(create_block(
hashprev=int(blocks[(i - 1) >> 1].hash, 16),
tmpl={
"height": start_height + (i + 1).bit_length(),
# Make sure each block has a different hash.
"curtime": blocks[-1].nTime + 1,
}
))
blocks[-1].solve()
print(f"B{i}: {blocks[-1].hash}")

self.log.info('Make sure B0 is accepted normally')
peer.send_blocks_and_test([blocks[0]], node, success=True)
# B0 must be active chain now.
assert_equal(node.getbestblockhash(), blocks[0].hash)

self.log.info('Send B1 and B2 headers, and then blocks in opposite order')
self.send_headers(node, blocks[1:3])
peer.send_blocks_and_test([blocks[2]], node, success=True)
peer.send_blocks_and_test([blocks[1]], node, success=False)
# B2 must be active chain now, as full data for B2 was received first.
assert_equal(node.getbestblockhash(), blocks[2].hash)

self.log.info('Send all further headers in order')
self.send_headers(node, blocks[3:])
# B2 is still the active chain, headers don't change this.
assert_equal(node.getbestblockhash(), blocks[2].hash)

self.log.info('Send blocks B7-B10')
peer.send_blocks_and_test([blocks[7]], node, success=False)
peer.send_blocks_and_test([blocks[8]], node, success=False)
peer.send_blocks_and_test([blocks[9]], node, success=False)
peer.send_blocks_and_test([blocks[10]], node, success=False)
# B2 is still the active chain, as B7-B10 have missing parents.
assert_equal(node.getbestblockhash(), blocks[2].hash)

self.log.info('Send parents B3-B4 of B8-B10 in reverse order')
peer.send_blocks_and_test([blocks[4]], node, success=False, force_send=True)
peer.send_blocks_and_test([blocks[3]], node, success=False, force_send=True)
# B9 is now active. Despite B7 being received earlier, the missing parent.
assert_equal(node.getbestblockhash(), blocks[9].hash)

self.log.info('Invalidate B9-B10')
node.invalidateblock(blocks[9].hash)
node.invalidateblock(blocks[10].hash)
# B7 is now active.
assert_equal(node.getbestblockhash(), blocks[7].hash)

if __name__ == '__main__':
ChainTiebreaksTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Expand Up @@ -354,6 +354,7 @@
'feature_includeconf.py',
'feature_addrman.py',
'feature_asmap.py',
'feature_chain_tiebreaks.py'
'feature_fastprune.py',
'mempool_unbroadcast.py',
'mempool_compatibility.py',
Expand Down

0 comments on commit e80c315

Please sign in to comment.