Skip to content

Commit 60b8164

Browse files
committed
Merge bitcoin/bitcoin#30644: fuzz: Faster utxo_snapshot fuzz target
fa899fb fuzz: Speed up utxo_snapshot fuzz target (MarcoFalke) fa38664 fuzz: Speed up utxo_snapshot by lazy re-init (MarcoFalke) fa645c7 fuzz: Remove unused DataStream object (MarcoFalke) fae8c73 test: 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-ACK fa899fb marcofleon: Re ACK fa899fb brunoerg: ACK fa899fb Tree-SHA512: d3a771bb12d7ef491eee61ca47325dd1cea5c20b6ad42554babf13ec98d03bef8e7786159d077e59cc7ab8112495037b0f6e55edae65b871c7cf1708687cf717
2 parents d79ea80 + fa899fb commit 60b8164

File tree

4 files changed

+126
-44
lines changed

4 files changed

+126
-44
lines changed

src/test/fuzz/utxo_snapshot.cpp

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,79 @@
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+
.min_validation_cache = true,
55+
}),
56+
};
57+
if constexpr (INVALID) {
58+
auto& chainman{*setup->m_node.chainman};
59+
for (const auto& block : chain) {
60+
BlockValidationState dummy;
61+
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
62+
Assert(processed);
63+
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
64+
Assert(index);
65+
}
66+
}
67+
g_setup = setup.get();
2968
}
3069

31-
FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
70+
template <bool INVALID>
71+
void utxo_snapshot_fuzz(FuzzBufferType buffer)
3272
{
3373
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};
74+
auto& setup{*g_setup};
75+
bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
76+
auto& chainman{*setup.m_node.chainman};
4377

4478
const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
4579

@@ -52,7 +86,6 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
5286
std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
5387
outfile << Span{metadata};
5488
} else {
55-
DataStream data_stream{};
5689
auto msg_start = chainman.GetParams().MessageStart();
5790
int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
5891
uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
@@ -75,6 +108,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
75108
height++;
76109
}
77110
}
111+
if constexpr (INVALID) {
112+
// Append an invalid coin to ensure invalidity. This error will be
113+
// detected late in PopulateAndValidateSnapshot, and allows the
114+
// INVALID fuzz target to reach more potential code coverage.
115+
const auto& coinbase{g_chain->back()->vtx.back()};
116+
outfile << coinbase->GetHash();
117+
WriteCompactSize(outfile, 1); // number of coins for the hash
118+
WriteCompactSize(outfile, 999); // index of coin
119+
outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
120+
}
78121
}
79122

80123
const auto ActivateFuzzedSnapshot{[&] {
@@ -90,12 +133,16 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
90133
}};
91134

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

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

src/test/policyestimator_tests.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright (c) 2011-2022 The Bitcoin Core developers
1+
// Copyright (c) 2011-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

55
#include <policy/fees.h>
6+
#include <policy/fees_args.h>
67
#include <policy/policy.h>
78
#include <test/util/txmempool.h>
89
#include <txmempool.h>
@@ -18,7 +19,7 @@ BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, ChainTestingSetup)
1819

1920
BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
2021
{
21-
CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator);
22+
CBlockPolicyEstimator feeEst{FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
2223
CTxMemPool& mpool = *Assert(m_node.mempool);
2324
m_node.validation_signals->RegisterValidationInterface(&feeEst);
2425
TestMemPoolEntryHelper entry;

src/test/util/setup_common.cpp

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2011-2022 The Bitcoin Core developers
1+
// Copyright (c) 2011-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

@@ -31,7 +31,6 @@
3131
#include <node/warnings.h>
3232
#include <noui.h>
3333
#include <policy/fees.h>
34-
#include <policy/fees_args.h>
3534
#include <pow.h>
3635
#include <random.h>
3736
#include <rpc/blockchain.h>
@@ -236,7 +235,6 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
236235
m_node.validation_signals = std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
237236
}
238237

239-
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
240238
bilingual_str error{};
241239
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node), error);
242240
Assert(error.empty());
@@ -246,24 +244,34 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
246244

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

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,
247+
m_make_chainman = [this, &chainparams, opts] {
248+
Assert(!m_node.chainman);
249+
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+
if (opts.min_validation_cache) {
258+
chainman_opts.script_execution_cache_bytes = 0;
259+
chainman_opts.signature_cache_bytes = 0;
260+
}
261+
const BlockManager::Options blockman_opts{
262+
.chainparams = chainman_opts.chainparams,
263+
.blocks_dir = m_args.GetBlocksDirPath(),
264+
.notifications = chainman_opts.notifications,
265+
};
266+
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown), chainman_opts, blockman_opts);
267+
LOCK(m_node.chainman->GetMutex());
268+
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<BlockTreeDB>(DBParams{
269+
.path = m_args.GetDataDirNet() / "blocks" / "index",
270+
.cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db),
271+
.memory_only = true,
272+
});
261273
};
262-
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown), chainman_opts, blockman_opts);
263-
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<BlockTreeDB>(DBParams{
264-
.path = m_args.GetDataDirNet() / "blocks" / "index",
265-
.cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db),
266-
.memory_only = true});
274+
m_make_chainman();
267275
}
268276

269277
ChainTestingSetup::~ChainTestingSetup()
@@ -276,7 +284,7 @@ ChainTestingSetup::~ChainTestingSetup()
276284
m_node.netgroupman.reset();
277285
m_node.args = nullptr;
278286
m_node.mempool.reset();
279-
m_node.fee_estimator.reset();
287+
Assert(!m_node.fee_estimator); // Each test must create a local object, if they wish to use the fee_estimator
280288
m_node.chainman.reset();
281289
m_node.validation_signals.reset();
282290
m_node.scheduler.reset();

src/test/util/setup_common.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2015-2022 The Bitcoin Core developers
1+
// Copyright (c) 2015-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

@@ -55,6 +55,7 @@ struct TestOpts {
5555
bool block_tree_db_in_memory{true};
5656
bool setup_net{true};
5757
bool setup_validation_interface{true};
58+
bool min_validation_cache{false}; // Equivalent of -maxsigcachebytes=0
5859
};
5960

6061
/** Basic testing setup.
@@ -81,6 +82,7 @@ struct ChainTestingSetup : public BasicTestingSetup {
8182
node::CacheSizes m_cache_sizes{};
8283
bool m_coins_db_in_memory{true};
8384
bool m_block_tree_db_in_memory{true};
85+
std::function<void()> m_make_chainman{};
8486

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

0 commit comments

Comments
 (0)