coin_selection.cpp
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 <interfaces/chain.h> 8 #include <node/context.h> 9 #include <outputtype.h> 10 #include <policy/feerate.h> 11 #include <policy/policy.h> 12 #include <primitives/transaction.h> 13 #include <random.h> 14 #include <sync.h> 15 #include <test/util/setup_common.h> 16 #include <util/result.h> 17 #include <wallet/coinselection.h> 18 #include <wallet/context.h> 19 #include <wallet/spend.h> 20 #include <wallet/test/util.h> 21 #include <wallet/transaction.h> 22 #include <wallet/wallet.h> 23 24 #include <cassert> 25 #include <map> 26 #include <memory> 27 #include <set> 28 #include <utility> 29 #include <vector> 30 31 namespace wallet { 32 static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<std::unique_ptr<CWalletTx>>& wtxs) 33 { 34 static int nextLockTime = 0; 35 CMutableTransaction tx; 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 // Simple benchmark for wallet coin selection. Note that it maybe be necessary 43 // to build up more complicated scenarios in order to get meaningful 44 // measurements of performance. From laanwj, "Wallet coin selection is probably 45 // the hardest, as you need a wider selection of scenarios, just testing the 46 // same one over and over isn't too useful. Generating random isn't useful 47 // either for measurements." 48 // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) 49 static void CoinSelection(benchmark::Bench& bench) 50 { 51 const auto test_setup = MakeNoLogFileContext<TestingSetup>(); 52 CWallet wallet(test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()); 53 std::vector<std::unique_ptr<CWalletTx>> wtxs; 54 LOCK(wallet.cs_wallet); 55 56 // Add coins. 57 for (int i = 0; i < 1000; ++i) { 58 addCoin(1000 * COIN, wallet, wtxs); 59 } 60 addCoin(3 * COIN, wallet, wtxs); 61 62 // Create coins 63 wallet::CoinsResult available_coins; 64 for (const auto& wtx : wtxs) { 65 const auto txout = wtx->tx->vout.at(0); 66 available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); 67 } 68 69 const CoinEligibilityFilter filter_standard(1, 6, 0); 70 FastRandomContext rand{}; 71 const CoinSelectionParams coin_selection_params{ 72 rand, 73 /*change_output_size=*/ 34, 74 /*change_spend_size=*/ 148, 75 /*min_change_target=*/ CHANGE_LOWER, 76 /*effective_feerate=*/ CFeeRate(20'000), 77 /*long_term_feerate=*/ CFeeRate(10'000), 78 /*discard_feerate=*/ CFeeRate(3000), 79 /*tx_noinputs_size=*/ 0, 80 /*avoid_partial=*/ false, 81 }; 82 auto group = wallet::GroupOutputs(wallet, available_coins, coin_selection_params, {{filter_standard}})[filter_standard]; 83 bench.run([&] { 84 auto result = AttemptSelection(wallet.chain(), 1002.99 * COIN, group, coin_selection_params, /*allow_mixed_output_types=*/true); 85 assert(result); 86 assert(result->GetSelectedValue() == 1003 * COIN); 87 assert(result->GetInputSet().size() == 2); 88 }); 89 } 90 91 // Copied from src/wallet/test/coinselector_tests.cpp 92 static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set) 93 { 94 CMutableTransaction tx; 95 tx.vout.resize(nInput + 1); 96 tx.vout[nInput].nValue = nValue; 97 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); 98 set.emplace_back(); 99 set.back().Insert(std::make_shared<COutput>(output), /*ancestors=*/ 0, /*cluster_count=*/ 0); 100 } 101 // Copied from src/wallet/test/coinselector_tests.cpp 102 static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool) 103 { 104 utxo_pool.clear(); 105 CAmount target = 0; 106 for (int i = 0; i < utxos; ++i) { 107 target += CAmount{1} << (utxos+i); 108 add_coin(CAmount{1} << (utxos+i), 2*i, utxo_pool); 109 add_coin((CAmount{1} << (utxos+i)) + (CAmount{1} << (utxos-1-i)), 2*i + 1, utxo_pool); 110 } 111 return target; 112 } 113 114 static void BnBExhaustion(benchmark::Bench& bench) 115 { 116 std::vector<OutputGroup> utxo_pool; 117 CAmount target; 118 bench.setup([&] { target = make_hard_case(17, utxo_pool); }) 119 .run([&] { 120 auto res{SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT)}; // Should exhaust 121 ankerl::nanobench::doNotOptimizeAway(res); 122 }); 123 } 124 125 BENCHMARK(CoinSelection); 126 BENCHMARK(BnBExhaustion); 127 }; // namespace wallet