LCOV - code coverage report
Current view: top level - src/test/fuzz - package_eval.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 211 211 100.0 %
Date: 2023-09-26 12:08:55 Functions: 25 25 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2023 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 <node/context.h>
       7             : #include <node/mempool_args.h>
       8             : #include <node/miner.h>
       9             : #include <test/fuzz/FuzzedDataProvider.h>
      10             : #include <test/fuzz/fuzz.h>
      11           2 : #include <test/fuzz/util.h>
      12           2 : #include <test/fuzz/util/mempool.h>
      13           2 : #include <test/util/mining.h>
      14           2 : #include <test/util/script.h>
      15           2 : #include <test/util/setup_common.h>
      16             : #include <test/util/txmempool.h>
      17           2 : #include <util/rbf.h>
      18           2 : #include <validation.h>
      19             : #include <validationinterface.h>
      20             : 
      21             : using node::BlockAssembler;
      22             : using node::NodeContext;
      23             : 
      24             : namespace {
      25             : 
      26             : const TestingSetup* g_setup;
      27           2 : std::vector<COutPoint> g_outpoints_coinbase_init_mature;
      28           2 : std::vector<COutPoint> g_outpoints_coinbase_init_immature;
      29             : 
      30             : struct MockedTxPool : public CTxMemPool {
      31        3045 :     void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
      32             :     {
      33        3045 :         LOCK(cs);
      34        3045 :         lastRollingFeeUpdate = GetTime();
      35        3045 :         blockSinceLastRollingFeeBump = true;
      36        3045 :     }
      37             : };
      38             : 
      39           1 : void initialize_tx_pool()
      40             : {
      41           1 :     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
      42           1 :     g_setup = testing_setup.get();
      43             : 
      44         201 :     for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
      45         200 :         COutPoint prevout{MineBlock(g_setup->m_node, P2WSH_OP_TRUE)};
      46             :         // Remember the txids to avoid expensive disk access later on
      47         200 :         auto& outpoints = i < COINBASE_MATURITY ?
      48             :                               g_outpoints_coinbase_init_mature :
      49             :                               g_outpoints_coinbase_init_immature;
      50         200 :         outpoints.push_back(prevout);
      51         200 :     }
      52           1 :     SyncWithValidationInterfaceQueue();
      53           1 : }
      54             : 
      55             : struct TransactionsDelta final : public CValidationInterface {
      56             :     std::set<CTransactionRef>& m_removed;
      57             :     std::set<CTransactionRef>& m_added;
      58             : 
      59        7672 :     explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a)
      60        7672 :         : m_removed{r}, m_added{a} {}
      61             : 
      62        2196 :     void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override
      63             :     {
      64             :         // Transactions may be entered and booted any number of times
      65        2196 :         m_added.insert(tx);
      66        2196 :     }
      67             : 
      68         309 :     void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
      69             :     {
      70             :         // Transactions may be entered and booted any number of times
      71         309 :         m_removed.insert(tx);
      72         309 :     }
      73             : };
      74           2 : 
      75        4859 : void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate)
      76             : {
      77        9718 :     WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
      78             :     {
      79        4859 :         BlockAssembler::Options options;
      80        4859 :         options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
      81        4859 :         options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
      82        4859 :         auto assembler = BlockAssembler{chainstate, &tx_pool, options};
      83        4859 :         auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE);
      84        4859 :         Assert(block_template->block.vtx.size() >= 1);
      85        4859 :     }
      86        4859 :     const auto info_all = tx_pool.infoAll();
      87        4859 :     if (!info_all.empty()) {
      88        1568 :         const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
      89        3136 :         WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, MemPoolRemovalReason::BLOCK /* dummy */));
      90        1568 :         std::vector<uint256> all_txids;
      91        1568 :         tx_pool.queryHashes(all_txids);
      92        1568 :         assert(all_txids.size() < info_all.size());
      93        3136 :         WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
      94        1568 :     }
      95        4859 :     SyncWithValidationInterfaceQueue();
      96        4859 : }
      97             : 
      98        7959 : void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
      99             : {
     100       15918 :     const auto time = ConsumeTime(fuzzed_data_provider,
     101        7959 :                                   chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
     102        7959 :                                   std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
     103        7959 :     SetMockTime(time);
     104        7959 : }
     105             : 
     106        4859 : CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
     107             : {
     108             :     // Take the default options for tests...
     109        4859 :     CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
     110             : 
     111             : 
     112             :     // ...override specific options for this specific fuzz suite
     113        4859 :     mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
     114        4859 :     mempool_opts.limits.ancestor_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
     115        4859 :     mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
     116        4859 :     mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
     117        4859 :     mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
     118        4859 :     mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
     119        4859 :     nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999);
     120             : 
     121        4859 :     mempool_opts.estimator = nullptr;
     122        4859 :     mempool_opts.check_ratio = 1;
     123        4859 :     mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
     124             : 
     125             :     // ...and construct a CTxMemPool from it
     126        4859 :     return CTxMemPool{mempool_opts};
     127             : }
     128             : 
     129        4863 : FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
     130             : {
     131        4859 :     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
     132        4859 :     const auto& node = g_setup->m_node;
     133        4859 :     auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
     134             : 
     135        4859 :     MockTime(fuzzed_data_provider, chainstate);
     136             : 
     137             :     // All RBF-spendable outpoints outside of the immediate package
     138        4859 :     std::set<COutPoint> outpoints_rbf;
     139             :     // All outpoints counting toward the total supply (subset of outpoints_rbf)
     140        4859 :     std::set<COutPoint> outpoints_supply;
     141        4859 :     std::map<COutPoint, CAmount> outpoints_value;
     142      490759 :     for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
     143      485900 :         Assert(outpoints_supply.insert(outpoint).second);
     144      485900 :         outpoints_value[outpoint] = 50 * COIN;
     145             :     }
     146             : 
     147             :     // Seeded by coinbase outputs first
     148        4859 :     outpoints_rbf = outpoints_supply;
     149             : 
     150        4859 :     CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)};
     151        4859 :     MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
     152             : 
     153        4859 :     chainstate.SetMempool(&tx_pool);
     154             : 
     155       12531 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
     156             :     {
     157        7672 :         Assert(!outpoints_supply.empty());
     158             : 
     159        7672 :         std::vector<CTransactionRef> txs;
     160        7672 :         std::map<uint256, CTransactionRef> wtxid_to_tx;
     161             : 
     162             : 
     163             :         // Make packages of 1-to-26 transactions
     164        7672 :         const auto num_txs = (size_t) fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 26);
     165        7672 :         std::set<COutPoint> package_outpoints;
     166       35142 :         while (txs.size() < num_txs) {
     167             : 
     168             :             // Last transaction in a package needs to be a child of parents to get further in validation
     169             :             // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
     170             :             // Note that this test currently only spends unconfirmed outputs in last transaction.
     171       27470 :             bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
     172             : 
     173             :             // Create transaction to add to the mempool
     174       54940 :             const CTransactionRef tx = [&] {
     175       27470 :                 CMutableTransaction tx_mut;
     176       27470 :                 tx_mut.nVersion = CTransaction::CURRENT_VERSION;
     177       27470 :                 tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
     178             :                 // Last tx will sweep all outpoints in package
     179       27470 :                 const auto num_in = last_tx ? package_outpoints.size()  : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
     180       27470 :                 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
     181             : 
     182       27470 :                 auto& outpoints = last_tx ? package_outpoints : outpoints_rbf;
     183             : 
     184       27470 :                 CAmount amount_in{0};
     185      347382 :                 for (size_t i = 0; i < num_in; ++i) {
     186             :                     // Pop random outpoint
     187      319912 :                     auto pop = outpoints.begin();
     188      319912 :                     std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
     189      319912 :                     const auto outpoint = *pop;
     190      319912 :                     outpoints.erase(pop);
     191      319912 :                     amount_in += outpoints_value.at(outpoint);
     192             : 
     193             :                     // Create input
     194      319912 :                     const auto sequence = ConsumeSequence(fuzzed_data_provider);
     195      319912 :                     const auto script_sig = CScript{};
     196      319912 :                     const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
     197      319912 :                     CTxIn in;
     198      319912 :                     in.prevout = outpoint;
     199      319912 :                     in.nSequence = sequence;
     200      319912 :                     in.scriptSig = script_sig;
     201      319912 :                     in.scriptWitness.stack = script_wit_stack;
     202             : 
     203      319912 :                     tx_mut.vin.push_back(in);
     204      319912 :                 }
     205       27470 :                 const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
     206       27470 :                 const auto amount_out = (amount_in - amount_fee) / num_out;
     207      404885 :                 for (int i = 0; i < num_out; ++i) {
     208      377415 :                     tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
     209      377415 :                 }
     210             :                 // TODO vary transaction sizes to catch size-related issues
     211       27470 :                 auto tx = MakeTransactionRef(tx_mut);
     212             :                 // Restore previously removed outpoints, except in-package outpoints
     213       27470 :                 if (!last_tx) {
     214      186608 :                     for (const auto& in : tx->vin) {
     215      162857 :                         Assert(outpoints.insert(in.prevout).second);
     216             :                     }
     217       23751 :                 }
     218             :                 // Cache the in-package outpoints being made
     219      404885 :                 for (size_t i = 0; i < tx->vout.size(); ++i) {
     220      377415 :                     package_outpoints.emplace(tx->GetHash(), i);
     221      377415 :                     outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
     222      377415 :                 }
     223       27470 :                 return tx;
     224       27470 :             }();
     225       27470 :             txs.push_back(tx);
     226       27470 :             wtxid_to_tx[tx->GetWitnessHash()] = tx;
     227       27470 :         }
     228             : 
     229        7672 :         if (fuzzed_data_provider.ConsumeBool()) {
     230        3100 :             MockTime(fuzzed_data_provider, chainstate);
     231        3100 :         }
     232        7672 :         if (fuzzed_data_provider.ConsumeBool()) {
     233        3045 :             tx_pool.RollingFeeUpdate();
     234        3045 :         }
     235        7672 :         if (fuzzed_data_provider.ConsumeBool()) {
     236        4648 :             const auto& txid = fuzzed_data_provider.ConsumeBool() ?
     237        1834 :                                    txs.back()->GetHash() :
     238         490 :                                    PickValue(fuzzed_data_provider, outpoints_rbf).hash;
     239        2324 :             const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
     240        2324 :             tx_pool.PrioritiseTransaction(txid, delta);
     241        2324 :         }
     242             : 
     243             :         // Remember all removed and added transactions
     244        7672 :         std::set<CTransactionRef> removed;
     245        7672 :         std::set<CTransactionRef> added;
     246        7672 :         auto txr = std::make_shared<TransactionsDelta>(removed, added);
     247        7672 :         RegisterSharedValidationInterface(txr);
     248        7672 :         const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
     249             : 
     250             :         // Single-tx packages should be rejected, so do that sometimes, and sometimes send it via single submission
     251             :         // to allow it into the mempool by itself to make more interesting mempool packages
     252       11625 :         auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
     253        7672 :         auto package_submit = !single_submit;
     254             : 
     255       15344 :         const auto result_package = WITH_LOCK(::cs_main,
     256             :                                     return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/!package_submit));
     257             :         // If something went wrong due to a package-specific policy, it might not return a
     258             :         // validation result for the transaction.
     259        7672 :         if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
     260        4892 :             auto it = result_package.m_tx_results.find(txs.back()->GetWitnessHash());
     261        4892 :             Assert(it != result_package.m_tx_results.end());
     262        4892 :             Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
     263             :                    it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID ||
     264             :                    it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
     265        4892 :         }
     266             : 
     267       15344 :         const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), bypass_limits, /*test_accept=*/!single_submit));
     268        7672 :         const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
     269             : 
     270        7672 :         SyncWithValidationInterfaceQueue();
     271        7672 :         UnregisterSharedValidationInterface(txr);
     272             : 
     273        7672 :         if (single_submit) {
     274        2481 :             Assert(accepted != added.empty());
     275        2481 :             Assert(accepted == res.m_state.IsValid());
     276        2481 :             Assert(accepted != res.m_state.IsInvalid());
     277        2481 :             if (accepted) {
     278         990 :                 Assert(added.size() == 1);
     279         990 :                 Assert(txs.back() == *added.begin());
     280         990 :             } else {
     281             :                 // Do not consider rejected transaction removed
     282        1491 :                 removed.erase(txs.back());
     283             :             }
     284        2481 :         } else {
     285             :             // This is empty if it fails early checks, or "full" if transactions are looked at deeper
     286        5191 :             Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
     287        5191 :             if (result_package.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
     288       22922 :                 for (const auto& tx : txs) {
     289       20142 :                     removed.erase(tx);
     290             :                 }
     291        2780 :             } else {
     292        7258 :                 for (const auto& [k, v] : result_package.m_tx_results) {
     293        4847 :                     if (v.m_result_type == MempoolAcceptResult::ResultType::INVALID) {
     294        3357 :                         removed.erase(wtxid_to_tx[k]);
     295        3357 :                     }
     296             :                 }
     297             :             }
     298             :         }
     299             : 
     300             :         // Helper to insert spent and created outpoints of a tx into collections
     301             :         using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>;
     302        2311 :         const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) {
     303       35303 :             for (size_t i{0}; i < tx.vout.size(); ++i) {
     304       98603 :                 for (auto& set : created_by_tx) {
     305       65611 :                     set.get().emplace(tx.GetHash(), i);
     306             :                 }
     307       32992 :             }
     308        7373 :             for (const auto& in : tx.vin) {
     309       10124 :                 for (auto& set : consumed_by_tx) {
     310        5062 :                     set.get().insert(in.prevout);
     311             :                 }
     312             :             }
     313        2311 :         };
     314             : 
     315             :         // Add created outpoints, remove spent outpoints
     316             :         {
     317             :             // Outpoints that no longer exist at all
     318        7672 :             std::set<COutPoint> consumed_erased;
     319             :             // Outpoints that no longer count toward the total supply
     320        7672 :             std::set<COutPoint> consumed_supply;
     321        7787 :             for (const auto& removed_tx : removed) {
     322         115 :                 insert_tx(/*created_by_tx=*/{consumed_erased}, /*consumed_by_tx=*/{outpoints_supply}, /*tx=*/*removed_tx);
     323             :             }
     324        9868 :             for (const auto& added_tx : added) {
     325        2196 :                 insert_tx(/*created_by_tx=*/{outpoints_supply, outpoints_rbf}, /*consumed_by_tx=*/{consumed_supply}, /*tx=*/*added_tx);
     326             :             }
     327        8045 :             for (const auto& p : consumed_erased) {
     328         373 :                 outpoints_supply.erase(p);
     329         373 :                 outpoints_rbf.erase(p);
     330             :             }
     331       12458 :             for (const auto& p : consumed_supply) {
     332        4786 :                 outpoints_supply.erase(p);
     333             :             }
     334        7672 :         }
     335        7672 :     }
     336        4859 :     Finish(fuzzed_data_provider, tx_pool, chainstate);
     337        4859 : }
     338             : } // namespace

Generated by: LCOV version 1.14