fuzz: keep backend assertions aligned to active backend
What changed, and why it matters
This commit fixes a bug in a Bitcoin Core fuzz test (an automated testing harness, not production code). The test was supposed to swap between two database backends during testing, but due to a C++ object-slicing mistake the swap never actually happened. The fix makes the swap work correctly and adds a cache reset when switching back to the original backend so that internal 'freshly created' flags don't become invalid. Because this only affects test code, it does not directly endanger live Bitcoin nodes or users' funds.
No production action required. Treat as a test-quality fix. Reviewers may want to confirm that the fuzz target now exercises the intended backend-switching behavior and that the reset guard correctly prevents false-positive assertions or missed bugs.
Security signals we found
Object slicing caused intended backend switch to be a silent no-op in fuzz test
Fix adds real backend pointer retargeting and cache reset on backend restore
FRESH flag inconsistency across different backends is explicitly mentioned as the reason for the reset
Only test/fuzz code is changed; no production code paths are affected
Evidence from the diff
In src/test/fuzz/coins_view.cpp, TestCoinsView previously accepted a CCoinsView& backend_coins_view and tried to replace it with a new empty CCoinsView via slice assignment (backend_coins_view = CCoinsView{}). That assignment was object slicing: only base-class members were copied, the vtable stayed unchanged, and the underlying backend (including CCoinsViewDB) was never actually switched. The patch changes the parameter to CCoinsView* so the pointer can be retargeted to a local empty CCoinsView or back to the original backend. It also adds a CreateResetGuard() before restoring the original backend, because FRESH flags that are valid against the empty backend may be invalid against the original backend that already contains those coins. The change is confined to fuzzing infrastructure and assertions; no production consensus or networking code is modified.
Changed components
src/test/fuzz/coins_view.cppTestCoinsView fuzz targetcoins_view, coins_view_db, coins_view_overlay fuzz targetsInspect captured patch +25 / −20
diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
index 9ef9e473..5af5996f 100644
--- a/src/test/fuzz/coins_view.cpp
+++ b/src/test/fuzz/coins_view.cpp
@@ -90,9 +90,11 @@ void initialize_coins_view()
static const auto testing_setup = MakeNoLogFileContext<>();
}
-void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView& backend_coins_view, bool is_db)
+void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view, bool is_db)
{
bool good_data{true};
+ auto* original_backend{backend_coins_view};
+ CCoinsView coins_view_empty{};
if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
COutPoint random_out_point;
@@ -124,15 +126,13 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
},
[&] {
uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
- // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
+ // `CCoinsViewDB::BatchWrite()` requires a non-null best block.
if (is_db && best_block.IsNull()) best_block = uint256::ONE;
coins_view_cache.SetBestBlock(best_block);
},
[&] {
- {
- const auto reset_guard{coins_view_cache.CreateResetGuard()};
- }
- // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
+ (void)coins_view_cache.CreateResetGuard();
+ // Reset() clears the best block, so reseed db-backed caches.
if (is_db) {
const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
if (best_block.IsNull()) {
@@ -150,10 +150,16 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
coins_view_cache.Uncache(random_out_point);
},
[&] {
- if (fuzzed_data_provider.ConsumeBool()) {
- backend_coins_view = CCoinsView{};
+ const bool use_original_backend{fuzzed_data_provider.ConsumeBool()};
+ if (use_original_backend && backend_coins_view != original_backend) {
+ // FRESH flags valid against the empty backend may be invalid
+ // against the original backend, so reset before restoring it.
+ (void)coins_view_cache.CreateResetGuard();
+ // Reset() clears the best block; db backends require a non-null hash.
+ if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
}
- coins_view_cache.SetBackend(backend_coins_view);
+ backend_coins_view = use_original_backend ? original_backend : &coins_view_empty;
+ coins_view_cache.SetBackend(*backend_coins_view);
},
[&] {
const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
@@ -232,13 +238,12 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
}
{
- if (is_db) {
- std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor();
- assert(!!coins_view_cursor);
+ if (is_db && backend_coins_view == original_backend) {
+ assert(backend_coins_view->Cursor());
}
- (void)backend_coins_view.EstimateSize();
- (void)backend_coins_view.GetBestBlock();
- (void)backend_coins_view.GetHeadBlocks();
+ (void)backend_coins_view->EstimateSize();
+ (void)backend_coins_view->GetBestBlock();
+ (void)backend_coins_view->GetHeadBlocks();
}
if (fuzzed_data_provider.ConsumeBool()) {
@@ -328,11 +333,11 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
}
// If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
- const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
+ const bool exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point);
if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
assert(exists_using_have_coin);
}
- if (auto coin{backend_coins_view.GetCoin(random_out_point)}) {
+ if (auto coin{backend_coins_view->GetCoin(random_out_point)}) {
assert(exists_using_have_coin_in_backend);
// Note we can't assert that `coin_using_get_coin == *coin` because the coin in
// the cache may have been modified but not yet flushed.
@@ -347,7 +352,7 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CCoinsView backend_coins_view;
CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
- TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_coins_view, /*is_db=*/false);
+ TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view, /*is_db=*/false);
}
FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
@@ -360,7 +365,7 @@ FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
};
CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
- TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_coins_view, /*is_db=*/true);
+ TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view, /*is_db=*/true);
}
// Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base.
@@ -373,5 +378,5 @@ FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
CCoinsView backend_base_coins_view;
MutationGuardCoinsViewCache backend_cache{&backend_base_coins_view, /*deterministic=*/true};
CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
- TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_cache, /*is_db=*/false);
+ TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache, /*is_db=*/false);
}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.