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.
751When self.use_v2transport is True, TestNode advertises NODE_P2P_V2 service flag753An inbound connection is made from TestNode <------ P2PConnection754- if TestNode doesn't advertise NODE_P2P_V2 service, P2PConnection sends version message and v1 P2P is followed755- if TestNode advertises NODE_P2P_V2 service, (and if P2PConnections supports v2 P2P)756P2PConnection sends ellswift bytes and v2 P2P is followed757"""758if 'dstport' not in kwargs:759kwargs['dstport'] = p2p_port(self.index)760if 'dstaddr' not in kwargs:761kwargs['dstaddr'] = '127.0.0.1'762if supports_v2_p2p is None:763supports_v2_p2p = self.use_v2transport765if self.use_v2transport:766kwargs['services'] = kwargs.get('services', P2P_SERVICES) | NODE_P2P_V2767supports_v2_p2p = self.use_v2transport and supports_v2_p2p768p2p_conn.peer_connect(**kwargs, send_version=send_version, net=self.chain, timeout_factor=self.timeout_factor, supports_v2_p2p=supports_v2_p2p)()770self.p2ps.append(p2p_conn)771if not expect_success:772return p2p_conn773p2p_conn.wait_until(lambda: p2p_conn.is_connected, check_connected=False)774if supports_v2_p2p and wait_for_v2_handshake:775p2p_conn.wait_until(lambda: p2p_conn.v2_state.tried_v2_handshake)776if send_version:777p2p_conn.wait_until(lambda: not p2p_conn.on_connection_send_msg)778if wait_for_verack:779# Wait for the node to send us the version and verack780p2p_conn.wait_for_verack()781# At this point we have sent our version message and received the version and verack, however the full node782# has not yet received the verack from us (in reply to their version). So, the connection is not yet fully783# established (fSuccessfullyConnected).784#785# This shouldn't lead to any issues when sending messages, since the verack will be in-flight before the786# message we send. However, it might lead to races where we are expecting to receive a message. E.g. a787# transaction that will be added to the mempool as soon as we return here.788#789# So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds)790# in comparison to the upside of making tests less fragile and unexpected intermittent errors less likely.791p2p_conn.sync_with_ping()793# Consistency check that the node received our user agent string.794# Find our connection in getpeerinfo by our address:port and theirs, as this combination is unique.795sockname = p2p_conn._transport.get_extra_info("socket").getsockname()796our_addr_and_port = f"{sockname[0]}:{sockname[1]}"797dst_addr_and_port = f"{p2p_conn.dstaddr}:{p2p_conn.dstport}"798info = [peer for peer in self.getpeerinfo() if peer["addr"] == our_addr_and_port and peer["addrbind"] == dst_addr_and_port]799assert_equal(len(info), 1)800assert_equal(info[0]["subver"], P2P_SUBVERSION)802return p2p_conn804def 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):805"""Add an outbound p2p connection from node. Must be an806"outbound-full-relay", "block-relay-only", "addr-fetch" or "feeler" connection.808This method adds the p2p connection to the self.p2ps list and returns809the connection to the caller.811p2p_idx must be different for simultaneously connected peers. When reusing it for the next peer812after disconnecting the previous one, it is necessary to wait for the disconnect to finish to avoid813a race condition.815Parameters:816supports_v2_p2p: whether p2p_conn supports v2 P2P or not817advertise_v2_p2p: whether p2p_conn is advertised to support v2 P2P or not819An outbound connection is made from TestNode -------> P2PConnection820- if P2PConnection doesn't advertise_v2_p2p, TestNode sends version message and v1 P2P is followed821- if P2PConnection both supports_v2_p2p and advertise_v2_p2p, TestNode sends ellswift bytes and v2 P2P is followed822- if P2PConnection doesn't supports_v2_p2p but advertise_v2_p2p,823TestNode sends ellswift bytes and P2PConnection disconnects,824TestNode reconnects by sending version message and v1 P2P is followed825"""827def addconnection_callback(address, port):828self.log.debug("Connecting to %s:%d %s" % (address, port, connection_type))829self.addconnection('%s:%d' % (address, port), connection_type, advertise_v2_p2p)831if supports_v2_p2p is None:832supports_v2_p2p = self.use_v2transport833if advertise_v2_p2p is None:834advertise_v2_p2p = self.use_v2transport836if advertise_v2_p2p:837kwargs['services'] = kwargs.get('services', P2P_SERVICES) | NODE_P2P_V2838assert self.use_v2transport # only a v2 TestNode could make a v2 outbound connection840# if P2PConnection is advertised to support v2 P2P when it doesn't actually support v2 P2P,841# reconnection needs to be attempted using v1 P2P by sending version message842reconnect = advertise_v2_p2p and not supports_v2_p2p843# P2PConnection needs to be advertised to support v2 P2P so that ellswift bytes are sent instead of msg_version844supports_v2_p2p = supports_v2_p2p and advertise_v2_p2p845p2p_conn.peer_accept_connection(connect_cb=addconnection_callback, connect_id=p2p_idx + 1, net=self.chain, timeout_factor=self.timeout_factor, supports_v2_p2p=supports_v2_p2p, reconnect=reconnect, **kwargs)()847if reconnect:848p2p_conn.wait_for_reconnect()850if connection_type == "feeler" or wait_for_disconnect:851# feeler connections are closed as soon as the node receives a `version` message852p2p_conn.wait_until(lambda: p2p_conn.message_count["version"] == 1, check_connected=False)853p2p_conn.wait_until(lambda: not p2p_conn.is_connected, check_connected=False)854else:855p2p_conn.wait_for_connect()856self.p2ps.append(p2p_conn)858if supports_v2_p2p:859p2p_conn.wait_until(lambda: p2p_conn.v2_state.tried_v2_handshake)860p2p_conn.wait_until(lambda: not p2p_conn.on_connection_send_msg)861if wait_for_verack:862p2p_conn.wait_for_verack()863p2p_conn.sync_with_ping()865return p2p_conn867def num_test_p2p_connections(self):868"""Return number of test framework p2p connections to the node."""869return len([peer for peer in self.getpeerinfo() if peer['subver'] == P2P_SUBVERSION])871def disconnect_p2ps(self):872"""Close all p2p connections to the node.873The state of the peers (such as txrequests) may not be fully cleared874yet, even after this method returns."""875for p in self.p2ps:876p.peer_disconnect()877del self.p2ps[:]879self.wait_until(lambda: self.num_test_p2p_connections() == 0)881def is_connected_to(self, other):882assert isinstance(other, TestNode)883other_subver = other.getnetworkinfo()["subversion"]884return any(peer["subver"] == other_subver for peer in self.getpeerinfo())886def bumpmocktime(self, seconds):887"""Fast forward using setmocktime to self.mocktime + seconds. Requires setmocktime to have888been called at some point in the past."""889assert self.mocktime890self.mocktime += seconds891self.setmocktime(self.mocktime)893def wait_until(self, test_function, timeout=60, check_interval=0.05):894return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.timeout_factor, check_interval=check_interval)896class TestNodeCLIAttr:897def __init__(self, cli, command):898self.cli = cli899self.command = command901def __call__(self, *args, **kwargs):902return self.cli.send_cli(self.command, *args, **kwargs)904def get_request(self, *args, **kwargs):905return lambda: self(*args, **kwargs)908def arg_to_cli(arg):909if isinstance(arg, bool):910return str(arg).lower()911elif arg is None:912return 'null'913elif isinstance(arg, dict) or isinstance(arg, list) or isinstance(arg, tuple):914return json.dumps(arg, default=serialization_fallback)915else:916return str(arg)919class TestNodeCLI():920"""Interface to bitcoin-cli for an individual node"""921def __init__(self, binaries):922self.options = []923self.binaries = binaries924self.input = None925self.log = logging.getLogger('TestFramework.bitcoincli')927def __call__(self, *options, input=None):928# TestNodeCLI is callable with bitcoin-cli command-line options929cli = TestNodeCLI(self.binaries)930cli.options = self.options + [str(o) for o in options]931cli.input = input932return cli934def __getattr__(self, command):935return TestNodeCLIAttr(self, command)937def batch(self, requests):938results = []939for request in requests:940try:941results.append(dict(result=request()))942except JSONRPCException as e:943results.append(dict(error=e))944return results946def send_cli(self, clicommand=None, *args, **kwargs):947"""Run bitcoin-cli command. Deserializes returned string as python object."""948pos_args = [arg_to_cli(arg) for arg in args]949named_args = [key + "=" + arg_to_cli(value) for (key, value) in kwargs.items() if value is not None]950p_args = self.binaries.rpc_argv() + self.options951if named_args:952p_args += ["-named"]953base_arg_pos = len(p_args)954if clicommand is not None:955p_args += [clicommand]956p_args += pos_args + named_args958# TEST_CLI_MAX_ARG_SIZE is set low enough that checking the string959# length is enough and encoding to bytes is not needed before960# calculating the sum.961sum_arg_size = sum(len(arg) for arg in p_args)962stdin_data = self.input963if sum_arg_size >= TEST_CLI_MAX_ARG_SIZE:964self.log.debug(f"Cli: Command size {sum_arg_size} too large, using stdin")965rpc_args = "\n".join([arg for arg in p_args[base_arg_pos:]])966if stdin_data is not None:967stdin_data += "\n" + rpc_args968else:969stdin_data = rpc_args970p_args = p_args[:base_arg_pos] + ['-stdin']972self.log.debug("Running bitcoin-cli {}".format(p_args[2:]))973process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)974cli_stdout, cli_stderr = process.communicate(input=stdin_data)975returncode = process.poll()976if returncode:977match = re.match(r'error code: ([-0-9]+)\nerror message:\n(.*)', cli_stderr)978if match:979code, message = match.groups()980raise JSONRPCException(dict(code=int(code), message=message))981match = re.match(r'error: Server response: (.*)\n?$', cli_stderr)982if match:983message = match.group(1)984raise JSONRPCException(dict(985code=-342,986message=f"non-JSON HTTP response with '503 Service Unavailable' from server: {message}",987), http_status=503)988# Ignore cli_stdout, raise with cli_stderr989raise subprocess.CalledProcessError(returncode, p_args, output=cli_stderr)990try:991if not cli_stdout.strip():992return None993return json.loads(cli_stdout, parse_float=decimal.Decimal)994except (json.JSONDecodeError, decimal.InvalidOperation):995return cli_stdout.rstrip("\n")
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.