Skip to content

Commit fa38664

Browse files
author
MarcoFalke
committed
fuzz: Speed up utxo_snapshot by lazy re-init
The re-init is expensive, so skip it if there is no need. Also, add an even faster fuzz target utxo_snapshot_invalid, which does not need any re-init at all.
1 parent fa645c7 commit fa38664

File tree

3 files changed

+114
-36
lines changed

3 files changed

+114
-36
lines changed

src/test/fuzz/utxo_snapshot.cpp

+90-19
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,78 @@
1-
// Copyright (c) 2021-2022 The Bitcoin Core developers
1+
// Copyright (c) 2021-present The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <chain.h>
56
#include <chainparams.h>
7+
#include <coins.h>
8+
#include <consensus/consensus.h>
69
#include <consensus/validation.h>
10+
#include <node/blockstorage.h>
711
#include <node/utxo_snapshot.h>
12+
#include <primitives/block.h>
13+
#include <primitives/transaction.h>
14+
#include <serialize.h>
15+
#include <span.h>
16+
#include <streams.h>
17+
#include <sync.h>
818
#include <test/fuzz/FuzzedDataProvider.h>
919
#include <test/fuzz/fuzz.h>
1020
#include <test/fuzz/util.h>
1121
#include <test/util/mining.h>
1222
#include <test/util/setup_common.h>
13-
#include <util/chaintype.h>
23+
#include <uint256.h>
24+
#include <util/check.h>
1425
#include <util/fs.h>
26+
#include <util/result.h>
1527
#include <validation.h>
16-
#include <validationinterface.h>
28+
29+
#include <cstdint>
30+
#include <functional>
31+
#include <ios>
32+
#include <memory>
33+
#include <optional>
34+
#include <vector>
1735

1836
using node::SnapshotMetadata;
1937

2038
namespace {
2139

2240
const std::vector<std::shared_ptr<CBlock>>* g_chain;
41+
TestingSetup* g_setup;
2342

43+
template <bool INVALID>
2444
void initialize_chain()
2545
{
2646
const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
2747
static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
2848
g_chain = &chain;
49+
static const auto setup{
50+
MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51+
TestOpts{
52+
.setup_net = false,
53+
.setup_validation_interface = false,
54+
}),
55+
};
56+
if constexpr (INVALID) {
57+
auto& chainman{*setup->m_node.chainman};
58+
for (const auto& block : chain) {
59+
BlockValidationState dummy;
60+
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
61+
Assert(processed);
62+
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
63+
Assert(index);
64+
}
65+
}
66+
g_setup = setup.get();
2967
}
3068

31-
FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
69+
template <bool INVALID>
70+
void utxo_snapshot_fuzz(FuzzBufferType buffer)
3271
{
3372
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
34-
std::unique_ptr<const TestingSetup> setup{
35-
MakeNoLogFileContext<const TestingSetup>(
36-
ChainType::REGTEST,
37-
TestOpts{
38-
.setup_net = false,
39-
.setup_validation_interface = false,
40-
})};
41-
const auto& node = setup->m_node;
42-
auto& chainman{*node.chainman};
73+
auto& setup{*g_setup};
74+
bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
75+
auto& chainman{*setup.m_node.chainman};
4376

4477
const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
4578

@@ -74,6 +107,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
74107
height++;
75108
}
76109
}
110+
if constexpr (INVALID) {
111+
// Append an invalid coin to ensure invalidity. This error will be
112+
// detected late in PopulateAndValidateSnapshot, and allows the
113+
// INVALID fuzz target to reach more potential code coverage.
114+
const auto& coinbase{g_chain->back()->vtx.back()};
115+
outfile << coinbase->GetHash();
116+
WriteCompactSize(outfile, 1); // number of coins for the hash
117+
WriteCompactSize(outfile, 999); // index of coin
118+
outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
119+
}
77120
}
78121

79122
const auto ActivateFuzzedSnapshot{[&] {
@@ -89,12 +132,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
89132
}};
90133

91134
if (fuzzed_data_provider.ConsumeBool()) {
92-
for (const auto& block : *g_chain) {
93-
BlockValidationState dummy;
94-
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
95-
Assert(processed);
96-
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
97-
Assert(index);
135+
// Consume the bool, but skip the code for the INVALID fuzz target
136+
if constexpr (!INVALID) {
137+
for (const auto& block : *g_chain) {
138+
BlockValidationState dummy;
139+
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
140+
Assert(processed);
141+
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
142+
Assert(index);
143+
}
144+
dirty_chainman = true;
98145
}
99146
}
100147

@@ -118,11 +165,35 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
118165
}
119166
}
120167
Assert(g_chain->size() == coinscache.GetCacheSize());
168+
dirty_chainman = true;
121169
} else {
122170
Assert(!chainman.SnapshotBlockhash());
123171
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
124172
}
125173
// Snapshot should refuse to load a second time regardless of validity
126174
Assert(!ActivateFuzzedSnapshot());
175+
if constexpr (INVALID) {
176+
// Activating the snapshot, or any other action that makes the chainman
177+
// "dirty" can and must not happen for the INVALID fuzz target
178+
Assert(!dirty_chainman);
179+
}
180+
if (dirty_chainman) {
181+
setup.m_node.chainman.reset();
182+
setup.m_make_chainman();
183+
setup.LoadVerifyActivateChainstate();
184+
}
127185
}
186+
187+
// There are two fuzz targets:
188+
//
189+
// The target 'utxo_snapshot', which allows valid snapshots, but is slow,
190+
// because it has to reset the chainstate manager on almost all fuzz inputs.
191+
// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
192+
// input execution into the next, which makes execution non-deterministic.
193+
//
194+
// The target 'utxo_snapshot_invalid', which is fast and does not require any
195+
// expensive state to be reset.
196+
FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
197+
FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
198+
128199
} // namespace

src/test/util/setup_common.cpp

+23-17
Original file line numberDiff line numberDiff line change
@@ -244,24 +244,30 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
244244

245245
m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status, *Assert(m_node.warnings));
246246

247-
const ChainstateManager::Options chainman_opts{
248-
.chainparams = chainparams,
249-
.datadir = m_args.GetDataDirNet(),
250-
.check_block_index = 1,
251-
.notifications = *m_node.notifications,
252-
.signals = m_node.validation_signals.get(),
253-
.worker_threads_num = 2,
247+
m_make_chainman = [this, &chainparams] {
248+
Assert(!m_node.chainman);
249+
const ChainstateManager::Options chainman_opts{
250+
.chainparams = chainparams,
251+
.datadir = m_args.GetDataDirNet(),
252+
.check_block_index = 1,
253+
.notifications = *m_node.notifications,
254+
.signals = m_node.validation_signals.get(),
255+
.worker_threads_num = 2,
256+
};
257+
const BlockManager::Options blockman_opts{
258+
.chainparams = chainman_opts.chainparams,
259+
.blocks_dir = m_args.GetBlocksDirPath(),
260+
.notifications = chainman_opts.notifications,
261+
};
262+
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown), chainman_opts, blockman_opts);
263+
LOCK(m_node.chainman->GetMutex());
264+
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<BlockTreeDB>(DBParams{
265+
.path = m_args.GetDataDirNet() / "blocks" / "index",
266+
.cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db),
267+
.memory_only = true,
268+
});
254269
};
255-
const BlockManager::Options blockman_opts{
256-
.chainparams = chainman_opts.chainparams,
257-
.blocks_dir = m_args.GetBlocksDirPath(),
258-
.notifications = chainman_opts.notifications,
259-
};
260-
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown), chainman_opts, blockman_opts);
261-
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<BlockTreeDB>(DBParams{
262-
.path = m_args.GetDataDirNet() / "blocks" / "index",
263-
.cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db),
264-
.memory_only = true});
270+
m_make_chainman();
265271
}
266272

267273
ChainTestingSetup::~ChainTestingSetup()

src/test/util/setup_common.h

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ struct ChainTestingSetup : public BasicTestingSetup {
8181
node::CacheSizes m_cache_sizes{};
8282
bool m_coins_db_in_memory{true};
8383
bool m_block_tree_db_in_memory{true};
84+
std::function<void()> m_make_chainman{};
8485

8586
explicit ChainTestingSetup(const ChainType chainType = ChainType::MAIN, TestOpts = {});
8687
~ChainTestingSetup();

0 commit comments

Comments
 (0)