/ test / functional / rpc_invalidateblock.py
rpc_invalidateblock.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-present 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 the invalidateblock RPC."""
  6  
  7  from test_framework.test_framework import BitcoinTestFramework
  8  from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
  9  from test_framework.blocktools import (
 10      create_block,
 11  )
 12  from test_framework.util import (
 13      assert_equal,
 14      assert_raises_rpc_error,
 15  )
 16  
 17  
 18  class InvalidateTest(BitcoinTestFramework):
 19      def set_test_params(self):
 20          self.setup_clean_chain = True
 21          self.num_nodes = 3
 22  
 23      def setup_network(self):
 24          self.setup_nodes()
 25  
 26      def run_test(self):
 27          self.log.info("Make sure we repopulate setBlockIndexCandidates after InvalidateBlock:")
 28          self.log.info("Mine 4 blocks on Node 0")
 29          self.generate(self.nodes[0], 4, sync_fun=self.no_op)
 30          assert_equal(self.nodes[0].getblockcount(), 4)
 31          besthash_n0 = self.nodes[0].getbestblockhash()
 32  
 33          self.log.info("Mine competing 6 blocks on Node 1")
 34          self.generate(self.nodes[1], 6, sync_fun=self.no_op)
 35          assert_equal(self.nodes[1].getblockcount(), 6)
 36  
 37          self.log.info("Connect nodes to force a reorg")
 38          self.connect_nodes(0, 1)
 39          self.sync_blocks(self.nodes[0:2])
 40          assert_equal(self.nodes[0].getblockcount(), 6)
 41  
 42          # Add a header to the tip of node 0 without submitting the block. This shouldn't
 43          # affect results since this chain will be invalidated next.
 44          tip = self.nodes[0].getbestblockhash()
 45          block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
 46          block = create_block(int(tip, 16), height=self.nodes[0].getblockcount(), ntime=block_time, version=4)
 47          block.solve()
 48          self.nodes[0].submitheader(block.serialize().hex())
 49          assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"] + 1)
 50  
 51          self.log.info("Invalidate block 2 on node 0 and verify we reorg to node 0's original chain")
 52          badhash = self.nodes[1].getblockhash(2)
 53          self.nodes[0].invalidateblock(badhash)
 54          assert_equal(self.nodes[0].getblockcount(), 4)
 55          assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
 56          # Should report consistent blockchain info
 57          assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"])
 58  
 59          self.log.info("Reconsider block 6 on node 0 again and verify that the best header is set correctly")
 60          self.nodes[0].reconsiderblock(tip)
 61          assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"] + 1)
 62  
 63          self.log.info("Invalidate block 2 on node 0 and verify we reorg to node 0's original chain again")
 64          self.nodes[0].invalidateblock(badhash)
 65          assert_equal(self.nodes[0].getblockcount(), 4)
 66          assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
 67          assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"])
 68  
 69          self.log.info("Make sure we won't reorg to a lower work chain:")
 70          self.connect_nodes(1, 2)
 71          self.log.info("Sync node 2 to node 1 so both have 6 blocks")
 72          self.sync_blocks(self.nodes[1:3])
 73          assert_equal(self.nodes[2].getblockcount(), 6)
 74          self.log.info("Invalidate block 5 on node 1 so its tip is now at 4")
 75          self.nodes[1].invalidateblock(self.nodes[1].getblockhash(5))
 76          assert_equal(self.nodes[1].getblockcount(), 4)
 77          self.log.info("Invalidate block 3 on node 2, so its tip is now 2")
 78          self.nodes[2].invalidateblock(self.nodes[2].getblockhash(3))
 79          assert_equal(self.nodes[2].getblockcount(), 2)
 80          self.log.info("..and then mine a block")
 81          self.generate(self.nodes[2], 1, sync_fun=self.no_op)
 82          self.log.info("Verify all nodes are at the right height")
 83          self.wait_until(lambda: self.nodes[2].getblockcount() == 3, timeout=5)
 84          self.wait_until(lambda: self.nodes[0].getblockcount() == 4, timeout=5)
 85          self.wait_until(lambda: self.nodes[1].getblockcount() == 4, timeout=5)
 86  
 87          self.log.info("Verify that ancestors can become chain tip candidates when we reconsider blocks")
 88          # Invalidate node0's current chain (1' -> 2' -> 3' -> 4') so that we don't reorg back to it in this test
 89          badhash = self.nodes[0].getblockhash(1)
 90          self.nodes[0].invalidateblock(badhash)
 91          # Reconsider the tip so that node0's chain becomes this chain again : 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> header 7
 92          self.nodes[0].reconsiderblock(tip)
 93          blockhash_3 = self.nodes[0].getblockhash(3)
 94          blockhash_4 = self.nodes[0].getblockhash(4)
 95          blockhash_6 = self.nodes[0].getblockhash(6)
 96          assert_equal(self.nodes[0].getbestblockhash(), blockhash_6)
 97  
 98          # Invalidate block 4 so that chain becomes : 1 -> 2 -> 3
 99          self.nodes[0].invalidateblock(blockhash_4)
100          assert_equal(self.nodes[0].getbestblockhash(), blockhash_3)
101          assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 3)
102          assert_equal(self.nodes[0].getblockchaininfo()['headers'], 3)
103  
104          # Reconsider the header
105          self.nodes[0].reconsiderblock(block.hash_hex)
106          # Since header doesn't have block data, it can't be chain tip
107          # Check if it's possible for an ancestor (with block data) to be the chain tip
108          assert_equal(self.nodes[0].getbestblockhash(), blockhash_6)
109          assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 6)
110          assert_equal(self.nodes[0].getblockchaininfo()['headers'], 7)
111  
112          self.log.info("Verify that we reconsider all ancestors as well")
113          blocks = self.generatetodescriptor(self.nodes[1], 10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR, sync_fun=self.no_op)
114          assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
115          # Invalidate the two blocks at the tip
116          self.nodes[1].invalidateblock(blocks[-1])
117          self.nodes[1].invalidateblock(blocks[-2])
118          assert_equal(self.nodes[1].getbestblockhash(), blocks[-3])
119          # Reconsider only the previous tip
120          self.nodes[1].reconsiderblock(blocks[-1])
121          # Should be back at the tip by now
122          assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
123  
124          self.log.info("Verify that we reconsider all descendants")
125          blocks = self.generatetodescriptor(self.nodes[1], 10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR, sync_fun=self.no_op)
126          assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
127          # Invalidate the two blocks at the tip
128          self.nodes[1].invalidateblock(blocks[-2])
129          self.nodes[1].invalidateblock(blocks[-4])
130          assert_equal(self.nodes[1].getbestblockhash(), blocks[-5])
131          # Reconsider only the previous tip
132          self.nodes[1].reconsiderblock(blocks[-4])
133          # Should be back at the tip by now
134          assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
135          # Should report consistent blockchain info
136          assert_equal(self.nodes[1].getblockchaininfo()["headers"], self.nodes[1].getblockchaininfo()["blocks"])
137  
138          self.log.info("Verify that invalidating an unknown block throws an error")
139          assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32)
140          assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
141  
142  
143  if __name__ == '__main__':
144      InvalidateTest(__file__).main()