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 : 0 : void initialize_miner()
26 : : {
27 [ # # # # ]: 0 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
28 : 0 : g_setup = testing_setup.get();
29 [ # # ]: 0 : for (uint32_t i = 0; i < uint32_t{100}; ++i) {
30 : 0 : g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
31 : : }
32 [ # # ]: 0 : }
33 : :
34 : : // Test that the MiniMiner can run with various outpoints and feerates.
35 [ # # ]: 0 : FUZZ_TARGET(mini_miner, .init = initialize_miner)
36 : : {
37 : 0 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
38 : 0 : bilingual_str error;
39 [ # # ]: 0 : CTxMemPool pool{CTxMemPool::Options{}, error};
40 [ # # ]: 0 : Assert(error.empty());
41 : 0 : std::vector<COutPoint> outpoints;
42 [ # # ]: 0 : std::deque<COutPoint> available_coins = g_available_coins;
43 [ # # # # ]: 0 : LOCK2(::cs_main, pool.cs);
44 : : // Cluster size cannot exceed 500
45 [ # # # # ]: 0 : LIMITED_WHILE(!available_coins.empty(), 500)
46 : : {
47 [ # # ]: 0 : CMutableTransaction mtx = CMutableTransaction();
48 : 0 : const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size());
49 : 0 : const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50);
50 [ # # ]: 0 : for (size_t n{0}; n < num_inputs; ++n) {
51 [ # # ]: 0 : auto prevout = available_coins.front();
52 [ # # ]: 0 : mtx.vin.emplace_back(prevout, CScript());
53 : 0 : available_coins.pop_front();
54 : : }
55 [ # # ]: 0 : for (uint32_t n{0}; n < num_outputs; ++n) {
56 [ # # ]: 0 : mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
57 : : }
58 [ # # ]: 0 : CTransactionRef tx = MakeTransactionRef(mtx);
59 : 0 : TestMemPoolEntryHelper entry;
60 : 0 : const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
61 [ # # ]: 0 : assert(MoneyRange(fee));
62 [ # # # # ]: 0 : pool.addUnchecked(entry.Fee(fee).FromTx(tx));
63 : :
64 : : // All outputs are available to spend
65 [ # # ]: 0 : for (uint32_t n{0}; n < num_outputs; ++n) {
66 [ # # ]: 0 : if (fuzzed_data_provider.ConsumeBool()) {
67 [ # # ]: 0 : available_coins.emplace_back(tx->GetHash(), n);
68 : : }
69 : : }
70 : :
71 [ # # # # ]: 0 : if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) {
72 : : // Add outpoint from this tx (may or not be spent by a later tx)
73 [ # # ]: 0 : outpoints.emplace_back(tx->GetHash(),
74 [ # # ]: 0 : (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 : 0 : auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
79 [ # # # # ]: 0 : if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) {
80 [ # # ]: 0 : outpoints.push_back(*outpoint);
81 : : }
82 : : }
83 : :
84 : 0 : }
85 : :
86 [ # # ]: 0 : const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}};
87 : 0 : std::optional<CAmount> total_bumpfee;
88 : 0 : CAmount sum_fees = 0;
89 : 0 : {
90 [ # # ]: 0 : node::MiniMiner mini_miner{pool, outpoints};
91 [ # # ]: 0 : assert(mini_miner.IsReadyToCalculate());
92 [ # # ]: 0 : const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
93 [ # # ]: 0 : for (const auto& outpoint : outpoints) {
94 : 0 : auto it = bump_fees.find(outpoint);
95 [ # # ]: 0 : assert(it != bump_fees.end());
96 [ # # ]: 0 : assert(it->second >= 0);
97 : 0 : sum_fees += it->second;
98 : : }
99 [ # # ]: 0 : assert(!mini_miner.IsReadyToCalculate());
100 : 0 : }
101 : 0 : {
102 [ # # ]: 0 : node::MiniMiner mini_miner{pool, outpoints};
103 [ # # ]: 0 : assert(mini_miner.IsReadyToCalculate());
104 [ # # ]: 0 : total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate);
105 [ # # ]: 0 : assert(total_bumpfee.has_value());
106 [ # # ]: 0 : assert(!mini_miner.IsReadyToCalculate());
107 : 0 : }
108 : : // Overlapping ancestry across multiple outpoints can only reduce the total bump fee.
109 [ # # ]: 0 : assert (sum_fees >= *total_bumpfee);
110 [ # # ]: 0 : }
111 : :
112 : : // Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints.
113 [ # # ]: 0 : FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
114 : : {
115 : 0 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
116 : 0 : bilingual_str error;
117 [ # # ]: 0 : CTxMemPool pool{CTxMemPool::Options{}, error};
118 [ # # ]: 0 : Assert(error.empty());
119 : : // Make a copy to preserve determinism.
120 [ # # ]: 0 : std::deque<COutPoint> available_coins = g_available_coins;
121 : 0 : std::vector<CTransactionRef> transactions;
122 : :
123 [ # # # # ]: 0 : LOCK2(::cs_main, pool.cs);
124 [ # # # # ]: 0 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
125 : : {
126 [ # # ]: 0 : CMutableTransaction mtx = CMutableTransaction();
127 [ # # ]: 0 : assert(!available_coins.empty());
128 [ # # ]: 0 : const size_t num_inputs = std::min(size_t{2}, available_coins.size());
129 : 0 : const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5);
130 [ # # ]: 0 : for (size_t n{0}; n < num_inputs; ++n) {
131 [ # # ]: 0 : auto prevout = available_coins.at(0);
132 [ # # ]: 0 : mtx.vin.emplace_back(prevout, CScript());
133 : 0 : available_coins.pop_front();
134 : : }
135 [ # # ]: 0 : for (uint32_t n{0}; n < num_outputs; ++n) {
136 [ # # ]: 0 : mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
137 : : }
138 [ # # ]: 0 : 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 [ # # ]: 0 : for (uint32_t n{0}; n < num_outputs - 1; ++n) {
144 [ # # ]: 0 : if (fuzzed_data_provider.ConsumeBool()) {
145 [ # # ]: 0 : available_coins.emplace_front(tx->GetHash(), n);
146 : : } else {
147 [ # # ]: 0 : 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 [ # # # # ]: 0 : if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= DEFAULT_BLOCK_MAX_WEIGHT) break;
154 : 0 : TestMemPoolEntryHelper entry;
155 : 0 : const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
156 [ # # ]: 0 : assert(MoneyRange(fee));
157 [ # # # # ]: 0 : pool.addUnchecked(entry.Fee(fee).FromTx(tx));
158 [ # # ]: 0 : transactions.push_back(tx);
159 : 0 : }
160 : 0 : std::vector<COutPoint> outpoints;
161 [ # # ]: 0 : for (const auto& coin : g_available_coins) {
162 [ # # # # : 0 : if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
# # ]
163 : : }
164 [ # # ]: 0 : for (const auto& tx : transactions) {
165 [ # # # # ]: 0 : assert(pool.exists(GenTxid::Txid(tx->GetHash())));
166 [ # # ]: 0 : for (uint32_t n{0}; n < tx->vout.size(); ++n) {
167 [ # # ]: 0 : COutPoint coin{tx->GetHash(), n};
168 [ # # # # : 0 : if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
# # ]
169 : : }
170 : : }
171 [ # # ]: 0 : const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
172 : :
173 [ # # ]: 0 : node::BlockAssembler::Options miner_options;
174 : 0 : miner_options.blockMinFeeRate = target_feerate;
175 : 0 : miner_options.nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
176 : 0 : miner_options.test_block_validity = false;
177 : :
178 [ # # # # ]: 0 : node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options};
179 [ # # ]: 0 : node::MiniMiner mini_miner{pool, outpoints};
180 [ # # ]: 0 : assert(mini_miner.IsReadyToCalculate());
181 : :
182 [ # # ]: 0 : 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 [ # # ]: 0 : const auto blocktemplate{miner.CreateNewBlock(spk_placeholder)};
186 [ # # ]: 0 : mini_miner.BuildMockTemplate(target_feerate);
187 [ # # ]: 0 : assert(!mini_miner.IsReadyToCalculate());
188 [ # # ]: 0 : auto mock_template_txids = mini_miner.GetMockTemplateTxids();
189 : : // MiniMiner doesn't add a coinbase tx.
190 [ # # ]: 0 : assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0);
191 [ # # ]: 0 : auto [iter, new_entry] = mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash());
192 [ # # ]: 0 : assert(new_entry);
193 : :
194 [ # # ]: 0 : assert(mock_template_txids.size() == blocktemplate->block.vtx.size());
195 [ # # ]: 0 : for (const auto& tx : blocktemplate->block.vtx) {
196 [ # # ]: 0 : assert(mock_template_txids.count(tx->GetHash()));
197 : : }
198 [ # # # # ]: 0 : }
199 : : } // namespace
|