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