mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 18:49:30 +01:00
tests: Add fuzzing harness for CCoinsViewCache
This commit is contained in:
parent
24f7029064
commit
f9b22e3bdb
@ -31,6 +31,7 @@ FUZZ_TARGETS = \
|
|||||||
test/fuzz/chain \
|
test/fuzz/chain \
|
||||||
test/fuzz/checkqueue \
|
test/fuzz/checkqueue \
|
||||||
test/fuzz/coins_deserialize \
|
test/fuzz/coins_deserialize \
|
||||||
|
test/fuzz/coins_view \
|
||||||
test/fuzz/cuckoocache \
|
test/fuzz/cuckoocache \
|
||||||
test/fuzz/decode_tx \
|
test/fuzz/decode_tx \
|
||||||
test/fuzz/descriptor_parse \
|
test/fuzz/descriptor_parse \
|
||||||
@ -466,6 +467,12 @@ test_fuzz_coins_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
|||||||
test_fuzz_coins_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
test_fuzz_coins_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||||
test_fuzz_coins_deserialize_SOURCES = test/fuzz/deserialize.cpp
|
test_fuzz_coins_deserialize_SOURCES = test/fuzz/deserialize.cpp
|
||||||
|
|
||||||
|
test_fuzz_coins_view_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||||
|
test_fuzz_coins_view_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||||
|
test_fuzz_coins_view_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||||
|
test_fuzz_coins_view_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||||
|
test_fuzz_coins_view_SOURCES = test/fuzz/coins_view.cpp
|
||||||
|
|
||||||
test_fuzz_cuckoocache_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
test_fuzz_cuckoocache_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||||
test_fuzz_cuckoocache_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
test_fuzz_cuckoocache_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||||
test_fuzz_cuckoocache_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
test_fuzz_cuckoocache_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||||
|
294
src/test/fuzz/coins_view.cpp
Normal file
294
src/test/fuzz/coins_view.cpp
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
// Copyright (c) 2020 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <amount.h>
|
||||||
|
#include <chainparams.h>
|
||||||
|
#include <chainparamsbase.h>
|
||||||
|
#include <coins.h>
|
||||||
|
#include <consensus/tx_verify.h>
|
||||||
|
#include <consensus/validation.h>
|
||||||
|
#include <key.h>
|
||||||
|
#include <node/coinstats.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
|
#include <primitives/transaction.h>
|
||||||
|
#include <pubkey.h>
|
||||||
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
|
#include <test/fuzz/fuzz.h>
|
||||||
|
#include <test/fuzz/util.h>
|
||||||
|
#include <validation.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const Coin EMPTY_COIN{};
|
||||||
|
|
||||||
|
bool operator==(const Coin& a, const Coin& b)
|
||||||
|
{
|
||||||
|
if (a.IsSpent() && b.IsSpent()) return true;
|
||||||
|
return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
static const ECCVerifyHandle ecc_verify_handle;
|
||||||
|
ECC_Start();
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_one_input(const std::vector<uint8_t>& buffer)
|
||||||
|
{
|
||||||
|
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||||
|
CCoinsView backend_coins_view;
|
||||||
|
CCoinsViewCache coins_view_cache{&backend_coins_view};
|
||||||
|
COutPoint random_out_point;
|
||||||
|
Coin random_coin;
|
||||||
|
CMutableTransaction random_mutable_transaction;
|
||||||
|
while (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 9)) {
|
||||||
|
case 0: {
|
||||||
|
if (random_coin.IsSpent()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Coin coin = random_coin;
|
||||||
|
bool expected_code_path = false;
|
||||||
|
const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
|
||||||
|
try {
|
||||||
|
coins_view_cache.AddCoin(random_out_point, std::move(coin), 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(expected_code_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
(void)coins_view_cache.Flush();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
Coin move_to;
|
||||||
|
(void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
coins_view_cache.Uncache(random_out_point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
if (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
backend_coins_view = CCoinsView{};
|
||||||
|
}
|
||||||
|
coins_view_cache.SetBackend(backend_coins_view);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
|
const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
|
||||||
|
if (!opt_out_point) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
random_out_point = *opt_out_point;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
|
||||||
|
if (!opt_coin) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
random_coin = *opt_coin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
|
||||||
|
if (!opt_mutable_transaction) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
random_mutable_transaction = *opt_mutable_transaction;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
CCoinsMap coins_map;
|
||||||
|
while (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
CCoinsCacheEntry coins_cache_entry;
|
||||||
|
coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>();
|
||||||
|
if (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
coins_cache_entry.coin = random_coin;
|
||||||
|
} else {
|
||||||
|
const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
|
||||||
|
if (!opt_coin) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
coins_cache_entry.coin = *opt_coin;
|
||||||
|
}
|
||||||
|
coins_map.emplace(random_out_point, std::move(coins_cache_entry));
|
||||||
|
}
|
||||||
|
bool expected_code_path = false;
|
||||||
|
try {
|
||||||
|
coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
|
||||||
|
const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
|
||||||
|
const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
|
||||||
|
const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
|
||||||
|
Coin coin_using_get_coin;
|
||||||
|
const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
|
||||||
|
if (exists_using_get_coin) {
|
||||||
|
assert(coin_using_get_coin == coin_using_access_coin);
|
||||||
|
}
|
||||||
|
assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) ||
|
||||||
|
(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin));
|
||||||
|
const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
|
||||||
|
if (exists_using_have_coin_in_backend) {
|
||||||
|
assert(exists_using_have_coin);
|
||||||
|
}
|
||||||
|
Coin coin_using_backend_get_coin;
|
||||||
|
if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) {
|
||||||
|
assert(exists_using_have_coin_in_backend);
|
||||||
|
assert(coin_using_get_coin == coin_using_backend_get_coin);
|
||||||
|
} else {
|
||||||
|
assert(!exists_using_have_coin_in_backend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool expected_code_path = false;
|
||||||
|
try {
|
||||||
|
(void)coins_view_cache.Cursor();
|
||||||
|
} catch (const std::logic_error&) {
|
||||||
|
expected_code_path = true;
|
||||||
|
}
|
||||||
|
assert(expected_code_path);
|
||||||
|
(void)coins_view_cache.DynamicMemoryUsage();
|
||||||
|
(void)coins_view_cache.EstimateSize();
|
||||||
|
(void)coins_view_cache.GetBestBlock();
|
||||||
|
(void)coins_view_cache.GetCacheSize();
|
||||||
|
(void)coins_view_cache.GetHeadBlocks();
|
||||||
|
(void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const CCoinsViewCursor* coins_view_cursor = backend_coins_view.Cursor();
|
||||||
|
assert(coins_view_cursor == nullptr);
|
||||||
|
(void)backend_coins_view.EstimateSize();
|
||||||
|
(void)backend_coins_view.GetBestBlock();
|
||||||
|
(void)backend_coins_view.GetHeadBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) {
|
||||||
|
case 0: {
|
||||||
|
const CTransaction transaction{random_mutable_transaction};
|
||||||
|
bool is_spent = false;
|
||||||
|
for (const CTxOut& tx_out : transaction.vout) {
|
||||||
|
if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
|
||||||
|
is_spent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_spent) {
|
||||||
|
// Avoid:
|
||||||
|
// coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bool expected_code_path = false;
|
||||||
|
const int height = fuzzed_data_provider.ConsumeIntegral<int>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(expected_code_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
(void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
TxValidationState state;
|
||||||
|
CAmount tx_fee_out;
|
||||||
|
const CTransaction transaction{random_mutable_transaction};
|
||||||
|
if (ContainsSpentInput(transaction, coins_view_cache)) {
|
||||||
|
// Avoid:
|
||||||
|
// consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
(void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out);
|
||||||
|
assert(MoneyRange(tx_fee_out));
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
const CTransaction transaction{random_mutable_transaction};
|
||||||
|
if (ContainsSpentInput(transaction, coins_view_cache)) {
|
||||||
|
// Avoid:
|
||||||
|
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(void)GetP2SHSigOpCount(transaction, coins_view_cache);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
const CTransaction transaction{random_mutable_transaction};
|
||||||
|
if (ContainsSpentInput(transaction, coins_view_cache)) {
|
||||||
|
// Avoid:
|
||||||
|
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const int flags = fuzzed_data_provider.ConsumeIntegral<int>();
|
||||||
|
if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
|
||||||
|
// Avoid:
|
||||||
|
// script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
CCoinsStats stats;
|
||||||
|
bool expected_code_path = false;
|
||||||
|
try {
|
||||||
|
(void)GetUTXOStats(&coins_view_cache, stats);
|
||||||
|
} catch (const std::logic_error&) {
|
||||||
|
expected_code_path = true;
|
||||||
|
}
|
||||||
|
assert(expected_code_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
|
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
#include <amount.h>
|
#include <amount.h>
|
||||||
#include <arith_uint256.h>
|
#include <arith_uint256.h>
|
||||||
#include <attributes.h>
|
#include <attributes.h>
|
||||||
|
#include <coins.h>
|
||||||
#include <consensus/consensus.h>
|
#include <consensus/consensus.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <script/script.h>
|
#include <script/script.h>
|
||||||
@ -149,4 +150,15 @@ NODISCARD bool AdditionOverflow(const T i, const T j) noexcept
|
|||||||
return std::numeric_limits<T>::max() - i < j;
|
return std::numeric_limits<T>::max() - i < j;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NODISCARD inline bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept
|
||||||
|
{
|
||||||
|
for (const CTxIn& tx_in : tx.vin) {
|
||||||
|
const Coin& coin = inputs.AccessCoin(tx_in.prevout);
|
||||||
|
if (coin.IsSpent()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // BITCOIN_TEST_FUZZ_UTIL_H
|
#endif // BITCOIN_TEST_FUZZ_UTIL_H
|
||||||
|
Loading…
Reference in New Issue
Block a user