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 [ + - ]: 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