Branch data Line data Source code
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 : 2 : void initialize_miner()
32 : : {
33 [ + - + - ]: 4 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
34 : 2 : g_setup = testing_setup.get();
35 [ + + ]: 202 : for (uint32_t i = 0; i < uint32_t{100}; ++i) {
36 : 200 : g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
37 : : }
38 [ + - ]: 4 : }
39 : :
40 : : // Test that the MiniMiner can run with various outpoints and feerates.
41 [ + - ]: 1595 : FUZZ_TARGET(mini_miner, .init = initialize_miner)
42 : : {
43 : 1163 : SeedRandomStateForTest(SeedRand::ZEROS);
44 : 1163 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
45 : 1163 : SetMockTime(ConsumeTime(fuzzed_data_provider));
46 : 1163 : bilingual_str error;
47 [ + - ]: 1163 : CTxMemPool pool{CTxMemPool::Options{}, error};
48 [ + - ]: 1163 : Assert(error.empty());
49 : 1163 : std::vector<COutPoint> outpoints;
50 [ + - ]: 1163 : std::deque<COutPoint> available_coins = g_available_coins;
51 [ + - + - ]: 1163 : LOCK2(::cs_main, pool.cs);
52 : : // Cluster size cannot exceed 500
53 [ + + + + ]: 165293 : LIMITED_WHILE(!available_coins.empty(), 500)
54 : : {
55 [ + - ]: 164130 : CMutableTransaction mtx = CMutableTransaction();
56 : 164130 : const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size());
57 : 164130 : const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50);
58 [ + + ]: 1518037 : for (size_t n{0}; n < num_inputs; ++n) {
59 [ + - ]: 1353907 : auto prevout = available_coins.front();
60 [ + - ]: 1353907 : mtx.vin.emplace_back(prevout, CScript());
61 : 1353907 : available_coins.pop_front();
62 : : }
63 [ + + ]: 2031972 : for (uint32_t n{0}; n < num_outputs; ++n) {
64 [ + - ]: 1867842 : mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
65 : : }
66 [ + - ]: 164130 : CTransactionRef tx = MakeTransactionRef(mtx);
67 : 164130 : TestMemPoolEntryHelper entry;
68 : 164130 : const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
69 [ - + ]: 164130 : assert(MoneyRange(fee));
70 [ + - + - ]: 164130 : AddToMempool(pool, entry.Fee(fee).FromTx(tx));
71 : :
72 : : // All outputs are available to spend
73 [ + + ]: 2031972 : for (uint32_t n{0}; n < num_outputs; ++n) {
74 [ + + ]: 1867842 : if (fuzzed_data_provider.ConsumeBool()) {
75 [ + - ]: 1252506 : available_coins.emplace_back(tx->GetHash(), n);
76 : : }
77 : : }
78 : :
79 [ + + + - ]: 164130 : if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) {
80 : : // Add outpoint from this tx (may or not be spent by a later tx)
81 [ + - ]: 84369 : outpoints.emplace_back(tx->GetHash(),
82 [ + - ]: 84369 : (uint32_t)fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, tx->vout.size()));
83 : : } else {
84 : : // Add some random outpoint (will be interpreted as confirmed or not yet submitted
85 : : // to mempool).
86 : 79761 : auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
87 [ + + + + ]: 79761 : if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) {
88 [ + - ]: 6129 : outpoints.push_back(*outpoint);
89 : : }
90 : : }
91 : :
92 : 328260 : }
93 : :
94 [ + - ]: 1163 : const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}};
95 : 1163 : std::optional<CAmount> total_bumpfee;
96 : 1163 : CAmount sum_fees = 0;
97 : 1163 : {
98 [ + - ]: 1163 : node::MiniMiner mini_miner{pool, outpoints};
99 [ - + ]: 1163 : assert(mini_miner.IsReadyToCalculate());
100 [ + - ]: 1163 : const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
101 [ + + ]: 91661 : for (const auto& outpoint : outpoints) {
102 : 90498 : auto it = bump_fees.find(outpoint);
103 [ - + ]: 90498 : assert(it != bump_fees.end());
104 [ - + ]: 90498 : assert(it->second >= 0);
105 : 90498 : sum_fees += it->second;
106 : : }
107 [ - + ]: 1163 : assert(!mini_miner.IsReadyToCalculate());
108 : 1163 : }
109 : 1163 : {
110 [ + - ]: 1163 : node::MiniMiner mini_miner{pool, outpoints};
111 [ - + ]: 1163 : assert(mini_miner.IsReadyToCalculate());
112 [ + - ]: 1163 : total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate);
113 [ - + ]: 1163 : assert(total_bumpfee.has_value());
114 [ - + ]: 1163 : assert(!mini_miner.IsReadyToCalculate());
115 : 1163 : }
116 : : // Overlapping ancestry across multiple outpoints can only reduce the total bump fee.
117 [ - + ]: 1163 : assert (sum_fees >= *total_bumpfee);
118 [ + - ]: 3489 : }
119 : :
120 : : // Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints.
121 [ + - ]: 1586 : FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
122 : : {
123 : 1154 : SeedRandomStateForTest(SeedRand::ZEROS);
124 : 1154 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
125 : 1154 : SetMockTime(ConsumeTime(fuzzed_data_provider));
126 : 1154 : bilingual_str error;
127 [ + - ]: 1154 : CTxMemPool pool{CTxMemPool::Options{}, error};
128 [ + - ]: 1154 : Assert(error.empty());
129 : : // Make a copy to preserve determinism.
130 [ + - ]: 1154 : std::deque<COutPoint> available_coins = g_available_coins;
131 : 1154 : std::vector<CTransactionRef> transactions;
132 : :
133 [ + - + - ]: 1154 : LOCK2(::cs_main, pool.cs);
134 [ + + + + ]: 28571 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
135 : : {
136 [ + - ]: 27417 : CMutableTransaction mtx = CMutableTransaction();
137 [ - + ]: 27417 : assert(!available_coins.empty());
138 [ + + ]: 27417 : const size_t num_inputs = std::min(size_t{2}, available_coins.size());
139 : 27417 : const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5);
140 [ + + ]: 82249 : for (size_t n{0}; n < num_inputs; ++n) {
141 [ + - ]: 54832 : auto prevout = available_coins.at(0);
142 [ + - ]: 54832 : mtx.vin.emplace_back(prevout, CScript());
143 : 54832 : available_coins.pop_front();
144 : : }
145 [ + + ]: 121833 : for (uint32_t n{0}; n < num_outputs; ++n) {
146 [ + - ]: 94416 : mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
147 : : }
148 [ + - ]: 27417 : 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 [ + + ]: 94416 : for (uint32_t n{0}; n < num_outputs - 1; ++n) {
154 [ + + ]: 66999 : if (fuzzed_data_provider.ConsumeBool()) {
155 [ + - ]: 57649 : available_coins.emplace_front(tx->GetHash(), n);
156 : : } else {
157 [ + - ]: 9350 : available_coins.emplace_back(tx->GetHash(), n);
158 : : }
159 : : }
160 : :
161 : 27417 : 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 [ + - + - ]: 27417 : if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= block_adjusted_max_weight) break;
165 : 27417 : TestMemPoolEntryHelper entry;
166 : 27417 : const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
167 [ - + ]: 27417 : assert(MoneyRange(fee));
168 [ + - + - ]: 27417 : AddToMempool(pool, entry.Fee(fee).FromTx(tx));
169 [ + - ]: 27417 : transactions.push_back(tx);
170 : 54834 : }
171 : 1154 : std::vector<COutPoint> outpoints;
172 [ + + ]: 116554 : for (const auto& coin : g_available_coins) {
173 [ + - + + : 115400 : if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
+ - ]
174 : : }
175 [ + + ]: 28571 : for (const auto& tx : transactions) {
176 [ + - - + ]: 27417 : assert(pool.exists(GenTxid::Txid(tx->GetHash())));
177 [ + + ]: 121833 : for (uint32_t n{0}; n < tx->vout.size(); ++n) {
178 [ + - ]: 94416 : COutPoint coin{tx->GetHash(), n};
179 [ + - + + : 94416 : if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
+ - ]
180 : : }
181 : : }
182 [ + - ]: 1154 : const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
183 : :
184 [ + - ]: 1154 : node::BlockAssembler::Options miner_options;
185 : 1154 : miner_options.blockMinFeeRate = target_feerate;
186 : 1154 : miner_options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
187 : 1154 : miner_options.test_block_validity = false;
188 [ + - ]: 1154 : miner_options.coinbase_output_script = CScript() << OP_0;
189 : :
190 [ + - + - ]: 1154 : node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options};
191 [ + - ]: 1154 : node::MiniMiner mini_miner{pool, outpoints};
192 [ - + ]: 1154 : 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 [ + - ]: 1154 : const auto blocktemplate{miner.CreateNewBlock()};
197 [ + - ]: 1154 : mini_miner.BuildMockTemplate(target_feerate);
198 [ - + ]: 1154 : assert(!mini_miner.IsReadyToCalculate());
199 [ + - ]: 1154 : auto mock_template_txids = mini_miner.GetMockTemplateTxids();
200 : : // MiniMiner doesn't add a coinbase tx.
201 [ - + ]: 1154 : assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0);
202 [ + - ]: 1154 : auto [iter, new_entry] = mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash());
203 [ - + ]: 1154 : assert(new_entry);
204 : :
205 [ - + ]: 1154 : assert(mock_template_txids.size() == blocktemplate->block.vtx.size());
206 [ + + ]: 29002 : for (const auto& tx : blocktemplate->block.vtx) {
207 [ - + ]: 27848 : assert(mock_template_txids.count(tx->GetHash()));
208 : : }
209 [ + - + - ]: 4616 : }
210 : : } // namespace
|