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