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