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