LCOV - code coverage report
Current view: top level - src/test/fuzz - mini_miner.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 11 137 8.0 %
Date: 2023-09-26 12:08:55 Functions: 10 13 76.9 %

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

Generated by: LCOV version 1.14