Add fuzzing harness for CCoinsViewCache.
See doc/fuzzing.md for information on how to fuzz Bitcoin Core. Don't forget to contribute any coverage increasing inputs you find to the Bitcoin Core fuzzing corpus repo.
Happy fuzzing :)
Add fuzzing harness for CCoinsViewCache.
See doc/fuzzing.md for information on how to fuzz Bitcoin Core. Don't forget to contribute any coverage increasing inputs you find to the Bitcoin Core fuzzing corpus repo.
Happy fuzzing :)
59 | + bool expected_code_path = false; 60 | + try { 61 | + coins_view_cache.AddCoin(random_out_point, std::move(coin), fuzzed_data_provider.ConsumeBool()); 62 | + expected_code_path = true; 63 | + } catch (const std::logic_error& e) { 64 | + if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
if (e.what() != std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) raise e;
Could expected_code_path be removed with this fixup? If yes, that seems preferable, as it is less code.
The current formulation is intentional. The problem with doing it the way you suggest is that the throw; line (I assume raise e; was a typo :)) will not be covered. That would be bad for src/test/fuzz/ where I want literally 100% line coverage to be able to see fuzzing harness gaps where our inputs are not able to reach :)
75 | + case 2: { 76 | + coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider)); 77 | + break; 78 | + } 79 | + case 3: { 80 | + Coin coin;
Coin move_to;
nice, Concept ACK
@MarcoFalke Would you mind reviewing? :)
Rebased! :)
28 | +const Coin EMPTY_COIN{}; 29 | + 30 | +bool operator==(const Coin& a, const Coin& b) 31 | +{ 32 | + if (a.IsSpent() && b.IsSpent()) return true; 33 | + return static_cast<bool>(a.fCoinBase) == static_cast<bool>(b.fCoinBase) && a.nHeight == b.nHeight && a.out == b.out;
any reason to use static cast here? this seems to silence any sanitizers that check for overflow if the cast value was not 0 or 1
56 | + break; 57 | + } 58 | + Coin coin = random_coin; 59 | + bool expected_code_path = false; 60 | + try { 61 | + coins_view_cache.AddCoin(random_out_point, std::move(coin), fuzzed_data_provider.ConsumeBool());
coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite);
60 | + try { 61 | + coins_view_cache.AddCoin(random_out_point, std::move(coin), fuzzed_data_provider.ConsumeBool()); 62 | + expected_code_path = true; 63 | + } catch (const std::logic_error& e) { 64 | + if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { 65 | + expected_code_path = true;
assert(possible_overwrite);
Could make this check a bit stricter?
133 | + if (fuzzed_data_provider.ConsumeBool()) { 134 | + const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); 135 | + if (!opt_out_point) { 136 | + break; 137 | + } 138 | + random_out_point = *opt_out_point;
What is this doing? How is this different from the fuzz engine running case 6 -> case 9?
143 | + try { 144 | + coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock()); 145 | + expected_code_path = true; 146 | + } catch (const std::logic_error& e) { 147 | + if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) { 148 | + expected_code_path = true;
Note to myself only: I thought about putting a more strict assert here, but the exception doesn't give any hints which coin failed, so I don't think this is possible for now.
222 | + try { 223 | + AddCoins(coins_view_cache, transaction, fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeBool()); 224 | + expected_code_path = true; 225 | + } catch (const std::logic_error& e) { 226 | + if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { 227 | + expected_code_path = true;
assert(possible_overwrite);
234 | + (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache); 235 | + break; 236 | + } 237 | + case 2: { 238 | + TxValidationState state; 239 | + CAmount tx_fee;
CAmount tx_fee_out;
242 | + // Avoid: 243 | + // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. 244 | + break; 245 | + } 246 | + try { 247 | + (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee);
can assert(MoneyRange(tx_fee_out)); when check was successful?
ACK. Left some ideas for more asserts
68dd5ee2c0934b6cb8f595b4a97ba6e5d5e2a31a
@MarcoFalke Thanks a lot for a very good review! All feedback addressed :)
54 | + case 0: { 55 | + if (random_coin.IsSpent()) { 56 | + break; 57 | + } 58 | + Coin coin = random_coin; 59 | + bool expected_code_path = false;
why is this needed?
Are you saying that the only exception that is allowed to be thrown is the one that is caught by the error string? Fair enough.
Exactly :)
ACK f9b22e3bdb
ACK f9b22e3bdb 📫
<details><summary>Show signature and timestamp</summary>
Signature:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
ACK f9b22e3bdb 📫
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUhWqQv/XMjZcKGe3IffrT2bXwemMhi9fKPwO9pJcYGnW6+V/8iHWrTF44+knH5Z
k+gO6xywZRkw2LwHFQ2DRK8YYQex+AtAK4gsMu13bGZn6NoJToqgjfN4FZzZafzn
ZJmyOlPP2jO2GKAh3ur0ymUVRB5IvuitTnkjHJpFYgv0T+fFEEmrT+IwB6Gj+c9i
vjRTqU1Pg4eYxtS/zygb1faD6IjN2QUK/uiIV6wdy1aXBVxivx3/p7lCgN9J7M16
TrgvkdTX8Ox4mDMHV+bxmmDKPkDM2Z3ZmAlG2fr4l6ZQF57wQZ4InXRLJS/Rqx/Z
R3UzNufWomLHuwvuOyCTuoVuEjk6MmRmBYxa1mTD1JdC1zEdDBCGrB6Yc/TUzHKI
oUSIDxnvkOq4NGetBBuR0KwWuMOvpGfywV5zryVeMYvD4FtbrIauzDCF9Khjwqtf
Znhrmdf70sQDm8kU36/w64Iph54oERYYsV4DbhxqeGJQ+K1j6YKzOIvR7bPsS7ZX
k+eVRXEB
=x9vw
-----END PGP SIGNATURE-----
Timestamp of file with hash ead5a215f8a35eb59ebc41b959be5926bd52e9eb70983fd9272038e5d010f341 -
</details>