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()