/ src / test / fuzz / mini_miner.cpp
mini_miner.cpp
  1  // Copyright (c) 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 <test/fuzz/FuzzedDataProvider.h>
  6  #include <test/fuzz/fuzz.h>
  7  #include <test/fuzz/util.h>
  8  #include <test/fuzz/util/mempool.h>
  9  #include <test/util/mining.h>
 10  #include <test/util/script.h>
 11  #include <test/util/setup_common.h>
 12  #include <test/util/time.h>
 13  #include <test/util/txmempool.h>
 14  
 15  #include <node/miner.h>
 16  #include <node/mini_miner.h>
 17  #include <node/types.h>
 18  #include <primitives/transaction.h>
 19  #include <random.h>
 20  #include <txmempool.h>
 21  #include <util/check.h>
 22  #include <util/time.h>
 23  #include <util/translation.h>
 24  
 25  #include <deque>
 26  #include <vector>
 27  
 28  namespace {
 29  
 30  std::deque<COutPoint> g_available_coins;
 31  void initialize_miner()
 32  {
 33      static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
 34      for (uint32_t i = 0; i < uint32_t{100}; ++i) {
 35          g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
 36      }
 37  }
 38  
 39  // Test that the MiniMiner can run with various outpoints and feerates.
 40  FUZZ_TARGET(mini_miner, .init = initialize_miner)
 41  {
 42      SeedRandomStateForTest(SeedRand::ZEROS);
 43      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 44      NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
 45      bilingual_str error;
 46      CTxMemPool pool{CTxMemPool::Options{}, error};
 47      Assert(error.empty());
 48      std::vector<COutPoint> outpoints;
 49      std::deque<COutPoint> available_coins = g_available_coins;
 50      LOCK2(::cs_main, pool.cs);
 51      // Cluster size cannot exceed 500
 52      LIMITED_WHILE(!available_coins.empty(), 100)
 53      {
 54          CMutableTransaction mtx = CMutableTransaction();
 55          const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size());
 56          const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50);
 57          for (size_t n{0}; n < num_inputs; ++n) {
 58              auto prevout = available_coins.front();
 59              mtx.vin.emplace_back(prevout, CScript());
 60              available_coins.pop_front();
 61          }
 62          for (uint32_t n{0}; n < num_outputs; ++n) {
 63              mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
 64          }
 65          CTransactionRef tx = MakeTransactionRef(mtx);
 66          TestMemPoolEntryHelper entry;
 67          const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
 68          assert(MoneyRange(fee));
 69          TryAddToMempool(pool, entry.Fee(fee).FromTx(tx));
 70  
 71          // All outputs are available to spend
 72          for (uint32_t n{0}; n < num_outputs; ++n) {
 73              if (fuzzed_data_provider.ConsumeBool()) {
 74                  available_coins.emplace_back(tx->GetHash(), n);
 75              }
 76          }
 77  
 78          if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) {
 79              // Add outpoint from this tx (may or not be spent by a later tx)
 80              outpoints.emplace_back(tx->GetHash(),
 81                                            (uint32_t)fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, tx->vout.size()));
 82          } else {
 83              // Add some random outpoint (will be interpreted as confirmed or not yet submitted
 84              // to mempool).
 85              auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
 86              if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) {
 87                  outpoints.push_back(*outpoint);
 88              }
 89          }
 90  
 91      }
 92  
 93      const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}};
 94      std::optional<CAmount> total_bumpfee;
 95      CAmount sum_fees = 0;
 96      {
 97          node::MiniMiner mini_miner{pool, outpoints};
 98          assert(mini_miner.IsReadyToCalculate());
 99          const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
100          for (const auto& outpoint : outpoints) {
101              auto it = bump_fees.find(outpoint);
102              assert(it != bump_fees.end());
103              assert(it->second >= 0);
104              sum_fees += it->second;
105          }
106          assert(!mini_miner.IsReadyToCalculate());
107      }
108      {
109          node::MiniMiner mini_miner{pool, outpoints};
110          assert(mini_miner.IsReadyToCalculate());
111          total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate);
112          assert(total_bumpfee.has_value());
113          assert(!mini_miner.IsReadyToCalculate());
114      }
115      // Overlapping ancestry across multiple outpoints can only reduce the total bump fee.
116      assert (sum_fees >= *total_bumpfee);
117  }
118  } // namespace