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// Copyright (c) 2011-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <addresstype.h>6#include <consensus/validation.h>7#include <net_processing.h>8#include <node/txdownloadman_impl.h>9#include <primitives/transaction.h>10#include <script/script.h>11#include <test/util/common.h>12#include <test/util/random.h>13#include <test/util/setup_common.h>14#include <validation.h>16#include <array>18#include <boost/test/unit_test.hpp>20BOOST_AUTO_TEST_SUITE(txdownload_tests)22struct Behaviors {23bool m_txid_in_rejects;24bool m_wtxid_in_rejects;25bool m_txid_in_rejects_recon;26bool m_wtxid_in_rejects_recon;27bool m_keep_for_compact;28bool m_ignore_inv_txid;29bool m_ignore_inv_wtxid;31// Constructor. We are passing and casting ints because they are more readable in a table (see expected_behaviors).32Behaviors(bool txid_rejects, bool wtxid_rejects, bool txid_recon, bool wtxid_recon, bool keep, bool txid_inv, bool wtxid_inv) :33m_txid_in_rejects(txid_rejects),34m_wtxid_in_rejects(wtxid_rejects),35m_txid_in_rejects_recon(txid_recon),36m_wtxid_in_rejects_recon(wtxid_recon),37m_keep_for_compact(keep),38m_ignore_inv_txid(txid_inv),39m_ignore_inv_wtxid(wtxid_inv)40{}42void CheckEqual(const Behaviors& other, bool segwit)43{44BOOST_CHECK_EQUAL(other.m_wtxid_in_rejects, m_wtxid_in_rejects);45BOOST_CHECK_EQUAL(other.m_wtxid_in_rejects_recon, m_wtxid_in_rejects_recon);46BOOST_CHECK_EQUAL(other.m_keep_for_compact, m_keep_for_compact);47BOOST_CHECK_EQUAL(other.m_ignore_inv_wtxid, m_ignore_inv_wtxid);49// false negatives for nonsegwit transactions, since txid == wtxid.50if (segwit) {51BOOST_CHECK_EQUAL(other.m_txid_in_rejects, m_txid_in_rejects);52BOOST_CHECK_EQUAL(other.m_txid_in_rejects_recon, m_txid_in_rejects_recon);53BOOST_CHECK_EQUAL(other.m_ignore_inv_txid, m_ignore_inv_txid);54}55}56};58// Map from failure reason to expected behavior for a segwit tx that fails59// Txid and Wtxid are assumed to be different here. For a nonsegwit transaction, use the wtxid results.60static std::map<TxValidationResult, Behaviors> expected_behaviors{61{TxValidationResult::TX_CONSENSUS, {/*txid_rejects*/0,/*wtxid_rejects*/1,/*txid_recon*/0,/*wtxid_recon*/0,/*keep*/1,/*txid_inv*/0,/*wtxid_inv*/1}},62{TxValidationResult::TX_INPUTS_NOT_STANDARD, { 1, 1, 0, 0, 1, 1, 1}},63{TxValidationResult::TX_NOT_STANDARD, { 0, 1, 0, 0, 1, 0, 1}},64{TxValidationResult::TX_MISSING_INPUTS, { 0, 0, 0, 0, 1, 0, 1}},65{TxValidationResult::TX_PREMATURE_SPEND, { 0, 1, 0, 0, 1, 0, 1}},66{TxValidationResult::TX_WITNESS_MUTATED, { 0, 1, 0, 0, 1, 0, 1}},67{TxValidationResult::TX_WITNESS_STRIPPED, { 0, 0, 0, 0, 0, 0, 0}},68{TxValidationResult::TX_CONFLICT, { 0, 1, 0, 0, 1, 0, 1}},69{TxValidationResult::TX_MEMPOOL_POLICY, { 0, 1, 0, 0, 1, 0, 1}},70{TxValidationResult::TX_NO_MEMPOOL, { 0, 1, 0, 0, 1, 0, 1}},71{TxValidationResult::TX_RECONSIDERABLE, { 0, 0, 0, 1, 1, 0, 1}},72{TxValidationResult::TX_UNKNOWN, { 0, 1, 0, 0, 1, 0, 1}},73};75static bool CheckOrphanBehavior(node::TxDownloadManagerImpl& txdownload_impl, const CTransactionRef& tx, const node::RejectedTxTodo& ret, std::string& err_msg,76bool expect_orphan, bool expect_keep, unsigned int expected_parents)77{78// Missing inputs can never result in a PackageToValidate.79if (ret.m_package_to_validate.has_value()) {80err_msg = strprintf("returned a PackageToValidate on missing inputs");81return false;82}84if (expect_orphan != txdownload_impl.m_orphanage->HaveTx(tx->GetWitnessHash())) {85err_msg = strprintf("unexpectedly %s tx in orphanage", expect_orphan ? "did not find" : "found");86return false;87}88if (expect_keep != ret.m_should_add_extra_compact_tx) {89err_msg = strprintf("unexpectedly returned %s add to vExtraTxnForCompact", expect_keep ? "should not" : "should");90return false;91}92if (expected_parents != ret.m_unique_parents.size()) {93err_msg = strprintf("expected %u unique_parents, got %u", expected_parents, ret.m_unique_parents.size());94return false;95}96return true;97}99static CTransactionRef CreatePlaceholderTx(bool segwit)100{101// Each tx returned from here spends the previous one.102static Txid prevout_hash{};104CMutableTransaction mtx;105mtx.vin.emplace_back(prevout_hash, 0);106// This makes txid != wtxid107if (segwit) mtx.vin[0].scriptWitness.stack.push_back({1});108mtx.vout.emplace_back(CENT, CScript());109auto ptx = MakeTransactionRef(mtx);110prevout_hash = ptx->GetHash();111return ptx;112}114BOOST_FIXTURE_TEST_CASE(tx_rejection_types, TestChain100Setup)115{116CTxMemPool& pool = *Assert(m_node.mempool);117node::TxDownloadOptions DEFAULT_OPTS{.m_mempool = pool, .m_deterministic_txrequest = true};119// A new TxDownloadManagerImpl is created for each tx so we can just reuse the same one.120TxValidationState state;121NodeId nodeid{0};122std::chrono::microseconds now{GetTime()};123node::TxDownloadConnectionInfo connection_info{/*m_preferred=*/false, /*m_relay_permissions=*/false, /*m_wtxid_relay=*/true};125for (const auto segwit_parent : {true, false}) {126for (const auto segwit_child : {true, false}) {127const auto ptx_parent = CreatePlaceholderTx(segwit_parent);128const auto ptx_child = CreatePlaceholderTx(segwit_child);129const auto& parent_txid = ptx_parent->GetHash();130const auto& parent_wtxid = ptx_parent->GetWitnessHash();131const auto& child_txid = ptx_child->GetHash();132const auto& child_wtxid = ptx_child->GetWitnessHash();134for (const auto& [result, expected_behavior] : expected_behaviors) {135node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS};136txdownload_impl.ConnectedPeer(nodeid, connection_info);137// Parent failure138state.Invalid(result, "");139const auto& [keep, unique_txids, package_to_validate] = txdownload_impl.MempoolRejectedTx(ptx_parent, state, nodeid, /*first_time_failure=*/true);141// No distinction between txid and wtxid caching for nonsegwit transactions, so only test these specific142// behaviors for segwit transactions.143Behaviors actual_behavior{144/*txid_rejects=*/txdownload_impl.RecentRejectsFilter().contains(parent_txid.ToUint256()),145/*wtxid_rejects=*/txdownload_impl.RecentRejectsFilter().contains(parent_wtxid.ToUint256()),146/*txid_recon=*/txdownload_impl.RecentRejectsReconsiderableFilter().contains(parent_txid.ToUint256()),147/*wtxid_recon=*/txdownload_impl.RecentRejectsReconsiderableFilter().contains(parent_wtxid.ToUint256()),148/*keep=*/keep,149/*txid_inv=*/txdownload_impl.AddTxAnnouncement(nodeid, parent_txid, now),150/*wtxid_inv=*/txdownload_impl.AddTxAnnouncement(nodeid, parent_wtxid, now),151};152BOOST_TEST_MESSAGE("Testing behavior for " << result << (segwit_parent ? " segwit " : " nonsegwit"));153actual_behavior.CheckEqual(expected_behavior, /*segwit=*/segwit_parent);155// Later, a child of this transaction fails for missing inputs156state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "");157txdownload_impl.MempoolRejectedTx(ptx_child, state, nodeid, /*first_time_failure=*/true);159// If parent (by txid) was rejected, child is too.160const bool parent_txid_rejected{segwit_parent ? expected_behavior.m_txid_in_rejects : expected_behavior.m_wtxid_in_rejects};161BOOST_CHECK_EQUAL(parent_txid_rejected, txdownload_impl.RecentRejectsFilter().contains(child_txid.ToUint256()));162BOOST_CHECK_EQUAL(parent_txid_rejected, txdownload_impl.RecentRejectsFilter().contains(child_wtxid.ToUint256()));164// Unless rejected, the child should be in orphanage.165BOOST_CHECK_EQUAL(!parent_txid_rejected, txdownload_impl.m_orphanage->HaveTx(ptx_child->GetWitnessHash()));166}167}168}169}171BOOST_FIXTURE_TEST_CASE(handle_missing_inputs, TestChain100Setup)172{173CTxMemPool& pool = *Assert(m_node.mempool);174node::TxDownloadOptions DEFAULT_OPTS{.m_mempool = pool, .m_deterministic_txrequest = true};175NodeId nodeid{1};176node::TxDownloadConnectionInfo DEFAULT_CONN{/*m_preferred=*/false, /*m_relay_permissions=*/false, /*m_wtxid_relay=*/true};178// We need mature coinbases179mineBlocks(20);181// Transactions with missing inputs are treated differently depending on how much we know about182// their parents.183CKey wallet_key = GenerateRandomKey();184CScript destination = GetScriptForDestination(PKHash(wallet_key.GetPubKey()));185// Amount for spending coinbase in a 1-in-1-out tx, at depth n, each time deducting 1000 from the amount as fees.186CAmount amount_depth_1{50 * COIN - 1000};187CAmount amount_depth_2{amount_depth_1 - 1000};188// Amount for spending coinbase in a 1-in-2-out tx, deducting 1000 in fees189CAmount amount_split_half{25 * COIN - 500};190int test_chain_height{100};192TxValidationState state_orphan;193state_orphan.Invalid(TxValidationResult::TX_MISSING_INPUTS, "");195// Transactions are not all submitted to mempool. Conserve the number of m_coinbase_txns we196// consume, and only increment this index number when we would conflict with an existing197// mempool transaction.198size_t coinbase_idx{0};200for (int decisions = 0; decisions < (1 << 4); ++decisions) {201auto mtx_single_parent = CreateValidMempoolTransaction(m_coinbase_txns[coinbase_idx], /*input_vout=*/0, test_chain_height, coinbaseKey, destination, amount_depth_1, /*submit=*/false);202auto single_parent = MakeTransactionRef(mtx_single_parent);204auto mtx_orphan = CreateValidMempoolTransaction(single_parent, /*input_vout=*/0, test_chain_height, wallet_key, destination, amount_depth_2, /*submit=*/false);205auto orphan = MakeTransactionRef(mtx_orphan);207node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS};208txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN);210// Each bit of decisions tells us whether the parent is in a particular cache.211// It is definitely possible for a transaction to be in multiple caches. For example, it212// may have both a low feerate and found to violate some mempool policy when validated213// in a 1p1c.214const bool parent_recent_rej(decisions & 1);215const bool parent_recent_rej_recon((decisions >> 1) & 1);216const bool parent_recent_conf((decisions >> 2) & 1);217const bool parent_in_mempool((decisions >> 3) & 1);219if (parent_recent_rej) txdownload_impl.RecentRejectsFilter().insert(single_parent->GetHash().ToUint256());220if (parent_recent_rej_recon) txdownload_impl.RecentRejectsReconsiderableFilter().insert(single_parent->GetHash().ToUint256());221if (parent_recent_conf) txdownload_impl.RecentConfirmedTransactionsFilter().insert(single_parent->GetHash().ToUint256());222if (parent_in_mempool) {223const auto mempool_result = WITH_LOCK(::cs_main, return m_node.chainman->ProcessTransaction(single_parent));224BOOST_CHECK(mempool_result.m_result_type == MempoolAcceptResult::ResultType::VALID);225coinbase_idx += 1;226assert(coinbase_idx < m_coinbase_txns.size());227}229// Whether or not the transaction is added as an orphan depends solely on whether or not230// it's in RecentRejectsFilter. Specifically, the parent is allowed to be in231// RecentRejectsReconsiderableFilter, but it cannot be in RecentRejectsFilter.232const bool expect_keep_orphan = !parent_recent_rej;233const unsigned int expected_parents = parent_recent_rej || parent_recent_conf || parent_in_mempool ? 0 : 1;234// If we don't expect to keep the orphan then expected_parents is 0.235// !expect_keep_orphan => (expected_parents == 0)236BOOST_CHECK(expect_keep_orphan || expected_parents == 0);237const auto ret_1p1c = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true);238std::string err_msg;239const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_1p1c, err_msg,240/*expect_orphan=*/expect_keep_orphan, /*expect_keep=*/true, /*expected_parents=*/expected_parents);241BOOST_CHECK_MESSAGE(ok, err_msg);242}244// Orphan with multiple parents245{246std::vector<CTransactionRef> parents;247std::vector<COutPoint> outpoints;248int32_t num_parents{24};249for (int32_t i = 0; i < num_parents; ++i) {250assert(coinbase_idx < m_coinbase_txns.size());
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.