Repository: bitcoin/bitcoin
File: test/functional/p2p_node_network_limited.py (Python)

Code before:
        self.wait_until(test_function, timeout=timeout)
    def send_getdata_for_block(self, blockhash):
        getdata_request = msg_getdata()
        getdata_request.inv.append(CInv(MSG_BLOCK, int(blockhash, 16)))
        self.send_message(getdata_request)

class NodeNetworkLimitedTest(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 3
        self.extra_args = [['-prune=550', '-addrmantest'], [], []]

    def disconnect_all(self):
        self.disconnect_nodes(0, 1)
        self.disconnect_nodes(0, 2)
        self.disconnect_nodes(1, 2)

    def setup_network(self):
        self.add_nodes(self.num_nodes, self.extra_args)
        self.start_nodes()

    def run_test(self):
        node = self.nodes[0].add_p2p_connection(P2PIgnoreInv())

        expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED
        if self.options.v2transport:
            expected_services |= NODE_P2P_V2

        self.log.info("Check that node has signalled expected services.")
        assert_equal(node.nServices, expected_services)

        self.log.info("Check that the localservices is as expected.")
        assert_equal(int(self.nodes[0].getnetworkinfo()['localservices'], 16), expected_services)

        self.log.info("Mine enough blocks to reach the NODE_NETWORK_LIMITED range.")
        self.connect_nodes(0, 1)
        blocks = self.generate(self.nodes[1], 292, sync_fun=lambda: self.sync_blocks([self.nodes[0], self.nodes[1]]))

        self.log.info("Make sure we can max retrieve block at tip-288.")
        node.send_getdata_for_block(blocks[1])  # last block in valid range
        node.wait_for_block(int(blocks[1], 16), timeout=3)

        self.log.info("Requesting block at height 2 (tip-289) must fail (ignored).")
        node.send_getdata_for_block(blocks[0])  # first block outside of the 288+2 limit
        node.wait_for_disconnect(5)

        self.log.info("Check local address relay, do a fresh connection.")
        self.nodes[0].disconnect_p2ps()
        node1 = self.nodes[0].add_p2p_connection(P2PIgnoreInv())
        node1.send_message(msg_verack())


Review comment (bug):
The test would fail if run standalone without the preceding tests. The failure is on line 78: `self.sync_blocks([miner, pruned_node])`. That is because the test relies on the pruned node being connected to the miner which is indeed the case - some leftover from previous tests. This diff makes it succeed when run standalone:

```diff
--- i/test/functional/p2p_node_network_limited.py
+++ w/test/functional/p2p_node_network_limited.py
@@ -54,25 +54,24 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
         # The request of blocks further than the NETWORK_NODE_LIMITED threshold causes a direct disconnection.
         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(0, 1)
         self.connect_nodes(1, 2)
-        self.connect_nodes(0, 2)
         self.generate(miner, 1, sync_fun=self.no_op)
 
         # Verify all peers are at the same height
         self.sync_blocks([miner, pruned_node])
                                      
         # Verify peers are out of IBD
         for node in self.nodes:
             assert not node.getblockchaininfo()['initialblockdownload']    
 
         # Disconnect full_node from the other two peers (the node will remain out of IBD)
-        self.disconnect_nodes(0, 2)
         self.disconnect_nodes(1, 2)                                       
         assert_equal(len(full_node.getpeerinfo()), 0)                     
 
         # Mine blocks and sync the pruned node. Surpass the NETWORK_NODE_LIMITED threshold.
```

It is also shorter and I think more clear.

Code after:
    def on_addr(self, message):
        self.firstAddrnServices = message.addrs[0].nServices
    def wait_for_addr(self, timeout=5):
        test_function = lambda: self.last_message.get("addr")
        self.wait_until(test_function, timeout=timeout)
    def send_getdata_for_block(self, blockhash):
        getdata_request = msg_getdata()
        getdata_request.inv.append(CInv(MSG_BLOCK, int(blockhash, 16)))
        self.send_message(getdata_request)

class NodeNetworkLimitedTest(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 3
        self.extra_args = [['-prune=550', '-addrmantest'], [], []]

    def disconnect_all(self):
        self.disconnect_nodes(0, 1)
        self.disconnect_nodes(0, 2)
        self.disconnect_nodes(1, 2)

    def setup_network(self):
        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.