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