/ src / test / fuzz / rbf.cpp
rbf.cpp
  1  // Copyright (c) 2020-2022 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/txmempool.h>
 15  #include <txmempool.h>
 16  
 17  #include <cstdint>
 18  #include <optional>
 19  #include <string>
 20  #include <vector>
 21  
 22  namespace {
 23  const BasicTestingSetup* g_setup;
 24  } // namespace
 25  
 26  const int NUM_ITERS = 10000;
 27  
 28  std::vector<COutPoint> g_outpoints;
 29  
 30  void initialize_rbf()
 31  {
 32      static const auto testing_setup = MakeNoLogFileContext<>();
 33      g_setup = testing_setup.get();
 34  }
 35  
 36  void initialize_package_rbf()
 37  {
 38      static const auto testing_setup = MakeNoLogFileContext<>();
 39      g_setup = testing_setup.get();
 40  
 41      // Create a fixed set of unique "UTXOs" to source parents from
 42      // to avoid fuzzer giving circular references
 43      for (int i = 0; i < NUM_ITERS; ++i) {
 44          g_outpoints.emplace_back();
 45          g_outpoints.back().n = i;
 46      }
 47  
 48  }
 49  
 50  FUZZ_TARGET(rbf, .init = initialize_rbf)
 51  {
 52      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 53      SetMockTime(ConsumeTime(fuzzed_data_provider));
 54      std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
 55      if (!mtx) {
 56          return;
 57      }
 58  
 59      CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)};
 60  
 61      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
 62      {
 63          const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
 64          if (!another_mtx) {
 65              break;
 66          }
 67          const CTransaction another_tx{*another_mtx};
 68          if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {
 69              mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
 70          }
 71          LOCK2(cs_main, pool.cs);
 72          pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
 73      }
 74      const CTransaction tx{*mtx};
 75      if (fuzzed_data_provider.ConsumeBool()) {
 76          LOCK2(cs_main, pool.cs);
 77          pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
 78      }
 79      {
 80          LOCK(pool.cs);
 81          (void)IsRBFOptIn(tx, pool);
 82      }
 83  }
 84  
 85  void CheckDiagramConcave(std::vector<FeeFrac>& diagram)
 86  {
 87      // Diagrams are in monotonically-decreasing feerate order.
 88      FeeFrac last_chunk = diagram.front();
 89      for (size_t i = 1; i<diagram.size(); ++i) {
 90          FeeFrac next_chunk = diagram[i] - diagram[i-1];
 91          assert(next_chunk <= last_chunk);
 92          last_chunk = next_chunk;
 93      }
 94  }
 95  
 96  FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
 97  {
 98      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 99      SetMockTime(ConsumeTime(fuzzed_data_provider));
100  
101      std::optional<CMutableTransaction> child = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
102      if (!child) return;
103  
104      CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)};
105  
106      // Add a bunch of parent-child pairs to the mempool, and remember them.
107      std::vector<CTransaction> mempool_txs;
108      size_t iter{0};
109  
110      LOCK2(cs_main, pool.cs);
111  
112      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
113      {
114          // Make sure txns only have one input, and that a unique input is given to avoid circular references
115          std::optional<CMutableTransaction> parent = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
116          if (!parent) {
117              continue;
118          }
119          assert(iter <= g_outpoints.size());
120          parent->vin.resize(1);
121          parent->vin[0].prevout = g_outpoints[iter++];
122  
123          mempool_txs.emplace_back(*parent);
124          pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back()));
125          if (fuzzed_data_provider.ConsumeBool() && !child->vin.empty()) {
126              child->vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
127          }
128          mempool_txs.emplace_back(*child);
129          pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back()));
130      }
131  
132      // Pick some transactions at random to be the direct conflicts
133      CTxMemPool::setEntries direct_conflicts;
134      for (auto& tx : mempool_txs) {
135          if (fuzzed_data_provider.ConsumeBool()) {
136              direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
137          }
138      }
139  
140      // Calculate all conflicts:
141      CTxMemPool::setEntries all_conflicts;
142      for (auto& txiter : direct_conflicts) {
143          pool.CalculateDescendants(txiter, all_conflicts);
144      }
145  
146      // Calculate the feerate diagrams for a replacement.
147      CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
148      int64_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 1000000);
149      auto calc_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
150  
151      if (calc_results.has_value()) {
152          // Sanity checks on the diagrams.
153  
154          // Diagrams start at 0.
155          assert(calc_results->first.front().size == 0);
156          assert(calc_results->first.front().fee == 0);
157          assert(calc_results->second.front().size == 0);
158          assert(calc_results->second.front().fee == 0);
159  
160          CheckDiagramConcave(calc_results->first);
161          CheckDiagramConcave(calc_results->second);
162  
163          CAmount replaced_fee{0};
164          int64_t replaced_size{0};
165          for (auto txiter : all_conflicts) {
166              replaced_fee += txiter->GetModifiedFee();
167              replaced_size += txiter->GetTxSize();
168          }
169          // The total fee of the new diagram should be the total fee of the old
170          // diagram - replaced_fee + replacement_fees
171          assert(calc_results->first.back().fee - replaced_fee + replacement_fees == calc_results->second.back().fee);
172          assert(calc_results->first.back().size - replaced_size + replacement_vsize == calc_results->second.back().size);
173      }
174  
175      // If internals report error, wrapper should too
176      auto err_tuple{ImprovesFeerateDiagram(pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)};
177      if (!calc_results.has_value()) assert(err_tuple.has_value());
178  }