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(), 500) 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 AddToMempool(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 120 // Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints. 121 FUZZ_TARGET(mini_miner_selection, .init = initialize_miner) 122 { 123 SeedRandomStateForTest(SeedRand::ZEROS); 124 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 125 SetMockTime(ConsumeTime(fuzzed_data_provider)); 126 bilingual_str error; 127 CTxMemPool pool{CTxMemPool::Options{}, error}; 128 Assert(error.empty()); 129 // Make a copy to preserve determinism. 130 std::deque<COutPoint> available_coins = g_available_coins; 131 std::vector<CTransactionRef> transactions; 132 133 LOCK2(::cs_main, pool.cs); 134 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) 135 { 136 CMutableTransaction mtx = CMutableTransaction(); 137 assert(!available_coins.empty()); 138 const size_t num_inputs = std::min(size_t{2}, available_coins.size()); 139 const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5); 140 for (size_t n{0}; n < num_inputs; ++n) { 141 auto prevout = available_coins.at(0); 142 mtx.vin.emplace_back(prevout, CScript()); 143 available_coins.pop_front(); 144 } 145 for (uint32_t n{0}; n < num_outputs; ++n) { 146 mtx.vout.emplace_back(100, P2WSH_OP_TRUE); 147 } 148 CTransactionRef tx = MakeTransactionRef(mtx); 149 150 // First 2 outputs are available to spend. The rest are added to outpoints to calculate bumpfees. 151 // There is no overlap between spendable coins and outpoints passed to MiniMiner because the 152 // MiniMiner interprets spent coins as to-be-replaced and excludes them. 153 for (uint32_t n{0}; n < num_outputs - 1; ++n) { 154 if (fuzzed_data_provider.ConsumeBool()) { 155 available_coins.emplace_front(tx->GetHash(), n); 156 } else { 157 available_coins.emplace_back(tx->GetHash(), n); 158 } 159 } 160 161 const auto block_adjusted_max_weight = MAX_BLOCK_WEIGHT - DEFAULT_BLOCK_RESERVED_WEIGHT; 162 // Stop if pool reaches block_adjusted_max_weight because BlockAssembler will stop when the 163 // block template reaches that, but the MiniMiner will keep going. 164 if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= block_adjusted_max_weight) break; 165 TestMemPoolEntryHelper entry; 166 const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)}; 167 assert(MoneyRange(fee)); 168 AddToMempool(pool, entry.Fee(fee).FromTx(tx)); 169 transactions.push_back(tx); 170 } 171 std::vector<COutPoint> outpoints; 172 for (const auto& coin : g_available_coins) { 173 if (!pool.GetConflictTx(coin)) outpoints.push_back(coin); 174 } 175 for (const auto& tx : transactions) { 176 assert(pool.exists(GenTxid::Txid(tx->GetHash()))); 177 for (uint32_t n{0}; n < tx->vout.size(); ++n) { 178 COutPoint coin{tx->GetHash(), n}; 179 if (!pool.GetConflictTx(coin)) outpoints.push_back(coin); 180 } 181 } 182 const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)}; 183 184 node::BlockAssembler::Options miner_options; 185 miner_options.blockMinFeeRate = target_feerate; 186 miner_options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; 187 miner_options.test_block_validity = false; 188 miner_options.coinbase_output_script = CScript() << OP_0; 189 190 node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options}; 191 node::MiniMiner mini_miner{pool, outpoints}; 192 assert(mini_miner.IsReadyToCalculate()); 193 194 // Use BlockAssembler as oracle. BlockAssembler and MiniMiner should select the same 195 // transactions, stopping once packages do not meet target_feerate. 196 const auto blocktemplate{miner.CreateNewBlock()}; 197 mini_miner.BuildMockTemplate(target_feerate); 198 assert(!mini_miner.IsReadyToCalculate()); 199 auto mock_template_txids = mini_miner.GetMockTemplateTxids(); 200 // MiniMiner doesn't add a coinbase tx. 201 assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0); 202 auto [iter, new_entry] = mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash()); 203 assert(new_entry); 204 205 assert(mock_template_txids.size() == blocktemplate->block.vtx.size()); 206 for (const auto& tx : blocktemplate->block.vtx) { 207 assert(mock_template_txids.count(tx->GetHash())); 208 } 209 } 210 } // namespace