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