feature_chain_tiebreaks.py
1 #!/usr/bin/env python3 2 # Copyright (c) The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test that the correct active block is chosen in complex reorgs.""" 6 7 from test_framework.blocktools import create_block 8 from test_framework.messages import CBlockHeader 9 from test_framework.p2p import P2PDataStore 10 from test_framework.test_framework import BitcoinTestFramework 11 from test_framework.util import assert_equal 12 13 class ChainTiebreaksTest(BitcoinTestFramework): 14 def set_test_params(self): 15 self.num_nodes = 2 16 self.setup_clean_chain = True 17 18 def setup_network(self): 19 self.setup_nodes() 20 21 @staticmethod 22 def send_headers(node, blocks): 23 """Submit headers for blocks to node.""" 24 for block in blocks: 25 # Use RPC rather than P2P, to prevent the message from being interpreted as a block 26 # announcement. 27 node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) 28 29 def test_chain_split_in_memory(self): 30 node = self.nodes[0] 31 # Add P2P connection to bitcoind 32 peer = node.add_p2p_connection(P2PDataStore()) 33 34 self.log.info('Precomputing blocks') 35 # 36 # /- B3 -- B7 37 # B1 \- B8 38 # / \ 39 # / \ B4 -- B9 40 # B0 \- B10 41 # \ 42 # \ /- B5 43 # B2 44 # \- B6 45 # 46 blocks = [] 47 48 # Construct B0, building off genesis. 49 start_height = node.getblockcount() 50 blocks.append(create_block( 51 hashprev=int(node.getbestblockhash(), 16), 52 tmpl={"height": start_height + 1} 53 )) 54 blocks[-1].solve() 55 56 # Construct B1-B10. 57 for i in range(1, 11): 58 blocks.append(create_block( 59 hashprev=blocks[(i - 1) >> 1].hash_int, 60 tmpl={ 61 "height": start_height + (i + 1).bit_length(), 62 # Make sure each block has a different hash. 63 "curtime": blocks[-1].nTime + 1, 64 } 65 )) 66 blocks[-1].solve() 67 68 self.log.info('Make sure B0 is accepted normally') 69 peer.send_blocks_and_test([blocks[0]], node, success=True) 70 # B0 must be active chain now. 71 assert_equal(node.getbestblockhash(), blocks[0].hash_hex) 72 73 self.log.info('Send B1 and B2 headers, and then blocks in opposite order') 74 self.send_headers(node, blocks[1:3]) 75 peer.send_blocks_and_test([blocks[2]], node, success=True) 76 peer.send_blocks_and_test([blocks[1]], node, success=False) 77 # B2 must be active chain now, as full data for B2 was received first. 78 assert_equal(node.getbestblockhash(), blocks[2].hash_hex) 79 80 self.log.info('Send all further headers in order') 81 self.send_headers(node, blocks[3:]) 82 # B2 is still the active chain, headers don't change this. 83 assert_equal(node.getbestblockhash(), blocks[2].hash_hex) 84 85 self.log.info('Send blocks B7-B10') 86 peer.send_blocks_and_test([blocks[7]], node, success=False) 87 peer.send_blocks_and_test([blocks[8]], node, success=False) 88 peer.send_blocks_and_test([blocks[9]], node, success=False) 89 peer.send_blocks_and_test([blocks[10]], node, success=False) 90 # B2 is still the active chain, as B7-B10 have missing parents. 91 assert_equal(node.getbestblockhash(), blocks[2].hash_hex) 92 93 self.log.info('Send parents B3-B4 of B8-B10 in reverse order') 94 peer.send_blocks_and_test([blocks[4]], node, success=False, force_send=True) 95 peer.send_blocks_and_test([blocks[3]], node, success=False, force_send=True) 96 # B9 is now active. Despite B7 being received earlier, the missing parent. 97 assert_equal(node.getbestblockhash(), blocks[9].hash_hex) 98 99 self.log.info('Invalidate B9-B10') 100 node.invalidateblock(blocks[9].hash_hex) 101 node.invalidateblock(blocks[10].hash_hex) 102 # B7 is now active. 103 assert_equal(node.getbestblockhash(), blocks[7].hash_hex) 104 105 # Invalidate blocks to start fresh on the next test 106 node.invalidateblock(blocks[0].hash_hex) 107 108 def test_chain_split_from_disk(self): 109 node = self.nodes[1] 110 peer = node.add_p2p_connection(P2PDataStore()) 111 112 self.generate(node, 1, sync_fun=self.no_op) 113 114 self.log.info('Precomputing blocks') 115 # 116 # /- A1 117 # / 118 # G -- B1 --- A2 119 # \ 120 # \- A3 121 # 122 blocks = [] 123 124 # Construct three equal-work blocks building from the tip. 125 start_height = node.getblockcount() 126 tip_block = node.getblock(node.getbestblockhash()) 127 prev_time = tip_block["time"] 128 129 for i in range(0, 3): 130 blocks.append(create_block( 131 hashprev=int(tip_block["hash"], 16), 132 tmpl={"height": start_height + 1, 133 # Make sure each block has a different hash. 134 "curtime": prev_time + i + 1, 135 } 136 )) 137 blocks[-1].solve() 138 139 # Send blocks and test that only the first one connects 140 self.log.info('Send A1, A2, and A3. Make sure that only the former connects') 141 peer.send_blocks_and_test([blocks[0]], node, success=True) 142 peer.send_blocks_and_test([blocks[1]], node, success=False) 143 peer.send_blocks_and_test([blocks[2]], node, success=False) 144 145 # Restart and send a new block 146 self.restart_node(1) 147 assert_equal(blocks[0].hash_hex, node.getbestblockhash()) 148 peer = node.add_p2p_connection(P2PDataStore()) 149 next_block = create_block( 150 hashprev=blocks[0].hash_int, 151 tmpl={"height": start_height + 2, 152 "curtime": prev_time + 10, 153 } 154 ) 155 next_block.solve() 156 peer.send_blocks_and_test([next_block], node, success=True) 157 158 def run_test(self): 159 self.test_chain_split_in_memory() 160 self.test_chain_split_from_disk() 161 162 163 if __name__ == '__main__': 164 ChainTiebreaksTest(__file__).main()