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