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