From eec551aaf1dff4cccc15e486d5618a8a44d8314c Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Sat, 3 Jan 2026 20:28:38 +0100 Subject: [PATCH] fuzz: keep `coinscache_sim` backend free of spent coins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CoinsViewBottom` roughly simulates a memory-backed `CCoinsViewDB`, which never stores spent coins. Stop returning spent coins from `GetCoin()`, erase spent entries in `BatchWrite()`, and tighten comparisons to expect `std::nullopt` when the simulator has no coin. Co-authored-by: Lőrinc --- src/test/fuzz/coinscache_sim.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index f57c25210e3..c6102c2a65c 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -138,8 +138,6 @@ struct CacheLevel /** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB). * * The initial state consists of the empty UTXO set. - * Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent. - * This exercises code paths with spent, non-DIRTY cache entries. */ class CoinsViewBottom final : public CCoinsView { @@ -148,16 +146,13 @@ class CoinsViewBottom final : public CCoinsView public: std::optional GetCoin(const COutPoint& outpoint) const final { - // TODO GetCoin shouldn't return spent coins - if (auto it = m_data.find(outpoint); it != m_data.end()) return it->second; + if (auto it{m_data.find(outpoint)}; it != m_data.end()) { + assert(!it->second.IsSpent()); + return it->second; + } return std::nullopt; } - bool HaveCoin(const COutPoint& outpoint) const final - { - return m_data.contains(outpoint); - } - uint256 GetBestBlock() const final { return {}; } std::vector GetHeadBlocks() const final { return {}; } std::unique_ptr Cursor() const final { return {}; } @@ -167,18 +162,20 @@ public: { for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) { if (it->second.IsDirty()) { - if (it->second.coin.IsSpent() && (it->first.n % 5) != 4) { + if (it->second.coin.IsSpent()) { m_data.erase(it->first); - } else if (cursor.WillErase(*it)) { - m_data[it->first] = std::move(it->second.coin); } else { - m_data[it->first] = it->second.coin; + if (cursor.WillErase(*it)) { + m_data[it->first] = std::move(it->second.coin); + } else { + m_data[it->first] = it->second.coin; + } } } else { /* For non-dirty entries being written, compare them with what we have. */ auto it2 = m_data.find(it->first); if (it->second.coin.IsSpent()) { - assert(it2 == m_data.end() || it2->second.IsSpent()); + assert(it2 == m_data.end()); } else { assert(it2 != m_data.end()); assert(it->second.coin.out == it2->second.out); @@ -263,7 +260,7 @@ FUZZ_TARGET(coinscache_sim) auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]); // Compare results. if (!sim.has_value()) { - assert(!realcoin || realcoin->IsSpent()); + assert(!realcoin); } else { assert(realcoin && !realcoin->IsSpent()); const auto& simcoin = data.coins[sim->first]; @@ -449,7 +446,7 @@ FUZZ_TARGET(coinscache_sim) auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]); auto sim = lookup(outpointidx, 0); if (!sim.has_value()) { - assert(!realcoin || realcoin->IsSpent()); + assert(!realcoin); } else { assert(realcoin && !realcoin->IsSpent()); assert(realcoin->out == data.coins[sim->first].out);