fuzz: prevent invalid FRESH entries and surface BatchWrite errors
Modify fuzzer logic to avoid setting `FRESH` for an outpoint that already exists unspent in the parent view, and ensure `FRESH` implies `DIRTY`. This keeps cursor invariants realistic and lets `BatchWrite` failures expose real bugs without resetting state.
This commit is contained in:
1 file changed
+10
-29
@@ -77,18 +77,7 @@ public:
|
|||||||
{
|
{
|
||||||
// Nothing must modify cacheCoins other than BatchWrite.
|
// Nothing must modify cacheCoins other than BatchWrite.
|
||||||
assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
|
assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
|
||||||
try {
|
CCoinsViewCache::BatchWrite(cursor, block_hash);
|
||||||
CCoinsViewCache::BatchWrite(cursor, block_hash);
|
|
||||||
} catch (const std::logic_error& e) {
|
|
||||||
// This error is thrown if the cursor contains a fresh entry for an outpoint that we already have a fresh
|
|
||||||
// entry for. This can happen if the fuzzer calls AddCoin -> Flush -> AddCoin -> Flush on the child cache.
|
|
||||||
// There's not an easy way to prevent the fuzzer from reaching this, so we handle it here.
|
|
||||||
// Since it is thrown in the middle of the write, we reset our own state and iterate through
|
|
||||||
// the cursor so the caller's state is also reset.
|
|
||||||
assert(e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"});
|
|
||||||
Reset();
|
|
||||||
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {}
|
|
||||||
}
|
|
||||||
m_expected_snapshot = ComputeCacheCoinsSnapshot();
|
m_expected_snapshot = ComputeCacheCoinsSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +188,6 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
|
|||||||
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
|
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
|
||||||
{
|
{
|
||||||
CCoinsCacheEntry coins_cache_entry;
|
CCoinsCacheEntry coins_cache_entry;
|
||||||
const auto dirty{fuzzed_data_provider.ConsumeBool()};
|
|
||||||
const auto fresh{fuzzed_data_provider.ConsumeBool()};
|
|
||||||
if (fuzzed_data_provider.ConsumeBool()) {
|
if (fuzzed_data_provider.ConsumeBool()) {
|
||||||
coins_cache_entry.coin = random_coin;
|
coins_cache_entry.coin = random_coin;
|
||||||
} else {
|
} else {
|
||||||
@@ -211,26 +198,20 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
|
|||||||
}
|
}
|
||||||
coins_cache_entry.coin = *opt_coin;
|
coins_cache_entry.coin = *opt_coin;
|
||||||
}
|
}
|
||||||
|
// Avoid setting FRESH for an outpoint that already exists unspent in the parent view.
|
||||||
|
bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()};
|
||||||
|
bool dirty{fresh || fuzzed_data_provider.ConsumeBool()};
|
||||||
auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
|
auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
|
||||||
if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
|
if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
|
||||||
if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
|
if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
|
||||||
dirty_count += dirty;
|
dirty_count += dirty;
|
||||||
}
|
}
|
||||||
bool expected_code_path = false;
|
auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
|
||||||
try {
|
uint256 best_block{coins_view_cache.GetBestBlock()};
|
||||||
auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
|
if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
|
||||||
uint256 best_block{coins_view_cache.GetBestBlock()};
|
// Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
|
||||||
if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
|
if (is_db && best_block.IsNull()) best_block = uint256::ONE;
|
||||||
// Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
|
coins_view_cache.BatchWrite(cursor, best_block);
|
||||||
if (is_db && best_block.IsNull()) best_block = uint256::ONE;
|
|
||||||
coins_view_cache.BatchWrite(cursor, best_block);
|
|
||||||
expected_code_path = true;
|
|
||||||
} catch (const std::logic_error& e) {
|
|
||||||
if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) {
|
|
||||||
expected_code_path = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(expected_code_path);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user