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# parent_normal can be relayed again even though parent1_witness_stripped was rejected252self.relay_transaction(peer1, parent_normal["tx"])253assert_equal(set(node.getrawmempool()), set([parent_normal["txid"], child_invalid_witness["txid"]]))255@cleanup256def test_orphan_multiple_parents(self):257node = self.nodes[0]258peer = node.add_p2p_connection(PeerTxRelayer())260self.log.info("Test orphan parent requests with a mixture of confirmed, in-mempool and missing parents")261# This UTXO confirmed a long time ago.262utxo_conf_old = self.wallet.send_self_transfer(from_node=node)["new_utxo"]263txid_conf_old = utxo_conf_old["txid"]264self.generate(self.wallet, 10)266# Create a fake reorg to trigger BlockDisconnected, which resets the rolling bloom filter.267# The alternative is to mine thousands of transactions to push it out of the filter.268last_block = node.getbestblockhash()269node.invalidateblock(last_block)270node.preciousblock(last_block)271node.syncwithvalidationinterfacequeue()273# This UTXO confirmed recently.274utxo_conf_recent = self.wallet.send_self_transfer(from_node=node)["new_utxo"]275self.generate(node, 1)277# This UTXO is unconfirmed and in the mempool.278assert_equal(len(node.getrawmempool()), 0)279mempool_tx = self.wallet.send_self_transfer(from_node=node)280utxo_unconf_mempool = mempool_tx["new_utxo"]282# This UTXO is unconfirmed and missing.283missing_tx = self.wallet.create_self_transfer()284utxo_unconf_missing = missing_tx["new_utxo"]285assert missing_tx["txid"] not in node.getrawmempool()287orphan = self.wallet.create_self_transfer_multi(utxos_to_spend=[utxo_conf_old,288utxo_conf_recent, utxo_unconf_mempool, utxo_unconf_missing])290self.relay_transaction(peer, orphan["tx"])291self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)292peer.sync_with_ping()293assert tx_in_orphanage(node, orphan["tx"])294assert_equal(len(peer.last_message["getdata"].inv), 2)295peer.wait_for_parent_requests([int(txid_conf_old, 16), int(missing_tx["txid"], 16)])297# Even though the peer would send a notfound for the "old" confirmed transaction, the node298# doesn't give up on the orphan. Once all of the missing parents are received, it should be299# submitted to mempool.300peer.send_without_ping(msg_notfound(vec=[CInv(MSG_WITNESS_TX, int(txid_conf_old, 16))]))301# Sync with ping to ensure orphans are reconsidered302peer.send_and_ping(msg_tx(missing_tx["tx"]))303assert_equal(node.getmempoolentry(orphan["txid"])["ancestorcount"], 3)305@cleanup306def test_orphans_overlapping_parents(self):307node = self.nodes[0]308# In the process of relaying inflight_parent_AB309peer_txrequest = node.add_p2p_connection(PeerTxRelayer())310# Sends the orphans311peer_orphans = node.add_p2p_connection(PeerTxRelayer())313confirmed_utxos = [self.wallet_nonsegwit.get_utxo() for _ in range(4)]314assert all([utxo["confirmations"] > 0 for utxo in confirmed_utxos])315self.log.info("Test handling of multiple orphans with missing parents that are already being requested")316# Parent of child_A only317missing_parent_A = self.wallet_nonsegwit.create_self_transfer(utxo_to_spend=confirmed_utxos[0])318# Parents of child_A and child_B319missing_parent_AB = self.wallet_nonsegwit.create_self_transfer(utxo_to_spend=confirmed_utxos[1])320inflight_parent_AB = self.wallet_nonsegwit.create_self_transfer(utxo_to_spend=confirmed_utxos[2])321# Parent of child_B only322missing_parent_B = self.wallet_nonsegwit.create_self_transfer(utxo_to_spend=confirmed_utxos[3])323child_A = self.wallet_nonsegwit.create_self_transfer_multi(324utxos_to_spend=[missing_parent_A["new_utxo"], missing_parent_AB["new_utxo"], inflight_parent_AB["new_utxo"]]325)326child_B = self.wallet_nonsegwit.create_self_transfer_multi(327utxos_to_spend=[missing_parent_B["new_utxo"], missing_parent_AB["new_utxo"], inflight_parent_AB["new_utxo"]]328)330# The wtxid and txid need to be the same for the node to recognize that the missing input331# and in-flight request for inflight_parent_AB are the same transaction.332assert_equal(inflight_parent_AB["txid"], inflight_parent_AB["wtxid"])334# Announce inflight_parent_AB and wait for getdata335peer_txrequest.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=inflight_parent_AB["tx"].wtxid_int)]))336self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)337peer_txrequest.wait_for_getdata([inflight_parent_AB["tx"].wtxid_int])339self.log.info("Test that the node does not request a parent if it has an in-flight txrequest")340# Relay orphan child_A341self.relay_transaction(peer_orphans, child_A["tx"])342self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)343assert tx_in_orphanage(node, child_A["tx"])344# There are 3 missing parents. missing_parent_A and missing_parent_AB should be requested.345# But inflight_parent_AB should not, because there is already an in-flight request for it.346peer_orphans.wait_for_parent_requests([int(missing_parent_A["txid"], 16), int(missing_parent_AB["txid"], 16)])348self.log.info("Test that the node does not request a parent if it has an in-flight orphan parent request")349# Relay orphan child_B350self.relay_transaction(peer_orphans, child_B["tx"])351self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)352assert tx_in_orphanage(node, child_B["tx"])353# Only missing_parent_B should be requested. Not inflight_parent_AB or missing_parent_AB354# because they are already being requested from peer_txrequest and peer_orphans respectively.355peer_orphans.wait_for_parent_requests([int(missing_parent_B["txid"], 16)])356peer_orphans.assert_never_requested(int(inflight_parent_AB["txid"], 16))358# But inflight_parent_AB will be requested eventually if original peer doesn't respond359node.bumpmocktime(GETDATA_TX_INTERVAL)360peer_orphans.wait_for_parent_requests([int(inflight_parent_AB["txid"], 16)])362@cleanup363def test_orphan_of_orphan(self):364node = self.nodes[0]365peer = node.add_p2p_connection(PeerTxRelayer())367self.log.info("Test handling of an orphan with a parent who is another orphan")368missing_grandparent = self.wallet_nonsegwit.create_self_transfer()369missing_parent_orphan = self.wallet_nonsegwit.create_self_transfer(utxo_to_spend=missing_grandparent["new_utxo"])370missing_parent = self.wallet_nonsegwit.create_self_transfer()371orphan = self.wallet_nonsegwit.create_self_transfer_multi(utxos_to_spend=[missing_parent["new_utxo"], missing_parent_orphan["new_utxo"]])373# The node should put missing_parent_orphan into the orphanage and request missing_grandparent374self.relay_transaction(peer, missing_parent_orphan["tx"])375self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)376assert tx_in_orphanage(node, missing_parent_orphan["tx"])377peer.wait_for_parent_requests([int(missing_grandparent["txid"], 16)])379# The node should put the orphan into the orphanage and request missing_parent, skipping380# missing_parent_orphan because it already has it in the orphanage.381self.relay_transaction(peer, orphan["tx"])382self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)383assert tx_in_orphanage(node, orphan["tx"])384peer.wait_for_parent_requests([int(missing_parent["txid"], 16)])386@cleanup387def test_orphan_parent_confirmed(self):388node = self.nodes[0]389peer = node.add_p2p_connection(PeerTxRelayer())391parent = self.wallet.create_self_transfer(fee_rate=0)392child = self.wallet.create_self_transfer(utxo_to_spend=parent["new_utxo"])394self.log.info("Test orphan reconsideration when its missing parent is confirmed")395self.relay_transaction(peer, child["tx"])396assert tx_in_orphanage(node, child["tx"])398# Withhold the requested parent so it can only become available through the block.399node.bumpmocktime(TXREQUEST_TIME_SKIP)400peer.wait_for_parent_requests([parent["tx"].txid_int])401assert_equal(node.getrawmempool(), [])403self.generateblock(404node,405output=self.wallet.get_address(),406transactions=[parent["hex"]],407)408self.wait_until(lambda: child["txid"] in node.getrawmempool())409assert not tx_in_orphanage(node, child["tx"])411@cleanup412def test_orphan_inherit_rejection(self):413node = self.nodes[0]414peer1 = node.add_p2p_connection(PeerTxRelayer())415peer2 = node.add_p2p_connection(PeerTxRelayer())416peer3 = node.add_p2p_connection(PeerTxRelayer(wtxidrelay=False))418self.log.info("Test that an orphan with rejected parents, along with any descendants, cannot be retried with an alternate witness")419parent_overly_large_nonsegwit = self.wallet_nonsegwit.create_self_transfer(target_vsize=int(MAX_STANDARD_TX_WEIGHT / 4) + 1)420assert_equal(parent_overly_large_nonsegwit["txid"], parent_overly_large_nonsegwit["tx"].wtxid_hex)421child = self.wallet.create_self_transfer(utxo_to_spend=parent_overly_large_nonsegwit["new_utxo"])422grandchild = self.wallet.create_self_transfer(utxo_to_spend=child["new_utxo"])423assert_not_equal(child["txid"], child["tx"].wtxid_hex)424assert_not_equal(grandchild["txid"], grandchild["tx"].wtxid_hex)426# Relay the parent. It should be rejected because it pays 0 fees.427self.relay_transaction(peer1, parent_overly_large_nonsegwit["tx"])428assert parent_overly_large_nonsegwit["txid"] not in node.getrawmempool()430# Relay the child. It should be rejected for having missing parents, and this rejection is431# cached by txid and wtxid.432self.relay_transaction(peer1, child["tx"])433assert_equal(0, len(node.getrawmempool()))434assert not tx_in_orphanage(node, child["tx"])435peer1.assert_never_requested(parent_overly_large_nonsegwit["txid"])437# Grandchild should also not be kept in orphanage because its parent has been rejected.438self.relay_transaction(peer2, grandchild["tx"])439assert_equal(0, len(node.getrawmempool()))440assert not tx_in_orphanage(node, grandchild["tx"])441peer2.assert_never_requested(child["txid"])442peer2.assert_never_requested(child["tx"].wtxid_hex)444# The child should never be requested, even if announced again with potentially different witness.445# Sync with ping to ensure orphans are reconsidered446peer3.send_and_ping(msg_inv([CInv(t=MSG_TX, h=int(child["txid"], 16))]))447self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)448peer3.assert_never_requested(child["txid"])450@cleanup451def test_same_txid_orphan(self):452self.log.info("Check what happens when orphan with same txid is already in orphanage")453node = self.nodes[0]455tx_parent = self.wallet.create_self_transfer()457# Create the real child458tx_child = self.wallet.create_self_transfer(utxo_to_spend=tx_parent["new_utxo"])460# Create a fake version of the child461tx_orphan_bad_wit = malleate_tx_to_invalid_witness(tx_child)463bad_peer = node.add_p2p_connection(P2PInterface())464honest_peer = node.add_p2p_connection(P2PInterface())466# 1. Fake orphan is received first. It is missing an input.467bad_peer.send_and_ping(msg_tx(tx_orphan_bad_wit))468assert tx_in_orphanage(node, tx_orphan_bad_wit)470# 2. Node requests the missing parent by txid.471parent_txid_int = int(tx_parent["txid"], 16)472node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)473bad_peer.wait_for_getdata([parent_txid_int])475# 3. Honest peer relays the real child, which is also missing parents and should be placed476# in the orphanage.477with node.assert_debug_log(["missingorspent"]):478honest_peer.send_and_ping(msg_tx(tx_child["tx"]))479assert tx_in_orphanage(node, tx_child["tx"])481# Time out the previous request for the parent (node will not request the same transaction482# from multiple nodes at the same time)483node.bumpmocktime(GETDATA_TX_INTERVAL)485# 4. The parent is requested. Honest peer sends it.486honest_peer.wait_for_getdata([parent_txid_int])487# Sync with ping to ensure orphans are reconsidered488honest_peer.send_and_ping(msg_tx(tx_parent["tx"]))490# 5. After parent is accepted, orphans should be reconsidered.491# The real child should be accepted and the fake one rejected.492node_mempool = node.getrawmempool()493assert tx_parent["txid"] in node_mempool494assert tx_child["txid"] in node_mempool495assert_equal(node.getmempoolentry(tx_child["txid"])["wtxid"], tx_child["wtxid"])497@cleanup498def test_same_txid_orphan_of_orphan(self):499self.log.info("Check what happens when orphan's parent with same txid is already in orphanage")500node = self.nodes[0]
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.