diff --git a/test/functional/p2p_private_broadcast.py b/test/functional/p2p_private_broadcast.py index 054058e946b..4ee6af72511 100755 --- a/test/functional/p2p_private_broadcast.py +++ b/test/functional/p2p_private_broadcast.py @@ -6,7 +6,6 @@ Test how locally submitted transactions are sent to the network when private broadcast is used. """ -import re import time import threading @@ -68,33 +67,32 @@ class P2PPrivateBroadcast(BitcoinTestFramework): self.trigger_no_relay_peer = False self.no_relay_peer = None - def find_connection_type_in_debug_log(to_addr, to_port): - """ - Scan the debug log of tx_originator for a connection attempt to to_addr:to_port. - Return the connection type (outbound-full-relay, private-broadcast, etc) or - None if there is no connection attempt to to_addr:to_port. - """ - with open(self.tx_originator_debug_log_path, mode="r", encoding="utf-8") as debug_log: - for line in debug_log.readlines(): - match = re.match(f".*trying v. connection \\((.+)\\) to \\[?{to_addr}]?:{to_port},.*", line) - if match: - return match.group(1) - return None - - def destinations_factory(requested_to_addr, requested_to_port): + def destinations_factory(requested_to_addr, requested_to_port, proxy_client): """ Instruct the SOCKS5 proxy to redirect connections: * The first automatic outbound connection -> P2PDataStore * The first private broadcast connection -> nodes[1] * Anything else -> P2PInterface + + proxy_client is the client's socket address as seen by the proxy (host:port), + equal to the node's addrbind for this connection. """ conn_type = None - def found_connection_in_debug_log(): - nonlocal conn_type - conn_type = find_connection_type_in_debug_log(requested_to_addr, requested_to_port) - return conn_type is not None + # SOCKS handlers run in separate threads, so each needs its own RPC connection. + rpc = self.nodes[0].create_new_rpc_connection() - self.wait_until(found_connection_in_debug_log) + def connection_type_found(): + nonlocal conn_type + # The proxy has already replied SUCCESS to the SOCKS5 request, so the node + # has finished ConnectNode and registered the peer (or is about to). + # The proxy client address equals the node's addrbind for this connection. + for peer in rpc.getpeerinfo(): + if peer.get("addrbind") == proxy_client: + conn_type = peer["connection_type"] + return True + return False + + self.wait_until(connection_type_found) with self.destinations_lock: i = len(self.destinations) @@ -233,7 +231,6 @@ class P2PPrivateBroadcast(BitcoinTestFramework): def run_test(self): tx_originator = self.nodes[0] - self.tx_originator_debug_log_path = tx_originator.debug_log_path tx_receiver = self.nodes[1] far_observer = tx_receiver.add_p2p_connection(P2PInterface()) diff --git a/test/functional/p2p_private_broadcast_retry_v1.py b/test/functional/p2p_private_broadcast_retry_v1.py index 87fa440a883..7b97119bba8 100755 --- a/test/functional/p2p_private_broadcast_retry_v1.py +++ b/test/functional/p2p_private_broadcast_retry_v1.py @@ -77,7 +77,7 @@ class P2PPrivateBroadcastRetryV1(BitcoinTestFramework): self.ipv4_via_tor_proxy_conn_versions.append(v2or1) def setup_nodes(self): - def destinations_factory_all_proxy(requested_to_addr, requested_to_port): + def destinations_factory_all_proxy(requested_to_addr, requested_to_port, _proxy_client): """ Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface objects that claim support for P2P_V2. @@ -102,7 +102,7 @@ class P2PPrivateBroadcastRetryV1(BitcoinTestFramework): self.ipv4_via_tor_proxy_addr_port = None # Remember the first IPv4 address connected to via the Tor proxy. self.ipv4_via_tor_proxy_conn_versions = [] # Transport versions tried on that address. - def destinations_factory_tor_proxy(requested_to_addr, requested_to_port): + def destinations_factory_tor_proxy(requested_to_addr, requested_to_port, _proxy_client): """ Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface, except the first connection to an IPv4 address and all subsequent connections to that diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index f7a1aabddd4..44d8e6fc82a 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -104,6 +104,7 @@ class Socks5Configuration(): # and it decides where the connection is redirected to. It is passed: # - the address the client requested to connect to # - the port the client requested to connect to + # - the client's socket address as seen by the proxy, formatted as host:port # It is supposed to return an object like: # { # "actual_to_addr": "127.0.0.1" @@ -140,8 +141,9 @@ class Socks5Connection(): """Handle socks5 request according to RFC1928.""" log_exception_prefix = "Socks5Connection.handle(): " try: + proxy_client = format_sock(self.conn, local=False) log_exception_prefix = ("Socks5Connection.handle(" - f"client={format_sock(self.conn, local=False)}, " + f"client={proxy_client}, " f"proxy={format_sock(self.conn, local=True)}): ") # Verify socks version @@ -193,7 +195,9 @@ class Socks5Connection(): port_hi,port_lo = recvall(self.conn, 2) port = (port_hi << 8) | port_lo - # Send dummy response + # Reply SUCCESS before calling destinations_factory, so the client can finish + # establishing the connection and register the peer; factories that consult + # getpeerinfo depend on that order. self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])) cmdin = Socks5Command(cmd, atyp, addr, port, username, password) @@ -205,7 +209,7 @@ class Socks5Connection(): if self.serv.is_running(): if self.serv.conf.destinations_factory is not None: - dest = self.serv.conf.destinations_factory(requested_to_addr, port) + dest = self.serv.conf.destinations_factory(requested_to_addr, port, proxy_client) if dest is not None: logger.debug(f"Serving connection to {requested_to}, will redirect it to " f"{dest['actual_to_addr']}:{dest['actual_to_port']} instead")