/ src / test / fuzz / mini_miner.cpp
mini_miner.cpp
  1  #include <test/fuzz/FuzzedDataProvider.h>
  2  #include <test/fuzz/fuzz.h>
  3  #include <test/fuzz/util.h>
  4  #include <test/fuzz/util/mempool.h>
  5  #include <test/util/script.h>
  6  #include <test/util/setup_common.h>
  7  #include <test/util/txmempool.h>
  8  #include <test/util/mining.h>
  9  
 10  #include <node/mini_miner.h>
 11  #include <node/miner.h>
 12  #include <primitives/transaction.h>
 13  #include <random.h>
 14  #include <txmempool.h>
 15  
 16  #include <deque>
 17  #include <vector>
 18  
 19  namespace {
 20  
 21  const TestingSetup* g_setup;
 22  std::deque<COutPoint> g_available_coins;
 23  void initialize_miner()
 24  {
 25      static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
 26      g_setup = testing_setup.get();
 27      for (uint32_t i = 0; i < uint32_t{100}; ++i) {
 28          g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
 29      }
 30  }
 31  
 32  // Test that the MiniMiner can run with various outpoints and feerates.
 33  FUZZ_TARGET(mini_miner, .init = initialize_miner)
 34  {
 35      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 36      CTxMemPool pool{CTxMemPool::Options{}};
 37      std::vector<COutPoint> outpoints;
 38      std::deque<COutPoint> available_coins = g_available_coins;
 39      LOCK2(::cs_main, pool.cs);
 40      // Cluster size cannot exceed 500
 41      LIMITED_WHILE(!available_coins.empty(), 500)
 42      {
 43          CMutableTransaction mtx = CMutableTransaction();
 44          const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size());
 45          const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50);
 46          for (size_t n{0}; n < num_inputs; ++n) {
 47              auto prevout = available_coins.front();
 48              mtx.vin.emplace_back(prevout, CScript());
 49              available_coins.pop_front();
 50          }
 51          for (uint32_t n{0}; n < num_outputs; ++n) {
 52              mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
 53          }
 54          CTransactionRef tx = MakeTransactionRef(mtx);
 55          TestMemPoolEntryHelper entry;
 56          const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
 57          assert(MoneyRange(fee));
 58          pool.addUnchecked(entry.Fee(fee).FromTx(tx));
 59  
 60          // All outputs are available to spend
 61          for (uint32_t n{0}; n < num_outputs; ++n) {
 62              if (fuzzed_data_provider.ConsumeBool()) {
 63                  available_coins.emplace_back(tx->GetHash(), n);
 64              }
 65          }
 66  
 67          if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) {
 68              // Add outpoint from this tx (may or not be spent by a later tx)
 69              outpoints.emplace_back(tx->GetHash(),
 70                                            (uint32_t)fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, tx->vout.size()));
 71          } else {
 72              // Add some random outpoint (will be interpreted as confirmed or not yet submitted
 73              // to mempool).
 74              auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
 75              if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) {
 76                  outpoints.push_back(*outpoint);
 77              }
 78          }
 79  
 80      }
 81  
 82      const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}};
 83      std::optional<CAmount> total_bumpfee;
 84      CAmount sum_fees = 0;
 85      {
 86          node::MiniMiner mini_miner{pool, outpoints};
 87          assert(mini_miner.IsReadyToCalculate());
 88          const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
 89          for (const auto& outpoint : outpoints) {
 90              auto it = bump_fees.find(outpoint);
 91              assert(it != bump_fees.end());
 92              assert(it->second >= 0);
 93              sum_fees += it->second;
 94          }
 95          assert(!mini_miner.IsReadyToCalculate());
 96      }
 97      {
 98          node::MiniMiner mini_miner{pool, outpoints};
 99          assert(mini_miner.IsReadyToCalculate());
100          total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate);
101          assert(total_bumpfee.has_value());
102          assert(!mini_miner.IsReadyToCalculate());
103      }
104      // Overlapping ancestry across multiple outpoints can only reduce the total bump fee.
105      assert (sum_fees >= *total_bumpfee);
106  }
107  
108  // Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints.
109  FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
110  {
111      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
112      CTxMemPool pool{CTxMemPool::Options{}};
113      // Make a copy to preserve determinism.
114      std::deque<COutPoint> available_coins = g_available_coins;
115      std::vector<CTransactionRef> transactions;
116  
117      LOCK2(::cs_main, pool.cs);
118      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
119      {
120          CMutableTransaction mtx = CMutableTransaction();
121          assert(!available_coins.empty());
122          const size_t num_inputs = std::min(size_t{2}, available_coins.size());
123          const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5);
124          for (size_t n{0}; n < num_inputs; ++n) {
125              auto prevout = available_coins.at(0);
126              mtx.vin.emplace_back(prevout, CScript());
127              available_coins.pop_front();
128          }
129          for (uint32_t n{0}; n < num_outputs; ++n) {
130              mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
131          }
132          CTransactionRef tx = MakeTransactionRef(mtx);
133  
134          // First 2 outputs are available to spend. The rest are added to outpoints to calculate bumpfees.
135          // There is no overlap between spendable coins and outpoints passed to MiniMiner because the
136          // MiniMiner interprets spent coins as to-be-replaced and excludes them.
137          for (uint32_t n{0}; n < num_outputs - 1; ++n) {
138              if (fuzzed_data_provider.ConsumeBool()) {
139                  available_coins.emplace_front(tx->GetHash(), n);
140              } else {
141                  available_coins.emplace_back(tx->GetHash(), n);
142              }
143          }
144  
145          // Stop if pool reaches DEFAULT_BLOCK_MAX_WEIGHT because BlockAssembler will stop when the
146          // block template reaches that, but the MiniMiner will keep going.
147          if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= DEFAULT_BLOCK_MAX_WEIGHT) break;
148          TestMemPoolEntryHelper entry;
149          const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
150          assert(MoneyRange(fee));
151          pool.addUnchecked(entry.Fee(fee).FromTx(tx));
152          transactions.push_back(tx);
153      }
154      std::vector<COutPoint> outpoints;
155      for (const auto& coin : g_available_coins) {
156          if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
157      }
158      for (const auto& tx : transactions) {
159          assert(pool.exists(GenTxid::Txid(tx->GetHash())));
160          for (uint32_t n{0}; n < tx->vout.size(); ++n) {
161              COutPoint coin{tx->GetHash(), n};
162              if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
163          }
164      }
165      const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
166  
167      node::BlockAssembler::Options miner_options;
168      miner_options.blockMinFeeRate = target_feerate;
169      miner_options.nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
170      miner_options.test_block_validity = false;
171  
172      node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options};
173      node::MiniMiner mini_miner{pool, outpoints};
174      assert(mini_miner.IsReadyToCalculate());
175  
176      CScript spk_placeholder = CScript() << OP_0;
177      // Use BlockAssembler as oracle. BlockAssembler and MiniMiner should select the same
178      // transactions, stopping once packages do not meet target_feerate.
179      const auto blocktemplate{miner.CreateNewBlock(spk_placeholder)};
180      mini_miner.BuildMockTemplate(target_feerate);
181      assert(!mini_miner.IsReadyToCalculate());
182      auto mock_template_txids = mini_miner.GetMockTemplateTxids();
183      // MiniMiner doesn't add a coinbase tx.
184      assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0);
185      mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash());
186      assert(mock_template_txids.size() <= blocktemplate->block.vtx.size());
187      assert(mock_template_txids.size() >= blocktemplate->block.vtx.size());
188      assert(mock_template_txids.size() == blocktemplate->block.vtx.size());
189      for (const auto& tx : blocktemplate->block.vtx) {
190          assert(mock_template_txids.count(tx->GetHash()));
191      }
192  }
193  } // namespace