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.
751NetworkThread.protos_accept_done = []753def run(self):754"""Start the network thread."""755NetworkThread.network_event_loop = asyncio.SelectorEventLoop() if platform.system() == "Windows" else asyncio.new_event_loop()756self.network_event_loop.run_forever()758def close(self, *, timeout):759"""Close the connections and network event loop."""760for p in NetworkThread.protos_accept_done:761p.peer_disconnect()762NetworkThread.protos_accept_done.clear()764listeners = list(NetworkThread.listeners.values())765NetworkThread.listeners.clear()767async def close_listeners():768for listener in listeners:769listener.close()770for listener in listeners:771await listener.wait_closed()772future = asyncio.run_coroutine_threadsafe(close_listeners(), self.network_event_loop)773future.result(timeout=timeout)775self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop)776wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout)777self.join(timeout)778self.network_event_loop.close()779# Safe to remove event loop.780NetworkThread.network_event_loop = None782@classmethod783def listen(cls, p2p, callback, port=None, addr=None, idx=1):784""" Ensure a listening server is running on the given port, and run the785protocol specified by `p2p` on the next connection to it. Once ready786for connections, call `callback`."""788if port is None:789assert 0 < idx <= MAX_NODES790port = p2p_port(MAX_NODES - idx)791if addr is None:792addr = '127.0.0.1'794def exception_handler(loop, context):795if not p2p.reconnect:796loop.default_exception_handler(context)798cls.network_event_loop.set_exception_handler(exception_handler)799coroutine = cls.create_listen_server(addr, port, callback, p2p)800cls.network_event_loop.call_soon_threadsafe(cls.network_event_loop.create_task, coroutine)802@classmethod803async def create_listen_server(cls, addr, port, callback, proto):804def peer_protocol():805"""Returns a function that does the protocol handling for a new806connection. To allow different connections to have different807behaviors, the protocol function is first put in the cls.protos808dict. When the connection is made, the function removes the809protocol function from that dict, and returns it so the event loop810can start executing it."""811response = cls.protos.get((addr, port))812# remove protocol function from dict only when reconnection doesn't need to happen/already happened813if not proto.reconnect:814cls.protos_accept_done.append(response)815cls.protos[(addr, port)] = None816return response818if port == 0 or (addr, port) not in cls.listeners:819# When creating a listener on a given (addr, port) we only need to820# do it once. If we want different behaviors for different821# connections, we can accomplish this by providing different822# `proto` functions824if port == 0:825# Manually create the socket in order to set the range to be826# used for the port before the bind() call.827if ipaddress.ip_address(addr).version == 4:828address_family = socket.AF_INET829else:830address_family = socket.AF_INET6831s = socket.socket(address_family)832set_ephemeral_port_range(s)833s.bind((addr, 0))834s.listen()835listener = await cls.network_event_loop.create_server(peer_protocol, sock=s)836port = listener.sockets[0].getsockname()[1]837else:838listener = await cls.network_event_loop.create_server(peer_protocol, addr, port)840logger.debug("Listening server on %s:%d should be started" % (addr, port))841cls.listeners[(addr, port)] = listener843cls.protos[(addr, port)] = proto844callback(addr, port)847class P2PDataStore(P2PInterface):848"""A P2P data store class.850Keeps a block and transaction store and responds correctly to getdata and getheaders requests."""852def __init__(self):853super().__init__()854# store of blocks. key is block hash, value is a CBlock object855self.block_store = {}856self.last_block_hash = ''857# store of txs. key is txid, value is a CTransaction object858self.tx_store = {}859self.getdata_requests = []861def on_getdata(self, message):862"""Check for the tx/block in our stores and if found, reply with MSG_TX or MSG_BLOCK."""863for inv in message.inv:864self.getdata_requests.append(inv.hash)865invtype = inv.type & MSG_TYPE_MASK866if (invtype == MSG_TX or invtype == MSG_WTX) and inv.hash in self.tx_store.keys():867self.send_without_ping(msg_tx(self.tx_store[inv.hash]))868elif invtype == MSG_BLOCK and inv.hash in self.block_store.keys():869self.send_without_ping(msg_block(self.block_store[inv.hash]))870else:871logger.debug('getdata message type {} received.'.format(hex(inv.type)))873def on_getheaders(self, message):874"""Search back through our block store for the locator, and reply with a headers message if found."""876locator, hash_stop = message.locator, message.hashstop878# Assume that the most recent block added is the tip879if not self.block_store:880return882headers_list = [self.block_store[self.last_block_hash]]883while headers_list[-1].hash_int not in locator.vHave:884# Walk back through the block store, adding headers to headers_list885# as we go.886prev_block_hash = headers_list[-1].hashPrevBlock887if prev_block_hash in self.block_store:888prev_block_header = CBlockHeader(self.block_store[prev_block_hash])889headers_list.append(prev_block_header)890if prev_block_header.hash_int == hash_stop:891# if this is the hashstop header, stop here892break893else:894logger.debug('block hash {} not found in block store'.format(hex(prev_block_hash)))895break897# Truncate the list if there are too many headers898headers_list = headers_list[:-MAX_HEADERS_RESULTS - 1:-1]899response = msg_headers(headers_list)901if response is not None:902self.send_without_ping(response)904def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False, reject_reason=None, expect_disconnect=False, timeout=60, is_decoy=False):905"""Send blocks to test node and test whether the tip advances.907- add all blocks to our block_store908- send a headers message for the final block909- the on_getheaders handler will ensure that any getheaders are responded to910- if force_send is False: wait for getdata for each of the blocks. The on_getdata handler will911ensure that any getdata messages are responded to. Otherwise send the full block unsolicited.912- if success is True: assert that the node's tip is the last block in blocks at the end of the operation.913- if success is False: assert that the node's tip isn't the last block in blocks at the end of the operation914- if reject_reason is set: assert that the correct reject message is logged"""916with p2p_lock:917for block in blocks:918self.block_store[block.hash_int] = block919self.last_block_hash = block.hash_int921reject_reason = [reject_reason] if reject_reason else []922with node.assert_debug_log(expected_msgs=reject_reason):923if is_decoy: # since decoy messages are ignored by the recipient - no need to wait for response924force_send = True925if force_send:926for b in blocks:927self.send_without_ping(msg_block(block=b), is_decoy)928else:929self.send_without_ping(msg_headers([CBlockHeader(block) for block in blocks]))930self.wait_until(931lambda: blocks[-1].hash_int in self.getdata_requests,932timeout=timeout,933check_connected=success,934)936if expect_disconnect:937self.wait_for_disconnect(timeout=timeout)938else:939self.sync_with_ping(timeout=timeout)941if success:942self.wait_until(lambda: node.getbestblockhash() == blocks[-1].hash_hex, timeout=timeout)943else:944assert_not_equal(node.getbestblockhash(), blocks[-1].hash_hex)946def send_txs_and_test(self, txs, node, *, success=True, reject_reason=None):947"""Send txs to test node and test whether they're accepted to the mempool.949- add all txs to our tx_store950- send tx messages for all txs951- if success is True/False: assert that the txs are/are not accepted to the mempool952- if reject_reason is set: assert that the correct reject message is logged."""954with p2p_lock:955for tx in txs:956self.tx_store[tx.txid_int] = tx958reject_reason = [reject_reason] if reject_reason else []959with node.assert_debug_log(expected_msgs=reject_reason):960for tx in txs:961self.send_without_ping(msg_tx(tx))963self.sync_with_ping()965raw_mempool = node.getrawmempool()966if success:967# Check that all txs are now in the mempool968for tx in txs:969assert tx.txid_hex in raw_mempool, "{} not found in mempool".format(tx.txid_hex)970else:971# Check that none of the txs are now in the mempool972for tx in txs:973assert tx.txid_hex not in raw_mempool, "{} tx found in mempool".format(tx.txid_hex)975class P2PTxInvStore(P2PInterface):976"""A P2PInterface which stores a count of how many times each txid has been announced."""977def __init__(self, **kwargs):978super().__init__(**kwargs)979self.tx_invs_received = defaultdict(int)981def on_inv(self, message):982super().on_inv(message) # Send getdata in response.983# Store how many times invs have been received for each tx.984for i in message.inv:985if (i.type == MSG_TX) or (i.type == MSG_WTX):986# save txid987self.tx_invs_received[i.hash] += 1989def get_invs(self):990with p2p_lock:991return list(self.tx_invs_received.keys())993def wait_for_broadcast(self, txns, *, timeout=60):994"""Waits for the txns (list of txids) to complete initial broadcast.995The mempool should mark unbroadcast=False for these transactions.996"""997# Wait until invs have been received (and getdatas sent) for each txid.998self.wait_until(lambda: set(self.tx_invs_received.keys()) == set([int(tx, 16) for tx in txns]), timeout=timeout)999# Flush messages and wait for the getdatas to be processed1000self.sync_with_ping()
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.