Skip to content

Commit c970c14

Browse files
author
MarcoFalke
committed
Merge #21380: tests: Add fuzzing harness for versionbits
1639c3b tests: Add fuzzing harness for versionbits (Anthony Towns) Pull request description: Adds a fuzzing harness for versionbits. ACKs for top commit: sipa: utACK 1639c3b jnewbery: utACK 1639c3b MarcoFalke: cr ACK 1639c3b Tree-SHA512: 6bcf4d302b2193b56c72eecdb79d156b90d05b02ce3a1ad8f4c8a0fcf5caab91e7c78fe61ea280a69cdadcb5006376d4ade877169cd56dc084c2e70359651f0b
2 parents 05757aa + 1639c3b commit c970c14

File tree

2 files changed

+347
-1
lines changed

2 files changed

+347
-1
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ test_fuzz_fuzz_SOURCES = \
298298
test/fuzz/tx_in.cpp \
299299
test/fuzz/tx_out.cpp \
300300
test/fuzz/txrequest.cpp \
301-
test/fuzz/validation_load_mempool.cpp
301+
test/fuzz/validation_load_mempool.cpp \
302+
test/fuzz/versionbits.cpp
302303
endif # ENABLE_FUZZ_BINARY
303304

304305
nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)

src/test/fuzz/versionbits.cpp

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
// Copyright (c) 2020-2021 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 <chain.h>
6+
#include <chainparams.h>
7+
#include <consensus/params.h>
8+
#include <primitives/block.h>
9+
#include <versionbits.h>
10+
11+
#include <test/fuzz/FuzzedDataProvider.h>
12+
#include <test/fuzz/fuzz.h>
13+
#include <test/fuzz/util.h>
14+
15+
#include <cstdint>
16+
#include <limits>
17+
#include <memory>
18+
#include <vector>
19+
20+
namespace {
21+
class TestConditionChecker : public AbstractThresholdConditionChecker
22+
{
23+
private:
24+
mutable ThresholdConditionCache m_cache;
25+
const Consensus::Params dummy_params{};
26+
27+
public:
28+
const int64_t m_begin = 0;
29+
const int64_t m_end = 0;
30+
const int m_period = 0;
31+
const int m_threshold = 0;
32+
const int m_bit = 0;
33+
34+
TestConditionChecker(int64_t begin, int64_t end, int period, int threshold, int bit)
35+
: m_begin{begin}, m_end{end}, m_period{period}, m_threshold{threshold}, m_bit{bit}
36+
{
37+
assert(m_period > 0);
38+
assert(0 <= m_threshold && m_threshold <= m_period);
39+
assert(0 <= m_bit && m_bit <= 32 && m_bit < VERSIONBITS_NUM_BITS);
40+
}
41+
42+
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); }
43+
int64_t BeginTime(const Consensus::Params& params) const override { return m_begin; }
44+
int64_t EndTime(const Consensus::Params& params) const override { return m_end; }
45+
int Period(const Consensus::Params& params) const override { return m_period; }
46+
int Threshold(const Consensus::Params& params) const override { return m_threshold; }
47+
48+
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, dummy_params, m_cache); }
49+
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, dummy_params, m_cache); }
50+
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindexPrev, dummy_params); }
51+
52+
bool Condition(int64_t version) const
53+
{
54+
return ((version >> m_bit) & 1) != 0 && (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS;
55+
}
56+
57+
bool Condition(const CBlockIndex* pindex) const { return Condition(pindex->nVersion); }
58+
};
59+
60+
/** Track blocks mined for test */
61+
class Blocks
62+
{
63+
private:
64+
std::vector<std::unique_ptr<CBlockIndex>> m_blocks;
65+
const uint32_t m_start_time;
66+
const uint32_t m_interval;
67+
const int32_t m_signal;
68+
const int32_t m_no_signal;
69+
70+
public:
71+
Blocks(uint32_t start_time, uint32_t interval, int32_t signal, int32_t no_signal)
72+
: m_start_time{start_time}, m_interval{interval}, m_signal{signal}, m_no_signal{no_signal} {}
73+
74+
size_t size() const { return m_blocks.size(); }
75+
76+
CBlockIndex* tip() const
77+
{
78+
return m_blocks.empty() ? nullptr : m_blocks.back().get();
79+
}
80+
81+
CBlockIndex* mine_block(bool signal)
82+
{
83+
CBlockHeader header;
84+
header.nVersion = signal ? m_signal : m_no_signal;
85+
header.nTime = m_start_time + m_blocks.size() * m_interval;
86+
header.nBits = 0x1d00ffff;
87+
88+
auto current_block = std::make_unique<CBlockIndex>(header);
89+
current_block->pprev = tip();
90+
current_block->nHeight = m_blocks.size();
91+
current_block->BuildSkip();
92+
93+
return m_blocks.emplace_back(std::move(current_block)).get();
94+
}
95+
};
96+
97+
void initialize()
98+
{
99+
SelectParams(CBaseChainParams::MAIN);
100+
}
101+
} // namespace
102+
103+
constexpr uint32_t MAX_TIME = 4102444800; // 2100-01-01
104+
105+
FUZZ_TARGET_INIT(versionbits, initialize)
106+
{
107+
const CChainParams& params = Params();
108+
109+
const int64_t interval = params.GetConsensus().nPowTargetSpacing;
110+
assert(interval > 1); // need to be able to halve it
111+
assert(interval < std::numeric_limits<int32_t>::max());
112+
113+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
114+
115+
// making period/max_periods larger slows these tests down significantly
116+
const int period = 32;
117+
const size_t max_periods = 16;
118+
const size_t max_blocks = 2 * period * max_periods;
119+
120+
const int threshold = fuzzed_data_provider.ConsumeIntegralInRange(1, period);
121+
assert(0 < threshold && threshold <= period); // must be able to both pass and fail threshold!
122+
123+
// too many blocks at 10min each might cause uint32_t time to overflow if
124+
// block_start_time is at the end of the range above
125+
assert(std::numeric_limits<uint32_t>::max() - MAX_TIME > interval * max_blocks);
126+
127+
const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_TIME);
128+
129+
// what values for version will we use to signal / not signal?
130+
const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
131+
const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
132+
133+
// select deployment parameters: bit, start time, timeout
134+
const int bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);
135+
136+
bool always_active_test = false;
137+
bool never_active_test = false;
138+
int64_t start_time;
139+
int64_t timeout;
140+
if (fuzzed_data_provider.ConsumeBool()) {
141+
// pick the timestamp to switch based on a block
142+
// note states will change *after* these blocks because mediantime lags
143+
int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
144+
int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(start_block, period * (max_periods - 3));
145+
146+
start_time = block_start_time + start_block * interval;
147+
timeout = block_start_time + end_block * interval;
148+
149+
assert(start_time <= timeout);
150+
151+
// allow for times to not exactly match a block
152+
if (fuzzed_data_provider.ConsumeBool()) start_time += interval / 2;
153+
if (fuzzed_data_provider.ConsumeBool()) timeout += interval / 2;
154+
155+
// this may make timeout too early; if so, don't run the test
156+
if (start_time > timeout) return;
157+
} else {
158+
if (fuzzed_data_provider.ConsumeBool()) {
159+
start_time = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
160+
timeout = Consensus::BIP9Deployment::NO_TIMEOUT;
161+
always_active_test = true;
162+
} else {
163+
start_time = 1199145601; // January 1, 2008
164+
timeout = 1230767999; // December 31, 2008
165+
never_active_test = true;
166+
}
167+
}
168+
169+
TestConditionChecker checker(start_time, timeout, period, threshold, bit);
170+
171+
// Early exit if the versions don't signal sensibly for the deployment
172+
if (!checker.Condition(ver_signal)) return;
173+
if (checker.Condition(ver_nosignal)) return;
174+
if (ver_nosignal < 0) return;
175+
176+
// TOP_BITS should ensure version will be positive
177+
assert(ver_signal > 0);
178+
179+
// Now that we have chosen time and versions, setup to mine blocks
180+
Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);
181+
182+
/* Strategy:
183+
* * we will mine a final period worth of blocks, with
184+
* randomised signalling according to a mask
185+
* * but before we mine those blocks, we will mine some
186+
* randomised number of prior periods; with either all
187+
* or no blocks in the period signalling
188+
*
189+
* We establish the mask first, then consume "bools" until
190+
* we run out of fuzz data to work out how many prior periods
191+
* there are and which ones will signal.
192+
*/
193+
194+
// establish the mask
195+
const uint32_t signalling_mask = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
196+
197+
// mine prior periods
198+
while (fuzzed_data_provider.remaining_bytes() > 0) {
199+
// all blocks in these periods either do or don't signal
200+
bool signal = fuzzed_data_provider.ConsumeBool();
201+
for (int b = 0; b < period; ++b) {
202+
blocks.mine_block(signal);
203+
}
204+
205+
// don't risk exceeding max_blocks or times may wrap around
206+
if (blocks.size() + period*2 > max_blocks) break;
207+
}
208+
// NOTE: fuzzed_data_provider may be fully consumed at this point and should not be used further
209+
210+
// now we mine the final period and check that everything looks sane
211+
212+
// count the number of signalling blocks
213+
int blocks_sig = 0;
214+
215+
// get the info for the first block of the period
216+
CBlockIndex* prev = blocks.tip();
217+
const int exp_since = checker.GetStateSinceHeightFor(prev);
218+
const ThresholdState exp_state = checker.GetStateFor(prev);
219+
BIP9Stats last_stats = checker.GetStateStatisticsFor(prev);
220+
221+
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
222+
assert(exp_since <= prev_next_height);
223+
224+
// mine (period-1) blocks and check state
225+
for (int b = 1; b < period; ++b) {
226+
const bool signal = (signalling_mask >> (b % 32)) & 1;
227+
if (signal) ++blocks_sig;
228+
229+
CBlockIndex* current_block = blocks.mine_block(signal);
230+
231+
// verify that signalling attempt was interpreted correctly
232+
assert(checker.Condition(current_block) == signal);
233+
234+
// state and since don't change within the period
235+
const ThresholdState state = checker.GetStateFor(current_block);
236+
const int since = checker.GetStateSinceHeightFor(current_block);
237+
assert(state == exp_state);
238+
assert(since == exp_since);
239+
240+
// GetStateStatistics may crash when state is not STARTED
241+
if (state != ThresholdState::STARTED) continue;
242+
243+
// check that after mining this block stats change as expected
244+
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
245+
assert(stats.period == period);
246+
assert(stats.threshold == threshold);
247+
assert(stats.elapsed == b);
248+
assert(stats.count == last_stats.count + (signal ? 1 : 0));
249+
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
250+
last_stats = stats;
251+
}
252+
253+
if (exp_state == ThresholdState::STARTED) {
254+
// double check that stats.possible is sane
255+
if (blocks_sig >= threshold - 1) assert(last_stats.possible);
256+
}
257+
258+
// mine the final block
259+
bool signal = (signalling_mask >> (period % 32)) & 1;
260+
if (signal) ++blocks_sig;
261+
CBlockIndex* current_block = blocks.mine_block(signal);
262+
assert(checker.Condition(current_block) == signal);
263+
264+
// GetStateStatistics is safe on a period boundary
265+
// and has progressed to a new period
266+
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
267+
assert(stats.period == period);
268+
assert(stats.threshold == threshold);
269+
assert(stats.elapsed == 0);
270+
assert(stats.count == 0);
271+
assert(stats.possible == true);
272+
273+
// More interesting is whether the state changed.
274+
const ThresholdState state = checker.GetStateFor(current_block);
275+
const int since = checker.GetStateSinceHeightFor(current_block);
276+
277+
// since is straightforward:
278+
assert(since % period == 0);
279+
assert(0 <= since && since <= current_block->nHeight + 1);
280+
if (state == exp_state) {
281+
assert(since == exp_since);
282+
} else {
283+
assert(since == current_block->nHeight + 1);
284+
}
285+
286+
// state is where everything interesting is
287+
switch (state) {
288+
case ThresholdState::DEFINED:
289+
assert(since == 0);
290+
assert(exp_state == ThresholdState::DEFINED);
291+
assert(current_block->GetMedianTimePast() < checker.m_begin);
292+
assert(current_block->GetMedianTimePast() < checker.m_end);
293+
break;
294+
case ThresholdState::STARTED:
295+
assert(current_block->GetMedianTimePast() >= checker.m_begin);
296+
assert(current_block->GetMedianTimePast() < checker.m_end);
297+
if (exp_state == ThresholdState::STARTED) {
298+
assert(blocks_sig < threshold);
299+
} else {
300+
assert(exp_state == ThresholdState::DEFINED);
301+
}
302+
break;
303+
case ThresholdState::LOCKED_IN:
304+
assert(exp_state == ThresholdState::STARTED);
305+
assert(current_block->GetMedianTimePast() < checker.m_end);
306+
assert(blocks_sig >= threshold);
307+
break;
308+
case ThresholdState::ACTIVE:
309+
assert(exp_state == ThresholdState::ACTIVE || exp_state == ThresholdState::LOCKED_IN);
310+
break;
311+
case ThresholdState::FAILED:
312+
assert(current_block->GetMedianTimePast() >= checker.m_end);
313+
assert(exp_state != ThresholdState::LOCKED_IN && exp_state != ThresholdState::ACTIVE);
314+
break;
315+
default:
316+
assert(false);
317+
}
318+
319+
if (blocks.size() >= max_periods * period) {
320+
// we chose the timeout (and block times) so that by the time we have this many blocks it's all over
321+
assert(state == ThresholdState::ACTIVE || state == ThresholdState::FAILED);
322+
}
323+
324+
// "always active" has additional restrictions
325+
if (always_active_test) {
326+
assert(state == ThresholdState::ACTIVE);
327+
assert(exp_state == ThresholdState::ACTIVE);
328+
assert(since == 0);
329+
} else {
330+
// except for always active, the initial state is always DEFINED
331+
assert(since > 0 || state == ThresholdState::DEFINED);
332+
assert(exp_since > 0 || exp_state == ThresholdState::DEFINED);
333+
}
334+
335+
// "never active" does too
336+
if (never_active_test) {
337+
assert(state == ThresholdState::FAILED);
338+
assert(since == period);
339+
if (exp_since == 0) {
340+
assert(exp_state == ThresholdState::DEFINED);
341+
} else {
342+
assert(exp_state == ThresholdState::FAILED);
343+
}
344+
}
345+
}

0 commit comments

Comments
 (0)