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) 2012-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 <bench/bench.h>6#include <consensus/amount.h>7#include <outputtype.h>8#include <policy/feerate.h>9#include <policy/policy.h>10#include <primitives/transaction.h>11#include <random.h>12#include <sync.h>13#include <test/util/setup_common.h>14#include <util/check.h>15#include <util/result.h>16#include <wallet/coinselection.h>17#include <wallet/spend.h>18#include <wallet/sqlite.h>19#include <wallet/transaction.h>20#include <wallet/wallet.h>22#include <cstddef>23#include <cstdint>24#include <map>25#include <memory>26#include <optional>27#include <string>28#include <utility>29#include <vector>31namespace wallet {32static void addCoin(const CAmount& nValue, std::vector<std::unique_ptr<CWalletTx>>& wtxs)33{34static int nextLockTime = 0;35CMutableTransaction tx;36tx.nLockTime = nextLockTime++; // so all transactions get different hashes37tx.vout.resize(1);38tx.vout[0].nValue = nValue;39wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)), TxStateInactive{}));40}42// This benchmark is based on a large diverse UTXO pool. The UTXOs are43// pseudorandomly generated and assigned one of the four relevant output types44// P2PKH, P2SH-P2WPKH, P2WPKH, and P2TR UTXOs.45// Smaller amounts are more likely to be generated than larger amounts. This46// UTXO pool is used to run coin selection for pseudorandom selection targets.47// Altogether, this gives us a deterministic benchmark with a somewhat48// representative coin selection scenario.49static void CoinSelection(benchmark::Bench& bench)50{51const auto test_setup = MakeNoLogFileContext<TestingSetup>();52CWallet wallet(test_setup->m_node.chain.get(), "", MakeInMemoryWalletDatabase());53std::vector<std::unique_ptr<CWalletTx>> wtxs;54LOCK(wallet.cs_wallet);56// Keep selection deterministic for benchmark stability57FastRandomContext det_rand{/*fDeterministic=*/true};59// Generate coin amounts biased towards smaller amounts60for (int i = 0; i < 400; ++i) {61CAmount amount;62int p{det_rand.randrange(100)};63if (p < 50) {64amount = 10'000 + det_rand.randrange(90'000);65} else if (p < 75) {66amount = 100'000 + det_rand.randrange(900'000);67} else if (p < 95) {68amount = 1'000'000 + det_rand.randrange(9'000'000);69} else {70amount = 10'000'000 + det_rand.randrange(90'000'000);71}72addCoin(amount, wtxs);73}75// Create coins from the amounts assigning them various output types76wallet::CoinsResult available_coins;77for (const auto& wtx : wtxs) {78const auto txout = wtx->GetTx()->vout.at(0);79OutputType outtype;80int input_bytes;81int y{det_rand.randrange(100)};82if (y < 35) {83outtype = OutputType::LEGACY;84input_bytes = 148;85} else if (y < 55) {86outtype = OutputType::P2SH_SEGWIT;87input_bytes = 91;88} else if (y < 90) {89outtype = OutputType::BECH32;90input_bytes = 68;91} else {92outtype = OutputType::BECH32M;93input_bytes = 58;94}95CAmount fees = 20 * input_bytes;96available_coins.coins[outtype].emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, /*input_bytes=*/input_bytes, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/fees);97}99const CoinEligibilityFilter filter_standard(/*conf_mine=*/1, /*conf_theirs=*/6, /*max_ancestors=*/0);101constexpr size_t NUM_TARGETS{10};102std::vector<CAmount> targets;103targets.reserve(NUM_TARGETS);104for (size_t i{0}; i < NUM_TARGETS; ++i) {105targets.push_back(10'000'000 + det_rand.randrange(90'000'000));106}108std::optional<FastRandomContext> rng;109std::optional<CoinSelectionParams> params;110std::vector<wallet::OutputGroupTypeMap> groups;111bench.batch(NUM_TARGETS).unit("selection").epochIterations(1)112.setup([&] {113rng.emplace(/*fDeterministic=*/true);114params.emplace(*rng);116params->change_output_size = 31;117params->change_spend_size = 68;118params->m_min_change_target = CHANGE_LOWER;119params->m_effective_feerate = CFeeRate{20'000};120params->m_long_term_feerate = CFeeRate{10'000};121params->m_discard_feerate = CFeeRate{3000};122params->tx_noinputs_size = 72;123params->m_avoid_partial_spends = false;125params->m_change_fee = params->m_effective_feerate.GetFee(params->change_output_size);126params->min_viable_change = params->m_discard_feerate.GetFee(params->change_spend_size);127params->m_cost_of_change = params->min_viable_change + params->m_change_fee;129groups.assign(NUM_TARGETS, wallet::GroupOutputs(wallet, available_coins, *params, {{filter_standard}})[filter_standard]);130})131.run([&] {132for (size_t i{0}; i < NUM_TARGETS; ++i) {133auto result{AttemptSelection(wallet.chain(), targets[i], groups[i], *params, /*allow_mixed_output_types=*/true)};134assert(result && result->GetSelectedValue() >= targets[i]);135}136});137}139static void add_coin(const CAmount& nValue, uint32_t nInput, std::vector<OutputGroup>& set)140{141CMutableTransaction tx;142tx.vout.resize(nInput + 1);143tx.vout[nInput].nValue = nValue;144COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/0, /*input_bytes=*/-1, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true, /*fees=*/0);145set.emplace_back();146set.back().Insert(std::make_shared<COutput>(output), /*ancestors=*/0, /*cluster_count=*/0);147}149static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)150{151utxo_pool.clear();152CAmount target = 0;153for (int i = 0; i < utxos; ++i) {154target += CAmount{1} << (utxos+i);155add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool);156add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool);157}158return target;159}161static void BnBExhaustion(benchmark::Bench& bench)162{163std::vector<OutputGroup> utxo_pool;164CAmount target;165bench.setup([&] { target = make_hard_case(17, utxo_pool); })166.run([&] {167auto res{SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT)}; // Should exhaust168ankerl::nanobench::doNotOptimizeAway(res);169});170}172BENCHMARK(CoinSelection);173BENCHMARK(BnBExhaustion);174}; // namespace wallet
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.