Skip to content

Commit c58ae19

Browse files
committed
Merge bitcoin/bitcoin#32198: fuzz: Make p2p_headers_presync more deterministic
faa3ce3 fuzz: Avoid influence on the global RNG from peerman m_rng (MarcoFalke) faf4c1b fuzz: Disable unused validation interface and scheduler in p2p_headers_presync (MarcoFalke) fafaca6 fuzz: Avoid setting the mock-time twice (MarcoFalke) fad2214 refactor: Use MockableSteadyClock in ReportHeadersPresync (MarcoFalke) fa9c387 test: Introduce MockableSteadyClock::mock_time_point and ElapseSteady helper (MarcoFalke) faf2d51 fuzz: Move global node id counter along with other global state (MarcoFalke) fa98455 fuzz: Set ignore_incoming_txs in p2p_headers_presync (MarcoFalke) faf2e23 fuzz: Shuffle files before testing them (MarcoFalke) Pull request description: This should make the `p2p_headers_presync` fuzz target more deterministic. Tracking issue: bitcoin/bitcoin#29018. The first commits adds an `ElapseSteady` helper and type aliases. The second commit uses those helpers in `ReportHeadersPresync` and in the fuzz target to increase determinism. ### Testing It can be tested via (setting 32 parallel threads): ``` cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/bld-cmake/ $PWD/../b-c-qa-assets/fuzz_corpora/ p2p_headers_presync 32 ``` The failing diff is contained in the commit messages, if applicable. ACKs for top commit: Crypt-iQ: tACK faa3ce3 janb84: Re-ACK [faa3ce3](bitcoin/bitcoin@faa3ce3) marcofleon: ACK faa3ce3 Tree-SHA512: 7e2e0ddf3b4e818300373d6906384df57a87f1eeb507fa43de1ba88cf03c8e6752a26b6e91bfb3ee26a21efcaf1d0d9eaf70d311d1637b671965ef4cb96e6b59
2 parents e1dfa4f + faa3ce3 commit c58ae19

File tree

10 files changed

+71
-20
lines changed

10 files changed

+71
-20
lines changed

contrib/devtools/deterministic-fuzz-coverage/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ fn deterministic_coverage(
133133
let output = {
134134
let mut cmd = Command::new(fuzz_exe);
135135
if using_libfuzzer {
136-
cmd.arg("-runs=1");
136+
cmd.args(["-runs=1", "-shuffle=1", "-prefer_small=0"]);
137137
}
138138
cmd
139139
}

src/test/fuzz/fuzz.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <util/sock.h>
1616
#include <util/time.h>
1717

18+
#include <algorithm>
1819
#include <csignal>
1920
#include <cstdint>
2021
#include <cstdio>
@@ -26,6 +27,7 @@
2627
#include <iostream>
2728
#include <map>
2829
#include <memory>
30+
#include <random>
2931
#include <string>
3032
#include <tuple>
3133
#include <utility>
@@ -241,10 +243,15 @@ int main(int argc, char** argv)
241243
for (int i = 1; i < argc; ++i) {
242244
fs::path input_path(*(argv + i));
243245
if (fs::is_directory(input_path)) {
246+
std::vector<fs::path> files;
244247
for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) {
245248
if (!fs::is_regular_file(it->path())) continue;
246-
g_input_path = it->path();
247-
Assert(read_file(it->path(), buffer));
249+
files.emplace_back(it->path());
250+
}
251+
std::ranges::shuffle(files, std::mt19937{std::random_device{}()});
252+
for (const auto& input_path : files) {
253+
g_input_path = input_path;
254+
Assert(read_file(input_path, buffer));
248255
test_one_input(buffer);
249256
++tested;
250257
buffer.clear();

src/test/fuzz/p2p_headers_presync.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <test/util/net.h>
1616
#include <test/util/script.h>
1717
#include <test/util/setup_common.h>
18+
#include <test/util/time.h>
1819
#include <uint256.h>
1920
#include <validation.h>
2021

@@ -31,6 +32,15 @@ class HeadersSyncSetup : public TestingSetup
3132
PeerManager::Options peerman_opts;
3233
node::ApplyArgsManOptions(*m_node.args, peerman_opts);
3334
peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
35+
// The peerman's rng is a global that is re-used, so it will be re-used
36+
// and may cause non-determinism between runs. This may even influence
37+
// the global RNG, because seeding may be done from the gloabl one. For
38+
// now, avoid it influencing the global RNG, and initialize it with a
39+
// constant instead.
40+
peerman_opts.deterministic_rng = true;
41+
// No txs are relayed. Disable irrelevant and possibly
42+
// non-deterministic code paths.
43+
peerman_opts.ignore_incoming_txs = true;
3444
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
3545
m_node.banman.get(), *m_node.chainman,
3646
*m_node.mempool, *m_node.warnings, peerman_opts);
@@ -51,7 +61,7 @@ void HeadersSyncSetup::ResetAndInitialize()
5161
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
5262
connman.StopNodes();
5363

54-
NodeId id{0};
64+
static NodeId id{0};
5565
std::vector<ConnectionType> conn_types = {
5666
ConnectionType::OUTBOUND_FULL_RELAY,
5767
ConnectionType::BLOCK_RELAY,
@@ -146,7 +156,12 @@ HeadersSyncSetup* g_testing_setup;
146156

147157
void initialize()
148158
{
149-
static auto setup = MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN);
159+
static auto setup{
160+
MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN,
161+
{
162+
.setup_validation_interface = false,
163+
}),
164+
};
150165
g_testing_setup = setup.get();
151166
}
152167
} // namespace
@@ -155,17 +170,18 @@ FUZZ_TARGET(p2p_headers_presync, .init = initialize)
155170
{
156171
SeedRandomStateForTest(SeedRand::ZEROS);
157172
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
158-
SetMockTime(ConsumeTime(fuzzed_data_provider));
173+
// The steady clock is currently only used for logging, so a constant
174+
// time-point seems acceptable for now.
175+
ElapseSteady elapse_steady{};
159176

160177
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
178+
CBlockHeader base{chainman.GetParams().GenesisBlock()};
179+
SetMockTime(base.nTime);
161180

162181
LOCK(NetEventsInterface::g_msgproc_mutex);
163182

164183
g_testing_setup->ResetAndInitialize();
165184

166-
CBlockHeader base{chainman.GetParams().GenesisBlock()};
167-
SetMockTime(base.nTime);
168-
169185
// The chain is just a single block, so this is equal to 1
170186
size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
171187
arith_uint256 total_work{WITH_LOCK(cs_main, return chainman.m_best_header->nChainWork)};
@@ -231,6 +247,4 @@ FUZZ_TARGET(p2p_headers_presync, .init = initialize)
231247
// to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
232248
// in the headers pre-sync logic.
233249
assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
234-
235-
g_testing_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
236250
}

src/test/util/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_library(test_util STATIC EXCLUDE_FROM_ALL
1515
script.cpp
1616
setup_common.cpp
1717
str.cpp
18+
time.cpp
1819
transaction_utils.cpp
1920
txmempool.cpp
2021
validation.cpp

src/test/util/time.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <test/util/time.h>

src/test/util/time.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_TEST_UTIL_TIME_H
6+
#define BITCOIN_TEST_UTIL_TIME_H
7+
8+
#include <util/time.h>
9+
10+
struct ElapseSteady {
11+
MockableSteadyClock::mock_time_point::duration t{MockableSteadyClock::INITIAL_MOCK_TIME};
12+
ElapseSteady()
13+
{
14+
(*this)(0s); // init
15+
}
16+
void operator()(std::chrono::milliseconds d)
17+
{
18+
t += d;
19+
MockableSteadyClock::SetMockTime(t);
20+
}
21+
};
22+
23+
#endif // BITCOIN_TEST_UTIL_TIME_H

src/util/time.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread
2121

2222
static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing
2323
std::atomic<bool> g_used_system_time{false};
24-
static std::atomic<std::chrono::milliseconds> g_mock_steady_time{}; //!< For testing
24+
static std::atomic<MockableSteadyClock::mock_time_point::duration> g_mock_steady_time{}; //!< For testing
2525

2626
NodeClock::time_point NodeClock::now() noexcept
2727
{
@@ -62,7 +62,7 @@ MockableSteadyClock::time_point MockableSteadyClock::now() noexcept
6262
return time_point{ret};
6363
};
6464

65-
void MockableSteadyClock::SetMockTime(std::chrono::milliseconds mock_time_in)
65+
void MockableSteadyClock::SetMockTime(mock_time_point::duration mock_time_in)
6666
{
6767
Assert(mock_time_in >= 0s);
6868
g_mock_steady_time.store(mock_time_in, std::memory_order_relaxed);

src/util/time.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ using SystemClock = std::chrono::system_clock;
3838
struct MockableSteadyClock : public std::chrono::steady_clock {
3939
using time_point = std::chrono::time_point<MockableSteadyClock>;
4040

41-
static constexpr std::chrono::milliseconds INITIAL_MOCK_TIME{1};
41+
using mock_time_point = std::chrono::time_point<MockableSteadyClock, std::chrono::milliseconds>;
42+
static constexpr mock_time_point::duration INITIAL_MOCK_TIME{1};
4243

4344
/** Return current system time or mocked time, if set */
4445
static time_point now() noexcept;
@@ -50,7 +51,7 @@ struct MockableSteadyClock : public std::chrono::steady_clock {
5051
* for testing.
5152
* To stop mocking, call ClearMockTime().
5253
*/
53-
static void SetMockTime(std::chrono::milliseconds mock_time_in);
54+
static void SetMockTime(mock_time_point::duration mock_time_in);
5455

5556
/** Clear mock time, go back to system steady clock. */
5657
static void ClearMockTime();

src/validation.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4456,16 +4456,16 @@ bool ChainstateManager::ProcessNewBlockHeaders(std::span<const CBlockHeader> hea
44564456

44574457
void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp)
44584458
{
4459-
AssertLockNotHeld(cs_main);
4459+
AssertLockNotHeld(GetMutex());
44604460
{
4461-
LOCK(cs_main);
4461+
LOCK(GetMutex());
44624462
// Don't report headers presync progress if we already have a post-minchainwork header chain.
44634463
// This means we lose reporting for potentially legitimate, but unlikely, deep reorgs, but
44644464
// prevent attackers that spam low-work headers from filling our logs.
44654465
if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return;
44664466
// Rate limit headers presync updates to 4 per second, as these are not subject to DoS
44674467
// protection.
4468-
auto now = std::chrono::steady_clock::now();
4468+
auto now = MockableSteadyClock::now();
44694469
if (now < m_last_presync_update + std::chrono::milliseconds{250}) return;
44704470
m_last_presync_update = now;
44714471
}

src/validation.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2022 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

@@ -933,7 +933,7 @@ class ChainstateManager
933933
friend Chainstate;
934934

935935
/** Most recent headers presync progress update, for rate-limiting. */
936-
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
936+
MockableSteadyClock::time_point m_last_presync_update GUARDED_BY(GetMutex()){};
937937

938938
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
939939

0 commit comments

Comments
 (0)