wallet_create_tx.cpp raw

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