rpc_getblockfrompeer.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2020-2022 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 getblockfrompeer RPC.""" 6 7 from test_framework.authproxy import JSONRPCException 8 from test_framework.messages import ( 9 CBlock, 10 from_hex, 11 msg_headers, 12 NODE_WITNESS, 13 ) 14 from test_framework.p2p import ( 15 P2P_SERVICES, 16 P2PInterface, 17 ) 18 from test_framework.test_framework import BitcoinTestFramework 19 from test_framework.util import ( 20 assert_equal, 21 assert_raises_rpc_error, 22 ) 23 24 25 class GetBlockFromPeerTest(BitcoinTestFramework): 26 def set_test_params(self): 27 self.num_nodes = 3 28 self.extra_args = [ 29 [], 30 [], 31 ["-fastprune", "-prune=1"] 32 ] 33 34 def setup_network(self): 35 self.setup_nodes() 36 37 def check_for_block(self, node, hash): 38 try: 39 self.nodes[node].getblock(hash) 40 return True 41 except JSONRPCException: 42 return False 43 44 def run_test(self): 45 self.log.info("Mine 4 blocks on Node 0") 46 self.generate(self.nodes[0], 4, sync_fun=self.no_op) 47 assert_equal(self.nodes[0].getblockcount(), 204) 48 49 self.log.info("Mine competing 3 blocks on Node 1") 50 self.generate(self.nodes[1], 3, sync_fun=self.no_op) 51 assert_equal(self.nodes[1].getblockcount(), 203) 52 short_tip = self.nodes[1].getbestblockhash() 53 54 self.log.info("Connect nodes to sync headers") 55 self.connect_nodes(0, 1) 56 self.sync_blocks(self.nodes[0:2]) 57 58 self.log.info("Node 0 should only have the header for node 1's block 3") 59 x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips())) 60 assert_equal(x['status'], "headers-only") 61 assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip) 62 63 self.log.info("Fetch block from node 1") 64 peers = self.nodes[0].getpeerinfo() 65 assert_equal(len(peers), 1) 66 peer_0_peer_1_id = peers[0]["id"] 67 68 self.log.info("Arguments must be valid") 69 assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id) 70 assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id) 71 assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0") 72 73 self.log.info("We must already have the header") 74 assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) 75 76 self.log.info("Non-existent peer generates error") 77 for peer_id in [-1, peer_0_peer_1_id + 1]: 78 assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_id) 79 80 self.log.info("Fetching from pre-segwit peer generates error") 81 self.nodes[0].add_p2p_connection(P2PInterface(), services=P2P_SERVICES & ~NODE_WITNESS) 82 peers = self.nodes[0].getpeerinfo() 83 assert_equal(len(peers), 2) 84 presegwit_peer_id = peers[1]["id"] 85 assert_raises_rpc_error(-1, "Pre-SegWit peer", self.nodes[0].getblockfrompeer, short_tip, presegwit_peer_id) 86 87 self.log.info("Successful fetch") 88 result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) 89 self.wait_until(lambda: self.check_for_block(node=0, hash=short_tip), timeout=1) 90 assert_equal(result, {}) 91 92 self.log.info("Don't fetch blocks we already have") 93 assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) 94 95 self.log.info("Don't fetch blocks while the node has not synced past it yet") 96 # For this test we need node 1 in prune mode and as a side effect this also disconnects 97 # the nodes which is also necessary for the rest of the test. 98 self.restart_node(1, ["-prune=550"]) 99 100 # Generate a block on the disconnected node that the pruning node is not connected to 101 blockhash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0] 102 block_hex = self.nodes[0].getblock(blockhash=blockhash, verbosity=0) 103 block = from_hex(CBlock(), block_hex) 104 105 # Connect a P2PInterface to the pruning node and have it submit only the header of the 106 # block that the pruning node has not seen 107 node1_interface = self.nodes[1].add_p2p_connection(P2PInterface()) 108 node1_interface.send_and_ping(msg_headers([block])) 109 110 # Get the peer id of the P2PInterface from the pruning node 111 node1_peers = self.nodes[1].getpeerinfo() 112 assert_equal(len(node1_peers), 1) 113 node1_interface_id = node1_peers[0]["id"] 114 115 # Trying to fetch this block from the P2PInterface should not be possible 116 error_msg = "In prune mode, only blocks that the node has already synced previously can be fetched from a peer" 117 assert_raises_rpc_error(-1, error_msg, self.nodes[1].getblockfrompeer, blockhash, node1_interface_id) 118 119 self.log.info("Connect pruned node") 120 self.connect_nodes(0, 2) 121 pruned_node = self.nodes[2] 122 self.sync_blocks([self.nodes[0], pruned_node]) 123 124 # We need to generate more blocks to be able to prune 125 self.generate(self.nodes[0], 400, sync_fun=self.no_op) 126 self.sync_blocks([self.nodes[0], pruned_node]) 127 pruneheight = pruned_node.pruneblockchain(300) 128 assert_equal(pruneheight, 248) 129 # Ensure the block is actually pruned 130 pruned_block = self.nodes[0].getblockhash(2) 131 assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block) 132 133 self.log.info("Fetch pruned block") 134 peers = pruned_node.getpeerinfo() 135 assert_equal(len(peers), 1) 136 pruned_node_peer_0_id = peers[0]["id"] 137 result = pruned_node.getblockfrompeer(pruned_block, pruned_node_peer_0_id) 138 self.wait_until(lambda: self.check_for_block(node=2, hash=pruned_block), timeout=1) 139 assert_equal(result, {}) 140 141 self.log.info("Fetched block persists after next pruning event") 142 self.generate(self.nodes[0], 250, sync_fun=self.no_op) 143 self.sync_blocks([self.nodes[0], pruned_node]) 144 pruneheight += 251 145 assert_equal(pruned_node.pruneblockchain(700), pruneheight) 146 assert_equal(pruned_node.getblock(pruned_block)["hash"], "36c56c5b5ebbaf90d76b0d1a074dcb32d42abab75b7ec6fa0ffd9b4fbce8f0f7") 147 148 self.log.info("Fetched block can be pruned again when prune height exceeds the height of the tip at the time when the block was fetched") 149 self.generate(self.nodes[0], 250, sync_fun=self.no_op) 150 self.sync_blocks([self.nodes[0], pruned_node]) 151 pruneheight += 250 152 assert_equal(pruned_node.pruneblockchain(1000), pruneheight) 153 assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block) 154 155 156 if __name__ == '__main__': 157 GetBlockFromPeerTest().main()