wallet_create_tx.cpp raw

   1  // Copyright (c) 2022-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or https://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <addresstype.h>
   6  #include <bench/bench.h>
   7  #include <chain.h>
   8  #include <chainparams.h>
   9  #include <consensus/amount.h>
  10  #include <consensus/consensus.h>
  11  #include <consensus/merkle.h>
  12  #include <kernel/chain.h>
  13  #include <kernel/types.h>
  14  #include <node/blockstorage.h>
  15  #include <outputtype.h>
  16  #include <policy/feerate.h>
  17  #include <primitives/block.h>
  18  #include <primitives/transaction.h>
  19  #include <script/script.h>
  20  #include <sync.h>
  21  #include <test/util/setup_common.h>
  22  #include <test/util/time.h>
  23  #include <uint256.h>
  24  #include <util/check.h>
  25  #include <util/result.h>
  26  #include <validation.h>
  27  #include <versionbits.h>
  28  #include <wallet/coincontrol.h>
  29  #include <wallet/coinselection.h>
  30  #include <wallet/db.h>
  31  #include <wallet/spend.h>
  32  #include <wallet/sqlite.h>
  33  #include <wallet/test/util.h>
  34  #include <wallet/types.h>
  35  #include <wallet/wallet.h>
  36  #include <wallet/walletutil.h>
  37  
  38  #include <cstdint>
  39  #include <map>
  40  #include <memory>
  41  #include <optional>
  42  #include <string>
  43  #include <utility>
  44  #include <vector>
  45  
  46  using kernel::ChainstateRole;
  47  using wallet::CWallet;
  48  using wallet::MakeInMemoryWalletDatabase;
  49  using wallet::WALLET_FLAG_DESCRIPTORS;
  50  
  51  struct TipBlock
  52  {
  53      uint256 prev_block_hash;
  54      int64_t prev_block_time;
  55      int tip_height;
  56  };
  57  
  58  TipBlock getTip(const CChainParams& params, const node::NodeContext& context)
  59  {
  60      auto tip = WITH_LOCK(::cs_main, return context.chainman->ActiveTip());
  61      return (tip) ? TipBlock{tip->GetBlockHash(), tip->GetBlockTime(), tip->nHeight} :
  62             TipBlock{params.GenesisBlock().GetHash(), params.GenesisBlock().GetBlockTime(), 0};
  63  }
  64  
  65  void generateFakeBlock(const CChainParams& params,
  66                         const node::NodeContext& context,
  67                         CWallet& wallet,
  68                         const CScript& coinbase_out_script)
  69  {
  70      TipBlock tip{getTip(params, context)};
  71  
  72      // Create block
  73      CBlock block;
  74      CMutableTransaction coinbase_tx;
  75      coinbase_tx.vin.resize(1);
  76      coinbase_tx.vin[0].prevout.SetNull();
  77      coinbase_tx.vout.resize(2);
  78      coinbase_tx.vout[0].scriptPubKey = coinbase_out_script;
  79      coinbase_tx.vout[0].nValue = 48 * COIN;
  80      coinbase_tx.vin[0].scriptSig = CScript() << ++tip.tip_height << OP_0;
  81      coinbase_tx.vout[1].scriptPubKey = coinbase_out_script; // extra output
  82      coinbase_tx.vout[1].nValue = 1 * COIN;
  83  
  84      // Fill the coinbase with outputs that don't belong to the wallet in order to benchmark
  85      // AvailableCoins' behavior with unnecessary TXOs
  86      for (int i = 0; i < 50; ++i) {
  87          coinbase_tx.vout.emplace_back(1 * COIN / 50, CScript(OP_TRUE));
  88      }
  89  
  90      block.vtx = {MakeTransactionRef(std::move(coinbase_tx))};
  91  
  92      block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION;
  93      block.hashPrevBlock = tip.prev_block_hash;
  94      block.hashMerkleRoot = BlockMerkleRoot(block);
  95      block.nTime = ++tip.prev_block_time;
  96      block.nBits = params.GenesisBlock().nBits;
  97      block.nNonce = 0;
  98  
  99      {
 100          LOCK(::cs_main);
 101          // Add it to the index
 102          CBlockIndex* pindex{context.chainman->m_blockman.AddToBlockIndex(block, context.chainman->m_best_header)};
 103          // add it to the chain
 104          context.chainman->ActiveChain().SetTip(*pindex);
 105      }
 106  
 107      // notify wallet
 108      const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip());
 109      wallet.blockConnected(ChainstateRole{}, kernel::MakeBlockInfo(pindex, &block));
 110  }
 111  
 112  struct PreSelectInputs {
 113      // How many coins from the wallet the process should select
 114      int num_of_internal_inputs;
 115      // future: this could have external inputs as well.
 116  };
 117  
 118  static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type, bool allow_other_inputs, std::optional<PreSelectInputs> preset_inputs)
 119  {
 120      const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
 121  
 122      // Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
 123      FakeNodeClock clock{test_setup->m_node.chainman->GetParams().GenesisBlock().Time()};
 124      CWallet wallet{test_setup->m_node.chain.get(), "", MakeInMemoryWalletDatabase()};
 125      {
 126          LOCK(wallet.cs_wallet);
 127          wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 128          wallet.SetupDescriptorScriptPubKeyMans();
 129      }
 130  
 131      // Generate destinations
 132      const auto dest{getNewDestination(wallet, output_type)};
 133  
 134      // Generate chain; each coinbase will have two outputs to fill-up the wallet
 135      const auto& params = Params();
 136      const CScript coinbase_out{GetScriptForDestination(dest)};
 137      unsigned int chain_size = 5000; // 5k blocks means 10k UTXO for the wallet (minus 200 due COINBASE_MATURITY)
 138      for (unsigned int i = 0; i < chain_size; ++i) {
 139          generateFakeBlock(params, test_setup->m_node, wallet, coinbase_out);
 140      }
 141  
 142      // Check available balance
 143      auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache
 144      assert(bal == 49 * COIN * (chain_size - COINBASE_MATURITY));
 145  
 146      wallet::CCoinControl coin_control;
 147      coin_control.m_allow_other_inputs = allow_other_inputs;
 148  
 149      CAmount target = 0;
 150      if (preset_inputs) {
 151          // Select inputs, each has 48 BTC
 152          wallet::CoinFilterParams filter_coins;
 153          filter_coins.max_count = preset_inputs->num_of_internal_inputs;
 154          const auto& res = WITH_LOCK(wallet.cs_wallet,
 155                                      return wallet::AvailableCoins(wallet, /*coinControl=*/nullptr, /*feerate=*/std::nullopt, filter_coins));
 156          for (int i=0; i < preset_inputs->num_of_internal_inputs; i++) {
 157              const auto& coin{res.coins.at(output_type)[i]};
 158              target += coin.txout.nValue;
 159              coin_control.Select(coin.outpoint);
 160          }
 161      }
 162  
 163      // If automatic coin selection is enabled, add the value of another UTXO to the target
 164      if (coin_control.m_allow_other_inputs) target += 50 * COIN;
 165      std::vector<wallet::CRecipient> recipients = {{dest, target, true}};
 166  
 167      bench.run([&] {
 168          LOCK(wallet.cs_wallet);
 169          const auto& tx_res = CreateTransaction(wallet, recipients, /*change_pos=*/std::nullopt, coin_control);
 170          assert(tx_res);
 171      });
 172  }
 173  
 174  static void AvailableCoins(benchmark::Bench& bench, const std::vector<OutputType>& output_type)
 175  {
 176      const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
 177      // Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
 178      FakeNodeClock clock{test_setup->m_node.chainman->GetParams().GenesisBlock().Time()};
 179      CWallet wallet{test_setup->m_node.chain.get(), "", MakeInMemoryWalletDatabase()};
 180      {
 181          LOCK(wallet.cs_wallet);
 182          wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 183          wallet.SetupDescriptorScriptPubKeyMans();
 184      }
 185  
 186      // Generate destinations
 187      std::vector<CScript> dest_wallet;
 188      dest_wallet.reserve(output_type.size());
 189      for (auto type : output_type) {
 190          dest_wallet.emplace_back(GetScriptForDestination(getNewDestination(wallet, type)));
 191      }
 192  
 193      // Generate chain; each coinbase will have two outputs to fill-up the wallet
 194      const auto& params = Params();
 195      unsigned int chain_size = 1000;
 196      for (unsigned int i = 0; i < chain_size / dest_wallet.size(); ++i) {
 197          for (const auto& dest : dest_wallet) {
 198              generateFakeBlock(params, test_setup->m_node, wallet, dest);
 199          }
 200      }
 201  
 202      // Check available balance
 203      auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache
 204      assert(bal == 49 * COIN * (chain_size - COINBASE_MATURITY));
 205  
 206      bench.run([&] {
 207          LOCK(wallet.cs_wallet);
 208          const auto& res = wallet::AvailableCoins(wallet);
 209          assert(res.All().size() == (chain_size - COINBASE_MATURITY) * 2);
 210      });
 211  }
 212  
 213  static void WalletCreateTxUseOnlyPresetInputs(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/false,
 214                                                                                          {{/*num_of_internal_inputs=*/4}}); }
 215  
 216  static void WalletCreateTxUsePresetInputsAndCoinSelection(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/true,
 217                                                                                                      {{/*num_of_internal_inputs=*/4}}); }
 218  
 219  static void WalletAvailableCoins(benchmark::Bench& bench) { AvailableCoins(bench, {OutputType::BECH32M}); }
 220  
 221  BENCHMARK(WalletCreateTxUseOnlyPresetInputs);
 222  BENCHMARK(WalletCreateTxUsePresetInputsAndCoinSelection);
 223  BENCHMARK(WalletAvailableCoins);
 224