p2p_unrequested_blocks.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2015-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 processing of unrequested blocks. 6 7 Setup: two nodes, node0 + node1, not connected to each other. Node1 will have 8 nMinimumChainWork set to 0x10, so it won't process low-work unrequested blocks. 9 10 We have one P2PInterface connection to node0 called test_node, and one to node1 11 called min_work_node. 12 13 The test: 14 1. Generate one block on each node, to leave IBD. 15 16 2. Mine a new block on each tip, and deliver to each node from node's peer. 17 The tip should advance for node0, but node1 should skip processing due to 18 nMinimumChainWork. 19 20 Node1 is unused in tests 3-7: 21 22 3. Mine a block that forks from the genesis block, and deliver to test_node. 23 Node0 should not process this block (just accept the header), because it 24 is unrequested and doesn't have more or equal work to the tip. 25 26 4a,b. Send another two blocks that build on the forking block. 27 Node0 should process the second block but be stuck on the shorter chain, 28 because it's missing an intermediate block. 29 30 4c.Send 288 more blocks on the longer chain (the number of blocks ahead 31 we currently store). 32 Node0 should process all but the last block (too far ahead in height). 33 34 5. Send a duplicate of the block in #3 to Node0. 35 Node0 should not process the block because it is unrequested, and stay on 36 the shorter chain. 37 38 6. Send Node0 an inv for the height 3 block produced in #4 above. 39 Node0 should figure out that Node0 has the missing height 2 block and send a 40 getdata. 41 42 7. Send Node0 the missing block again. 43 Node0 should process and the tip should advance. 44 45 8. Create a fork which is invalid at a height longer than the current chain 46 (ie to which the node will try to reorg) but which has headers built on top 47 of the invalid block. Check that we get disconnected if we send more headers 48 on the chain the node now knows to be invalid. 49 50 9. Test Node1 is able to sync when connected to node0 (which should have sufficient 51 work on its chain). 52 """ 53 54 import time 55 56 from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script 57 from test_framework.messages import CBlockHeader, CInv, MSG_BLOCK, msg_block, msg_headers, msg_inv 58 from test_framework.p2p import p2p_lock, P2PInterface 59 from test_framework.test_framework import BitcoinTestFramework 60 from test_framework.util import ( 61 assert_equal, 62 assert_raises_rpc_error, 63 ) 64 65 66 class AcceptBlockTest(BitcoinTestFramework): 67 def set_test_params(self): 68 self.setup_clean_chain = True 69 self.num_nodes = 2 70 self.extra_args = [[], ["-minimumchainwork=0x10"]] 71 72 def setup_network(self): 73 self.setup_nodes() 74 75 def check_hash_in_chaintips(self, node, blockhash): 76 tips = node.getchaintips() 77 for x in tips: 78 if x["hash"] == blockhash: 79 return True 80 return False 81 82 def run_test(self): 83 test_node = self.nodes[0].add_p2p_connection(P2PInterface()) 84 min_work_node = self.nodes[1].add_p2p_connection(P2PInterface()) 85 86 # 1. Have nodes mine a block (leave IBD) 87 [self.generate(n, 1, sync_fun=self.no_op) for n in self.nodes] 88 tips = [int("0x" + n.getbestblockhash(), 0) for n in self.nodes] 89 90 # 2. Send one block that builds on each tip. 91 # This should be accepted by node0 92 blocks_h2 = [] # the height 2 blocks on each node's chain 93 block_time = int(time.time()) + 1 94 for i in range(2): 95 blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time)) 96 blocks_h2[i].solve() 97 block_time += 1 98 test_node.send_and_ping(msg_block(blocks_h2[0])) 99 100 with self.nodes[1].assert_debug_log(expected_msgs=[f"AcceptBlockHeader: not adding new block header {blocks_h2[1].hash_hex}, missing anti-dos proof-of-work validation"]): 101 min_work_node.send_and_ping(msg_block(blocks_h2[1])) 102 103 assert_equal(self.nodes[0].getblockcount(), 2) 104 assert_equal(self.nodes[1].getblockcount(), 1) 105 106 # Ensure that the header of the second block was also not accepted by node1 107 assert_equal(self.check_hash_in_chaintips(self.nodes[1], blocks_h2[1].hash_hex), False) 108 self.log.info("First height 2 block accepted by node0; correctly rejected by node1") 109 110 # 3. Send another block that builds on genesis. 111 block_h1f = create_block(int("0x" + self.nodes[0].getblockhash(0), 0), create_coinbase(1), block_time) 112 block_time += 1 113 block_h1f.solve() 114 test_node.send_and_ping(msg_block(block_h1f)) 115 116 tip_entry_found = False 117 for x in self.nodes[0].getchaintips(): 118 if x['hash'] == block_h1f.hash_hex: 119 assert_equal(x['status'], "headers-only") 120 tip_entry_found = True 121 assert tip_entry_found 122 assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_h1f.hash_hex) 123 124 # 4. Send another two block that build on the fork. 125 block_h2f = create_block(block_h1f.hash_int, create_coinbase(2), block_time) 126 block_time += 1 127 block_h2f.solve() 128 test_node.send_and_ping(msg_block(block_h2f)) 129 130 # Since the earlier block was not processed by node, the new block 131 # can't be fully validated. 132 tip_entry_found = False 133 for x in self.nodes[0].getchaintips(): 134 if x['hash'] == block_h2f.hash_hex: 135 assert_equal(x['status'], "headers-only") 136 tip_entry_found = True 137 assert tip_entry_found 138 139 # But this block should be accepted by node since it has equal work. 140 self.nodes[0].getblock(block_h2f.hash_hex) 141 self.log.info("Second height 2 block accepted, but not reorg'ed to") 142 143 # 4b. Now send another block that builds on the forking chain. 144 block_h3 = create_block(block_h2f.hash_int, create_coinbase(3), block_h2f.nTime+1) 145 block_h3.solve() 146 test_node.send_and_ping(msg_block(block_h3)) 147 148 # Since the earlier block was not processed by node, the new block 149 # can't be fully validated. 150 tip_entry_found = False 151 for x in self.nodes[0].getchaintips(): 152 if x['hash'] == block_h3.hash_hex: 153 assert_equal(x['status'], "headers-only") 154 tip_entry_found = True 155 assert tip_entry_found 156 self.nodes[0].getblock(block_h3.hash_hex) 157 158 # But this block should be accepted by node since it has more work. 159 self.nodes[0].getblock(block_h3.hash_hex) 160 self.log.info("Unrequested more-work block accepted") 161 162 # 4c. Now mine 288 more blocks and deliver; all should be processed but 163 # the last (height-too-high) on node (as long as it is not missing any headers) 164 tip = block_h3 165 all_blocks = [] 166 for i in range(288): 167 next_block = create_block(tip.hash_int, create_coinbase(i + 4), tip.nTime+1) 168 next_block.solve() 169 all_blocks.append(next_block) 170 tip = next_block 171 172 # Now send the block at height 5 and check that it wasn't accepted (missing header) 173 test_node.send_without_ping(msg_block(all_blocks[1])) 174 test_node.wait_for_disconnect() 175 assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, all_blocks[1].hash_hex) 176 assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockheader, all_blocks[1].hash_hex) 177 test_node = self.nodes[0].add_p2p_connection(P2PInterface()) 178 179 # The block at height 5 should be accepted if we provide the missing header, though 180 headers_message = msg_headers() 181 headers_message.headers.append(CBlockHeader(all_blocks[0])) 182 test_node.send_without_ping(headers_message) 183 test_node.send_and_ping(msg_block(all_blocks[1])) 184 self.nodes[0].getblock(all_blocks[1].hash_hex) 185 186 # Now send the blocks in all_blocks 187 for i in range(288): 188 test_node.send_without_ping(msg_block(all_blocks[i])) 189 test_node.sync_with_ping() 190 191 # Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead 192 for x in all_blocks[:-1]: 193 self.nodes[0].getblock(x.hash_hex) 194 assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[-1].hash_hex) 195 196 # 5. Test handling of unrequested block on the node that didn't process 197 # Should still not be processed (even though it has a child that has more 198 # work). 199 200 # The node should have requested the blocks at some point, so 201 # disconnect/reconnect first 202 203 self.nodes[0].disconnect_p2ps() 204 self.nodes[1].disconnect_p2ps() 205 206 test_node = self.nodes[0].add_p2p_connection(P2PInterface()) 207 208 test_node.send_and_ping(msg_block(block_h1f)) 209 assert_equal(self.nodes[0].getblockcount(), 2) 210 self.log.info("Unrequested block that would complete more-work chain was ignored") 211 212 # 6. Try to get node to request the missing block. 213 # Poke the node with an inv for block at height 3 and see if that 214 # triggers a getdata on block 2 (it should if block 2 is missing). 215 with p2p_lock: 216 # Clear state so we can check the getdata request 217 test_node.last_message.pop("getdata", None) 218 test_node.send_without_ping(msg_inv([CInv(MSG_BLOCK, block_h3.hash_int)])) 219 220 test_node.sync_with_ping() 221 with p2p_lock: 222 getdata = test_node.last_message["getdata"] 223 224 # Check that the getdata includes the right block 225 assert_equal(getdata.inv[0].hash, block_h1f.hash_int) 226 self.log.info("Inv at tip triggered getdata for unprocessed block") 227 228 # 7. Send the missing block for the third time (now it is requested) 229 test_node.send_and_ping(msg_block(block_h1f)) 230 assert_equal(self.nodes[0].getblockcount(), 290) 231 self.nodes[0].getblock(all_blocks[286].hash_hex) 232 assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash_hex) 233 assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[287].hash_hex) 234 self.log.info("Successfully reorged to longer chain") 235 236 # 8. Create a chain which is invalid at a height longer than the 237 # current chain, but which has more blocks on top of that 238 block_289f = create_block(all_blocks[284].hash_int, create_coinbase(289), all_blocks[284].nTime+1) 239 block_289f.solve() 240 block_290f = create_block(block_289f.hash_int, create_coinbase(290), block_289f.nTime+1) 241 block_290f.solve() 242 # block_291 spends a coinbase below maturity! 243 tx_to_add = create_tx_with_script(block_290f.vtx[0], 0, script_sig=b"42", amount=1) 244 block_291 = create_block(block_290f.hash_int, create_coinbase(291), block_290f.nTime+1, txlist=[tx_to_add]) 245 block_291.solve() 246 block_292 = create_block(block_291.hash_int, create_coinbase(292), block_291.nTime+1) 247 block_292.solve() 248 249 # Now send all the headers on the chain and enough blocks to trigger reorg 250 headers_message = msg_headers() 251 headers_message.headers.append(CBlockHeader(block_289f)) 252 headers_message.headers.append(CBlockHeader(block_290f)) 253 headers_message.headers.append(CBlockHeader(block_291)) 254 headers_message.headers.append(CBlockHeader(block_292)) 255 test_node.send_and_ping(headers_message) 256 257 tip_entry_found = False 258 for x in self.nodes[0].getchaintips(): 259 if x['hash'] == block_292.hash_hex: 260 assert_equal(x['status'], "headers-only") 261 tip_entry_found = True 262 assert tip_entry_found 263 assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_292.hash_hex) 264 265 test_node.send_without_ping(msg_block(block_289f)) 266 test_node.send_and_ping(msg_block(block_290f)) 267 268 self.nodes[0].getblock(block_289f.hash_hex) 269 self.nodes[0].getblock(block_290f.hash_hex) 270 271 test_node.send_without_ping(msg_block(block_291)) 272 273 # At this point we've sent an obviously-bogus block, wait for full processing 274 # and assume disconnection 275 test_node.wait_for_disconnect() 276 277 self.nodes[0].disconnect_p2ps() 278 test_node = self.nodes[0].add_p2p_connection(P2PInterface()) 279 280 # We should have failed reorg and switched back to 290 (but have block 291) 281 assert_equal(self.nodes[0].getblockcount(), 290) 282 assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash_hex) 283 assert_equal(self.nodes[0].getblock(block_291.hash_hex)["confirmations"], -1) 284 285 # Now send a new header on the invalid chain, indicating we're forked off, and expect to get disconnected 286 block_293 = create_block(block_292.hash_int, create_coinbase(293), block_292.nTime+1) 287 block_293.solve() 288 headers_message = msg_headers() 289 headers_message.headers.append(CBlockHeader(block_293)) 290 test_node.send_without_ping(headers_message) 291 test_node.wait_for_disconnect() 292 293 # 9. Connect node1 to node0 and ensure it is able to sync 294 self.connect_nodes(0, 1) 295 self.sync_blocks([self.nodes[0], self.nodes[1]]) 296 self.log.info("Successfully synced nodes 1 and 0") 297 298 if __name__ == '__main__': 299 AcceptBlockTest(__file__).main()