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);