Files
bitcoin/src/test/fuzz/headerssync.cpp
Hodlinator cc5dda1de3 headerssync: Make HeadersSyncState more flexible and move constants
Move calculated constants from the top of src/headerssync.cpp into src/kernel/chainparams.cpp.

Instead of being hardcoded to mainnet parameters, HeadersSyncState can now vary depending on chain or test. (This means we can reset TARGET_BLOCKS back to the nice round number of 15'000).

Signet and testnets got new HeadersSyncParams constants through temporarily altering headerssync-params.py with corresponding GENESIS_TIME and MINCHAINWORK_HEADERS (based off defaultAssumeValid block height comments, corresponding to nMinimumChainWork). Regtest doesn't have a default assume valid block height, so the values are copied from Testnet 4. Since the constants only affect memory usage, and have very low impact unless dealing with a largely malicious chain, it's not that critical to keep updating them for non-mainnet chains.

GENESIS_TIMEs (UTC):
Testnet3: 1296688602 = datetime(2011, 2, 2)
Testnet4: 1714777860 = datetime(2024, 5, 3)
Signet: 1598918400 = datetime(2020, 9, 1)
2025-09-12 22:28:41 +02:00

130 lines
4.8 KiB
C++

// Copyright (c) 2022-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://opensource.org/license/mit.
#include <arith_uint256.h>
#include <chain.h>
#include <chainparams.h>
#include <headerssync.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <util/chaintype.h>
#include <util/time.h>
#include <validation.h>
#include <iterator>
#include <vector>
static void initialize_headers_sync_state_fuzz()
{
static const auto testing_setup = MakeNoLogFileContext<>(
/*chain_type=*/ChainType::MAIN);
}
void MakeHeadersContinuous(
const CBlockHeader& genesis_header,
const std::vector<CBlockHeader>& all_headers,
std::vector<CBlockHeader>& new_headers)
{
Assume(!new_headers.empty());
const CBlockHeader* prev_header{
all_headers.empty() ? &genesis_header : &all_headers.back()};
for (auto& header : new_headers) {
header.hashPrevBlock = prev_header->GetHash();
prev_header = &header;
}
}
class FuzzedHeadersSyncState : public HeadersSyncState
{
public:
FuzzedHeadersSyncState(const HeadersSyncParams& sync_params, const size_t commit_offset,
const CBlockIndex* chain_start, const arith_uint256& minimum_required_work)
: HeadersSyncState(/*id=*/0, Params().GetConsensus(), sync_params, chain_start, minimum_required_work)
{
const_cast<size_t&>(m_commit_offset) = commit_offset;
}
};
FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
auto mock_time{ConsumeTime(fuzzed_data_provider)};
CBlockHeader genesis_header{Params().GenesisBlock()};
CBlockIndex start_index(genesis_header);
if (mock_time < start_index.GetMedianTimePast()) return;
SetMockTime(mock_time);
const uint256 genesis_hash = genesis_header.GetHash();
start_index.phashBlock = &genesis_hash;
const HeadersSyncParams params{
.commitment_period = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, Params().HeadersSync().commitment_period * 2),
.redownload_buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, Params().HeadersSync().redownload_buffer_size * 2),
};
arith_uint256 min_work{UintToArith256(ConsumeUInt256(fuzzed_data_provider))};
FuzzedHeadersSyncState headers_sync(
params,
/*commit_offset=*/fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, params.commitment_period - 1),
/*chain_start=*/&start_index,
/*minimum_required_work=*/min_work);
// Store headers for potential redownload phase.
std::vector<CBlockHeader> all_headers;
std::vector<CBlockHeader>::const_iterator redownloaded_it;
bool presync{true};
bool requested_more{true};
while (requested_more) {
std::vector<CBlockHeader> headers;
// Consume headers from fuzzer or maybe replay headers if we got to the
// redownload phase.
if (presync || fuzzed_data_provider.ConsumeBool()) {
auto deser_headers = ConsumeDeserializable<std::vector<CBlockHeader>>(fuzzed_data_provider);
if (!deser_headers || deser_headers->empty()) return;
if (fuzzed_data_provider.ConsumeBool()) {
MakeHeadersContinuous(genesis_header, all_headers, *deser_headers);
}
headers.swap(*deser_headers);
} else if (auto num_headers_left{std::distance(redownloaded_it, all_headers.cend())}; num_headers_left > 0) {
// Consume some headers from the redownload buffer (At least one
// header is consumed).
auto begin_it{redownloaded_it};
std::advance(redownloaded_it, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, num_headers_left));
headers.insert(headers.cend(), begin_it, redownloaded_it);
}
if (headers.empty()) return;
auto result = headers_sync.ProcessNextHeaders(headers, fuzzed_data_provider.ConsumeBool());
requested_more = result.request_more;
if (result.request_more) {
if (presync) {
all_headers.insert(all_headers.cend(), headers.cbegin(), headers.cend());
if (headers_sync.GetState() == HeadersSyncState::State::REDOWNLOAD) {
presync = false;
redownloaded_it = all_headers.cbegin();
// If we get to redownloading, the presynced headers need
// to have the min amount of work on them.
assert(CalculateClaimedHeadersWork(all_headers) >= min_work);
}
}
(void)headers_sync.NextHeadersRequestLocator();
}
}
}