aboutsummaryrefslogtreecommitdiff
path: root/src/test/fuzz/utxo_snapshot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/fuzz/utxo_snapshot.cpp')
-rw-r--r--src/test/fuzz/utxo_snapshot.cpp151
1 files changed, 129 insertions, 22 deletions
diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
index 8c9c67a91c..1241bba8be 100644
--- a/src/test/fuzz/utxo_snapshot.cpp
+++ b/src/test/fuzz/utxo_snapshot.cpp
@@ -1,39 +1,79 @@
-// Copyright (c) 2021-2022 The Bitcoin Core developers
+// Copyright (c) 2021-present 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 <chain.h>
#include <chainparams.h>
+#include <coins.h>
+#include <consensus/consensus.h>
#include <consensus/validation.h>
+#include <node/blockstorage.h>
#include <node/utxo_snapshot.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+#include <serialize.h>
+#include <span.h>
+#include <streams.h>
+#include <sync.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
-#include <util/chaintype.h>
+#include <uint256.h>
+#include <util/check.h>
#include <util/fs.h>
+#include <util/result.h>
#include <validation.h>
-#include <validationinterface.h>
+
+#include <cstdint>
+#include <functional>
+#include <ios>
+#include <memory>
+#include <optional>
+#include <vector>
using node::SnapshotMetadata;
namespace {
const std::vector<std::shared_ptr<CBlock>>* g_chain;
+TestingSetup* g_setup;
+template <bool INVALID>
void initialize_chain()
{
const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
g_chain = &chain;
+ static const auto setup{
+ MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
+ TestOpts{
+ .setup_net = false,
+ .setup_validation_interface = false,
+ .min_validation_cache = true,
+ }),
+ };
+ if constexpr (INVALID) {
+ auto& chainman{*setup->m_node.chainman};
+ for (const auto& block : chain) {
+ BlockValidationState dummy;
+ bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
+ Assert(processed);
+ const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ Assert(index);
+ }
+ }
+ g_setup = setup.get();
}
-FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
+template <bool INVALID>
+void utxo_snapshot_fuzz(FuzzBufferType buffer)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- std::unique_ptr<const TestingSetup> setup{MakeNoLogFileContext<const TestingSetup>()};
- const auto& node = setup->m_node;
- auto& chainman{*node.chainman};
+ auto& setup{*g_setup};
+ bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
+ auto& chainman{*setup.m_node.chainman};
const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
@@ -41,29 +81,68 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
{
AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
- const auto file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
- outfile << Span{file_data};
+ // Metadata
+ if (fuzzed_data_provider.ConsumeBool()) {
+ std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
+ outfile << Span{metadata};
+ } else {
+ auto msg_start = chainman.GetParams().MessageStart();
+ int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
+ uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
+ uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
+ SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
+ outfile << metadata;
+ }
+ // Coins
+ if (fuzzed_data_provider.ConsumeBool()) {
+ std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
+ outfile << Span{file_data};
+ } else {
+ int height{0};
+ for (const auto& block : *g_chain) {
+ auto coinbase{block->vtx.at(0)};
+ outfile << coinbase->GetHash();
+ WriteCompactSize(outfile, 1); // number of coins for the hash
+ WriteCompactSize(outfile, 0); // index of coin
+ outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
+ height++;
+ }
+ }
+ if constexpr (INVALID) {
+ // Append an invalid coin to ensure invalidity. This error will be
+ // detected late in PopulateAndValidateSnapshot, and allows the
+ // INVALID fuzz target to reach more potential code coverage.
+ const auto& coinbase{g_chain->back()->vtx.back()};
+ outfile << coinbase->GetHash();
+ WriteCompactSize(outfile, 1); // number of coins for the hash
+ WriteCompactSize(outfile, 999); // index of coin
+ outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
+ }
}
const auto ActivateFuzzedSnapshot{[&] {
AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
- auto msg_start = Params().MessageStart();
+ auto msg_start = chainman.GetParams().MessageStart();
SnapshotMetadata metadata{msg_start};
try {
infile >> metadata;
} catch (const std::ios_base::failure&) {
return false;
}
- return chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
+ return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
}};
if (fuzzed_data_provider.ConsumeBool()) {
- for (const auto& block : *g_chain) {
- BlockValidationState dummy;
- bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
- Assert(processed);
- const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
- Assert(index);
+ // Consume the bool, but skip the code for the INVALID fuzz target
+ if constexpr (!INVALID) {
+ for (const auto& block : *g_chain) {
+ BlockValidationState dummy;
+ bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
+ Assert(processed);
+ const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
+ Assert(index);
+ }
+ dirty_chainman = true;
}
}
@@ -73,21 +152,49 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
*chainman.SnapshotBlockhash());
const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
- int64_t chain_tx{};
for (const auto& block : *g_chain) {
Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
- const auto num_tx{Assert(index)->nTx};
- Assert(num_tx == 1);
- chain_tx += num_tx;
+ Assert(index);
+ Assert(index->nTx == 0);
+ if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
+ auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
+ Assert(params.has_value());
+ Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
+ } else {
+ Assert(index->m_chain_tx_count == 0);
+ }
}
Assert(g_chain->size() == coinscache.GetCacheSize());
- Assert(chain_tx == chainman.ActiveTip()->nChainTx);
+ dirty_chainman = true;
} else {
Assert(!chainman.SnapshotBlockhash());
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
}
// Snapshot should refuse to load a second time regardless of validity
Assert(!ActivateFuzzedSnapshot());
+ if constexpr (INVALID) {
+ // Activating the snapshot, or any other action that makes the chainman
+ // "dirty" can and must not happen for the INVALID fuzz target
+ Assert(!dirty_chainman);
+ }
+ if (dirty_chainman) {
+ setup.m_node.chainman.reset();
+ setup.m_make_chainman();
+ setup.LoadVerifyActivateChainstate();
+ }
}
+
+// There are two fuzz targets:
+//
+// The target 'utxo_snapshot', which allows valid snapshots, but is slow,
+// because it has to reset the chainstate manager on almost all fuzz inputs.
+// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
+// input execution into the next, which makes execution non-deterministic.
+//
+// The target 'utxo_snapshot_invalid', which is fast and does not require any
+// expensive state to be reset.
+FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
+FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
+
} // namespace