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