fuzz: make AddCoins query view for overwrites

In validation, `AddCoins(check_for_overwrite=false)` is only used after BIP30 has already ensured the transaction does not overwrite any unspent outputs in the UTXO view.
The coins view fuzz target can call `AddCoins` with arbitrary txids, so using the `check_for_overwrite=false` fast path on non-coinbase transactions may violate the `AddCoin` caller contract and trigger logic errors.
Only use `check_for_overwrite=false` when we have first confirmed that none of the outputs are currently unspent.
Otherwise, fall back to `check_for_overwrite=true` so `AddCoins` determines overwrites via the view.
This commit is contained in:
Lőrinc 2026-02-22 13:42:08 +01:00
parent b8fa6f0f70
commit d7e0d510f2
No known key found for this signature in database
GPG Key ID: 669FFF0FFA477A76

View File

@ -280,19 +280,14 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
// coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
return;
}
bool expected_code_path = false;
const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
try {
AddCoins(coins_view_cache, transaction, height, possible_overwrite);
expected_code_path = true;
} catch (const std::logic_error& e) {
if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
assert(!possible_overwrite);
expected_code_path = true;
const bool check_for_overwrite{transaction.IsCoinBase() || [&] {
for (uint32_t i{0}; i < transaction.vout.size(); ++i) {
if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true;
}
}
assert(expected_code_path);
return fuzzed_data_provider.ConsumeBool();
}()}; // We can only skip the check if the current txid has no unspent outputs
AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
},
[&] {
(void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);