/ src / bench / coin_selection.cpp
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