Skip to content

Commit c17d4d3

Browse files
committed
Merge bitcoin/bitcoin#26662: fuzz: Add HeadersSyncState target
3153e7d [fuzz] Add HeadersSyncState target (dergoegge) 53552af [headerssync] Make m_commit_offset protected (dergoegge) Pull request description: This adds a fuzz target for the `HeadersSyncState` class. I am unsure how well this is able to cover the logic since it is just processing unserialized CBlockHeaders straight from the fuzz input (headers are sometimes made continuous). However, it does manage to get to the redownload phase so i thought it is better then not having fuzzing at all. It would also be nice to fuzz the p2p logic that is using `HeadersSyncState` (e.g. `TryLowWorkHeadersSync`, `IsContinuationOfLowWorkHeadersSync`) but that likely requires some more work (refactoring👻). ACKs for top commit: mzumsande: ACK 3153e7d Tree-SHA512: 8a4630ceeeb30e4eeabaa8eb5491d98f0bf900efe7cda07384eaac9f2afaccfbcaa979cc1cc7f0b6ca297a8f5c17a7759f94809dd87eb87d35348d847c83e8ab
2 parents 53eb4b7 + 3153e7d commit c17d4d3

File tree

4 files changed

+126
-7
lines changed

4 files changed

+126
-7
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ test_fuzz_fuzz_SOURCES = \
272272
test/fuzz/flatfile.cpp \
273273
test/fuzz/float.cpp \
274274
test/fuzz/golomb_rice.cpp \
275+
test/fuzz/headerssync.cpp \
275276
test/fuzz/hex.cpp \
276277
test/fuzz/http_request.cpp \
277278
test/fuzz/i2p.cpp \

src/headerssync.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ static_assert(sizeof(CompressedHeader) == 48);
2424

2525
HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params,
2626
const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) :
27+
m_commit_offset(GetRand<unsigned>(HEADER_COMMITMENT_PERIOD)),
2728
m_id(id), m_consensus_params(consensus_params),
2829
m_chain_start(chain_start),
2930
m_minimum_required_work(minimum_required_work),
3031
m_current_chain_work(chain_start->nChainWork),
31-
m_commit_offset(GetRand<unsigned>(HEADER_COMMITMENT_PERIOD)),
3232
m_last_header_received(m_chain_start->GetBlockHeader()),
3333
m_current_height(chain_start->nHeight)
3434
{

src/headerssync.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ class HeadersSyncState {
175175
*/
176176
CBlockLocator NextHeadersRequestLocator() const;
177177

178+
protected:
179+
/** The (secret) offset on the heights for which to create commitments.
180+
*
181+
* m_header_commitments entries are created at any height h for which
182+
* (h % HEADER_COMMITMENT_PERIOD) == m_commit_offset. */
183+
const unsigned m_commit_offset;
184+
178185
private:
179186
/** Clear out all download state that might be in progress (freeing any used
180187
* memory), and mark this object as no longer usable.
@@ -222,12 +229,6 @@ class HeadersSyncState {
222229
/** A queue of commitment bits, created during the 1st phase, and verified during the 2nd. */
223230
bitdeque<> m_header_commitments;
224231

225-
/** The (secret) offset on the heights for which to create commitments.
226-
*
227-
* m_header_commitments entries are created at any height h for which
228-
* (h % HEADER_COMMITMENT_PERIOD) == m_commit_offset. */
229-
const unsigned m_commit_offset;
230-
231232
/** m_max_commitments is a bound we calculate on how long an honest peer's chain could be,
232233
* given the MTP rule.
233234
*

src/test/fuzz/headerssync.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#include <arith_uint256.h>
2+
#include <chain.h>
3+
#include <chainparams.h>
4+
#include <headerssync.h>
5+
#include <test/fuzz/fuzz.h>
6+
#include <test/fuzz/util.h>
7+
#include <test/util/setup_common.h>
8+
#include <uint256.h>
9+
#include <util/time.h>
10+
#include <validation.h>
11+
12+
#include <iterator>
13+
#include <vector>
14+
15+
static void initialize_headers_sync_state_fuzz()
16+
{
17+
static const auto testing_setup = MakeNoLogFileContext<>(
18+
/*chain_name=*/CBaseChainParams::MAIN);
19+
}
20+
21+
void MakeHeadersContinuous(
22+
const CBlockHeader& genesis_header,
23+
const std::vector<CBlockHeader>& all_headers,
24+
std::vector<CBlockHeader>& new_headers)
25+
{
26+
Assume(!new_headers.empty());
27+
28+
const CBlockHeader* prev_header{
29+
all_headers.empty() ? &genesis_header : &all_headers.back()};
30+
31+
for (auto& header : new_headers) {
32+
header.hashPrevBlock = prev_header->GetHash();
33+
34+
prev_header = &header;
35+
}
36+
}
37+
38+
class FuzzedHeadersSyncState : public HeadersSyncState
39+
{
40+
public:
41+
FuzzedHeadersSyncState(const unsigned commit_offset, const CBlockIndex* chain_start, const arith_uint256& minimum_required_work)
42+
: HeadersSyncState(/*id=*/0, Params().GetConsensus(), chain_start, minimum_required_work)
43+
{
44+
const_cast<unsigned&>(m_commit_offset) = commit_offset;
45+
}
46+
};
47+
48+
FUZZ_TARGET_INIT(headers_sync_state, initialize_headers_sync_state_fuzz)
49+
{
50+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
51+
auto mock_time{ConsumeTime(fuzzed_data_provider)};
52+
53+
CBlockHeader genesis_header{Params().GenesisBlock()};
54+
CBlockIndex start_index(genesis_header);
55+
56+
if (mock_time < start_index.GetMedianTimePast()) return;
57+
SetMockTime(mock_time);
58+
59+
const uint256 genesis_hash = genesis_header.GetHash();
60+
start_index.phashBlock = &genesis_hash;
61+
62+
arith_uint256 min_work{UintToArith256(ConsumeUInt256(fuzzed_data_provider))};
63+
FuzzedHeadersSyncState headers_sync(
64+
/*commit_offset=*/fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 1024),
65+
/*chain_start=*/&start_index,
66+
/*minimum_required_work=*/min_work);
67+
68+
// Store headers for potential redownload phase.
69+
std::vector<CBlockHeader> all_headers;
70+
std::vector<CBlockHeader>::const_iterator redownloaded_it;
71+
bool presync{true};
72+
bool requested_more{true};
73+
74+
while (requested_more) {
75+
std::vector<CBlockHeader> headers;
76+
77+
// Consume headers from fuzzer or maybe replay headers if we got to the
78+
// redownload phase.
79+
if (presync || fuzzed_data_provider.ConsumeBool()) {
80+
auto deser_headers = ConsumeDeserializable<std::vector<CBlockHeader>>(fuzzed_data_provider);
81+
if (!deser_headers || deser_headers->empty()) return;
82+
83+
if (fuzzed_data_provider.ConsumeBool()) {
84+
MakeHeadersContinuous(genesis_header, all_headers, *deser_headers);
85+
}
86+
87+
headers.swap(*deser_headers);
88+
} else if (auto num_headers_left{std::distance(redownloaded_it, all_headers.cend())}; num_headers_left > 0) {
89+
// Consume some headers from the redownload buffer (At least one
90+
// header is consumed).
91+
auto begin_it{redownloaded_it};
92+
std::advance(redownloaded_it, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, num_headers_left));
93+
headers.insert(headers.cend(), begin_it, redownloaded_it);
94+
}
95+
96+
if (headers.empty()) return;
97+
auto result = headers_sync.ProcessNextHeaders(headers, fuzzed_data_provider.ConsumeBool());
98+
requested_more = result.request_more;
99+
100+
if (result.request_more) {
101+
if (presync) {
102+
all_headers.insert(all_headers.cend(), headers.cbegin(), headers.cend());
103+
104+
if (headers_sync.GetState() == HeadersSyncState::State::REDOWNLOAD) {
105+
presync = false;
106+
redownloaded_it = all_headers.cbegin();
107+
108+
// If we get to redownloading, the presynced headers need
109+
// to have the min amount of work on them.
110+
assert(CalculateHeadersWork(all_headers) >= min_work);
111+
}
112+
}
113+
114+
(void)headers_sync.NextHeadersRequestLocator();
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)