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 available (not fully downloaded)", 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 71 # cli handles wrong types differently 72 if not self.options.usecli: 73 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) 74 assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0") 75 76 self.log.info("We must already have the header") 77 assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) 78 79 self.log.info("Non-existent peer generates error") 80 for peer_id in [-1, peer_0_peer_1_id + 1]: 81 assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_id) 82 83 self.log.info("Fetching from pre-segwit peer generates error") 84 self.nodes[0].add_p2p_connection(P2PInterface(), services=P2P_SERVICES & ~NODE_WITNESS) 85 peers = self.nodes[0].getpeerinfo() 86 assert_equal(len(peers), 2) 87 presegwit_peer_id = peers[1]["id"] 88 assert_raises_rpc_error(-1, "Pre-SegWit peer", self.nodes[0].getblockfrompeer, short_tip, presegwit_peer_id) 89 90 self.log.info("Successful fetch") 91 result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) 92 self.wait_until(lambda: self.check_for_block(node=0, hash=short_tip), timeout=1) 93 assert_equal(result, {}) 94 95 self.log.info("Don't fetch blocks we already have") 96 assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) 97 98 self.log.info("Don't fetch blocks while the node has not synced past it yet") 99 # For this test we need node 1 in prune mode and as a side effect this also disconnects 100 # the nodes which is also necessary for the rest of the test. 101 self.restart_node(1, ["-prune=550"]) 102 103 # Generate a block on the disconnected node that the pruning node is not connected to 104 blockhash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0] 105 block_hex = self.nodes[0].getblock(blockhash=blockhash, verbosity=0) 106 block = from_hex(CBlock(), block_hex) 107 108 # Connect a P2PInterface to the pruning node and have it submit only the header of the 109 # block that the pruning node has not seen 110 node1_interface = self.nodes[1].add_p2p_connection(P2PInterface()) 111 node1_interface.send_and_ping(msg_headers([block])) 112 113 # Get the peer id of the P2PInterface from the pruning node 114 node1_peers = self.nodes[1].getpeerinfo() 115 assert_equal(len(node1_peers), 1) 116 node1_interface_id = node1_peers[0]["id"] 117 118 # Trying to fetch this block from the P2PInterface should not be possible 119 error_msg = "In prune mode, only blocks that the node has already synced previously can be fetched from a peer" 120 assert_raises_rpc_error(-1, error_msg, self.nodes[1].getblockfrompeer, blockhash, node1_interface_id) 121 122 self.log.info("Connect pruned node") 123 self.connect_nodes(0, 2) 124 pruned_node = self.nodes[2] 125 self.sync_blocks([self.nodes[0], pruned_node]) 126 127 # We need to generate more blocks to be able to prune 128 self.generate(self.nodes[0], 400, sync_fun=self.no_op) 129 self.sync_blocks([self.nodes[0], pruned_node]) 130 pruneheight = pruned_node.pruneblockchain(300) 131 assert_equal(pruneheight, 248) 132 # Ensure the block is actually pruned 133 pruned_block = self.nodes[0].getblockhash(2) 134 assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block) 135 136 self.log.info("Fetch pruned block") 137 peers = pruned_node.getpeerinfo() 138 assert_equal(len(peers), 1) 139 pruned_node_peer_0_id = peers[0]["id"] 140 result = pruned_node.getblockfrompeer(pruned_block, pruned_node_peer_0_id) 141 self.wait_until(lambda: self.check_for_block(node=2, hash=pruned_block), timeout=1) 142 assert_equal(result, {}) 143 144 self.log.info("Fetched block persists after next pruning event") 145 self.generate(self.nodes[0], 250, sync_fun=self.no_op) 146 self.sync_blocks([self.nodes[0], pruned_node]) 147 pruneheight += 251 148 assert_equal(pruned_node.pruneblockchain(700), pruneheight) 149 assert_equal(pruned_node.getblock(pruned_block)["hash"], "196ee3a1a6db2353965081c48ef8e6b031cb2115d084bec6fec937e91a2c6277") 150 151 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") 152 self.generate(self.nodes[0], 250, sync_fun=self.no_op) 153 self.sync_blocks([self.nodes[0], pruned_node]) 154 pruneheight += 250 155 assert_equal(pruned_node.pruneblockchain(1000), pruneheight) 156 assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block) 157 158 159 if __name__ == '__main__': 160 GetBlockFromPeerTest(__file__).main()