From dd2561003dd2766f1ac872e5055bb526c11dc217 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 29 Jul 2026 15:24:50 -0400 Subject: [PATCH] fuzz: cover the mempool interface for transaction announcement This adds coverage for the recently-added ExtractBestByMiningScoreWithTopology method. --- src/test/fuzz/tx_pool.cpp | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 23c40c9fc44..47c2c69be8b 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -116,6 +116,32 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr ToString(fuzzed_data_provider.ConsumeIntegralInRange(0, 999))); } +/** Get a list of wtxids to query from the mempool for relay. Make the list heterogeneous with + * wtxids of mempool transactions, non-mempool transactions, and duplicates. */ +std::vector WtxidsToRelay(FuzzedDataProvider& fuzzed_data_provider, const MockedTxPool& tx_pool) +{ + LOCK(tx_pool.cs); + std::vector res; + + uint8_t dummy{0}; + const auto mempool_entries{tx_pool.entryAll()}; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) { + if (!mempool_entries.empty() && fuzzed_data_provider.ConsumeBool()) { + // Wtxid of an in-mempool transaction + const auto& entry_ref{PickValue(fuzzed_data_provider, mempool_entries).get()}; + res.push_back(entry_ref.GetTx().GetWitnessHash()); + // Don't remove it from the mempool, so the next pick is possibly a duplicate + } else { + // Wtxid of a not-in-mempool transaction + res.push_back(Wtxid::FromUint256(uint256{dummy})); + // Possibly make the next wtxid of a not-in-mempool transaction, a duplicate + if (fuzzed_data_provider.ConsumeBool()) dummy++; + } + } + + return res; +} + void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate) { WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); @@ -151,6 +177,28 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha assert(tx_pool.size() < info_all.size()); } + // Query a number of mempool entries as if to relay them, and assert some invariants on the result. + auto wtxids_to_relay{WtxidsToRelay(fuzzed_data_provider, tx_pool)}; + const auto wtxids_count_before{wtxids_to_relay.size()}; + const auto n_to_sort{fuzzed_data_provider.ConsumeIntegralInRange(0, 100)}; + const auto sorted_iter{WITH_LOCK(tx_pool.cs, return tx_pool.ExtractBestByMiningScoreWithTopology(wtxids_to_relay, n_to_sort))}; + const auto expected_count{std::min(n_to_sort, wtxids_count_before)}; + // We removed at least as many transactions from the list as we expected sorted entries. + Assert(wtxids_to_relay.size() <= wtxids_count_before - expected_count); + // When there is enough non-duplicate in-mempool transactions (list of remaining wtxids is + // non-empty), we must have received the expected number of entries. + Assert(sorted_iter.size() == expected_count || wtxids_to_relay.empty()); + if (n_to_sort > 0) { + // If we asked for a positive number of entries, we must have removed all wtxids that do + // not correspond to a mempool entry.. + const auto is_in_mempool = [&](const auto& wtxid) EXCLUSIVE_LOCKS_REQUIRED(tx_pool.cs) { return tx_pool.GetIter(wtxid).has_value(); }; + Assert(WITH_LOCK(tx_pool.cs, return std::ranges::all_of(wtxids_to_relay, is_in_mempool))); + // ..As well as all duplicates. + const auto wtxids_count{wtxids_to_relay.size()}; + const std::set unique_wtxids{std::make_move_iterator(wtxids_to_relay.begin()), std::make_move_iterator(wtxids_to_relay.end())}; + Assert(unique_wtxids.size() == wtxids_count); + } + if (fuzzed_data_provider.ConsumeBool()) { // Try eviction LOCK2(::cs_main, tx_pool.cs);