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