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