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) 2023-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.6import time8from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT9from test_framework.mempool_util import (10create_large_orphan,11tx_in_orphanage,12)13from test_framework.messages import (14CInv,15DEFAULT_ANCESTOR_LIMIT,16MSG_TX,17MSG_WITNESS_TX,18MSG_WTX,19malleate_tx_to_invalid_witness,20msg_getdata,21msg_inv,22msg_notfound,23msg_tx,24tx_from_hex,25)26from test_framework.p2p import (27GETDATA_TX_INTERVAL,28NONPREF_PEER_TX_DELAY,29OVERLOADED_PEER_TX_DELAY,30p2p_lock,31P2PInterface,32P2PTxInvStore,33TXID_RELAY_DELAY,34)35from test_framework.util import (36assert_not_equal,37assert_equal,38)39from test_framework.test_framework import BitcoinTestFramework40from test_framework.wallet import (41MiniWallet,42MiniWalletMode,43)45# Time to bump forward (using setmocktime) before waiting for the node to send getdata(tx) in response46# to an inv(tx), in seconds. This delay includes all possible delays + 1, so it should only be used47# when the value of the delay is not interesting. If we want to test that the node waits x seconds48# for one peer and y seconds for another, use specific values instead.49TXREQUEST_TIME_SKIP = NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY + OVERLOADED_PEER_TX_DELAY + 151def cleanup(func):52def wrapper(self):53func(self)55# Clear mempool56self.generate(self.nodes[0], 1)57self.nodes[0].disconnect_p2ps()58# Check that mempool and orphanage have been cleared59self.wait_until(lambda: len(self.nodes[0].getorphantxs()) == 0)60assert_equal(0, len(self.nodes[0].getrawmempool()))62self.restart_node(0, extra_args=["-persistmempool=0"])63# Allow use of bumpmocktime again64self.nodes[0].setmocktime(int(time.time()))65self.wallet.rescan_utxos(include_mempool=True)66return wrapper68class PeerTxRelayer(P2PTxInvStore):69"""A P2PTxInvStore that also remembers all of the getdata and tx messages it receives."""70def __init__(self, wtxidrelay=True):71super().__init__(wtxidrelay=wtxidrelay)72self._tx_received = []73self._getdata_received = []75@property76def tx_received(self):77with p2p_lock:78return self._tx_received80@property81def getdata_received(self):82with p2p_lock:83return self._getdata_received85def on_tx(self, message):86self._tx_received.append(message)88def on_getdata(self, message):89self._getdata_received.append(message)91def wait_for_parent_requests(self, txids):92"""Wait for requests for missing parents by txid with witness data (MSG_WITNESS_TX or93WitnessTx). Requires that the getdata message match these txids exactly; all txids must be94requested and no additional requests are allowed."""95def test_function():96last_getdata = self.last_message.get('getdata')97if not last_getdata:98return False99return len(last_getdata.inv) == len(txids) and all([item.type == MSG_WITNESS_TX and item.hash in txids for item in last_getdata.inv])100self.wait_until(test_function, timeout=10)102def assert_no_immediate_response(self, message):103"""Check that the node does not immediately respond to this message with any of getdata,104inv, tx. The node may respond later.105"""106prev_lastmessage = self.last_message107self.send_and_ping(message)108after_lastmessage = self.last_message109for msgtype in ["getdata", "inv", "tx"]:110if msgtype not in prev_lastmessage:111assert msgtype not in after_lastmessage112else:113assert_equal(prev_lastmessage[msgtype], after_lastmessage[msgtype])115def assert_never_requested(self, txhash):116"""Check that the node has never sent us a getdata for this hash (int type)"""117self.sync_with_ping()118for getdata in self.getdata_received:119for request in getdata.inv:120assert_not_equal(request.hash, txhash)122class OrphanHandlingTest(BitcoinTestFramework):123def set_test_params(self):124self.num_nodes = 1125self.extra_args = [[]]127def create_parent_and_child(self):128"""Create package with 1 parent and 1 child, normal fees (no cpfp)."""129parent = self.wallet.create_self_transfer()130child = self.wallet.create_self_transfer(utxo_to_spend=parent['new_utxo'])131return child["tx"].wtxid_hex, child["tx"], parent["tx"]133def relay_transaction(self, peer, tx):134"""Relay transaction using MSG_WTX"""135wtxid = tx.wtxid_int136peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=wtxid)]))137self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)138peer.wait_for_getdata([wtxid])139peer.send_and_ping(msg_tx(tx))141@cleanup142def test_arrival_timing_orphan(self):143self.log.info("Test missing parents that arrive during delay are not requested")144node = self.nodes[0]145tx_parent_arrives = self.wallet.create_self_transfer()146tx_parent_doesnt_arrive = self.wallet.create_self_transfer()147# Fake orphan spends nonexistent outputs of the two parents148tx_fake_orphan = self.wallet.create_self_transfer_multi(utxos_to_spend=[149{"txid": tx_parent_doesnt_arrive["txid"], "vout": 10, "value": tx_parent_doesnt_arrive["new_utxo"]["value"]},150{"txid": tx_parent_arrives["txid"], "vout": 10, "value": tx_parent_arrives["new_utxo"]["value"]}151])153peer_spy = node.add_p2p_connection(PeerTxRelayer())154peer_normal = node.add_p2p_connection(PeerTxRelayer())155# This transaction is an orphan because it is missing inputs. It is a "fake" orphan that the156# spy peer has crafted to learn information about tx_parent_arrives even though it isn't157# able to spend a real output of it, but it could also just be a normal, real child tx.158# The node should not immediately respond with a request for orphan parents.159# Also, no request should be sent later because it will be resolved by160# the time the request is scheduled to be sent.161peer_spy.assert_no_immediate_response(msg_tx(tx_fake_orphan["tx"]))163# Node receives transaction. It attempts to obfuscate the exact timing at which this164# transaction entered its mempool. Send unsolicited because otherwise we need to wait for165# request delays.166peer_normal.send_and_ping(msg_tx(tx_parent_arrives["tx"]))167assert tx_parent_arrives["txid"] in node.getrawmempool()169# Spy peer should not be able to query the node for the parent yet, since it hasn't been170# announced / insufficient time has elapsed.171parent_inv = CInv(t=MSG_WTX, h=tx_parent_arrives["tx"].wtxid_int)172assert_equal(len(peer_spy.get_invs()), 0)173peer_spy.assert_no_immediate_response(msg_getdata([parent_inv]))175# Request would be scheduled with this delay because it is not a preferred relay peer.176self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)177peer_spy.assert_never_requested(int(tx_parent_arrives["txid"], 16))178peer_spy.assert_never_requested(int(tx_parent_doesnt_arrive["txid"], 16))179# Request would be scheduled with this delay because it is by txid.180self.nodes[0].bumpmocktime(TXID_RELAY_DELAY)181peer_spy.wait_for_parent_requests([int(tx_parent_doesnt_arrive["txid"], 16)])182peer_spy.assert_never_requested(int(tx_parent_arrives["txid"], 16))184@cleanup185def test_orphan_rejected_parents_exceptions(self):186node = self.nodes[0]187peer1 = node.add_p2p_connection(PeerTxRelayer())188peer2 = node.add_p2p_connection(PeerTxRelayer())190self.log.info("Test orphan handling when a nonsegwit parent is known to be invalid")191parent_overly_large_nonsegwit = self.wallet_nonsegwit.create_self_transfer(target_vsize=int(MAX_STANDARD_TX_WEIGHT / 4) + 1)192assert_equal(parent_overly_large_nonsegwit["txid"], parent_overly_large_nonsegwit["tx"].wtxid_hex)193parent_other = self.wallet_nonsegwit.create_self_transfer()194child_nonsegwit = self.wallet_nonsegwit.create_self_transfer_multi(195utxos_to_spend=[parent_other["new_utxo"], parent_overly_large_nonsegwit["new_utxo"]])197# Relay the parent. It should be rejected (and not reconsiderable) because it violated size limitations.198self.relay_transaction(peer1, parent_overly_large_nonsegwit["tx"])199assert parent_overly_large_nonsegwit["txid"] not in node.getrawmempool()201# Relay the child. It should not be accepted because it has missing inputs.202# Its parent should not be requested because its hash (txid == wtxid) has been added to the rejection filter.203self.relay_transaction(peer2, child_nonsegwit["tx"])204assert child_nonsegwit["txid"] not in node.getrawmempool()205assert not tx_in_orphanage(node, child_nonsegwit["tx"])207# No parents are requested.208self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL)209peer1.assert_never_requested(int(parent_other["txid"], 16))210peer2.assert_never_requested(int(parent_other["txid"], 16))211peer2.assert_never_requested(int(parent_overly_large_nonsegwit["txid"], 16))213self.log.info("Test orphan handling when a segwit parent was invalid but may be retried with another witness")214parent_low_fee = self.wallet.create_self_transfer(fee_rate=0)215child_low_fee = self.wallet.create_self_transfer(utxo_to_spend=parent_low_fee["new_utxo"])217# Relay the low fee parent. It should not be accepted.218self.relay_transaction(peer1, parent_low_fee["tx"])219assert parent_low_fee["txid"] not in node.getrawmempool()221# Relay the child. It should not be accepted because it has missing inputs.222self.relay_transaction(peer2, child_low_fee["tx"])223assert child_low_fee["txid"] not in node.getrawmempool()224assert tx_in_orphanage(node, child_low_fee["tx"])226# The parent should be requested because even though the txid commits to the fee, it doesn't227# commit to the feerate. Delayed because it's by txid and this is not a preferred relay peer.228self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)229peer2.wait_for_getdata([parent_low_fee["tx"].txid_int])231self.log.info("Test orphan handling when a parent was previously downloaded with witness stripped")232parent_normal = self.wallet.create_self_transfer()233parent1_witness_stripped = tx_from_hex(parent_normal["tx"].serialize_without_witness().hex())234child_invalid_witness = self.wallet.create_self_transfer(utxo_to_spend=parent_normal["new_utxo"])236# Relay the parent with witness stripped. It should not be accepted.237self.relay_transaction(peer1, parent1_witness_stripped)238assert_equal(parent_normal["txid"], parent1_witness_stripped.txid_hex)239assert parent1_witness_stripped.txid_hex not in node.getrawmempool()241# Relay the child. It should not be accepted because it has missing inputs.242self.relay_transaction(peer2, child_invalid_witness["tx"])243assert child_invalid_witness["txid"] not in node.getrawmempool()244assert tx_in_orphanage(node, child_invalid_witness["tx"])246# The parent should be requested since the unstripped wtxid would differ. Delayed because247# it's by txid and this is not a preferred relay peer.248self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)249peer2.wait_for_getdata([parent_normal["tx"].txid_int])
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.