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