mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-15 10:07:41 +02:00
fuzz: keep backend assertions aligned to active backend
`TestCoinsView` switches the `CCoinsViewCache` backend during fuzzing and then queries the backend for cross-checks.
Pass the backend as a `CCoinsView*` (to make it relocatable) and retarget it when toggling between the original backend and a local empty `CCoinsView` so assertions always refer to the active backend. This will be switched to a singleton in the next commit.
Note that the previous slice-assignment (`backend_coins_view = CCoinsView{}`) was a silent noop for the db target, it only copied base-class members (none) without changing the vtable, so the backend was never actually switched.
The pointer approach makes the switch real for both targets, which revealed that when restoring the original backend after the empty one, the cache must be reset first to avoid carrying FRESH flags that were valid relative to the empty backend but invalid relative to the original (which may already contain those coins).
Co-authored-by: Anthony Towns <aj@erisian.com.au>
Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
Co-authored-by: marcofleon <marleo23@proton.me>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user