From b0ff31084006ac7d4a7afba3190ca75f5f8441af Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 1 Feb 2023 18:28:08 -0500 Subject: Add CCoinsViewCache::SanityCheck() and use it in fuzz test --- src/coins.cpp | 17 +++++++++++++++++ src/coins.h | 3 +++ src/test/fuzz/coinscache_sim.cpp | 9 ++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/coins.cpp b/src/coins.cpp index e98bf816ab..8d99019bb0 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -314,6 +314,23 @@ void CCoinsViewCache::ReallocateCache() ::new (&cacheCoins) CCoinsMap(); } +void CCoinsViewCache::SanityCheck() const +{ + size_t recomputed_usage = 0; + for (const auto& [_, entry] : cacheCoins) { + unsigned attr = 0; + if (entry.flags & CCoinsCacheEntry::DIRTY) attr |= 1; + if (entry.flags & CCoinsCacheEntry::FRESH) attr |= 2; + if (entry.coin.IsSpent()) attr |= 4; + // Only 5 combinations are possible. + assert(attr != 2 && attr != 4 && attr != 7); + + // Recompute cachedCoinsUsage. + recomputed_usage += entry.coin.DynamicMemoryUsage(); + } + assert(recomputed_usage == cachedCoinsUsage); +} + static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION); static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT; diff --git a/src/coins.h b/src/coins.h index 710b8c7c83..a2764d32bb 100644 --- a/src/coins.h +++ b/src/coins.h @@ -320,6 +320,9 @@ public: //! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory void ReallocateCache(); + //! Run an internal sanity check on the cache data structure. */ + void SanityCheck() const; + private: /** * @note this is marked const, but may actually append to `cacheCoins`, increasing diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index c3f732f075..b794888ca2 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -51,7 +51,8 @@ struct PrecomputedData const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)}; uint256 hash; CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin()); - /* Convert hash to scriptPubkeys. */ + /* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory + * usage check has a chance to detect mismatches). */ switch (i % 5U) { case 0: /* P2PKH */ coins[i].out.scriptPubKey.resize(25); @@ -381,6 +382,7 @@ FUZZ_TARGET(coinscache_sim) [&]() { // Remove a cache level. // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data). + caches.back()->SanityCheck(); caches.pop_back(); }, @@ -420,6 +422,11 @@ FUZZ_TARGET(coinscache_sim) ); } + // Sanity check all the remaining caches + for (const auto& cache : caches) { + cache->SanityCheck(); + } + // Full comparison between caches and simulation data, from bottom to top, // as AccessCoin on a higher cache may affect caches below it. for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) { -- cgit v1.2.3