Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2026-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""6Ensure that when v2 private broadcast connection to IPv4 fails the v1 retry7will also be made through the Tor proxy.9The test does:10* Add a bunch of IPv4 addresses to the node's addrman (they will be added without P2P_V2 flag).11* Get them to report P2P_V2 in their service flags and connect to each one, so that the flags12in addrman are updated to contain P2P_V2.13* Get one successful connection to a Tor peer (.onion) so that bitcoind assumes the configured14Tor proxy works and is indeed a proxy to the Tor network. This will make it open private15broadcast connections also to IPv4 addresses via that proxy.16* Start some private broadcast connections.17* Remember the destination IPv4 address of the first connection and get it to fail the v218transport.19* Wait for a subsequent connection also through the Tor proxy to the same IPv4 and expect20it to be v1, i.e. the v2->v1 downgrade retry.21"""23from test_framework.netutil import (24format_addr_port25)26from test_framework.p2p import (27P2PConnection,28P2PInterface,29P2P_SERVICES,30start_p2p_listener,31)32from test_framework.messages import (33CAddress,34NODE_P2P_V2,35)36from test_framework.socks5 import (37start_socks5_server,38)39from test_framework.test_framework import (40BitcoinTestFramework,41)42from test_framework.v2_p2p import (43EncryptedP2PState,44)45from test_framework.wallet import (46MiniWallet,47)49class P2PDetermineV2or1AndClose(P2PConnection):50def __init__(self, on_v2or1_determined):51super().__init__()52self.on_v2or1_determined = on_v2or1_determined54# https://docs.python.org/3/library/asyncio-protocol.html#asyncio.Protocol.data_received55def data_received(self, data):56self.recvbuf += data57if len(self.recvbuf) >= 4:58self.on_v2or1_determined(1 if self.recvbuf[0:4] == self.magic_bytes else 2)59self.peer_disconnect()61def on_open(self):62pass64def on_close(self):65pass67class P2PPrivateBroadcastRetryV1(BitcoinTestFramework):68def set_test_params(self):69self.disable_autoconnect = False70self.num_nodes = 172def ipv4_via_tor_proxy_conn_versions_append(self, v2or1):73"""74Add to the transport versions (v2 or v1) tried towards the first IPv4 which75nodes[0] tries to connect to via the Tor proxy.76"""77self.ipv4_via_tor_proxy_conn_versions.append(v2or1)79def setup_nodes(self):80def destinations_factory_all_proxy(requested_to_addr, requested_to_port, _proxy_client):81"""82Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface83objects that claim support for P2P_V2.84"""85listener = P2PInterface()86listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)87listener.peer_connect_send_version(services=P2P_SERVICES | NODE_P2P_V2)89actual_to_addr, actual_to_port = start_p2p_listener(self.network_thread, listener)91self.log.debug("Instructing the common proxy to redirect connection for "92f"{format_addr_port(requested_to_addr, requested_to_port)} to "93f"{format_addr_port(actual_to_addr, actual_to_port)} (Python {type(listener).__name__})")95return {96"actual_to_addr": actual_to_addr,97"actual_to_port": actual_to_port,98}100self.all_proxy = start_socks5_server(destinations_factory_all_proxy)102self.ipv4_via_tor_proxy_addr_port = None # Remember the first IPv4 address connected to via the Tor proxy.103self.ipv4_via_tor_proxy_conn_versions = [] # Transport versions tried on that address.105def destinations_factory_tor_proxy(requested_to_addr, requested_to_port, _proxy_client):106"""107Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface,108except the first connection to an IPv4 address and all subsequent connections to that109address which are redirected to P2PDetermineV2or1AndClose.110"""111requested_to = format_addr_port(requested_to_addr, requested_to_port)113if not requested_to_addr.endswith(".onion") and self.ipv4_via_tor_proxy_addr_port is None: # First IPv4114self.ipv4_via_tor_proxy_addr_port = requested_to116if self.ipv4_via_tor_proxy_addr_port == requested_to:117# This is either the first (v2) or the second (the expected v1 retry) connection to requested_to.118listener = P2PDetermineV2or1AndClose(self.ipv4_via_tor_proxy_conn_versions_append)119listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)120else:121listener = P2PInterface()122listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)123listener.peer_connect_send_version(services=P2P_SERVICES | NODE_P2P_V2)124if not requested_to_addr.endswith(".onion"):125listener.v2_state = EncryptedP2PState(initiating=False, net=self.chain)127actual_to_addr, actual_to_port = start_p2p_listener(self.network_thread, listener)129self.log.debug(f"Instructing the Tor proxy to redirect connection for {requested_to} to "130f"{format_addr_port(actual_to_addr, actual_to_port)} (Python {type(listener).__name__})")132return {133"actual_to_addr": actual_to_addr,134"actual_to_port": actual_to_port,135}137self.tor_proxy = start_socks5_server(destinations_factory_tor_proxy)139self.extra_args = [140[141"-privatebroadcast=1",142f"-proxy={self.all_proxy.conf.addr[0]}:{self.all_proxy.conf.addr[1]}",143f"-onion={self.tor_proxy.conf.addr[0]}:{self.tor_proxy.conf.addr[1]}",144"-test=addrman",145"-v2transport=0",146],147]149super().setup_nodes()151def setup_network(self):152self.setup_nodes()154def run_test(self):155node0 = self.nodes[0]157self.log.info("Filling node0's addrman with addresses")158self.fill_node_addrman(node_index=0, address_types_to_add=[CAddress.NET_IPV4])160self.log.info("Opening manual connections to all IPv4 addresses to add P2P_V2 flag to addrman entries")161for a in node0.getnodeaddresses(count=0, network="ipv4"):162node0.addnode(node=format_addr_port(a["address"], a["port"]), command="onetry", v2transport=False)164self.log.info("Waiting for all IPv4 addresses to get P2P_V2 as a result of peers advertising support")165self.wait_until(lambda: all(a["services"] & NODE_P2P_V2 != 0 for a in node0.getnodeaddresses(count=0, network="ipv4")))167# The destinations behind the -proxy= don't actually support v2. When bitcoind runs with -v2transport=1168# and tries v2 on them they would print benign "magic byte mismatch" warnings.169# Disable those since none of them are needed anymore.170self.all_proxy.conf.destinations_factory = None172self.restart_node(0, extra_args=self.extra_args[0] + ["-v2transport=1"])174self.log.info("Opening a connection to a Tor addresses, so bitcoind considers -onion= is a real Tor proxy")175node0.addnode(node="testonlyad777777777777777777777777777777777777777775b6qd.onion:1234", command="onetry", v2transport=False)177self.log.info("Waiting for at least one Tor connection")178self.wait_until(lambda: any(p["network"] == "onion" for p in node0.getpeerinfo()))180self.log.info("Starting private broadcast connections")181wallet = MiniWallet(node0)182tx = wallet.create_self_transfer()183node0.sendrawtransaction(hexstring=tx["hex"])185self.log.info("Tor proxy: waiting for connection to an IPv4 address")186self.wait_until(lambda: self.ipv4_via_tor_proxy_addr_port is not None)187self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port}, waiting for v2")188self.wait_until(lambda: 2 in self.ipv4_via_tor_proxy_conn_versions)189self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port} v2, waiting for v1")190self.wait_until(lambda: 1 in self.ipv4_via_tor_proxy_conn_versions)191self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port} v2, v1")193self.stop_node(0)194self.all_proxy.stop()195self.tor_proxy.stop()198if __name__ == "__main__":199P2PPrivateBroadcastRetryV1(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.