Bitcoin Core 31.99.0
P2P Digital Currency
coin_selection.cpp
Go to the documentation of this file.
1// Copyright (c) 2012-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <bench/bench.h>
6#include <consensus/amount.h>
7#include <outputtype.h>
8#include <policy/feerate.h>
9#include <policy/policy.h>
11#include <random.h>
12#include <sync.h>
14#include <util/check.h>
15#include <util/result.h>
17#include <wallet/spend.h>
18#include <wallet/sqlite.h>
19#include <wallet/transaction.h>
20#include <wallet/wallet.h>
21
22#include <cstddef>
23#include <cstdint>
24#include <map>
25#include <memory>
26#include <optional>
27#include <string>
28#include <utility>
29#include <vector>
30
31namespace wallet {
32static void addCoin(const CAmount& nValue, std::vector<std::unique_ptr<CWalletTx>>& wtxs)
33{
34 static int nextLockTime = 0;
36 tx.nLockTime = nextLockTime++; // so all transactions get different hashes
37 tx.vout.resize(1);
38 tx.vout[0].nValue = nValue;
39 wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
40}
41
42// This benchmark is based on a large diverse UTXO pool. The UTXOs are
43// pseudorandomly generated and assigned one of the four relevant output types
44// P2PKH, P2SH-P2WPKH, P2WPKH, and P2TR UTXOs.
45// Smaller amounts are more likely to be generated than larger amounts. This
46// UTXO pool is used to run coin selection for pseudorandom selection targets.
47// Altogether, this gives us a deterministic benchmark with a somewhat
48// representative coin selection scenario.
50{
51 const auto test_setup = MakeNoLogFileContext<TestingSetup>();
52 CWallet wallet(test_setup->m_node.chain.get(), "", MakeInMemoryWalletDatabase());
53 std::vector<std::unique_ptr<CWalletTx>> wtxs;
54 LOCK(wallet.cs_wallet);
55
56 // Keep selection deterministic for benchmark stability
57 FastRandomContext det_rand{/*fDeterministic=*/true};
58
59 // Generate coin amounts biased towards smaller amounts
60 for (int i = 0; i < 400; ++i) {
61 CAmount amount;
62 int p{det_rand.randrange(100)};
63 if (p < 50) {
64 amount = 10'000 + det_rand.randrange(90'000);
65 } else if (p < 75) {
66 amount = 100'000 + det_rand.randrange(900'000);
67 } else if (p < 95) {
68 amount = 1'000'000 + det_rand.randrange(9'000'000);
69 } else {
70 amount = 10'000'000 + det_rand.randrange(90'000'000);
71 }
72 addCoin(amount, wtxs);
73 }
74
75 // Create coins from the amounts assigning them various output types
76 wallet::CoinsResult available_coins;
77 for (const auto& wtx : wtxs) {
78 const auto txout = wtx->GetTx()->vout.at(0);
79 OutputType outtype;
80 int input_bytes;
81 int y{det_rand.randrange(100)};
82 if (y < 35) {
83 outtype = OutputType::LEGACY;
84 input_bytes = 148;
85 } else if (y < 55) {
87 input_bytes = 91;
88 } else if (y < 90) {
89 outtype = OutputType::BECH32;
90 input_bytes = 68;
91 } else {
92 outtype = OutputType::BECH32M;
93 input_bytes = 58;
94 }
95 CAmount fees = 20 * input_bytes;
96 available_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 }
98
99 const CoinEligibilityFilter filter_standard(/*conf_mine=*/1, /*conf_theirs=*/6, /*max_ancestors=*/0);
100
101 constexpr size_t NUM_TARGETS{10};
102 std::vector<CAmount> targets;
103 targets.reserve(NUM_TARGETS);
104 for (size_t i{0}; i < NUM_TARGETS; ++i) {
105 targets.push_back(10'000'000 + det_rand.randrange(90'000'000));
106 }
107
108 std::optional<FastRandomContext> rng;
109 std::optional<CoinSelectionParams> params;
110 std::vector<wallet::OutputGroupTypeMap> groups;
111 bench.batch(NUM_TARGETS).unit("selection").epochIterations(1)
112 .setup([&] {
113 rng.emplace(/*fDeterministic=*/true);
114 params.emplace(*rng);
115
116 params->change_output_size = 31;
117 params->change_spend_size = 68;
118 params->m_min_change_target = CHANGE_LOWER;
119 params->m_effective_feerate = CFeeRate{20'000};
120 params->m_long_term_feerate = CFeeRate{10'000};
121 params->m_discard_feerate = CFeeRate{3000};
122 params->tx_noinputs_size = 72;
123 params->m_avoid_partial_spends = false;
124
125 params->m_change_fee = params->m_effective_feerate.GetFee(params->change_output_size);
126 params->min_viable_change = params->m_discard_feerate.GetFee(params->change_spend_size);
127 params->m_cost_of_change = params->min_viable_change + params->m_change_fee;
128
129 groups.assign(NUM_TARGETS, wallet::GroupOutputs(wallet, available_coins, *params, {{filter_standard}})[filter_standard]);
130 })
131 .run([&] {
132 for (size_t i{0}; i < NUM_TARGETS; ++i) {
133 auto result{AttemptSelection(wallet.chain(), targets[i], groups[i], *params, /*allow_mixed_output_types=*/true)};
134 assert(result && result->GetSelectedValue() >= targets[i]);
135 }
136 });
137}
138
139static void add_coin(const CAmount& nValue, uint32_t nInput, std::vector<OutputGroup>& set)
140{
142 tx.vout.resize(nInput + 1);
143 tx.vout[nInput].nValue = nValue;
144 COutput 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);
145 set.emplace_back();
146 set.back().Insert(std::make_shared<COutput>(output), /*ancestors=*/0, /*cluster_count=*/0);
147}
148
149static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)
150{
151 utxo_pool.clear();
152 CAmount target = 0;
153 for (int i = 0; i < utxos; ++i) {
154 target += CAmount{1} << (utxos+i);
155 add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool);
156 add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool);
157 }
158 return target;
159}
160
162{
163 std::vector<OutputGroup> utxo_pool;
164 CAmount target;
165 bench.setup([&] { target = make_hard_case(17, utxo_pool); })
166 .run([&] {
167 auto res{SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT)}; // Should exhaust
169 });
170}
171
174}; // namespace wallet
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
Definition: feerate.h:32
CAmount GetFee(int32_t virtual_bytes) const
Return the fee in satoshis for the given vsize in vbytes.
Definition: feerate.cpp:21
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
Fast randomness source.
Definition: random.h:386
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
Main entry point to nanobench's benchmarking facility.
Definition: nanobench.h:649
Bench & batch(T b) noexcept
Sets the batch size.
Definition: nanobench.h:1332
Bench & unit(char const *unit)
Sets the operation unit.
detail::SetupRunner< SetupOp > setup(SetupOp setupOp)
Configure an untimed setup step per epoch (forces single-iteration epochs).
Definition: nanobench.h:1302
Bench & epochIterations(uint64_t numIters) noexcept
Sets exactly the number of iterations for each epoch.
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:310
void doNotOptimizeAway(Arg &&arg)
Makes sure none of the given arguments are optimized away by the compiler.
Definition: nanobench.h:1353
std::unique_ptr< WalletDatabase > MakeInMemoryWalletDatabase()
Definition: sqlite.cpp:729
static const CoinEligibilityFilter filter_standard(1, 6, 0)
FilteredOutputGroups GroupOutputs(const CWallet &wallet, const CoinsResult &coins, const CoinSelectionParams &coin_sel_params, const std::vector< SelectionFilter > &filters, std::vector< OutputGroup > &ret_discarded_groups)
Definition: spend.cpp:568
constexpr CAmount CHANGE_LOWER
lower bound for randomly-chosen target change amount
Definition: coinselection.h:23
util::Result< SelectionResult > SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, const CAmount &cost_of_change, int max_selection_weight)
static void add_coin(const CAmount &nValue, uint32_t nInput, std::vector< OutputGroup > &set)
util::Result< SelectionResult > AttemptSelection(interfaces::Chain &chain, const CAmount &nTargetValue, OutputGroupTypeMap &groups, const CoinSelectionParams &coin_selection_params, bool allow_mixed_output_types)
Attempt to find a valid input set that preserves privacy by not mixing OutputTypes.
Definition: spend.cpp:698
static void addCoin(const CAmount &nValue, std::vector< std::unique_ptr< CWalletTx > > &wtxs)
static void CoinSelection(benchmark::Bench &bench)
static void BnBExhaustion(benchmark::Bench &bench)
BENCHMARK(BnBExhaustion)
static CAmount make_hard_case(int utxos, std::vector< OutputGroup > &utxo_pool)
static int nextLockTime
OutputType
Definition: outputtype.h:18
constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition: policy.h:38
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
A mutable version of CTransaction.
Definition: transaction.h:358
std::vector< CTxOut > vout
Definition: transaction.h:360
Txid GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:69
A UTXO under consideration for use in funding a new transaction.
Definition: coinselection.h:28
Parameters for filtering which OutputGroups we may use in coin selection.
COutputs available for spending, stored by OutputType.
Definition: spend.h:45
std::map< OutputType, std::vector< COutput > > coins
Definition: spend.h:46
State of transaction not confirmed or conflicting with a known block and not in the mempool.
Definition: transaction.h:59
#define LOCK(cs)
Definition: sync.h:268
FastRandomContext rng
Definition: dbwrapper.cpp:413
assert(!tx.IsCoinBase())