diff options
author | glozow <gloriajzhao@gmail.com> | 2024-03-19 17:19:41 +0000 |
---|---|---|
committer | glozow <gloriajzhao@gmail.com> | 2024-03-19 17:22:04 +0000 |
commit | 3d216baf91ca754e46e89788205513a956ec6d0a (patch) | |
tree | 573878b983eefbeb713cee6a05efa080c8566006 /test | |
parent | 479ecc0515b58e755f0f60f51592dd24cc15821e (diff) | |
parent | 2f23987849758537f76df7374d85a7e87b578b61 (diff) | |
download | bitcoin-3d216baf91ca754e46e89788205513a956ec6d0a.tar.xz |
Merge bitcoin/bitcoin#29279: test: p2p: check disconnect due to lack of desirable service flags
2f23987849758537f76df7374d85a7e87b578b61 test: p2p: check limited peers desirability (depending on best block depth) (Sebastian Falbesoner)
c4a67d396d0aa99f658cafe381e39622859eb0be test: p2p: check disconnect due to lack of desirable service flags (Sebastian Falbesoner)
405ac819af1eb0f6cf6d1805cb668f4e8ab4a6f3 test: p2p: support disconnect waiting for `add_outbound_p2p_connection` (Sebastian Falbesoner)
Pull request description:
This PR adds missing test coverage for disconnecting peers which don't offer the desirable service flags in their VERSION message:
https://github.com/bitcoin/bitcoin/blob/5f3a0574c45477288bc678b15f24940486084576/src/net_processing.cpp#L3384-L3389
This check is relevant for the connection types "outbound-full-relay", "block-relay-only" and "addr-fetch" (see `CNode::ExpectServicesFromConn(...)`). Feeler connections always disconnect, which is also tested here.
In lack of finding a proper file where this test would fit in, I created a new one. Happy to take suggestions there.
ACKs for top commit:
davidgumberg:
reACK https://github.com/bitcoin/bitcoin/commit/2f23987849758537f76df7374d85a7e87b578b61
itornaza:
tested ACK 2f23987849758537f76df7374d85a7e87b578b61
fjahr:
re-utACK 2f23987849758537f76df7374d85a7e87b578b61
cbergqvist:
re ACK 2f23987849758537f76df7374d85a7e87b578b61
stratospher:
tested ACK 2f23987. 🚀
Tree-SHA512: cf75d9d4379d0f34fa1e13152e6a8d93cd51b9573466ab3a2fec32dc3e1ac49b174bd1063cae558bc736b111c8a6e7058b1b57a496df56255221bf367d29eb5d
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/p2p_handshake.py | 87 | ||||
-rwxr-xr-x | test/functional/test_framework/messages.py | 1 | ||||
-rwxr-xr-x | test/functional/test_framework/test_node.py | 4 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 2 |
4 files changed, 92 insertions, 2 deletions
diff --git a/test/functional/p2p_handshake.py b/test/functional/p2p_handshake.py new file mode 100755 index 0000000000..3fbb940cbd --- /dev/null +++ b/test/functional/p2p_handshake.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test P2P behaviour during the handshake phase (VERSION, VERACK messages). +""" +import itertools +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + NODE_NETWORK, + NODE_NETWORK_LIMITED, + NODE_NONE, + NODE_P2P_V2, + NODE_WITNESS, +) +from test_framework.p2p import P2PInterface + + +# Desirable service flags for outbound non-pruned and pruned peers. Note that +# the desirable service flags for pruned peers are dynamic and only apply if +# 1. the peer's service flag NODE_NETWORK_LIMITED is set *and* +# 2. the local chain is close to the tip (<24h) +DESIRABLE_SERVICE_FLAGS_FULL = NODE_NETWORK | NODE_WITNESS +DESIRABLE_SERVICE_FLAGS_PRUNED = NODE_NETWORK_LIMITED | NODE_WITNESS + + +class P2PHandshakeTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def add_outbound_connection(self, node, connection_type, services, wait_for_disconnect): + peer = node.add_outbound_p2p_connection( + P2PInterface(), p2p_idx=0, wait_for_disconnect=wait_for_disconnect, + connection_type=connection_type, services=services, + supports_v2_p2p=self.options.v2transport, advertise_v2_p2p=self.options.v2transport) + if not wait_for_disconnect: + # check that connection is alive past the version handshake and disconnect manually + peer.sync_with_ping() + peer.peer_disconnect() + peer.wait_for_disconnect() + + def test_desirable_service_flags(self, node, service_flag_tests, desirable_service_flags, expect_disconnect): + """Check that connecting to a peer either fails or succeeds depending on its offered + service flags in the VERSION message. The test is exercised for all relevant + outbound connection types where the desirable service flags check is done.""" + CONNECTION_TYPES = ["outbound-full-relay", "block-relay-only", "addr-fetch"] + for conn_type, services in itertools.product(CONNECTION_TYPES, service_flag_tests): + if self.options.v2transport: + services |= NODE_P2P_V2 + expected_result = "disconnect" if expect_disconnect else "connect" + self.log.info(f' - services 0x{services:08x}, type "{conn_type}" [{expected_result}]') + if expect_disconnect: + assert (services & desirable_service_flags) != desirable_service_flags + expected_debug_log = f'does not offer the expected services ' \ + f'({services:08x} offered, {desirable_service_flags:08x} expected)' + with node.assert_debug_log([expected_debug_log]): + self.add_outbound_connection(node, conn_type, services, wait_for_disconnect=True) + else: + assert (services & desirable_service_flags) == desirable_service_flags + self.add_outbound_connection(node, conn_type, services, wait_for_disconnect=False) + + def run_test(self): + node = self.nodes[0] + self.log.info("Check that lacking desired service flags leads to disconnect (non-pruned peers)") + self.test_desirable_service_flags(node, [NODE_NONE, NODE_NETWORK, NODE_WITNESS], + DESIRABLE_SERVICE_FLAGS_FULL, expect_disconnect=True) + self.test_desirable_service_flags(node, [NODE_NETWORK | NODE_WITNESS], + DESIRABLE_SERVICE_FLAGS_FULL, expect_disconnect=False) + + self.log.info("Check that limited peers are only desired if the local chain is close to the tip (<24h)") + node.setmocktime(int(time.time()) + 25 * 3600) # tip outside the 24h window, should fail + self.test_desirable_service_flags(node, [NODE_NETWORK_LIMITED | NODE_WITNESS], + DESIRABLE_SERVICE_FLAGS_FULL, expect_disconnect=True) + node.setmocktime(int(time.time()) + 23 * 3600) # tip inside the 24h window, should succeed + self.test_desirable_service_flags(node, [NODE_NETWORK_LIMITED | NODE_WITNESS], + DESIRABLE_SERVICE_FLAGS_PRUNED, expect_disconnect=False) + + self.log.info("Check that feeler connections get disconnected immediately") + with node.assert_debug_log([f"feeler connection completed"]): + self.add_outbound_connection(node, "feeler", NODE_NONE, wait_for_disconnect=True) + + +if __name__ == '__main__': + P2PHandshakeTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 1780678de1..4e496a9275 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -46,6 +46,7 @@ MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol mes MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message +NODE_NONE = 0 NODE_NETWORK = (1 << 0) NODE_BLOOM = (1 << 2) NODE_WITNESS = (1 << 3) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index ef6b9dd5ea..67e0be5280 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -724,7 +724,7 @@ class TestNode(): return p2p_conn - def add_outbound_p2p_connection(self, p2p_conn, *, wait_for_verack=True, p2p_idx, connection_type="outbound-full-relay", supports_v2_p2p=None, advertise_v2_p2p=None, **kwargs): + def add_outbound_p2p_connection(self, p2p_conn, *, wait_for_verack=True, wait_for_disconnect=False, p2p_idx, connection_type="outbound-full-relay", supports_v2_p2p=None, advertise_v2_p2p=None, **kwargs): """Add an outbound p2p connection from node. Must be an "outbound-full-relay", "block-relay-only", "addr-fetch" or "feeler" connection. @@ -771,7 +771,7 @@ class TestNode(): if reconnect: p2p_conn.wait_for_reconnect() - if connection_type == "feeler": + if connection_type == "feeler" or wait_for_disconnect: # feeler connections are closed as soon as the node receives a `version` message p2p_conn.wait_until(lambda: p2p_conn.message_count["version"] == 1, check_connected=False) p2p_conn.wait_until(lambda: not p2p_conn.is_connected, check_connected=False) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9325b0a250..1408854e02 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -395,6 +395,8 @@ BASE_SCRIPTS = [ 'rpc_getdescriptorinfo.py', 'rpc_mempool_info.py', 'rpc_help.py', + 'p2p_handshake.py', + 'p2p_handshake.py --v2transport', 'feature_dirsymlinks.py', 'feature_help.py', 'feature_shutdown.py', |