/ test / functional / feature_chain_tiebreaks.py
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()