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) 2019-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"""6Test transaction download behavior7"""8from decimal import Decimal9from enum import Enum10import time12from test_framework.mempool_util import (13fill_mempool,14)15from test_framework.messages import (16CInv,17MSG_TX,18MSG_TYPE_MASK,19MSG_WITNESS_TX,20MSG_WTX,21msg_inv,22msg_notfound,23msg_tx,24)25from test_framework.p2p import (26P2PInterface,27p2p_lock,28NONPREF_PEER_TX_DELAY,29GETDATA_TX_INTERVAL,30TXID_RELAY_DELAY,31OVERLOADED_PEER_TX_DELAY32)33from test_framework.test_framework import BitcoinTestFramework34from test_framework.util import (35assert_equal,36)37from test_framework.wallet import MiniWallet40class TestP2PConn(P2PInterface):41def __init__(self, wtxidrelay=True):42super().__init__(wtxidrelay=wtxidrelay)43self.tx_getdata_count = 045def on_getdata(self, message):46for i in message.inv:47if i.type & MSG_TYPE_MASK == MSG_TX or i.type & MSG_TYPE_MASK == MSG_WTX:48self.tx_getdata_count += 151# Constants from txdownloadman52MAX_PEER_TX_REQUEST_IN_FLIGHT = 10053MAX_PEER_TX_ANNOUNCEMENTS = 500055# Python test constants56NUM_INBOUND = 1057MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY59class ConnectionType(Enum):60""" Different connection types611. INBOUND: Incoming connection, not whitelisted622. OUTBOUND: Outgoing connection633. WHITELIST: Incoming connection, but whitelisted64"""65INBOUND = 066OUTBOUND = 167WHITELIST = 269class TxDownloadTest(BitcoinTestFramework):70def set_test_params(self):71self.num_nodes = 272self.extra_args= [['-maxmempool=5', '-persistmempool=0']] * self.num_nodes74def test_tx_requests(self):75self.log.info("Test that we request transactions from all our peers, eventually")77txid = 0xdeadbeef79self.log.info("Announce the txid from each incoming peer to node 0")80msg = msg_inv([CInv(t=MSG_WTX, h=txid)])81for p in self.nodes[0].p2ps:82p.send_and_ping(msg)84outstanding_peer_index = [i for i in range(len(self.nodes[0].p2ps))]86def getdata_found(peer_index):87p = self.nodes[0].p2ps[peer_index]88with p2p_lock:89return p.last_message.get("getdata") and p.last_message["getdata"].inv[-1].hash == txid91node_0_mocktime = int(time.time())92while outstanding_peer_index:93node_0_mocktime += MAX_GETDATA_INBOUND_WAIT94self.nodes[0].setmocktime(node_0_mocktime)95self.wait_until(lambda: any(getdata_found(i) for i in outstanding_peer_index))96for i in outstanding_peer_index:97if getdata_found(i):98outstanding_peer_index.remove(i)100self.nodes[0].setmocktime(0)101self.log.info("All outstanding peers received a getdata")103def test_inv_block(self):104self.log.info("Generate a transaction on node 0")105tx = self.wallet.create_self_transfer()106wtxid = int(tx['wtxid'], 16)108self.nodes[0].setmocktime(int(time.time()))110self.log.info(111"Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND))112msg = msg_inv([CInv(t=MSG_WTX, h=wtxid)])113for p in self.peers:114p.send_and_ping(msg)116self.log.info("Put the tx in node 0's mempool")117self.nodes[0].sendrawtransaction(tx['hex'])119# Since node 1 is connected outbound to an honest peer (node 0), it120# should get the tx within a timeout.121# However, node 0 sees the same connection as inbound, so it may not122# announce the tx within the timeout.123# The timeout is the sum of124# * the worst case until the tx is first requested from an inbound125# peer, plus126# * the first time it is re-requested from the outbound peer, plus127# * 2 seconds to avoid races128assert_equal(self.nodes[0].getpeerinfo()[0]["inbound"], True)129assert_equal(self.nodes[0].getpeerinfo()[0]["inv_to_send"], 1)130assert_equal(self.nodes[1].getpeerinfo()[0]['inbound'], False)131timeout = 2 + NONPREF_PEER_TX_DELAY + GETDATA_TX_INTERVAL132self.log.info("Tx should be received at node 1 after {} seconds".format(timeout))133self.nodes[0].bumpmocktime(timeout)134# Use an unrelated peer to ensure node 0 calls `SendMessages` at least once135self.nodes[0].p2ps[0].sync_with_ping()136assert_equal(self.nodes[0].getpeerinfo()[0]['inv_to_send'], 0) # May fail rarely, retry the test137self.sync_mempools()139self.nodes[0].setmocktime(0)141def test_in_flight_max(self):142self.log.info("Test that we don't load peers with more than {} transaction requests immediately".format(MAX_PEER_TX_REQUEST_IN_FLIGHT))143txids = [i for i in range(MAX_PEER_TX_REQUEST_IN_FLIGHT + 2)]145p = self.nodes[0].p2ps[0]147with p2p_lock:148p.tx_getdata_count = 0150mock_time = int(time.time() + 1)151self.nodes[0].setmocktime(mock_time)152for i in range(MAX_PEER_TX_REQUEST_IN_FLIGHT):153p.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=txids[i])]))154p.sync_with_ping()155mock_time += NONPREF_PEER_TX_DELAY156self.nodes[0].setmocktime(mock_time)157p.wait_until(lambda: p.tx_getdata_count >= MAX_PEER_TX_REQUEST_IN_FLIGHT)158for i in range(MAX_PEER_TX_REQUEST_IN_FLIGHT, len(txids)):159p.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=txids[i])]))160p.sync_with_ping()161self.log.info("No more than {} requests should be seen within {} seconds after announcement".format(MAX_PEER_TX_REQUEST_IN_FLIGHT, NONPREF_PEER_TX_DELAY + OVERLOADED_PEER_TX_DELAY - 1))162self.nodes[0].setmocktime(mock_time + NONPREF_PEER_TX_DELAY + OVERLOADED_PEER_TX_DELAY - 1)163p.sync_with_ping()164with p2p_lock:165assert_equal(p.tx_getdata_count, MAX_PEER_TX_REQUEST_IN_FLIGHT)166self.log.info("If we wait {} seconds after announcement, we should eventually get more requests".format(NONPREF_PEER_TX_DELAY + OVERLOADED_PEER_TX_DELAY))167self.nodes[0].setmocktime(mock_time + NONPREF_PEER_TX_DELAY + OVERLOADED_PEER_TX_DELAY)168p.wait_until(lambda: p.tx_getdata_count == len(txids))170def test_expiry_fallback(self):171self.log.info('Check that expiry will select another peer for download')172WTXID = 0xffaa173peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())174peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())175for p in [peer1, peer2]:176p.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))177# One of the peers is asked for the tx178peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)179with p2p_lock:180_peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)181assert_equal(peer_fallback.tx_getdata_count, 0)182self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to _peer_expiry to expire183peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)184self.restart_node(0) # reset mocktime186def test_disconnect_fallback(self):187self.log.info('Check that disconnect will select another peer for download')188WTXID = 0xffbb189peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())190peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())191for p in [peer1, peer2]:192p.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))193# One of the peers is asked for the tx194peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)195with p2p_lock:196peer_disconnect, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)197assert_equal(peer_fallback.tx_getdata_count, 0)198peer_disconnect.peer_disconnect()199peer_disconnect.wait_for_disconnect()200peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)202def test_notfound_fallback(self):203self.log.info('Check that notfounds will select another peer for download immediately')204WTXID = 0xffdd205peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())206peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())207for p in [peer1, peer2]:208p.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))209# One of the peers is asked for the tx210peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)211with p2p_lock:212peer_notfound, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)213assert_equal(peer_fallback.tx_getdata_count, 0)214peer_notfound.send_and_ping(msg_notfound(vec=[CInv(MSG_WTX, WTXID)])) # Send notfound, so that fallback peer is selected215peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)217def test_preferred_inv(self, connection_type: ConnectionType):218if connection_type == ConnectionType.WHITELIST:219self.log.info('Check invs from preferred (whitelisted) peers are downloaded immediately')220self.restart_node(0, extra_args=['[email protected]'])221elif connection_type == ConnectionType.OUTBOUND:222self.log.info('Check invs from preferred (outbound) peers are downloaded immediately')223self.restart_node(0)224elif connection_type == ConnectionType.INBOUND:225self.log.info('Check invs from non-preferred peers are downloaded after {} s'.format(NONPREF_PEER_TX_DELAY))226self.restart_node(0)227else:228raise Exception("invalid connection_type")230mock_time = int(time.time() + 1)231self.nodes[0].setmocktime(mock_time)233if connection_type == ConnectionType.OUTBOUND:234peer = self.nodes[0].add_outbound_p2p_connection(235TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")236else:237peer = self.nodes[0].add_p2p_connection(TestP2PConn())239peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))240if connection_type != ConnectionType.INBOUND:241peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)242else:243with p2p_lock:244assert_equal(peer.tx_getdata_count, 0)245self.nodes[0].setmocktime(mock_time + NONPREF_PEER_TX_DELAY)246peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)248def test_preferred_tiebreaker_inv(self):249self.log.info("Test that preferred peers are always selected over non-preferred when ready")
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.