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.
502tx_grandparent = self.wallet.create_self_transfer()504# Create middle tx (both parent and child) which will be in orphanage.505tx_middle = self.wallet.create_self_transfer(utxo_to_spend=tx_grandparent["new_utxo"])507# Create a fake version of the middle tx508tx_orphan_bad_wit = malleate_tx_to_invalid_witness(tx_middle)510# Create grandchild spending from tx_middle (and spending from tx_orphan_bad_wit since they511# have the same txid).512tx_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_middle["new_utxo"])514bad_peer = node.add_p2p_connection(P2PInterface())515honest_peer = node.add_p2p_connection(P2PInterface())517# 1. Fake orphan is received first. It is missing an input.518bad_peer.send_and_ping(msg_tx(tx_orphan_bad_wit))519assert tx_in_orphanage(node, tx_orphan_bad_wit)521# 2. Node requests missing tx_grandparent by txid.522grandparent_txid_int = int(tx_grandparent["txid"], 16)523node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)524bad_peer.wait_for_getdata([grandparent_txid_int])526# 3. Honest peer relays the grandchild, which is missing a parent. The parent by txid already527# exists in orphanage, but should be re-requested because the node shouldn't assume that the528# witness data is the same. In this case, a same-txid-different-witness transaction exists!529honest_peer.send_and_ping(msg_tx(tx_grandchild["tx"]))530assert tx_in_orphanage(node, tx_grandchild["tx"])531middle_txid_int = int(tx_middle["txid"], 16)532node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)533honest_peer.wait_for_getdata([middle_txid_int])535# 4. Honest peer relays the real child, which is also missing parents and should be placed536# in the orphanage.537honest_peer.send_and_ping(msg_tx(tx_middle["tx"]))538assert tx_in_orphanage(node, tx_middle["tx"])539assert_equal(len(node.getrawmempool()), 0)541# 5. Honest peer sends tx_grandparent542honest_peer.send_and_ping(msg_tx(tx_grandparent["tx"]))544# 6. After parent is accepted, orphans should be reconsidered.545# The real child should be accepted and the fake one rejected.546node_mempool = node.getrawmempool()547assert tx_grandparent["txid"] in node_mempool548assert tx_middle["txid"] in node_mempool549assert tx_grandchild["txid"] in node_mempool550assert_equal(node.getmempoolentry(tx_middle["txid"])["wtxid"], tx_middle["wtxid"])551self.wait_until(lambda: len(node.getorphantxs()) == 0)553@cleanup554def test_orphan_txid_inv(self):555self.log.info("Check node does not ignore announcement with same txid as tx in orphanage")556node = self.nodes[0]558tx_parent = self.wallet.create_self_transfer()560# Create the real child and fake version561tx_child = self.wallet.create_self_transfer(utxo_to_spend=tx_parent["new_utxo"])562tx_orphan_bad_wit = malleate_tx_to_invalid_witness(tx_child)564bad_peer = node.add_p2p_connection(PeerTxRelayer())565# Must not send wtxidrelay because otherwise the inv(TX) will be ignored later566honest_peer = node.add_p2p_connection(P2PInterface(wtxidrelay=False))568# 1. Fake orphan is received first. It is missing an input.569bad_peer.send_and_ping(msg_tx(tx_orphan_bad_wit))570assert tx_in_orphanage(node, tx_orphan_bad_wit)572# 2. Node requests the missing parent by txid.573parent_txid_int = int(tx_parent["txid"], 16)574node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)575bad_peer.wait_for_getdata([parent_txid_int])577# 3. Honest peer announces the real child, by txid (this isn't common but the node should578# still keep track of it).579child_txid_int = int(tx_child["txid"], 16)580honest_peer.send_and_ping(msg_inv([CInv(t=MSG_TX, h=child_txid_int)]))582# 4. The child is requested. Honest peer sends it.583node.bumpmocktime(TXREQUEST_TIME_SKIP)584honest_peer.wait_for_getdata([child_txid_int])585honest_peer.send_and_ping(msg_tx(tx_child["tx"]))586assert tx_in_orphanage(node, tx_child["tx"])588# 5. After first parent request times out, the node sends another one for the missing parent589# of the real orphan child.590node.bumpmocktime(GETDATA_TX_INTERVAL)591honest_peer.wait_for_getdata([parent_txid_int])592honest_peer.send_and_ping(msg_tx(tx_parent["tx"]))594# 6. After parent is accepted, orphans should be reconsidered.595# The real child should be accepted and the fake one rejected. This may happen in either596# order since the message-processing is randomized. If tx_orphan_bad_wit is validated first,597# its consensus error leads to disconnection of bad_peer. If tx_child is validated first,598# tx_orphan_bad_wit is rejected for txn-same-nonwitness-data-in-mempool (no punishment).599node_mempool = node.getrawmempool()600assert tx_parent["txid"] in node_mempool601assert tx_child["txid"] in node_mempool602assert_equal(node.getmempoolentry(tx_child["txid"])["wtxid"], tx_child["wtxid"])603self.wait_until(lambda: len(node.getorphantxs()) == 0)606@cleanup607def test_orphan_handling_prefer_outbound(self):608self.log.info("Test that the node prefers requesting from outbound peers")609node = self.nodes[0]610orphan_wtxid, orphan_tx, parent_tx = self.create_parent_and_child()611orphan_inv = CInv(t=MSG_WTX, h=int(orphan_wtxid, 16))613peer_inbound = node.add_p2p_connection(PeerTxRelayer())614peer_outbound = node.add_outbound_p2p_connection(PeerTxRelayer(), p2p_idx=1)616# Inbound peer relays the transaction.617peer_inbound.send_and_ping(msg_inv([orphan_inv]))618self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)619peer_inbound.wait_for_getdata([int(orphan_wtxid, 16)])621# Both peers send invs for the orphan, so the node can expect both to know its ancestors.622peer_outbound.send_and_ping(msg_inv([orphan_inv]))624peer_inbound.send_and_ping(msg_tx(orphan_tx))626# There should be 1 orphan with 2 announcers (we don't know what their peer IDs are)627orphanage = node.getorphantxs(verbosity=2)628assert_equal(orphanage[0]["wtxid"], orphan_wtxid)629assert_equal(len(orphanage[0]["from"]), 2)631# The outbound peer should be preferred for getting orphan parents632self.nodes[0].bumpmocktime(TXID_RELAY_DELAY)633peer_outbound.wait_for_parent_requests([parent_tx.txid_int])635# There should be no request to the inbound peer636peer_inbound.assert_never_requested(parent_tx.txid_int)638self.log.info("Test that, if the preferred peer doesn't respond, the node sends another request")639self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL)640peer_inbound.sync_with_ping()641peer_inbound.wait_for_parent_requests([parent_tx.txid_int])643@cleanup644def test_maximal_package_protected(self):645self.log.info("Test that a node only announcing a maximally sized ancestor package is protected in orphanage")646self.nodes[0].setmocktime(int(time.time()))647node = self.nodes[0]649peer_normal = node.add_p2p_connection(P2PInterface())650peer_doser = node.add_p2p_connection(P2PInterface())652# Each of the num_peers peers creates a distinct set of orphans653large_orphans = [create_large_orphan() for _ in range(60)]655# Check to make sure these are orphans, within max standard size (to be accepted into the orphanage)656for large_orphan in large_orphans:657testres = node.testmempoolaccept([large_orphan.serialize().hex()])658assert not testres[0]["allowed"]659assert_equal(testres[0]["reject-reason"], "missing-inputs")661num_individual_dosers = 20662self.log.info(f"Connect {num_individual_dosers} peers and send a very large orphan from each one")663# This test assumes that unrequested transactions are processed (skipping inv and664# getdata steps because they require going through request delays)665# Connect 20 peers and have each of them send a large orphan.666for large_orphan in large_orphans[:num_individual_dosers]:667peer_doser_individual = node.add_p2p_connection(P2PInterface())668peer_doser_individual.send_and_ping(msg_tx(large_orphan))669node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY + 1)670peer_doser_individual.wait_for_getdata([large_orphan.vin[0].prevout.hash])672# Make sure that these transactions are going through the orphan handling codepaths.673# Subsequent rounds will not wait for getdata because the time mocking will cause the674# normal package request to time out.675self.wait_until(lambda: len(node.getorphantxs()) == num_individual_dosers)677# Now honest peer will send a maximally sized ancestor package of 24 orphans chaining678# off of a single missing transaction, with a total vsize 404,000Wu679ancestor_package = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_ANCESTOR_LIMIT - 1)680sum_ancestor_package_vsize = sum([tx["tx"].get_vsize() for tx in ancestor_package])681final_tx = self.wallet.create_self_transfer(utxo_to_spend=ancestor_package[-1]["new_utxo"], target_vsize=101000 - sum_ancestor_package_vsize)682ancestor_package.append(final_tx)684# Peer sends all but first tx to fill up orphange with their orphans685for orphan in ancestor_package[1:]:686peer_normal.send_and_ping(msg_tx(orphan["tx"]))688orphan_set = node.getorphantxs()689for orphan in ancestor_package[1:]:690assert orphan["txid"] in orphan_set692# Wait for ultimate parent request (the root ancestor transaction)693parent_txid_int = ancestor_package[0]["tx"].txid_int694node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)696self.wait_until(lambda: "getdata" in peer_normal.last_message and parent_txid_int in [inv.hash for inv in peer_normal.last_message.get("getdata").inv])698self.log.info("Send another round of very large orphans from a DoSy peer")699for large_orphan in large_orphans[num_individual_dosers:]:700peer_doser.send_and_ping(msg_tx(large_orphan))702self.log.info("Provide the top ancestor. The whole package should be re-evaluated after enough time.")703peer_normal.send_and_ping(msg_tx(ancestor_package[0]["tx"]))705# Wait until all transactions have been processed. When the last tx is accepted, it's706# guaranteed to have all ancestors.707self.wait_until(lambda: node.getmempoolentry(final_tx["txid"])["ancestorcount"] == DEFAULT_ANCESTOR_LIMIT)709@cleanup710def test_announcers_before_and_after(self):711self.log.info("Test that the node uses all peers who announced the tx prior to realizing it's an orphan")712node = self.nodes[0]713orphan_wtxid, orphan_tx, parent_tx = self.create_parent_and_child()714orphan_inv = CInv(t=MSG_WTX, h=int(orphan_wtxid, 16))716# Announces before tx is sent, disconnects while node is requesting parents717peer_early_disconnected = node.add_outbound_p2p_connection(PeerTxRelayer(), p2p_idx=3)718# Announces before tx is sent, doesn't respond to parent request719peer_early_unresponsive = node.add_p2p_connection(PeerTxRelayer())721# Announces after tx is sent722peer_late_announcer = node.add_p2p_connection(PeerTxRelayer())724# Both peers send invs for the orphan, so the node can expect both to know its ancestors.725peer_early_disconnected.send_and_ping(msg_inv([orphan_inv]))726self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)727peer_early_disconnected.wait_for_getdata([int(orphan_wtxid, 16)])728peer_early_unresponsive.send_and_ping(msg_inv([orphan_inv]))729peer_early_disconnected.send_and_ping(msg_tx(orphan_tx))731# There should be 1 orphan with 2 announcers (we don't know what their peer IDs are)732orphanage = node.getorphantxs(verbosity=2)733assert_equal(len(orphanage), 1)734assert_equal(orphanage[0]["wtxid"], orphan_wtxid)735assert_equal(len(orphanage[0]["from"]), 2)737# Peer disconnects before responding to request738self.nodes[0].bumpmocktime(TXID_RELAY_DELAY)739peer_early_disconnected.wait_for_parent_requests([parent_tx.txid_int])740peer_early_disconnected.peer_disconnect()742# The orphan should have 1 announcer left after the node finishes disconnecting peer_early_disconnected.743self.wait_until(lambda: len(node.getorphantxs(verbosity=2)[0]["from"]) == 1)745# The node should retry with the other peer that announced the orphan earlier.746# This node's request was additionally delayed because it's an inbound peer.747self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)748peer_early_unresponsive.wait_for_parent_requests([parent_tx.txid_int])750self.log.info("Test that the node uses peers who announce the tx after realizing it's an orphan")
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.