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