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 <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 = 48 * 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 79 // Fill the coinbase with outputs that don't belong to the wallet in order to benchmark 80 // AvailableCoins' behavior with unnecessary TXOs 81 for (int i = 0; i < 50; ++i) { 82 coinbase_tx.vout.emplace_back(1 * COIN / 50, CScript(OP_TRUE)); 83 } 84 85 block.vtx = {MakeTransactionRef(std::move(coinbase_tx))}; 86 87 block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION; 88 block.hashPrevBlock = tip.prev_block_hash; 89 block.hashMerkleRoot = BlockMerkleRoot(block); 90 block.nTime = ++tip.prev_block_time; 91 block.nBits = params.GenesisBlock().nBits; 92 block.nNonce = 0; 93 94 { 95 LOCK(::cs_main); 96 // Add it to the index 97 CBlockIndex* pindex{context.chainman->m_blockman.AddToBlockIndex(block, context.chainman->m_best_header)}; 98 // add it to the chain 99 context.chainman->ActiveChain().SetTip(*pindex); 100 } 101 102 // notify wallet 103 const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip()); 104 wallet.blockConnected(ChainstateRole::NORMAL, kernel::MakeBlockInfo(pindex, &block)); 105 } 106 107 struct PreSelectInputs { 108 // How many coins from the wallet the process should select 109 int num_of_internal_inputs; 110 // future: this could have external inputs as well. 111 }; 112 113 static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type, bool allow_other_inputs, std::optional<PreSelectInputs> preset_inputs) 114 { 115 const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); 116 117 // Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process. 118 SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime); 119 CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()}; 120 { 121 LOCK(wallet.cs_wallet); 122 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); 123 wallet.SetupDescriptorScriptPubKeyMans(); 124 } 125 126 // Generate destinations 127 const auto dest{getNewDestination(wallet, output_type)}; 128 129 // Generate chain; each coinbase will have two outputs to fill-up the wallet 130 const auto& params = Params(); 131 const CScript coinbase_out{GetScriptForDestination(dest)}; 132 unsigned int chain_size = 5000; // 5k blocks means 10k UTXO for the wallet (minus 200 due COINBASE_MATURITY) 133 for (unsigned int i = 0; i < chain_size; ++i) { 134 generateFakeBlock(params, test_setup->m_node, wallet, coinbase_out); 135 } 136 137 // Check available balance 138 auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache 139 assert(bal == 49 * COIN * (chain_size - COINBASE_MATURITY)); 140 141 wallet::CCoinControl coin_control; 142 coin_control.m_allow_other_inputs = allow_other_inputs; 143 144 CAmount target = 0; 145 if (preset_inputs) { 146 // Select inputs, each has 48 BTC 147 wallet::CoinFilterParams filter_coins; 148 filter_coins.max_count = preset_inputs->num_of_internal_inputs; 149 const auto& res = WITH_LOCK(wallet.cs_wallet, 150 return wallet::AvailableCoins(wallet, /*coinControl=*/nullptr, /*feerate=*/std::nullopt, filter_coins)); 151 for (int i=0; i < preset_inputs->num_of_internal_inputs; i++) { 152 const auto& coin{res.coins.at(output_type)[i]}; 153 target += coin.txout.nValue; 154 coin_control.Select(coin.outpoint); 155 } 156 } 157 158 // If automatic coin selection is enabled, add the value of another UTXO to the target 159 if (coin_control.m_allow_other_inputs) target += 50 * COIN; 160 std::vector<wallet::CRecipient> recipients = {{dest, target, true}}; 161 162 bench.run([&] { 163 LOCK(wallet.cs_wallet); 164 const auto& tx_res = CreateTransaction(wallet, recipients, /*change_pos=*/std::nullopt, coin_control); 165 assert(tx_res); 166 }); 167 } 168 169 static void AvailableCoins(benchmark::Bench& bench, const std::vector<OutputType>& output_type) 170 { 171 const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); 172 // Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process. 173 SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime); 174 CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()}; 175 { 176 LOCK(wallet.cs_wallet); 177 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); 178 wallet.SetupDescriptorScriptPubKeyMans(); 179 } 180 181 // Generate destinations 182 std::vector<CScript> dest_wallet; 183 dest_wallet.reserve(output_type.size()); 184 for (auto type : output_type) { 185 dest_wallet.emplace_back(GetScriptForDestination(getNewDestination(wallet, type))); 186 } 187 188 // Generate chain; each coinbase will have two outputs to fill-up the wallet 189 const auto& params = Params(); 190 unsigned int chain_size = 1000; 191 for (unsigned int i = 0; i < chain_size / dest_wallet.size(); ++i) { 192 for (const auto& dest : dest_wallet) { 193 generateFakeBlock(params, test_setup->m_node, wallet, dest); 194 } 195 } 196 197 // Check available balance 198 auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache 199 assert(bal == 49 * COIN * (chain_size - COINBASE_MATURITY)); 200 201 bench.run([&] { 202 LOCK(wallet.cs_wallet); 203 const auto& res = wallet::AvailableCoins(wallet); 204 assert(res.All().size() == (chain_size - COINBASE_MATURITY) * 2); 205 }); 206 } 207 208 static void WalletCreateTxUseOnlyPresetInputs(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/false, 209 {{/*num_of_internal_inputs=*/4}}); } 210 211 static void WalletCreateTxUsePresetInputsAndCoinSelection(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/true, 212 {{/*num_of_internal_inputs=*/4}}); } 213 214 static void WalletAvailableCoins(benchmark::Bench& bench) { AvailableCoins(bench, {OutputType::BECH32M}); } 215 216 BENCHMARK(WalletCreateTxUseOnlyPresetInputs, benchmark::PriorityLevel::LOW) 217 BENCHMARK(WalletCreateTxUsePresetInputsAndCoinSelection, benchmark::PriorityLevel::LOW) 218 BENCHMARK(WalletAvailableCoins, benchmark::PriorityLevel::LOW);