/root/bitcoin/src/test/fuzz/mini_miner.cpp
Line | Count | Source |
1 | | // Copyright (c) The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <test/fuzz/FuzzedDataProvider.h> |
6 | | #include <test/fuzz/fuzz.h> |
7 | | #include <test/fuzz/util.h> |
8 | | #include <test/fuzz/util/mempool.h> |
9 | | #include <test/util/script.h> |
10 | | #include <test/util/setup_common.h> |
11 | | #include <test/util/txmempool.h> |
12 | | #include <test/util/mining.h> |
13 | | |
14 | | #include <node/miner.h> |
15 | | #include <node/mini_miner.h> |
16 | | #include <node/types.h> |
17 | | #include <primitives/transaction.h> |
18 | | #include <random.h> |
19 | | #include <txmempool.h> |
20 | | #include <util/check.h> |
21 | | #include <util/time.h> |
22 | | #include <util/translation.h> |
23 | | |
24 | | #include <deque> |
25 | | #include <vector> |
26 | | |
27 | | namespace { |
28 | | |
29 | | const TestingSetup* g_setup; |
30 | | std::deque<COutPoint> g_available_coins; |
31 | | void initialize_miner() |
32 | 0 | { |
33 | 0 | static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); |
34 | 0 | g_setup = testing_setup.get(); |
35 | 0 | for (uint32_t i = 0; i < uint32_t{100}; ++i) { |
36 | 0 | g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i); |
37 | 0 | } |
38 | 0 | } |
39 | | |
40 | | // Test that the MiniMiner can run with various outpoints and feerates. |
41 | | FUZZ_TARGET(mini_miner, .init = initialize_miner) |
42 | 1.23k | { |
43 | 1.23k | SeedRandomStateForTest(SeedRand::ZEROS); |
44 | 1.23k | FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; |
45 | 1.23k | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
46 | 1.23k | bilingual_str error; |
47 | 1.23k | CTxMemPool pool{CTxMemPool::Options{}, error}; |
48 | 1.23k | Assert(error.empty()); |
49 | 1.23k | std::vector<COutPoint> outpoints; |
50 | 1.23k | std::deque<COutPoint> available_coins = g_available_coins; |
51 | 1.23k | LOCK2(::cs_main, pool.cs); |
52 | | // Cluster size cannot exceed 500 |
53 | 1.23k | LIMITED_WHILE(!available_coins.empty(), 100) |
54 | 48.4k | { |
55 | 48.4k | CMutableTransaction mtx = CMutableTransaction(); |
56 | 48.4k | const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size()); |
57 | 48.4k | const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50); |
58 | 384k | for (size_t n{0}; n < num_inputs; ++n) { |
59 | 336k | auto prevout = available_coins.front(); |
60 | 336k | mtx.vin.emplace_back(prevout, CScript()); |
61 | 336k | available_coins.pop_front(); |
62 | 336k | } |
63 | 504k | for (uint32_t n{0}; n < num_outputs; ++n) { |
64 | 455k | mtx.vout.emplace_back(100, P2WSH_OP_TRUE); |
65 | 455k | } |
66 | 48.4k | CTransactionRef tx = MakeTransactionRef(mtx); |
67 | 48.4k | TestMemPoolEntryHelper entry; |
68 | 48.4k | const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)}; |
69 | 48.4k | assert(MoneyRange(fee)); |
70 | 48.4k | AddToMempool(pool, entry.Fee(fee).FromTx(tx)); |
71 | | |
72 | | // All outputs are available to spend |
73 | 504k | for (uint32_t n{0}; n < num_outputs; ++n) { |
74 | 455k | if (fuzzed_data_provider.ConsumeBool()) { |
75 | 314k | available_coins.emplace_back(tx->GetHash(), n); |
76 | 314k | } |
77 | 455k | } |
78 | | |
79 | 48.4k | if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) { |
80 | | // Add outpoint from this tx (may or not be spent by a later tx) |
81 | 25.7k | outpoints.emplace_back(tx->GetHash(), |
82 | 25.7k | (uint32_t)fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, tx->vout.size())); |
83 | 25.7k | } else { |
84 | | // Add some random outpoint (will be interpreted as confirmed or not yet submitted |
85 | | // to mempool). |
86 | 22.6k | auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); |
87 | 22.6k | if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) { |
88 | 1.89k | outpoints.push_back(*outpoint); |
89 | 1.89k | } |
90 | 22.6k | } |
91 | | |
92 | 48.4k | } |
93 | | |
94 | 1.23k | const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}}; |
95 | 1.23k | std::optional<CAmount> total_bumpfee; |
96 | 1.23k | CAmount sum_fees = 0; |
97 | 1.23k | { |
98 | 1.23k | node::MiniMiner mini_miner{pool, outpoints}; |
99 | 1.23k | assert(mini_miner.IsReadyToCalculate()); |
100 | 615 | const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate); |
101 | 27.6k | for (const auto& outpoint : outpoints) { |
102 | 27.6k | auto it = bump_fees.find(outpoint); |
103 | 27.6k | assert(it != bump_fees.end()); |
104 | 27.6k | assert(it->second >= 0); |
105 | 27.6k | sum_fees += it->second; |
106 | 27.6k | } |
107 | 615 | assert(!mini_miner.IsReadyToCalculate()); |
108 | 615 | } |
109 | 615 | { |
110 | 615 | node::MiniMiner mini_miner{pool, outpoints}; |
111 | 615 | assert(mini_miner.IsReadyToCalculate()); |
112 | 615 | total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate); |
113 | 615 | assert(total_bumpfee.has_value()); |
114 | 615 | assert(!mini_miner.IsReadyToCalculate()); |
115 | 615 | } |
116 | | // Overlapping ancestry across multiple outpoints can only reduce the total bump fee. |
117 | 615 | assert (sum_fees >= *total_bumpfee); |
118 | 615 | } |
119 | | |
120 | | // Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints. |
121 | | FUZZ_TARGET(mini_miner_selection, .init = initialize_miner) |
122 | 0 | { |
123 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
124 | 0 | FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; |
125 | 0 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
126 | 0 | bilingual_str error; |
127 | 0 | CTxMemPool pool{CTxMemPool::Options{}, error}; |
128 | 0 | Assert(error.empty()); |
129 | | // Make a copy to preserve determinism. |
130 | 0 | std::deque<COutPoint> available_coins = g_available_coins; |
131 | 0 | std::vector<CTransactionRef> transactions; |
132 | |
|
133 | 0 | LOCK2(::cs_main, pool.cs); |
134 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) |
135 | 0 | { |
136 | 0 | CMutableTransaction mtx = CMutableTransaction(); |
137 | 0 | assert(!available_coins.empty()); |
138 | 0 | const size_t num_inputs = std::min(size_t{2}, available_coins.size()); |
139 | 0 | const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5); |
140 | 0 | for (size_t n{0}; n < num_inputs; ++n) { |
141 | 0 | auto prevout = available_coins.at(0); |
142 | 0 | mtx.vin.emplace_back(prevout, CScript()); |
143 | 0 | available_coins.pop_front(); |
144 | 0 | } |
145 | 0 | for (uint32_t n{0}; n < num_outputs; ++n) { |
146 | 0 | mtx.vout.emplace_back(100, P2WSH_OP_TRUE); |
147 | 0 | } |
148 | 0 | CTransactionRef tx = MakeTransactionRef(mtx); |
149 | | |
150 | | // First 2 outputs are available to spend. The rest are added to outpoints to calculate bumpfees. |
151 | | // There is no overlap between spendable coins and outpoints passed to MiniMiner because the |
152 | | // MiniMiner interprets spent coins as to-be-replaced and excludes them. |
153 | 0 | for (uint32_t n{0}; n < num_outputs - 1; ++n) { |
154 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
155 | 0 | available_coins.emplace_front(tx->GetHash(), n); |
156 | 0 | } else { |
157 | 0 | available_coins.emplace_back(tx->GetHash(), n); |
158 | 0 | } |
159 | 0 | } |
160 | |
|
161 | 0 | const auto block_adjusted_max_weight = MAX_BLOCK_WEIGHT - DEFAULT_BLOCK_RESERVED_WEIGHT; |
162 | | // Stop if pool reaches block_adjusted_max_weight because BlockAssembler will stop when the |
163 | | // block template reaches that, but the MiniMiner will keep going. |
164 | 0 | if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= block_adjusted_max_weight) break; |
165 | 0 | TestMemPoolEntryHelper entry; |
166 | 0 | const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)}; |
167 | 0 | assert(MoneyRange(fee)); |
168 | 0 | AddToMempool(pool, entry.Fee(fee).FromTx(tx)); |
169 | 0 | transactions.push_back(tx); |
170 | 0 | } |
171 | 0 | std::vector<COutPoint> outpoints; |
172 | 0 | for (const auto& coin : g_available_coins) { |
173 | 0 | if (!pool.GetConflictTx(coin)) outpoints.push_back(coin); |
174 | 0 | } |
175 | 0 | for (const auto& tx : transactions) { |
176 | 0 | assert(pool.exists(tx->GetHash())); |
177 | 0 | for (uint32_t n{0}; n < tx->vout.size(); ++n) { |
178 | 0 | COutPoint coin{tx->GetHash(), n}; |
179 | 0 | if (!pool.GetConflictTx(coin)) outpoints.push_back(coin); |
180 | 0 | } |
181 | 0 | } |
182 | 0 | const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)}; |
183 | |
|
184 | 0 | node::BlockAssembler::Options miner_options; |
185 | 0 | miner_options.blockMinFeeRate = target_feerate; |
186 | 0 | miner_options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; |
187 | 0 | miner_options.test_block_validity = false; |
188 | 0 | miner_options.coinbase_output_script = CScript() << OP_0; |
189 | |
|
190 | 0 | node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options}; |
191 | 0 | node::MiniMiner mini_miner{pool, outpoints}; |
192 | 0 | assert(mini_miner.IsReadyToCalculate()); |
193 | | |
194 | | // Use BlockAssembler as oracle. BlockAssembler and MiniMiner should select the same |
195 | | // transactions, stopping once packages do not meet target_feerate. |
196 | 0 | const auto blocktemplate{miner.CreateNewBlock()}; |
197 | 0 | mini_miner.BuildMockTemplate(target_feerate); |
198 | 0 | assert(!mini_miner.IsReadyToCalculate()); |
199 | 0 | auto mock_template_txids = mini_miner.GetMockTemplateTxids(); |
200 | | // MiniMiner doesn't add a coinbase tx. |
201 | 0 | assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0); |
202 | 0 | auto [iter, new_entry] = mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash()); |
203 | 0 | assert(new_entry); |
204 | | |
205 | 0 | assert(mock_template_txids.size() == blocktemplate->block.vtx.size()); |
206 | 0 | for (const auto& tx : blocktemplate->block.vtx) { |
207 | | assert(mock_template_txids.count(tx->GetHash())); |
208 | 0 | } |
209 | 0 | } |
210 | | } // namespace |