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