mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge bitcoin/bitcoin#32198: fuzz: Make p2p_headers_presync more deterministic
faa3ce3199
fuzz: Avoid influence on the global RNG from peerman m_rng (MarcoFalke)faf4c1b6fc
fuzz: Disable unused validation interface and scheduler in p2p_headers_presync (MarcoFalke)fafaca6cbc
fuzz: Avoid setting the mock-time twice (MarcoFalke)fad22149f4
refactor: Use MockableSteadyClock in ReportHeadersPresync (MarcoFalke)fa9c38794e
test: Introduce MockableSteadyClock::mock_time_point and ElapseSteady helper (MarcoFalke)faf2d512c5
fuzz: Move global node id counter along with other global state (MarcoFalke)fa98455e4b
fuzz: Set ignore_incoming_txs in p2p_headers_presync (MarcoFalke)faf2e238fb
fuzz: Shuffle files before testing them (MarcoFalke) Pull request description: This should make the `p2p_headers_presync` fuzz target more deterministic. Tracking issue: https://github.com/bitcoin/bitcoin/issues/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: tACKfaa3ce3199
janb84: Re-ACK [faa3ce3
](faa3ce3199
) marcofleon: ACKfaa3ce3199
Tree-SHA512: 7e2e0ddf3b4e818300373d6906384df57a87f1eeb507fa43de1ba88cf03c8e6752a26b6e91bfb3ee26a21efcaf1d0d9eaf70d311d1637b671965ef4cb96e6b59
This commit is contained in:
commit
c58ae197a3
10 changed files with 71 additions and 20 deletions
|
@ -133,7 +133,7 @@ fn deterministic_coverage(
|
||||||
let output = {
|
let output = {
|
||||||
let mut cmd = Command::new(fuzz_exe);
|
let mut cmd = Command::new(fuzz_exe);
|
||||||
if using_libfuzzer {
|
if using_libfuzzer {
|
||||||
cmd.arg("-runs=1");
|
cmd.args(["-runs=1", "-shuffle=1", "-prefer_small=0"]);
|
||||||
}
|
}
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <util/sock.h>
|
#include <util/sock.h>
|
||||||
#include <util/time.h>
|
#include <util/time.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -241,10 +243,15 @@ int main(int argc, char** argv)
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
fs::path input_path(*(argv + i));
|
fs::path input_path(*(argv + i));
|
||||||
if (fs::is_directory(input_path)) {
|
if (fs::is_directory(input_path)) {
|
||||||
|
std::vector<fs::path> files;
|
||||||
for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) {
|
for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) {
|
||||||
if (!fs::is_regular_file(it->path())) continue;
|
if (!fs::is_regular_file(it->path())) continue;
|
||||||
g_input_path = it->path();
|
files.emplace_back(it->path());
|
||||||
Assert(read_file(it->path(), buffer));
|
}
|
||||||
|
std::ranges::shuffle(files, std::mt19937{std::random_device{}()});
|
||||||
|
for (const auto& input_path : files) {
|
||||||
|
g_input_path = input_path;
|
||||||
|
Assert(read_file(input_path, buffer));
|
||||||
test_one_input(buffer);
|
test_one_input(buffer);
|
||||||
++tested;
|
++tested;
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <test/util/net.h>
|
#include <test/util/net.h>
|
||||||
#include <test/util/script.h>
|
#include <test/util/script.h>
|
||||||
#include <test/util/setup_common.h>
|
#include <test/util/setup_common.h>
|
||||||
|
#include <test/util/time.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
|
@ -31,6 +32,15 @@ public:
|
||||||
PeerManager::Options peerman_opts;
|
PeerManager::Options peerman_opts;
|
||||||
node::ApplyArgsManOptions(*m_node.args, peerman_opts);
|
node::ApplyArgsManOptions(*m_node.args, peerman_opts);
|
||||||
peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
|
peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
|
||||||
|
// The peerman's rng is a global that is re-used, so it will be re-used
|
||||||
|
// and may cause non-determinism between runs. This may even influence
|
||||||
|
// the global RNG, because seeding may be done from the gloabl one. For
|
||||||
|
// now, avoid it influencing the global RNG, and initialize it with a
|
||||||
|
// constant instead.
|
||||||
|
peerman_opts.deterministic_rng = true;
|
||||||
|
// No txs are relayed. Disable irrelevant and possibly
|
||||||
|
// non-deterministic code paths.
|
||||||
|
peerman_opts.ignore_incoming_txs = true;
|
||||||
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
|
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
|
||||||
m_node.banman.get(), *m_node.chainman,
|
m_node.banman.get(), *m_node.chainman,
|
||||||
*m_node.mempool, *m_node.warnings, peerman_opts);
|
*m_node.mempool, *m_node.warnings, peerman_opts);
|
||||||
|
@ -51,7 +61,7 @@ void HeadersSyncSetup::ResetAndInitialize()
|
||||||
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
|
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
|
||||||
connman.StopNodes();
|
connman.StopNodes();
|
||||||
|
|
||||||
NodeId id{0};
|
static NodeId id{0};
|
||||||
std::vector<ConnectionType> conn_types = {
|
std::vector<ConnectionType> conn_types = {
|
||||||
ConnectionType::OUTBOUND_FULL_RELAY,
|
ConnectionType::OUTBOUND_FULL_RELAY,
|
||||||
ConnectionType::BLOCK_RELAY,
|
ConnectionType::BLOCK_RELAY,
|
||||||
|
@ -146,7 +156,12 @@ HeadersSyncSetup* g_testing_setup;
|
||||||
|
|
||||||
void initialize()
|
void initialize()
|
||||||
{
|
{
|
||||||
static auto setup = MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN);
|
static auto setup{
|
||||||
|
MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN,
|
||||||
|
{
|
||||||
|
.setup_validation_interface = false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
g_testing_setup = setup.get();
|
g_testing_setup = setup.get();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -155,17 +170,18 @@ FUZZ_TARGET(p2p_headers_presync, .init = initialize)
|
||||||
{
|
{
|
||||||
SeedRandomStateForTest(SeedRand::ZEROS);
|
SeedRandomStateForTest(SeedRand::ZEROS);
|
||||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||||
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
// The steady clock is currently only used for logging, so a constant
|
||||||
|
// time-point seems acceptable for now.
|
||||||
|
ElapseSteady elapse_steady{};
|
||||||
|
|
||||||
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
|
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
|
||||||
|
CBlockHeader base{chainman.GetParams().GenesisBlock()};
|
||||||
|
SetMockTime(base.nTime);
|
||||||
|
|
||||||
LOCK(NetEventsInterface::g_msgproc_mutex);
|
LOCK(NetEventsInterface::g_msgproc_mutex);
|
||||||
|
|
||||||
g_testing_setup->ResetAndInitialize();
|
g_testing_setup->ResetAndInitialize();
|
||||||
|
|
||||||
CBlockHeader base{chainman.GetParams().GenesisBlock()};
|
|
||||||
SetMockTime(base.nTime);
|
|
||||||
|
|
||||||
// The chain is just a single block, so this is equal to 1
|
// The chain is just a single block, so this is equal to 1
|
||||||
size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
|
size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
|
||||||
arith_uint256 total_work{WITH_LOCK(cs_main, return chainman.m_best_header->nChainWork)};
|
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)
|
||||||
// to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
|
// to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
|
||||||
// in the headers pre-sync logic.
|
// in the headers pre-sync logic.
|
||||||
assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
|
assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
|
||||||
|
|
||||||
g_testing_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ add_library(test_util STATIC EXCLUDE_FROM_ALL
|
||||||
script.cpp
|
script.cpp
|
||||||
setup_common.cpp
|
setup_common.cpp
|
||||||
str.cpp
|
str.cpp
|
||||||
|
time.cpp
|
||||||
transaction_utils.cpp
|
transaction_utils.cpp
|
||||||
txmempool.cpp
|
txmempool.cpp
|
||||||
validation.cpp
|
validation.cpp
|
||||||
|
|
5
src/test/util/time.cpp
Normal file
5
src/test/util/time.cpp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright (c) 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 <test/util/time.h>
|
23
src/test/util/time.h
Normal file
23
src/test/util/time.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef BITCOIN_TEST_UTIL_TIME_H
|
||||||
|
#define BITCOIN_TEST_UTIL_TIME_H
|
||||||
|
|
||||||
|
#include <util/time.h>
|
||||||
|
|
||||||
|
struct ElapseSteady {
|
||||||
|
MockableSteadyClock::mock_time_point::duration t{MockableSteadyClock::INITIAL_MOCK_TIME};
|
||||||
|
ElapseSteady()
|
||||||
|
{
|
||||||
|
(*this)(0s); // init
|
||||||
|
}
|
||||||
|
void operator()(std::chrono::milliseconds d)
|
||||||
|
{
|
||||||
|
t += d;
|
||||||
|
MockableSteadyClock::SetMockTime(t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BITCOIN_TEST_UTIL_TIME_H
|
|
@ -21,7 +21,7 @@ void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread
|
||||||
|
|
||||||
static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing
|
static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing
|
||||||
std::atomic<bool> g_used_system_time{false};
|
std::atomic<bool> g_used_system_time{false};
|
||||||
static std::atomic<std::chrono::milliseconds> g_mock_steady_time{}; //!< For testing
|
static std::atomic<MockableSteadyClock::mock_time_point::duration> g_mock_steady_time{}; //!< For testing
|
||||||
|
|
||||||
NodeClock::time_point NodeClock::now() noexcept
|
NodeClock::time_point NodeClock::now() noexcept
|
||||||
{
|
{
|
||||||
|
@ -62,7 +62,7 @@ MockableSteadyClock::time_point MockableSteadyClock::now() noexcept
|
||||||
return time_point{ret};
|
return time_point{ret};
|
||||||
};
|
};
|
||||||
|
|
||||||
void MockableSteadyClock::SetMockTime(std::chrono::milliseconds mock_time_in)
|
void MockableSteadyClock::SetMockTime(mock_time_point::duration mock_time_in)
|
||||||
{
|
{
|
||||||
Assert(mock_time_in >= 0s);
|
Assert(mock_time_in >= 0s);
|
||||||
g_mock_steady_time.store(mock_time_in, std::memory_order_relaxed);
|
g_mock_steady_time.store(mock_time_in, std::memory_order_relaxed);
|
||||||
|
|
|
@ -38,7 +38,8 @@ using SystemClock = std::chrono::system_clock;
|
||||||
struct MockableSteadyClock : public std::chrono::steady_clock {
|
struct MockableSteadyClock : public std::chrono::steady_clock {
|
||||||
using time_point = std::chrono::time_point<MockableSteadyClock>;
|
using time_point = std::chrono::time_point<MockableSteadyClock>;
|
||||||
|
|
||||||
static constexpr std::chrono::milliseconds INITIAL_MOCK_TIME{1};
|
using mock_time_point = std::chrono::time_point<MockableSteadyClock, std::chrono::milliseconds>;
|
||||||
|
static constexpr mock_time_point::duration INITIAL_MOCK_TIME{1};
|
||||||
|
|
||||||
/** Return current system time or mocked time, if set */
|
/** Return current system time or mocked time, if set */
|
||||||
static time_point now() noexcept;
|
static time_point now() noexcept;
|
||||||
|
@ -50,7 +51,7 @@ struct MockableSteadyClock : public std::chrono::steady_clock {
|
||||||
* for testing.
|
* for testing.
|
||||||
* To stop mocking, call ClearMockTime().
|
* To stop mocking, call ClearMockTime().
|
||||||
*/
|
*/
|
||||||
static void SetMockTime(std::chrono::milliseconds mock_time_in);
|
static void SetMockTime(mock_time_point::duration mock_time_in);
|
||||||
|
|
||||||
/** Clear mock time, go back to system steady clock. */
|
/** Clear mock time, go back to system steady clock. */
|
||||||
static void ClearMockTime();
|
static void ClearMockTime();
|
||||||
|
|
|
@ -4456,16 +4456,16 @@ bool ChainstateManager::ProcessNewBlockHeaders(std::span<const CBlockHeader> hea
|
||||||
|
|
||||||
void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp)
|
void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp)
|
||||||
{
|
{
|
||||||
AssertLockNotHeld(cs_main);
|
AssertLockNotHeld(GetMutex());
|
||||||
{
|
{
|
||||||
LOCK(cs_main);
|
LOCK(GetMutex());
|
||||||
// Don't report headers presync progress if we already have a post-minchainwork header chain.
|
// Don't report headers presync progress if we already have a post-minchainwork header chain.
|
||||||
// This means we lose reporting for potentially legitimate, but unlikely, deep reorgs, but
|
// This means we lose reporting for potentially legitimate, but unlikely, deep reorgs, but
|
||||||
// prevent attackers that spam low-work headers from filling our logs.
|
// prevent attackers that spam low-work headers from filling our logs.
|
||||||
if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return;
|
if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return;
|
||||||
// Rate limit headers presync updates to 4 per second, as these are not subject to DoS
|
// Rate limit headers presync updates to 4 per second, as these are not subject to DoS
|
||||||
// protection.
|
// protection.
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = MockableSteadyClock::now();
|
||||||
if (now < m_last_presync_update + std::chrono::milliseconds{250}) return;
|
if (now < m_last_presync_update + std::chrono::milliseconds{250}) return;
|
||||||
m_last_presync_update = now;
|
m_last_presync_update = now;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||||
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
// Copyright (c) 2009-present The Bitcoin Core developers
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
@ -933,7 +933,7 @@ private:
|
||||||
friend Chainstate;
|
friend Chainstate;
|
||||||
|
|
||||||
/** Most recent headers presync progress update, for rate-limiting. */
|
/** Most recent headers presync progress update, for rate-limiting. */
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
|
MockableSteadyClock::time_point m_last_presync_update GUARDED_BY(GetMutex()){};
|
||||||
|
|
||||||
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
|
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue