/ test / functional / rpc_getblockfrompeer.py
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()