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