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) 2023-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 <consensus/validation.h>6#include <node/context.h>7#include <node/mempool_args.h>8#include <node/miner.h>9#include <node/txdownloadman.h>10#include <node/txdownloadman_impl.h>11#include <test/fuzz/FuzzedDataProvider.h>12#include <test/fuzz/fuzz.h>13#include <test/fuzz/util.h>14#include <test/fuzz/util/mempool.h>15#include <test/util/mining.h>16#include <test/util/script.h>17#include <test/util/setup_common.h>18#include <test/util/time.h>19#include <test/util/txmempool.h>20#include <txmempool.h>21#include <util/hasher.h>22#include <util/rbf.h>23#include <util/time.h>24#include <validation.h>25#include <validationinterface.h>27namespace {29const TestingSetup* g_setup;31constexpr size_t NUM_COINS{50};32COutPoint COINS[NUM_COINS];34static TxValidationResult TESTED_TX_RESULTS[] = {35// Skip TX_RESULT_UNSET36TxValidationResult::TX_CONSENSUS,37TxValidationResult::TX_INPUTS_NOT_STANDARD,38TxValidationResult::TX_NOT_STANDARD,39TxValidationResult::TX_MISSING_INPUTS,40TxValidationResult::TX_PREMATURE_SPEND,41TxValidationResult::TX_WITNESS_MUTATED,42TxValidationResult::TX_WITNESS_STRIPPED,43TxValidationResult::TX_CONFLICT,44TxValidationResult::TX_MEMPOOL_POLICY,45// Skip TX_NO_MEMPOOL46TxValidationResult::TX_RECONSIDERABLE,47TxValidationResult::TX_UNKNOWN,48};50// Precomputed transactions. Some may conflict with each other.51std::vector<CTransactionRef> TRANSACTIONS;53// Limit the total number of peers because we don't expect coverage to change much with lots more peers.54constexpr int NUM_PEERS = 16;56// Precomputed random durations (positive and negative, each ~exponentially distributed).57std::chrono::microseconds TIME_SKIPS[128];59static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, size_t num_outputs, bool add_witness)60{61CMutableTransaction tx;62// If no outpoints are given, create a random one.63for (const auto& outpoint : outpoints) {64tx.vin.emplace_back(outpoint);65}66if (add_witness) {67tx.vin[0].scriptWitness.stack.push_back({1});68}69for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE);70return MakeTransactionRef(tx);71}72static std::vector<COutPoint> PickCoins(FuzzedDataProvider& fuzzed_data_provider)73{74std::vector<COutPoint> ret;75ret.push_back(fuzzed_data_provider.PickValueInArray(COINS));76LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 10) {77ret.push_back(fuzzed_data_provider.PickValueInArray(COINS));78}79return ret;80}82void initialize()83{84static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();85g_setup = testing_setup.get();86for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) {87COINS[i] = COutPoint{Txid::FromUint256((HashWriter() << i).GetHash()), i};88}89size_t outpoints_index = 0;90// 2 transactions same txid different witness91{92auto tx1{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/false)};93auto tx2{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/true)};94Assert(tx1->GetHash() == tx2->GetHash());95TRANSACTIONS.emplace_back(tx1);96TRANSACTIONS.emplace_back(tx2);97outpoints_index += 1;98}99// 2 parents 1 child100{101auto tx_parent_1{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/true)};102TRANSACTIONS.emplace_back(tx_parent_1);103auto tx_parent_2{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/false)};104TRANSACTIONS.emplace_back(tx_parent_2);105TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent_1->GetHash(), 0}, COutPoint{tx_parent_2->GetHash(), 0}},106/*num_outputs=*/1, /*add_witness=*/true));107}108// 1 parent 2 children109{110auto tx_parent{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/2, /*add_witness=*/true)};111TRANSACTIONS.emplace_back(tx_parent);112TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 0}},113/*num_outputs=*/1, /*add_witness=*/true));114TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 1}},115/*num_outputs=*/1, /*add_witness=*/true));116}117// chain of 5 segwit118{119COutPoint& last_outpoint = COINS[outpoints_index++];120for (auto i{0}; i < 5; ++i) {121auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/true)};122TRANSACTIONS.emplace_back(tx);123last_outpoint = COutPoint{tx->GetHash(), 0};124}125}126// chain of 5 non-segwit127{128COutPoint& last_outpoint = COINS[outpoints_index++];129for (auto i{0}; i < 5; ++i) {130auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/false)};131TRANSACTIONS.emplace_back(tx);132last_outpoint = COutPoint{tx->GetHash(), 0};133}134}135// Also create a loose tx for each outpoint. Some of these transactions conflict with the above136// or have the same txid.137for (const auto& outpoint : COINS) {138TRANSACTIONS.emplace_back(MakeTransactionSpending({outpoint}, /*num_outputs=*/1, /*add_witness=*/true));139}141// Create random-looking time jumps142int i = 0;143// TIME_SKIPS[N] for N=0..15 is just N microseconds.144for (; i < 16; ++i) {145TIME_SKIPS[i] = std::chrono::microseconds{i};146}147// TIME_SKIPS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to148// 198.416453 seconds.149for (; i < 128; ++i) {150int diff_bits = ((i - 10) * 2) / 9;151uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits));152TIME_SKIPS[i] = TIME_SKIPS[i - 1] + std::chrono::microseconds{diff};153}154}156void CheckPackageToValidate(const node::PackageToValidate& package_to_validate, NodeId peer)157{158Assert(package_to_validate.m_senders.size() == 2);159Assert(package_to_validate.m_senders.front() == peer);160Assert(package_to_validate.m_senders.back() < NUM_PEERS);162// Package is a 1p1c163const auto& package = package_to_validate.m_txns;164Assert(IsChildWithParents(package));165Assert(package.size() == 2);166}168FUZZ_TARGET(txdownloadman, .init = initialize)169{170SeedRandomStateForTest(SeedRand::ZEROS);171FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());172FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};174// Initialize txdownloadman175bilingual_str error;176CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};177node::TxDownloadManager txdownloadman{node::TxDownloadOptions{.m_mempool = pool, .m_deterministic_txrequest = true}};179std::chrono::microseconds time{244466666};181LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 500) {182NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1);184// Transaction can be one of the premade ones or a randomly generated one185auto rand_tx = fuzzed_data_provider.ConsumeBool() ?186MakeTransactionSpending(PickCoins(fuzzed_data_provider),187/*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500),188/*add_witness=*/fuzzed_data_provider.ConsumeBool()) :189TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1));191CallOneOf(192fuzzed_data_provider,193[&] {194node::TxDownloadConnectionInfo info{195.m_preferred = fuzzed_data_provider.ConsumeBool(),196.m_relay_permissions = fuzzed_data_provider.ConsumeBool(),197.m_wtxid_relay = fuzzed_data_provider.ConsumeBool()198};199txdownloadman.ConnectedPeer(rand_peer, info);200},201[&] {202txdownloadman.DisconnectedPeer(rand_peer);203txdownloadman.CheckIsEmpty(rand_peer);204},205[&] {206txdownloadman.ActiveTipChange();207},208[&] {209CBlock block;210block.vtx.push_back(rand_tx);211txdownloadman.BlockConnected(std::make_shared<CBlock>(block));212},213[&] {214txdownloadman.BlockDisconnected();215},216[&] {217txdownloadman.MempoolAcceptedTx(rand_tx);218},219[&] {220TxValidationState state;221state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), "");222bool first_time_failure{fuzzed_data_provider.ConsumeBool()};224node::RejectedTxTodo todo = txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure);225Assert(first_time_failure || !todo.m_should_add_extra_compact_tx);226},227[&] {228auto gtxid = fuzzed_data_provider.ConsumeBool() ?229GenTxid{rand_tx->GetHash()} :230GenTxid{rand_tx->GetWitnessHash()};231txdownloadman.AddTxAnnouncement(rand_peer, gtxid, time);232},233[&] {234txdownloadman.GetRequestsToSend(rand_peer, time);235},236[&] {237txdownloadman.ReceivedTx(rand_peer, rand_tx);238const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx);239// The only possible results should be:240// - Don't validate the tx, no package.241// - Don't validate the tx, package.242// - Validate the tx, no package.243// The only combination that doesn't make sense is validate both tx and package.244Assert(!(should_validate && maybe_package.has_value()));245if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer);246},247[&] {248txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()});249},250[&] {
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.