diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 89c35e943b5..05d227990e2 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -21,8 +21,12 @@ from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, + try_rpc ) +# Minimum blocks required to signal NODE_NETWORK_LIMITED # +NODE_NETWORK_LIMITED_MIN_BLOCKS = 288 class P2PIgnoreInv(P2PInterface): firstAddrnServices = 0 @@ -54,6 +58,63 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() + def test_avoid_requesting_historical_blocks(self): + self.log.info("Test full node does not request blocks beyond the limited peer threshold") + pruned_node = self.nodes[0] + miner = self.nodes[1] + full_node = self.nodes[2] + + # Connect and generate block to ensure IBD=false + self.connect_nodes(1, 0) + self.connect_nodes(1, 2) + self.generate(miner, 1) + + # Verify peers are out of IBD + for node in self.nodes: + assert not node.getblockchaininfo()['initialblockdownload'] + + # Isolate full_node (the node will remain out of IBD) + full_node.setnetworkactive(False) + self.wait_until(lambda: len(full_node.getpeerinfo()) == 0) + + # Mine blocks and sync the pruned node. Surpass the NETWORK_NODE_LIMITED threshold. + # Blocks deeper than the threshold are considered "historical blocks" + num_historial_blocks = 12 + self.generate(miner, NODE_NETWORK_LIMITED_MIN_BLOCKS + num_historial_blocks, sync_fun=self.no_op) + self.sync_blocks([miner, pruned_node]) + + # Connect full_node to prune_node and check peers don't disconnect right away. + # (they will disconnect if full_node, which is chain-wise behind, request blocks + # older than NODE_NETWORK_LIMITED_MIN_BLOCKS) + start_height_full_node = full_node.getblockcount() + full_node.setnetworkactive(True) + self.connect_nodes(2, 0) + assert_equal(len(full_node.getpeerinfo()), 1) + + # Wait until the full_node is headers-wise sync + best_block_hash = pruned_node.getbestblockhash() + self.wait_until(lambda: next(filter(lambda x: x['hash'] == best_block_hash, full_node.getchaintips()))['status'] == "headers-only") + + # Now, since the node aims to download a window of 1024 blocks, + # ensure it requests the blocks below the threshold only (with a + # 2-block buffer). And also, ensure it does not request any + # historical block. + tip_height = pruned_node.getblockcount() + limit_buffer = 2 + # Prevent races by waiting for the tip to arrive first + self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash())) + for height in range(start_height_full_node + 1, tip_height + 1): + if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer): + assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height)) + else: + full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception + + # Lastly, ensure the full_node is not sync and verify it can get synced by + # establishing a connection with another full node capable of providing them. + assert_equal(full_node.getblockcount(), start_height_full_node) + self.connect_nodes(2, 1) + self.sync_blocks([miner, full_node]) + def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) @@ -118,5 +179,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): # sync must be possible, node 1 is no longer in IBD and should therefore connect to node 0 (NODE_NETWORK_LIMITED) self.sync_blocks([self.nodes[0], self.nodes[1]]) + self.test_avoid_requesting_historical_blocks() + if __name__ == '__main__': NodeNetworkLimitedTest().main()