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) 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 <bench/bench.h>6#include <consensus/consensus.h>7#include <consensus/validation.h>8#include <net.h>9#include <policy/policy.h>10#include <primitives/block.h>11#include <primitives/transaction.h>12#include <node/txorphanage.h>13#include <random.h>14#include <test/util/transaction_utils.h>15#include <threadsafety.h>16#include <util/check.h>18#include <algorithm>19#include <cstddef>20#include <cstdint>21#include <memory>22#include <numeric>23#include <vector>25static constexpr node::TxOrphanage::Usage TINY_TX_WEIGHT{240};26static constexpr int64_t APPROX_WEIGHT_PER_INPUT{200};28// Creates a transaction with num_inputs inputs and 1 output, padded to target_weight. Use this function to maximize m_outpoint_to_orphan_it operations.29// If num_inputs is 0, we maximize the number of inputs.30static CTransactionRef MakeTransactionBulkedTo(unsigned int num_inputs, int64_t target_weight, FastRandomContext& det_rand)31{32CMutableTransaction tx;33assert(target_weight >= 40 + APPROX_WEIGHT_PER_INPUT);34if (!num_inputs) num_inputs = (target_weight - 40) / APPROX_WEIGHT_PER_INPUT;35for (unsigned int i = 0; i < num_inputs; ++i) {36tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0);37}38assert(GetTransactionWeight(*MakeTransactionRef(tx)) <= target_weight);40tx.vout.resize(1);42// If necessary, pad the transaction to the target weight.43if (GetTransactionWeight(*MakeTransactionRef(tx)) < target_weight - 4) {44BulkTransaction(tx, target_weight);45}46return MakeTransactionRef(tx);47}49// Constructs a transaction using a subset of inputs[start_input : start_input + num_inputs] up to the weight_limit.50static CTransactionRef MakeTransactionSpendingUpTo(const std::vector<CTxIn>& inputs, unsigned int start_input, unsigned int num_inputs, int64_t weight_limit)51{52CMutableTransaction tx;53for (unsigned int i{start_input}; i < start_input + num_inputs; ++i) {54if (GetTransactionWeight(*MakeTransactionRef(tx)) + APPROX_WEIGHT_PER_INPUT >= weight_limit) break;55tx.vin.emplace_back(inputs.at(i % inputs.size()));56}57assert(tx.vin.size() > 0);58return MakeTransactionRef(tx);59}60static void OrphanageSinglePeerEviction(benchmark::Bench& bench)61{62FastRandomContext det_rand{true};64// Fill up announcements slots with tiny txns, followed by a single large one65unsigned int NUM_TINY_TRANSACTIONS((node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE));67// Construct transactions to submit to orphanage: 1-in-1-out tiny transactions68std::vector<CTransactionRef> tiny_txs;69tiny_txs.reserve(NUM_TINY_TRANSACTIONS);70for (unsigned int i{0}; i < NUM_TINY_TRANSACTIONS; ++i) {71tiny_txs.emplace_back(MakeTransactionBulkedTo(1, TINY_TX_WEIGHT, det_rand));72}73auto large_tx = MakeTransactionBulkedTo(1, MAX_STANDARD_TX_WEIGHT, det_rand);74assert(GetTransactionWeight(*large_tx) <= MAX_STANDARD_TX_WEIGHT);76const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};78// Populate the orphanage. To maximize the number of evictions, first fill up with tiny transactions, then add a huge one.79NodeId peer{0};80// Add tiny transactions until we are just about to hit the memory limit, up to the max number of announcements.81// We use the same tiny transactions for all peers to minimize their contribution to the usage limit.82int64_t total_weight_to_add{0};83for (unsigned int txindex{0}; txindex < NUM_TINY_TRANSACTIONS; ++txindex) {84const auto& tx{tiny_txs.at(txindex)};86total_weight_to_add += GetTransactionWeight(*tx);87if (total_weight_to_add > orphanage->MaxGlobalUsage()) break;89assert(orphanage->AddTx(tx, peer));91// Sanity check: we should always be exiting at the point of hitting the weight limit.92assert(txindex < NUM_TINY_TRANSACTIONS - 1);93}95// In the real world, we always trim after each new tx.96// If we need to trim already, that means the benchmark is not representative of what LimitOrphans may do in a single call.97assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());98assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());99assert(orphanage->TotalOrphanUsage() + TINY_TX_WEIGHT > orphanage->MaxGlobalUsage());101bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {102// Lastly, add the large transaction.103const auto num_announcements_before_trim{orphanage->CountAnnouncements()};104assert(orphanage->AddTx(large_tx, peer));106// If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.107const auto num_announcements_after_trim{orphanage->CountAnnouncements()};108const auto num_evicted{num_announcements_before_trim - num_announcements_after_trim};110// The number of evictions is the same regardless of the number of peers. In both cases, we can exceed the111// usage limit using 1 maximally-sized transaction.112assert(num_evicted == MAX_STANDARD_TX_WEIGHT / TINY_TX_WEIGHT);113});114}115static void OrphanageMultiPeerEviction(benchmark::Bench& bench)116{117// Best number is just below sqrt(DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE)118static constexpr unsigned int NUM_PEERS{39};119// All peers will have the same transactions. We want to be just under the weight limit, so divide the max usage limit by the number of unique transactions.120static constexpr node::TxOrphanage::Count NUM_UNIQUE_TXNS{node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS};121static constexpr node::TxOrphanage::Usage TOTAL_USAGE_LIMIT{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER * NUM_PEERS};122// Subtract 4 because BulkTransaction rounds up and we must avoid going over the weight limit early.123static constexpr node::TxOrphanage::Usage LARGE_TX_WEIGHT{TOTAL_USAGE_LIMIT / NUM_UNIQUE_TXNS - 4};124static_assert(LARGE_TX_WEIGHT >= TINY_TX_WEIGHT * 2, "Tx is too small, increase NUM_PEERS");125// The orphanage does not permit any transactions larger than 400'000, so this test will not work if the large tx is much larger.126static_assert(LARGE_TX_WEIGHT <= MAX_STANDARD_TX_WEIGHT, "Tx is too large, decrease NUM_PEERS");128FastRandomContext det_rand{true};129// Construct large transactions130std::vector<CTransactionRef> shared_txs;131shared_txs.reserve(NUM_UNIQUE_TXNS);132for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {133shared_txs.emplace_back(MakeTransactionBulkedTo(9, LARGE_TX_WEIGHT, det_rand));134}135std::vector<size_t> indexes;136indexes.resize(NUM_UNIQUE_TXNS);137std::iota(indexes.begin(), indexes.end(), 0);139const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};140// Every peer sends the same transactions, all from shared_txs.141// Each peer has 1 or 2 assigned transactions, which they must place as the last and second-to-last positions.142// The assignments ensure that every transaction is in some peer's last 2 transactions, and is thus remains in the orphanage until the end of LimitOrphans.143static_assert(NUM_UNIQUE_TXNS <= NUM_PEERS * 2);145// We need each peer to send some transactions so that the global limit (which is a function of the number of peers providing at least 1 announcement) rises.146for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {147for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {148const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};149CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};151const auto& tx{shared_txs.at(indexes.at(i))};152if (tx == reserved_last_tx) {153// Skip154} else if (reserved_second_to_last_tx && tx == reserved_second_to_last_tx) {155// Skip156} else {157orphanage->AddTx(tx, peer);158}159}160}162// Now add the final reserved transactions.163for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {164const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};165CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};166// Add the final reserved transactions.167if (reserved_second_to_last_tx) {168orphanage->AddTx(reserved_second_to_last_tx, peer);169}170orphanage->AddTx(reserved_last_tx, peer);171}173assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_UNIQUE_TXNS);174const auto total_usage{orphanage->TotalOrphanUsage()};175const auto max_usage{orphanage->MaxGlobalUsage()};176assert(max_usage - total_usage <= LARGE_TX_WEIGHT);177assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());179auto last_tx = MakeTransactionBulkedTo(0, max_usage - total_usage + 1, det_rand);181bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {182const auto num_announcements_before_trim{orphanage->CountAnnouncements()};183// There is a small gap between the total usage and the max usage. Add a transaction to fill it.184assert(orphanage->AddTx(last_tx, 0));186// If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.187const auto num_evicted{num_announcements_before_trim - orphanage->CountAnnouncements() + 1};188// The trimming happens as a round robin. In the first NUM_UNIQUE_TXNS - 2 rounds for each peer, only duplicates are evicted.189// Once each peer has 2 transactions left, it's possible to select a peer whose oldest transaction is unique.190assert(num_evicted >= (NUM_UNIQUE_TXNS - 2) * NUM_PEERS);191});192}194static void OrphanageEraseAll(benchmark::Bench& bench, bool block_or_disconnect)195{196FastRandomContext det_rand{true};197const auto orphanage{node::MakeTxOrphanage(/*max_global_latency_score=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};198// This is an unrealistically large number of inputs for a block, as there is almost no room given to witness data,199// outputs, and overhead for individual transactions. The entire block is 1 transaction with 20,000 inputs.200constexpr unsigned int NUM_BLOCK_INPUTS{MAX_BLOCK_WEIGHT / APPROX_WEIGHT_PER_INPUT};201const auto block_tx{MakeTransactionBulkedTo(NUM_BLOCK_INPUTS, MAX_BLOCK_WEIGHT - 4000, det_rand)};202CBlock block;203block.vtx.push_back(block_tx);205// Transactions with 9 inputs maximize the computation / LatencyScore ratio.206constexpr unsigned int INPUTS_PER_TX{9};207constexpr unsigned int NUM_PEERS{125};208constexpr unsigned int NUM_TXNS_PER_PEER = node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS;210// Divide the block's inputs evenly among the peers.211constexpr unsigned int INPUTS_PER_PEER = NUM_BLOCK_INPUTS / NUM_PEERS;212static_assert(INPUTS_PER_PEER > 0);213// All the block inputs are spent by the orphanage transactions. Each peer is assigned 76 of them.214// Each peer has 24 transactions spending 9 inputs each, so jumping by 3 ensures we cover all of the inputs.215static_assert(7 * NUM_TXNS_PER_PEER + INPUTS_PER_TX - 1 >= INPUTS_PER_PEER);217for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {218int64_t weight_left_for_peer{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};219for (unsigned int txnum{0}; txnum < node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS; ++txnum) {220// Transactions must be unique since they use different (though overlapping) inputs.221const unsigned int start_input = peer * INPUTS_PER_PEER + txnum * 7;223// Note that we shouldn't be able to hit the weight limit with these small transactions.224const int64_t weight_limit{std::min<int64_t>(weight_left_for_peer, MAX_STANDARD_TX_WEIGHT)};225auto ptx = MakeTransactionSpendingUpTo(block_tx->vin, /*start_input=*/start_input, /*num_inputs=*/INPUTS_PER_TX, /*weight_limit=*/weight_limit);227assert(GetTransactionWeight(*ptx) <= MAX_STANDARD_TX_WEIGHT);228assert(!orphanage->HaveTx(ptx->GetWitnessHash()));229assert(orphanage->AddTx(ptx, peer));231weight_left_for_peer -= GetTransactionWeight(*ptx);232if (weight_left_for_peer < TINY_TX_WEIGHT * 2) break;233}234}236// If these fail, it means this benchmark is not realistic because the orphanage would have been trimmed already.237assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());238assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());240// 3000 announcements (and unique transactions) in the orphanage.241// They spend a total of 27,000 inputs (20,000 unique ones)242assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_TXNS_PER_PEER);243assert(orphanage->TotalLatencyScore() == orphanage->CountAnnouncements());245bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {246if (block_or_disconnect) {247// Erase everything through EraseForBlock.248// Every tx conflicts with this block.249orphanage->EraseForBlock(block);250assert(orphanage->CountAnnouncements() == 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.