1 // Copyright (c) 2021 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 <consensus/validation.h>
6 #include <miner.h>
7 #include <test/fuzz/FuzzedDataProvider.h>
8 #include <test/fuzz/fuzz.h>
9 #include <test/fuzz/util.h>
10 #include <test/util/mining.h>
11 #include <test/util/script.h>
12 #include <test/util/setup_common.h>
13 #include <util/rbf.h>
14 #include <validation.h>
15 #include <validationinterface.h>
16
17 namespace {
18
19 const TestingSetup* g_setup;
20 std::vector<COutPoint> g_outpoints_coinbase_init_mature;
21 std::vector<COutPoint> g_outpoints_coinbase_init_immature;
22
23 struct MockedTxPool : public CTxMemPool {
RollingFeeUpdate__anon9cbdfd370111::MockedTxPool24 void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
25 {
26 LOCK(cs);
27 lastRollingFeeUpdate = GetTime();
28 blockSinceLastRollingFeeBump = true;
29 }
30 };
31
initialize_tx_pool()32 void initialize_tx_pool()
33 {
34 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
35 g_setup = testing_setup.get();
36
37 for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
38 CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE);
39 // Remember the txids to avoid expensive disk access later on
40 auto& outpoints = i < COINBASE_MATURITY ?
41 g_outpoints_coinbase_init_mature :
42 g_outpoints_coinbase_init_immature;
43 outpoints.push_back(in.prevout);
44 }
45 SyncWithValidationInterfaceQueue();
46 }
47
48 struct TransactionsDelta final : public CValidationInterface {
49 std::set<CTransactionRef>& m_removed;
50 std::set<CTransactionRef>& m_added;
51
TransactionsDelta__anon9cbdfd370111::TransactionsDelta52 explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a)
53 : m_removed{r}, m_added{a} {}
54
TransactionAddedToMempool__anon9cbdfd370111::TransactionsDelta55 void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override
56 {
57 Assert(m_added.insert(tx).second);
58 }
59
TransactionRemovedFromMempool__anon9cbdfd370111::TransactionsDelta60 void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
61 {
62 Assert(m_removed.insert(tx).second);
63 }
64 };
65
SetMempoolConstraints(ArgsManager & args,FuzzedDataProvider & fuzzed_data_provider)66 void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider)
67 {
68 args.ForceSetArg("-limitancestorcount",
69 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50)));
70 args.ForceSetArg("-limitancestorsize",
71 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202)));
72 args.ForceSetArg("-limitdescendantcount",
73 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50)));
74 args.ForceSetArg("-limitdescendantsize",
75 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202)));
76 args.ForceSetArg("-maxmempool",
77 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200)));
78 args.ForceSetArg("-mempoolexpiry",
79 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)));
80 }
81
Finish(FuzzedDataProvider & fuzzed_data_provider,MockedTxPool & tx_pool,CChainState & chainstate)82 void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate)
83 {
84 WITH_LOCK(::cs_main, tx_pool.check(chainstate));
85 {
86 BlockAssembler::Options options;
87 options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
88 options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /* max */ COIN)};
89 auto assembler = BlockAssembler{chainstate, *static_cast<CTxMemPool*>(&tx_pool), ::Params(), options};
90 auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE);
91 Assert(block_template->block.vtx.size() >= 1);
92 }
93 const auto info_all = tx_pool.infoAll();
94 if (!info_all.empty()) {
95 const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
96 WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK));
97 std::vector<uint256> all_txids;
98 tx_pool.queryHashes(all_txids);
99 assert(all_txids.size() < info_all.size());
100 WITH_LOCK(::cs_main, tx_pool.check(chainstate));
101 }
102 SyncWithValidationInterfaceQueue();
103 }
104
MockTime(FuzzedDataProvider & fuzzed_data_provider,const CChainState & chainstate)105 void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chainstate)
106 {
107 const auto time = ConsumeTime(fuzzed_data_provider,
108 chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
109 std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
110 SetMockTime(time);
111 }
112
FUZZ_TARGET_INIT(tx_pool_standard,initialize_tx_pool)113 FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
114 {
115 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
116 const auto& node = g_setup->m_node;
117 auto& chainstate = node.chainman->ActiveChainstate();
118
119 MockTime(fuzzed_data_provider, chainstate);
120 SetMempoolConstraints(*node.args, fuzzed_data_provider);
121
122 // All RBF-spendable outpoints
123 std::set<COutPoint> outpoints_rbf;
124 // All outpoints counting toward the total supply (subset of outpoints_rbf)
125 std::set<COutPoint> outpoints_supply;
126 for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
127 Assert(outpoints_supply.insert(outpoint).second);
128 }
129 outpoints_rbf = outpoints_supply;
130
131 // The sum of the values of all spendable outpoints
132 constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN};
133
134 CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1};
135 MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
136
137 // Helper to query an amount
138 const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
139 const auto GetAmount = [&](const COutPoint& outpoint) {
140 Coin c;
141 Assert(amount_view.GetCoin(outpoint, c));
142 return c.out.nValue;
143 };
144
145 while (fuzzed_data_provider.ConsumeBool()) {
146 {
147 // Total supply is the mempool fee + all outpoints
148 CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())};
149 for (const auto& op : outpoints_supply) {
150 supply_now += GetAmount(op);
151 }
152 Assert(supply_now == SUPPLY_TOTAL);
153 }
154 Assert(!outpoints_supply.empty());
155
156 // Create transaction to add to the mempool
157 const CTransactionRef tx = [&] {
158 CMutableTransaction tx_mut;
159 tx_mut.nVersion = CTransaction::CURRENT_VERSION;
160 tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
161 const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
162 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
163
164 CAmount amount_in{0};
165 for (int i = 0; i < num_in; ++i) {
166 // Pop random outpoint
167 auto pop = outpoints_rbf.begin();
168 std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1));
169 const auto outpoint = *pop;
170 outpoints_rbf.erase(pop);
171 amount_in += GetAmount(outpoint);
172
173 // Create input
174 const auto sequence = ConsumeSequence(fuzzed_data_provider);
175 const auto script_sig = CScript{};
176 const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
177 CTxIn in;
178 in.prevout = outpoint;
179 in.nSequence = sequence;
180 in.scriptSig = script_sig;
181 in.scriptWitness.stack = script_wit_stack;
182
183 tx_mut.vin.push_back(in);
184 }
185 const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in);
186 const auto amount_out = (amount_in - amount_fee) / num_out;
187 for (int i = 0; i < num_out; ++i) {
188 tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
189 }
190 const auto tx = MakeTransactionRef(tx_mut);
191 // Restore previously removed outpoints
192 for (const auto& in : tx->vin) {
193 Assert(outpoints_rbf.insert(in.prevout).second);
194 }
195 return tx;
196 }();
197
198 if (fuzzed_data_provider.ConsumeBool()) {
199 MockTime(fuzzed_data_provider, chainstate);
200 }
201 if (fuzzed_data_provider.ConsumeBool()) {
202 SetMempoolConstraints(*node.args, fuzzed_data_provider);
203 }
204 if (fuzzed_data_provider.ConsumeBool()) {
205 tx_pool.RollingFeeUpdate();
206 }
207 if (fuzzed_data_provider.ConsumeBool()) {
208 const auto& txid = fuzzed_data_provider.ConsumeBool() ?
209 tx->GetHash() :
210 PickValue(fuzzed_data_provider, outpoints_rbf).hash;
211 const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
212 tx_pool.PrioritiseTransaction(txid, delta);
213 }
214
215 // Remember all removed and added transactions
216 std::set<CTransactionRef> removed;
217 std::set<CTransactionRef> added;
218 auto txr = std::make_shared<TransactionsDelta>(removed, added);
219 RegisterSharedValidationInterface(txr);
220 const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
221 ::fRequireStandard = fuzzed_data_provider.ConsumeBool();
222
223 // Make sure ProcessNewPackage on one transaction works and always fully validates the transaction.
224 // The result is not guaranteed to be the same as what is returned by ATMP.
225 const auto result_package = WITH_LOCK(::cs_main,
226 return ProcessNewPackage(node.chainman->ActiveChainstate(), tx_pool, {tx}, true));
227 auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
228 Assert(it != result_package.m_tx_results.end());
229 Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
230 it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
231
232 const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx_pool, tx, bypass_limits));
233 const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
234 SyncWithValidationInterfaceQueue();
235 UnregisterSharedValidationInterface(txr);
236
237 Assert(accepted != added.empty());
238 Assert(accepted == res.m_state.IsValid());
239 Assert(accepted != res.m_state.IsInvalid());
240 if (accepted) {
241 Assert(added.size() == 1); // For now, no package acceptance
242 Assert(tx == *added.begin());
243 } else {
244 // Do not consider rejected transaction removed
245 removed.erase(tx);
246 }
247
248 // Helper to insert spent and created outpoints of a tx into collections
249 using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>;
250 const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) {
251 for (size_t i{0}; i < tx.vout.size(); ++i) {
252 for (auto& set : created_by_tx) {
253 Assert(set.get().emplace(tx.GetHash(), i).second);
254 }
255 }
256 for (const auto& in : tx.vin) {
257 for (auto& set : consumed_by_tx) {
258 Assert(set.get().insert(in.prevout).second);
259 }
260 }
261 };
262 // Add created outpoints, remove spent outpoints
263 {
264 // Outpoints that no longer exist at all
265 std::set<COutPoint> consumed_erased;
266 // Outpoints that no longer count toward the total supply
267 std::set<COutPoint> consumed_supply;
268 for (const auto& removed_tx : removed) {
269 insert_tx(/* created_by_tx */ {consumed_erased}, /* consumed_by_tx */ {outpoints_supply}, /* tx */ *removed_tx);
270 }
271 for (const auto& added_tx : added) {
272 insert_tx(/* created_by_tx */ {outpoints_supply, outpoints_rbf}, /* consumed_by_tx */ {consumed_supply}, /* tx */ *added_tx);
273 }
274 for (const auto& p : consumed_erased) {
275 Assert(outpoints_supply.erase(p) == 1);
276 Assert(outpoints_rbf.erase(p) == 1);
277 }
278 for (const auto& p : consumed_supply) {
279 Assert(outpoints_supply.erase(p) == 1);
280 }
281 }
282 }
283 Finish(fuzzed_data_provider, tx_pool, chainstate);
284 }
285
FUZZ_TARGET_INIT(tx_pool,initialize_tx_pool)286 FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
287 {
288 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
289 const auto& node = g_setup->m_node;
290 auto& chainstate = node.chainman->ActiveChainstate();
291
292 MockTime(fuzzed_data_provider, chainstate);
293 SetMempoolConstraints(*node.args, fuzzed_data_provider);
294
295 std::vector<uint256> txids;
296 for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
297 txids.push_back(outpoint.hash);
298 }
299 for (int i{0}; i <= 3; ++i) {
300 // Add some immature and non-existent outpoints
301 txids.push_back(g_outpoints_coinbase_init_immature.at(i).hash);
302 txids.push_back(ConsumeUInt256(fuzzed_data_provider));
303 }
304
305 CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1};
306 MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
307
308 while (fuzzed_data_provider.ConsumeBool()) {
309 const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
310
311 if (fuzzed_data_provider.ConsumeBool()) {
312 MockTime(fuzzed_data_provider, chainstate);
313 }
314 if (fuzzed_data_provider.ConsumeBool()) {
315 SetMempoolConstraints(*node.args, fuzzed_data_provider);
316 }
317 if (fuzzed_data_provider.ConsumeBool()) {
318 tx_pool.RollingFeeUpdate();
319 }
320 if (fuzzed_data_provider.ConsumeBool()) {
321 const auto& txid = fuzzed_data_provider.ConsumeBool() ?
322 mut_tx.GetHash() :
323 PickValue(fuzzed_data_provider, txids);
324 const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
325 tx_pool.PrioritiseTransaction(txid, delta);
326 }
327
328 const auto tx = MakeTransactionRef(mut_tx);
329 const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
330 ::fRequireStandard = fuzzed_data_provider.ConsumeBool();
331 const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(node.chainman->ActiveChainstate(), tx_pool, tx, bypass_limits));
332 const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
333 if (accepted) {
334 txids.push_back(tx->GetHash());
335 }
336 }
337 Finish(fuzzed_data_provider, tx_pool, chainstate);
338 }
339 } // namespace
340