rpc_getblockfrompeer.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-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 getblockfrompeer RPC."""
   6  
   7  from test_framework.messages import (
   8      CBlock,
   9      from_hex,
  10      msg_headers,
  11      NODE_WITNESS,
  12  )
  13  from test_framework.p2p import (
  14      P2P_SERVICES,
  15      P2PInterface,
  16  )
  17  from test_framework.test_framework import BitcoinTestFramework
  18  from test_framework.util import (
  19      assert_equal,
  20      assert_raises_rpc_error,
  21      JSONRPCException,
  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, 249)
 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 += 252
 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 += 251
 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()
 161