fuzz: use per-level fetch scopes in coinscache_sim

Keep one StartFetching guard per cache level instead of a single guard
for the top level, so overlays continue fetching while new cache levels
are added on top. Tear the guards down top down, since resetting a
lower cache while an upper overlay's workers read through it would cause a data race.

Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
This commit is contained in:
Andrew Toth
2026-07-15 13:25:06 -04:00
parent 5292386b78
commit 8e4b7ab725

View File

@@ -226,8 +226,8 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
CoinsViewBottom bottom;
/** Real CCoinsViewCache objects. */
std::vector<std::unique_ptr<CCoinsViewCache>> caches;
/** Long-lived StartFetching guard (nullptr unless corresponding level is a CoinsViewOverlay). */
std::unique_ptr<OverlayFetchScope> overlay_fetch_scope;
/** Long-lived StartFetching guards, parallel to `caches` (entries are nullptr unless corresponding level is a CoinsViewOverlay). */
std::vector<std::unique_ptr<OverlayFetchScope>> fetch_scopes;
/** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */
CacheLevel sim_caches[MAX_CACHES + 1];
/** Current height in the simulation. */
@@ -265,6 +265,12 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
}
};
/** Helper creating a fetch scope for the top cache (which must be a CoinsViewOverlay). */
const auto make_fetch_scope{[&] {
auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
return std::make_unique<OverlayFetchScope>(overlay, data.block);
}};
// Main simulation loop: read commands from the fuzzer input, and apply them
// to both the real cache stack and the simulation.
FuzzedDataProvider provider(buffer.data(), buffer.size());
@@ -275,8 +281,10 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
// Make sure there is always at least one CCoinsViewCache.
if (caches.empty()) {
caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
fetch_scopes.emplace_back();
sim_caches[caches.size()].Wipe();
}
assert(caches.size() == fetch_scopes.size());
// Execute command.
CallOneOf(
@@ -403,17 +411,13 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
[&]() { // Add a cache level (if not already at the max).
if (caches.size() != MAX_CACHES) {
if (overlay_fetch_scope) {
overlay_fetch_scope.reset();
sim_caches[caches.size()].Wipe();
}
// Apply to real caches.
if (provider.ConsumeBool()) {
caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
fetch_scopes.emplace_back();
} else {
caches.emplace_back(new CoinsViewOverlay(&*caches.back(), g_thread_pool, /*deterministic=*/true));
auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
fetch_scopes.emplace_back(make_fetch_scope());
}
// Apply to simulation data.
sim_caches[caches.size()].Wipe();
@@ -423,7 +427,7 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
[&]() { // Remove a cache level.
// Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
caches.back()->SanityCheck();
overlay_fetch_scope.reset();
fetch_scopes.pop_back();
caches.pop_back();
},
@@ -440,7 +444,7 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
},
[&]() { // Sync.
if (overlay_fetch_scope) return; // CoinsViewOverlay::Sync() is never called in production
if (fetch_scopes.back()) return; // CoinsViewOverlay::Sync() is never called in production
// Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).
flush();
// Apply to real caches.
@@ -450,10 +454,9 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
[&]() { // Reset.
sim_caches[caches.size()].Wipe();
// Apply to real caches. Optionally start fetching again.
if (overlay_fetch_scope && provider.ConsumeBool()) {
overlay_fetch_scope.reset();
auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
if (fetch_scopes.back() && provider.ConsumeBool()) {
fetch_scopes.back().reset(); // Stop fetching before starting again.
fetch_scopes.back() = make_fetch_scope();
} else {
(void)caches.back()->CreateResetGuard();
}
@@ -514,4 +517,7 @@ FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<
assert(realcoin->nHeight == sim->second);
}
}
// Tear down the fetch scopes top down. Otherwise lower level could reset while upper level is reading from it.
while (!fetch_scopes.empty()) fetch_scopes.pop_back();
}