LCOV - code coverage report
Current view: top level - src/test/fuzz - rbf.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 135 135
Test Date: 2026-03-31 04:13:53 Functions: 100.0 % 6 6
Branches: 63.2 % 242 153

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2020-present 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 <node/mempool_args.h>
       6                 :             : #include <policy/rbf.h>
       7                 :             : #include <primitives/transaction.h>
       8                 :             : #include <sync.h>
       9                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      10                 :             : #include <test/fuzz/fuzz.h>
      11                 :             : #include <test/fuzz/util.h>
      12                 :             : #include <test/fuzz/util/mempool.h>
      13                 :             : #include <test/util/setup_common.h>
      14                 :             : #include <test/util/time.h>
      15                 :             : #include <test/util/txmempool.h>
      16                 :             : #include <txmempool.h>
      17                 :             : #include <util/check.h>
      18                 :             : #include <util/translation.h>
      19                 :             : 
      20                 :             : #include <cstdint>
      21                 :             : #include <optional>
      22                 :             : #include <string>
      23                 :             : #include <vector>
      24                 :             : 
      25                 :             : namespace {
      26                 :             : const BasicTestingSetup* g_setup;
      27                 :             : } // namespace
      28                 :             : 
      29                 :             : const int NUM_ITERS = 10000;
      30                 :             : 
      31                 :             : std::vector<COutPoint> g_outpoints;
      32                 :             : 
      33                 :           1 : void initialize_rbf()
      34                 :             : {
      35   [ +  -  +  -  :           2 :     static const auto testing_setup = MakeNoLogFileContext<>();
                   +  - ]
      36                 :           1 :     g_setup = testing_setup.get();
      37                 :           1 : }
      38                 :             : 
      39                 :           1 : void initialize_package_rbf()
      40                 :             : {
      41   [ +  -  +  -  :           2 :     static const auto testing_setup = MakeNoLogFileContext<>();
                   +  - ]
      42                 :           1 :     g_setup = testing_setup.get();
      43                 :             : 
      44                 :             :     // Create a fixed set of unique "UTXOs" to source parents from
      45                 :             :     // to avoid fuzzer giving circular references
      46         [ +  + ]:       10001 :     for (int i = 0; i < NUM_ITERS; ++i) {
      47                 :       10000 :         g_outpoints.emplace_back();
      48                 :       10000 :         g_outpoints.back().n = i;
      49                 :             :     }
      50                 :             : 
      51                 :           1 : }
      52                 :             : 
      53         [ +  - ]:        1081 : FUZZ_TARGET(rbf, .init = initialize_rbf)
      54                 :             : {
      55                 :         623 :     SeedRandomStateForTest(SeedRand::ZEROS);
      56                 :         623 :     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
      57                 :         623 :     NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
      58                 :         623 :     std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
      59         [ +  + ]:         623 :     if (!mtx) {
      60                 :          21 :         return;
      61                 :             :     }
      62                 :             : 
      63         [ +  - ]:         602 :     bilingual_str error;
      64   [ +  -  +  - ]:         602 :     CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
      65         [ -  + ]:         602 :     Assert(error.empty());
      66                 :             : 
      67   [ +  +  +  - ]:      189866 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
      68                 :             :     {
      69                 :      189480 :         const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
      70         [ +  + ]:      189480 :         if (!another_mtx) {
      71                 :             :             break;
      72                 :             :         }
      73         [ +  - ]:      189264 :         const CTransaction another_tx{*another_mtx};
      74   [ +  +  +  + ]:      189264 :         if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {
      75                 :       19294 :             mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
      76                 :             :         }
      77   [ +  -  +  - ]:      189264 :         LOCK2(cs_main, pool.cs);
      78   [ +  -  +  + ]:      189264 :         if (!pool.GetIter(another_tx.GetHash())) {
      79         [ +  - ]:      160522 :             TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
      80                 :             :         }
      81   [ +  -  +  - ]:      757272 :     }
      82         [ +  - ]:         602 :     const CTransaction tx{*mtx};
      83         [ +  + ]:         602 :     if (fuzzed_data_provider.ConsumeBool()) {
      84   [ +  -  +  - ]:         228 :         LOCK2(cs_main, pool.cs);
      85   [ +  -  +  + ]:         228 :         if (!pool.GetIter(tx.GetHash())) {
      86         [ +  - ]:         193 :             TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
      87                 :             :         }
      88         [ +  - ]:         456 :     }
      89                 :         602 :     {
      90         [ +  - ]:         602 :         LOCK(pool.cs);
      91         [ +  - ]:         602 :         (void)IsRBFOptIn(tx, pool);
      92                 :         602 :     }
      93         [ +  - ]:        1827 : }
      94                 :             : 
      95         [ +  - ]:        1111 : FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
      96                 :             : {
      97                 :         653 :     SeedRandomStateForTest(SeedRand::ZEROS);
      98                 :         653 :     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
      99                 :         653 :     NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
     100                 :             : 
     101                 :             :     // "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values
     102                 :             :     // so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent.
     103         [ +  - ]:         653 :     CMutableTransaction child;
     104         [ +  - ]:         653 :     child.vin.resize(1);
     105                 :             : 
     106         [ +  - ]:         653 :     bilingual_str error;
     107   [ +  -  +  - ]:         653 :     CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
     108         [ -  + ]:         653 :     Assert(error.empty());
     109                 :             : 
     110                 :             :     // Add a bunch of parent-child pairs to the mempool, and remember them.
     111                 :         653 :     std::vector<CTransaction> mempool_txs;
     112                 :         653 :     uint32_t iter{0};
     113                 :             : 
     114                 :             :     // Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
     115                 :             :     // Add replacement_vsize since this is added to new diagram during RBF check
     116                 :         653 :     std::optional<CMutableTransaction> replacement_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
     117         [ +  + ]:         653 :     if (!replacement_tx) {
     118                 :          59 :         return;
     119                 :             :     }
     120         [ +  - ]:         594 :     replacement_tx->vin.resize(1);
     121   [ +  -  +  - ]:         594 :     replacement_tx->vin[0].prevout = g_outpoints.at(iter++);
     122         [ +  - ]:         594 :     CTransaction replacement_tx_final{*replacement_tx};
     123                 :         594 :     auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);
     124         [ +  - ]:         594 :     int32_t replacement_weight = replacement_entry.GetAdjustedWeight();
     125                 :             :     // Ensure that we don't hit FeeFrac limits, as we store TxGraph entries in terms of FeePerWeight
     126         [ +  - ]:         594 :     int64_t running_vsize_total{replacement_entry.GetTxSize()};
     127                 :             : 
     128   [ +  -  +  - ]:         594 :     LOCK2(cs_main, pool.cs);
     129                 :             : 
     130         [ +  + ]:      666516 :     while (fuzzed_data_provider.ConsumeBool()) {
     131         [ +  + ]:      665995 :         if (iter >= NUM_ITERS) break;
     132                 :             : 
     133                 :             :         // Make sure txns only have one input, and that a unique input is given to avoid circular references
     134         [ +  - ]:      665994 :         CMutableTransaction parent;
     135         [ +  - ]:      665994 :         parent.vin.resize(1);
     136         [ +  - ]:      665994 :         parent.vin[0].prevout = g_outpoints.at(iter++);
     137         [ +  - ]:      665994 :         parent.vout.emplace_back(0, CScript());
     138                 :             : 
     139         [ +  - ]:      665994 :         mempool_txs.emplace_back(parent);
     140                 :      665994 :         const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
     141         [ +  - ]:      665994 :         running_vsize_total += parent_entry.GetTxSize();
     142         [ +  + ]:      665994 :         if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {
     143                 :             :             // We aren't adding this final tx to mempool, so we don't want to conflict with it
     144                 :          51 :             mempool_txs.pop_back();
     145                 :          51 :             break;
     146                 :             :         }
     147   [ +  -  -  + ]:      665943 :         assert(!pool.GetIter(parent_entry.GetTx().GetHash()));
     148         [ +  - ]:      665943 :         TryAddToMempool(pool, parent_entry);
     149                 :             : 
     150                 :             :         // It's possible that adding this to the mempool failed due to cluster
     151                 :             :         // size limits; if so bail out.
     152   [ +  -  +  + ]:      665943 :         if(!pool.GetIter(parent_entry.GetTx().GetHash())) {
     153                 :      221075 :             mempool_txs.pop_back();
     154                 :      221075 :             continue;
     155                 :             :         }
     156                 :             : 
     157         [ +  - ]:      444868 :         child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
     158         [ +  - ]:      444868 :         mempool_txs.emplace_back(child);
     159                 :      444868 :         const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
     160         [ +  - ]:      444868 :         running_vsize_total += child_entry.GetTxSize();
     161         [ +  + ]:      444868 :         if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {
     162                 :             :             // We aren't adding this final tx to mempool, so we don't want to conflict with it
     163                 :          21 :             mempool_txs.pop_back();
     164                 :          21 :             break;
     165                 :             :         }
     166   [ +  -  +  - ]:      444847 :         if (!pool.GetIter(child_entry.GetTx().GetHash())) {
     167         [ +  - ]:      444847 :             TryAddToMempool(pool, child_entry);
     168                 :             :             // Adding this transaction to the mempool may fail due to cluster
     169                 :             :             // size limits; if so bail out.
     170   [ +  -  +  + ]:      444847 :             if(!pool.GetIter(child_entry.GetTx().GetHash())) {
     171                 :      190407 :                 mempool_txs.pop_back();
     172                 :      190407 :                 continue;
     173                 :             :             }
     174                 :             :         }
     175                 :             : 
     176         [ +  + ]:      254440 :         if (fuzzed_data_provider.ConsumeBool()) {
     177         [ +  - ]:      254229 :             pool.PrioritiseTransaction(mempool_txs.back().GetHash(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
     178                 :             :         }
     179                 :     1331988 :     }
     180                 :             : 
     181                 :             :     // Pick some transactions at random to be the direct conflicts
     182                 :         594 :     CTxMemPool::setEntries direct_conflicts;
     183         [ +  + ]:      699902 :     for (auto& tx : mempool_txs) {
     184   [ +  +  +  -  :      699308 :         if (fuzzed_data_provider.ConsumeBool() && pool.GetIter(tx.GetHash())) {
                   +  - ]
     185   [ +  -  +  - ]:      555757 :             direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
     186                 :             :         }
     187                 :             :     }
     188                 :             : 
     189                 :             :     // Calculate all conflicts:
     190                 :         594 :     CTxMemPool::setEntries all_conflicts;
     191         [ +  + ]:      556351 :     for (auto& txiter : direct_conflicts) {
     192         [ +  - ]:      555757 :         pool.CalculateDescendants(txiter, all_conflicts);
     193                 :             :     }
     194                 :             : 
     195                 :         594 :     CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
     196         [ +  - ]:         594 :     auto changeset = pool.GetChangeSet();
     197         [ +  + ]:      573212 :     for (auto& txiter : all_conflicts) {
     198         [ +  - ]:      572618 :         changeset->StageRemoval(txiter);
     199                 :             :     }
     200   [ +  -  +  - ]:        1188 :     changeset->StageAddition(replacement_entry.GetSharedTx(), replacement_fees,
     201         [ +  - ]:         594 :             replacement_entry.GetTime().count(), replacement_entry.GetHeight(),
     202         [ +  - ]:         594 :             replacement_entry.GetSequence(), replacement_entry.GetSpendsCoinbase(),
     203         [ +  - ]:         594 :             replacement_entry.GetSigOpCost(), replacement_entry.GetLockPoints());
     204                 :             :     // Calculate the chunks for a replacement.
     205         [ +  - ]:         594 :     auto calc_results{changeset->CalculateChunksForRBF()};
     206                 :             : 
     207         [ +  + ]:         594 :     if (calc_results.has_value()) {
     208                 :             :         // Sanity checks on the chunks.
     209                 :             : 
     210                 :             :         // Feerates are monotonically decreasing.
     211                 :         450 :         FeeFrac first_sum;
     212   [ -  +  +  + ]:      525887 :         for (size_t i = 0; i < calc_results->first.size(); ++i) {
     213         [ +  + ]:      525437 :             first_sum += calc_results->first[i];
     214   [ +  +  -  + ]:      525437 :             if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
     215                 :             :         }
     216                 :         450 :         FeeFrac second_sum;
     217   [ -  +  +  + ]:       27506 :         for (size_t i = 0; i < calc_results->second.size(); ++i) {
     218         [ +  + ]:       27056 :             second_sum += calc_results->second[i];
     219   [ +  +  -  + ]:       27056 :             if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
     220                 :             :         }
     221                 :             : 
     222                 :         450 :         FeeFrac replaced;
     223         [ +  + ]:      549594 :         for (auto txiter : all_conflicts) {
     224         [ +  - ]:      549144 :             replaced.fee += txiter->GetModifiedFee();
     225         [ +  - ]:      549144 :             replaced.size += txiter->GetAdjustedWeight();
     226                 :             :         }
     227                 :             :         // The total fee & size of the new diagram minus replaced fee & size should be the total
     228                 :             :         // fee & size of the old diagram minus replacement fee & size.
     229         [ +  - ]:         450 :         assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_weight}));
     230                 :             :     }
     231                 :             : 
     232                 :             :     // If internals report error, wrapper should too
     233         [ +  - ]:         594 :     auto err_tuple{ImprovesFeerateDiagram(*changeset)};
     234         [ +  + ]:         594 :     if (!calc_results.has_value()) {
     235   [ +  -  -  + ]:         144 :          assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
     236                 :             :     } else {
     237                 :             :         // Diagram check succeeded
     238                 :         450 :         auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
     239                 :         450 :         auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
     240         [ +  + ]:         450 :         if (!err_tuple.has_value()) {
     241                 :             :             // New diagram's final fee should always match or exceed old diagram's
     242         [ -  + ]:          25 :             assert(old_sum.fee <= new_sum.fee);
     243         [ +  + ]:         425 :         } else if (old_sum.fee > new_sum.fee) {
     244                 :             :             // Or it failed, and if old diagram had higher fees, it should be a failure
     245         [ -  + ]:         351 :             assert(err_tuple.value().first == DiagramCheckError::FAILURE);
     246                 :             :         }
     247                 :             :     }
     248   [ +  -  +  -  :        4335 : }
                   +  - ]
        

Generated by: LCOV version 2.0-1