mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-23 14:10:15 +01:00
fa899fb7aafuzz: Speed up utxo_snapshot fuzz target (MarcoFalke)fa386642b4fuzz: Speed up utxo_snapshot by lazy re-init (MarcoFalke)fa645c7a86fuzz: Remove unused DataStream object (MarcoFalke)fae8c73d9etest: Disallow fee_estimator construction in ChainTestingSetup (MarcoFalke) Pull request description: Two commits to speed up unit and fuzz tests. Can be tested by running the fuzz target and looking at the time it took, or by looking at the flamegraph. For example: ``` FUZZ=utxo_snapshot perf record -g --call-graph dwarf ./src/test/fuzz/fuzz -runs=100 hotspot ./perf.data ACKs for top commit: TheCharlatan: Re-ACKfa899fb7aamarcofleon: Re ACKfa899fb7aabrunoerg: ACKfa899fb7aaTree-SHA512: d3a771bb12d7ef491eee61ca47325dd1cea5c20b6ad42554babf13ec98d03bef8e7786159d077e59cc7ab8112495037b0f6e55edae65b871c7cf1708687cf717
201 lines
7.9 KiB
C++
201 lines
7.9 KiB
C++
// 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 <uint256.h>
|
|
#include <util/check.h>
|
|
#include <util/fs.h>
|
|
#include <util/result.h>
|
|
#include <validation.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}, true, dummy)};
|
|
Assert(processed);
|
|
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
|
|
Assert(index);
|
|
}
|
|
}
|
|
g_setup = setup.get();
|
|
}
|
|
|
|
template <bool INVALID>
|
|
void utxo_snapshot_fuzz(FuzzBufferType buffer)
|
|
{
|
|
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
|
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";
|
|
|
|
Assert(!chainman.SnapshotBlockhash());
|
|
|
|
{
|
|
AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
|
|
// 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 = 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);
|
|
}};
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
// 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}, true, dummy)};
|
|
Assert(processed);
|
|
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
|
|
Assert(index);
|
|
}
|
|
dirty_chainman = true;
|
|
}
|
|
}
|
|
|
|
if (ActivateFuzzedSnapshot()) {
|
|
LOCK(::cs_main);
|
|
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
|
|
Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
|
|
*chainman.SnapshotBlockhash());
|
|
const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
|
|
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())};
|
|
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());
|
|
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
|