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