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