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

Code before:
    from_hex,
    msg_headers,
    CBlockHeader,
)
from test_framework.p2p import (
    P2PInterface,
)
from test_framework.test_framework import BitcoinTestFramework

# Timeouts (in minutes)
CHAIN_SYNC_TIMEOUT = 20 * 60
HEADERS_RESPONSE_TIME = 2 * 60


class P2POutEvict(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1

    def test_outbound_eviction_unprotected(self):
        # This tests the eviction logic for **unprotected** outbound peers (that is, PeerManagerImpl::ConsiderEviction)
        node = self.nodes[0]
        cur_mock_time = node.mocktime

        self.log.info("Create an outbound connection and don't send any headers")
        # Test disconnect due to no block being announced in 22+ minutes (headers are not even exchanged)
        test_node = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")
        # Wait for over 20 min to trigger the first eviction timeout. This sets the last call past 2 min in the future.
        test_node.sync_with_ping()
        cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1)
        node.setmocktime(cur_mock_time)
        test_node.sync_with_ping()
        # Wait for over 2 more min to trigger the disconnection
        test_node.wait_for_getheaders()
        cur_mock_time += (HEADERS_RESPONSE_TIME + 1)
        node.setmocktime(cur_mock_time)
        self.log.info("Test that the peer gets evicted")
        test_node.wait_for_disconnect()

        self.log.info("Create an outbound connection and send header but never catch up")
        # Mimic a node that just falls behind for long enough
        # This should also apply for a node doing IBD that does not catch up in time
        # Get our tip header and its parent
        tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False))
        prev_header = from_hex(CBlockHeader(), node.getblockheader(f"{tip_header.hashPrevBlock:064x}", False))

        # Connect a peer and make it send us headers ending in our tip's parent
        test_node = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")
        test_node.send_and_ping(msg_headers([prev_header]))

        # Trigger the timeouts
        cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1)

Review comment (suggestion):
8fa272d: can we call `test_node` variable `peer` instead? `add_outbound_p2p_connection` returns `P2PInterface` objects and not a `TestNode` object like `test_node` implies.

Code after:
    from_hex,
    msg_headers,
    CBlockHeader,
)
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework

# Timeouts (in seconds)
CHAIN_SYNC_TIMEOUT = 20 * 60
HEADERS_RESPONSE_TIME = 2 * 60


class P2POutEvict(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1

    def test_outbound_eviction_unprotected(self):
        # This tests the eviction logic for **unprotected** outbound peers (that is, PeerManagerImpl::ConsiderEviction)
        node = self.nodes[0]
        cur_mock_time = node.mocktime

        # Get our tip header and its parent
        tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False))
        prev_header = from_hex(CBlockHeader(), node.getblockheader(f"{tip_header.hashPrevBlock:064x}", False))

        self.log.info("Create an outbound connection and don't send any headers")
        # Test disconnect due to no block being announced in 22+ minutes (headers are not even exchanged)
        peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")
        # Wait for over 20 min to trigger the first eviction timeout. This sets the last call past 2 min in the future.
        cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1)
        node.setmocktime(cur_mock_time)
        peer.sync_with_ping()
        # Wait for over 2 more min to trigger the disconnection
        peer.wait_for_getheaders(block_hash=tip_header.hashPrevBlock)
        cur_mock_time += (HEADERS_RESPONSE_TIME + 1)
        node.setmocktime(cur_mock_time)
        self.log.info("Test that the peer gets evicted")
        peer.wait_for_disconnect()

        self.log.info("Create an outbound connection and send header but never catch up")
        # Mimic a node that just falls behind for long enough
        # This should also apply for a node doing IBD that does not catch up in time
        # Connect a peer and make it send us headers ending in our tip's parent
        peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")
        peer.send_and_ping(msg_headers([prev_header]))

        # Trigger the timeouts
        cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1)
        node.setmocktime(cur_mock_time)
        peer.sync_with_ping()
        peer.wait_for_getheaders(block_hash=tip_header.hashPrevBlock)