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.
251# don't display warning if reconnection needs to be attempted using v1 P2P252if exc and not self.reconnect:253logger.warning("Connection lost to {}:{} due to {}".format(self.dstaddr, self.dstport, exc))254else:255logger.debug("Closed connection to: %s:%d" % (self.dstaddr, self.dstport))256self._transport = None257self.recvbuf = b""258self.on_close()260# v2 handshake method261def _on_data_v2_handshake(self):262"""v2 handshake performed before P2P messages are exchanged (see BIP324). P2PConnection is the initiator263(in inbound connections to TestNode) and the responder (in outbound connections from TestNode).264Performed by:265* initiator using `initiate_v2_handshake()`, `complete_handshake()` and `authenticate_handshake()`266* responder using `respond_v2_handshake()`, `complete_handshake()` and `authenticate_handshake()`268`initiate_v2_handshake()` is immediately done by the initiator when the connection is established in269`connection_made()`. The rest of the initial v2 handshake functions are handled here.270"""271if not self.v2_state.peer:272if not self.v2_state.initiating and not self.v2_state.sent_garbage:273# if the responder hasn't sent garbage yet, the responder is still reading ellswift bytes274# reads ellswift bytes till the first mismatch from 12 bytes V1_PREFIX275length, send_handshake_bytes = self.v2_state.respond_v2_handshake(BytesIO(self.recvbuf))276self.recvbuf = self.recvbuf[length:]277if send_handshake_bytes == -1:278self.v2_state = None279return280elif send_handshake_bytes:281logger.debug(f"sending {len(self.v2_state.sent_garbage)} bytes of garbage data")282self.send_raw_message(send_handshake_bytes)283elif send_handshake_bytes == b"":284return # only after send_handshake_bytes are sent can `complete_handshake()` be done286# `complete_handshake()` reads the remaining ellswift bytes from recvbuf287# and sends response after deriving shared ECDH secret using received ellswift bytes288length, response = self.v2_state.complete_handshake(BytesIO(self.recvbuf))289self.recvbuf = self.recvbuf[length:]290if response:291self.send_raw_message(response)292else:293return # only after response is sent can `authenticate_handshake()` be done295# `self.v2_state.peer` is instantiated only after shared ECDH secret/BIP324 derived keys and ciphers296# is derived in `complete_handshake()`.297# so `authenticate_handshake()` which uses the BIP324 derived ciphers gets called after `complete_handshake()`.298assert self.v2_state.peer299length, is_mac_auth = self.v2_state.authenticate_handshake(self.recvbuf)300if not is_mac_auth:301raise ValueError("invalid v2 mac tag in handshake authentication")302self.recvbuf = self.recvbuf[length:]303if self.v2_state.tried_v2_handshake:304# for v2 outbound connections, send version message immediately after v2 handshake305if self.p2p_connected_to_node:306self.send_version()307# process post-v2-handshake data immediately, if available308if len(self.recvbuf) > 0:309self._on_data()311# Socket read methods313def data_received(self, t):314"""asyncio callback when data is read from the socket."""315if len(t) > 0:316self.recvbuf += t317if self.supports_v2_p2p and not self.v2_state.tried_v2_handshake:318self._on_data_v2_handshake()319else:320self._on_data()322def _on_data(self):323"""Try to read P2P messages from the recv buffer.325This method reads data from the buffer in a loop. It deserializes,326parses and verifies the P2P header, then passes the P2P payload to327the on_message callback for processing."""328try:329while True:330if self.supports_v2_p2p:331# v2 P2P messages are read332msglen, msg = self.v2_state.v2_receive_packet(self.recvbuf)333if msglen == -1:334raise ValueError("invalid v2 mac tag " + repr(self.recvbuf))335elif msglen == 0: # need to receive more bytes in recvbuf336return337self.recvbuf = self.recvbuf[msglen:]339if msg is None: # ignore decoy messages340return341assert msg # application layer messages (which aren't decoy messages) are non-empty342shortid = msg[0] # 1-byte short message type ID343if shortid == 0:344# next 12 bytes are interpreted as ASCII message type if shortid is b'\x00'345if len(msg) < 13:346raise IndexError("msg needs minimum required length of 13 bytes")347msgtype = msg[1:13].rstrip(b'\x00')348msg = msg[13:] # msg is set to be payload349else:350# a 1-byte short message type ID351msgtype = SHORTID.get(shortid, f"unknown-{shortid}")352msg = msg[1:]353else:354# v1 P2P messages are read355if len(self.recvbuf) < 4:356return357if self.recvbuf[:4] != self.magic_bytes:358raise ValueError("magic bytes mismatch: {} != {}".format(repr(self.magic_bytes), repr(self.recvbuf)))359if len(self.recvbuf) < 4 + 12 + 4 + 4:360return361msgtype = self.recvbuf[4:4+12].split(b"\x00", 1)[0]362msglen = struct.unpack("<i", self.recvbuf[4+12:4+12+4])[0]363checksum = self.recvbuf[4+12+4:4+12+4+4]364if len(self.recvbuf) < 4 + 12 + 4 + 4 + msglen:365return366msg = self.recvbuf[4+12+4+4:4+12+4+4+msglen]367th = sha256(msg)368h = sha256(th)369if checksum != h[:4]:370raise ValueError("got bad checksum " + repr(self.recvbuf))371self.recvbuf = self.recvbuf[4+12+4+4+msglen:]372if msgtype not in MESSAGEMAP:373raise ValueError("Received unknown msgtype from %s:%d: '%s' %s" % (self.dstaddr, self.dstport, msgtype, repr(msg)))374f = BytesIO(msg)375t = MESSAGEMAP[msgtype]()376t.deserialize(f)377self._log_message("receive", t)378self.on_message(t)379except Exception as e:380if not self.reconnect:381logger.exception(f"Error reading message: {repr(e)}")382raise384def on_message(self, message):385"""Callback for processing a P2P payload. Must be overridden by derived class."""386raise NotImplementedError388# Socket write methods390def send_without_ping(self, message, is_decoy=False):391"""Send a P2P message over the socket.393This method takes a P2P payload, builds the P2P header and adds394the message to the send buffer to be sent over the socket.396When a message does not lead to a disconnect, send_and_ping is usually397preferred to send a message. This can help to reduce intermittent test398failures due to a missing sync. Also, it includes a call to399sync_with_ping, allowing for concise test code.400"""401with self._send_lock:402tmsg = self.build_message(message, is_decoy)403self._log_message("send", message)404return self.send_raw_message(tmsg)406def send_raw_message(self, raw_message_bytes):407if not self.is_connected:408raise IOError('Not connected')410def maybe_write():411if not self._transport:412return413if self._transport.is_closing():414return415self._transport.write(raw_message_bytes)416NetworkThread.network_event_loop.call_soon_threadsafe(maybe_write)418# Class utility methods420def build_message(self, message, is_decoy=False):421"""Build a serialized P2P message"""422msgtype = message.msgtype423data = message.serialize()424if self.supports_v2_p2p:425if msgtype in SHORTID.values():426tmsg = MSGTYPE_TO_SHORTID.get(msgtype).to_bytes(1, 'big')427else:428tmsg = b"\x00"429tmsg += msgtype430tmsg += b"\x00" * (12 - len(msgtype))431tmsg += data432return self.v2_state.v2_enc_packet(tmsg, ignore=is_decoy)433else:434tmsg = self.magic_bytes435tmsg += msgtype436tmsg += b"\x00" * (12 - len(msgtype))437tmsg += len(data).to_bytes(4, "little")438th = sha256(data)439h = sha256(th)440tmsg += h[:4]441tmsg += data442return tmsg444def _log_message(self, direction, msg):445"""Logs a message being sent or received over the connection."""446if direction == "send":447log_message = "Send message to "448elif direction == "receive":449log_message = "Received message from "450log_message += "%s:%d: %s" % (self.dstaddr, self.dstport, repr(msg)[:500])451if len(log_message) > 500:452log_message += "... (msg truncated)"453logger.debug(log_message)456class P2PInterface(P2PConnection):457"""A high-level P2P interface class for communicating with a Bitcoin node.459This class provides high-level callbacks for processing P2P message460payloads, as well as convenience methods for interacting with the461node over P2P.463Individual testcases should subclass this and override the on_* methods464if they want to alter message handling behaviour."""465def __init__(self, support_addrv2=False, wtxidrelay=True):466super().__init__()468# Track number of messages of each type received.469# Should be read-only in a test.470self.message_count = defaultdict(int)472# Track the most recent message of each type.473# To wait for a message to be received, pop that message from474# this and use self.wait_until.475self.last_message = {}477# A count of the number of ping messages we've sent to the node478self.ping_counter = 1480# The network services received from the peer481self.nServices = 0483self.support_addrv2 = support_addrv2485# If the peer supports wtxid-relay486self.wtxidrelay = wtxidrelay488def peer_connect_send_version(self, services):489# Send a version msg490vt = msg_version()491vt.nVersion = P2P_VERSION492vt.strSubVer = P2P_SUBVERSION493vt.relay = P2P_VERSION_RELAY494vt.nServices = services495vt.addrTo.ip = self.dstaddr496vt.addrTo.port = self.dstport497vt.addrFrom.ip = "0.0.0.0"498vt.addrFrom.port = 0499self.on_connection_send_msg = vt # Will be sent in connection_made callback
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.