feature_chain_tiebreaks.py raw
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()
165