Skip to content

Commit 7565563

Browse files
committed
tests: refactor versionbits fuzz test
Test `VersionBitsConditionChecker` behaviour directly, rather than reimplementing it, thus slightly improving fuzz test coverage of the real code.
1 parent 2e4e9b9 commit 7565563

File tree

2 files changed

+68
-83
lines changed

2 files changed

+68
-83
lines changed

src/test/fuzz/versionbits.cpp

Lines changed: 62 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,22 @@
2121
#include <vector>
2222

2323
namespace {
24-
class TestConditionChecker : public AbstractThresholdConditionChecker
24+
class TestConditionChecker : public VersionBitsConditionChecker
2525
{
2626
private:
2727
mutable ThresholdConditionCache m_cache;
2828

2929
public:
30-
const int64_t m_begin;
31-
const int64_t m_end;
32-
const int m_period;
33-
const int m_threshold;
34-
const int m_min_activation_height;
35-
const int m_bit;
36-
37-
TestConditionChecker(int64_t begin, int64_t end, int period, int threshold, int min_activation_height, int bit)
38-
: m_begin{begin}, m_end{end}, m_period{period}, m_threshold{threshold}, m_min_activation_height{min_activation_height}, m_bit{bit}
30+
TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep}
3931
{
40-
assert(m_period > 0);
41-
assert(0 <= m_threshold && m_threshold <= m_period);
42-
assert(0 <= m_bit && m_bit < 32 && m_bit < VERSIONBITS_NUM_BITS);
43-
assert(0 <= m_min_activation_height);
32+
assert(dep.period > 0);
33+
assert(dep.threshold <= dep.period);
34+
assert(0 <= dep.bit && dep.bit < 32 && dep.bit < VERSIONBITS_NUM_BITS);
35+
assert(0 <= dep.min_activation_height);
4436
}
4537

46-
bool Condition(const CBlockIndex* pindex) const override { return Condition(pindex->nVersion); }
47-
int64_t BeginTime() const override { return m_begin; }
48-
int64_t EndTime() const override { return m_end; }
49-
int Period() const override { return m_period; }
50-
int Threshold() const override { return m_threshold; }
51-
int MinActivationHeight() const override { return m_min_activation_height; }
52-
5338
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, m_cache); }
5439
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, m_cache); }
55-
56-
bool Condition(int32_t version) const
57-
{
58-
uint32_t mask = (uint32_t{1}) << m_bit;
59-
return (((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & mask) != 0);
60-
}
6140
};
6241

6342
/** Track blocks mined for test */
@@ -122,9 +101,6 @@ FUZZ_TARGET(versionbits, .init = initialize)
122101
const size_t max_periods = 16;
123102
const size_t max_blocks = 2 * period * max_periods;
124103

125-
const uint32_t threshold = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, period);
126-
assert(0 < threshold && threshold <= period); // must be able to both pass and fail threshold!
127-
128104
// too many blocks at 10min each might cause uint32_t time to overflow if
129105
// block_start_time is at the end of the range above
130106
assert(std::numeric_limits<uint32_t>::max() - MAX_START_TIME > interval * max_blocks);
@@ -134,53 +110,57 @@ FUZZ_TARGET(versionbits, .init = initialize)
134110
// what values for version will we use to signal / not signal?
135111
const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
136112
const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
113+
if (ver_nosignal < 0) return; // negative values are uninteresting
137114

138-
// select deployment parameters: bit, start time, timeout
139-
const int bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);
140-
141-
bool always_active_test = false;
142-
bool never_active_test = false;
143-
int64_t start_time;
144-
int64_t timeout;
145-
if (fuzzed_data_provider.ConsumeBool()) {
146-
// pick the timestamp to switch based on a block
147-
// note states will change *after* these blocks because mediantime lags
148-
int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
149-
int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
150-
151-
start_time = block_start_time + start_block * interval;
152-
timeout = block_start_time + end_block * interval;
153-
154-
// allow for times to not exactly match a block
155-
if (fuzzed_data_provider.ConsumeBool()) start_time += interval / 2;
156-
if (fuzzed_data_provider.ConsumeBool()) timeout += interval / 2;
157-
} else {
158-
if (fuzzed_data_provider.ConsumeBool()) {
159-
start_time = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
160-
always_active_test = true;
115+
// Now that we have chosen time and versions, setup to mine blocks
116+
Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);
117+
118+
const bool always_active_test = fuzzed_data_provider.ConsumeBool();
119+
const bool never_active_test = !always_active_test && fuzzed_data_provider.ConsumeBool();
120+
121+
const Consensus::BIP9Deployment dep{[&]() {
122+
Consensus::BIP9Deployment dep;
123+
dep.period = period;
124+
125+
dep.threshold = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, period);
126+
assert(0 < dep.threshold && dep.threshold <= dep.period); // must be able to both pass and fail threshold!
127+
128+
// select deployment parameters: bit, start time, timeout
129+
dep.bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);
130+
131+
if (always_active_test) {
132+
dep.nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
133+
dep.nTimeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
134+
} else if (never_active_test) {
135+
dep.nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
136+
dep.nTimeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
161137
} else {
162-
start_time = Consensus::BIP9Deployment::NEVER_ACTIVE;
163-
never_active_test = true;
164-
}
165-
timeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
166-
}
167-
int min_activation = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * max_periods);
138+
// pick the timestamp to switch based on a block
139+
// note states will change *after* these blocks because mediantime lags
140+
int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
141+
int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
168142

169-
TestConditionChecker checker(start_time, timeout, period, threshold, min_activation, bit);
143+
dep.nStartTime = block_start_time + start_block * interval;
144+
dep.nTimeout = block_start_time + end_block * interval;
145+
146+
// allow for times to not exactly match a block
147+
if (fuzzed_data_provider.ConsumeBool()) dep.nStartTime += interval / 2;
148+
if (fuzzed_data_provider.ConsumeBool()) dep.nTimeout += interval / 2;
149+
}
150+
dep.min_activation_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * max_periods);
151+
return dep;
152+
}()};
153+
TestConditionChecker checker(dep);
170154

171155
// Early exit if the versions don't signal sensibly for the deployment
172156
if (!checker.Condition(ver_signal)) return;
173157
if (checker.Condition(ver_nosignal)) return;
174-
if (ver_nosignal < 0) return;
175158

176159
// TOP_BITS should ensure version will be positive and meet min
177160
// version requirement
178161
assert(ver_signal > 0);
179162
assert(ver_signal >= VERSIONBITS_LAST_OLD_BLOCK_VERSION);
180163

181-
// Now that we have chosen time and versions, setup to mine blocks
182-
Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);
183-
184164
/* Strategy:
185165
* * we will mine a final period worth of blocks, with
186166
* randomised signalling according to a mask
@@ -222,9 +202,9 @@ FUZZ_TARGET(versionbits, .init = initialize)
222202
// get statistics from end of previous period, then reset
223203
BIP9Stats last_stats;
224204
last_stats.period = period;
225-
last_stats.threshold = threshold;
205+
last_stats.threshold = dep.threshold;
226206
last_stats.count = last_stats.elapsed = 0;
227-
last_stats.possible = (period >= threshold);
207+
last_stats.possible = (period >= dep.threshold);
228208
std::vector<bool> last_signals{};
229209

230210
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
@@ -238,7 +218,7 @@ FUZZ_TARGET(versionbits, .init = initialize)
238218
CBlockIndex* current_block = blocks.mine_block(signal);
239219

240220
// verify that signalling attempt was interpreted correctly
241-
assert(checker.Condition(current_block) == signal);
221+
assert(checker.Condition(current_block->nVersion) == signal);
242222

243223
// state and since don't change within the period
244224
const ThresholdState state = checker.GetStateFor(current_block);
@@ -255,10 +235,10 @@ FUZZ_TARGET(versionbits, .init = initialize)
255235
&& stats.possible == stats_no_signals.possible);
256236

257237
assert(stats.period == period);
258-
assert(stats.threshold == threshold);
238+
assert(stats.threshold == dep.threshold);
259239
assert(stats.elapsed == b);
260240
assert(stats.count == last_stats.count + (signal ? 1 : 0));
261-
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
241+
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.threshold));
262242
last_stats = stats;
263243

264244
assert(signals.size() == last_signals.size() + 1);
@@ -269,21 +249,21 @@ FUZZ_TARGET(versionbits, .init = initialize)
269249

270250
if (exp_state == ThresholdState::STARTED) {
271251
// double check that stats.possible is sane
272-
if (blocks_sig >= threshold - 1) assert(last_stats.possible);
252+
if (blocks_sig >= dep.threshold - 1) assert(last_stats.possible);
273253
}
274254

275255
// mine the final block
276256
bool signal = (signalling_mask >> (period % 32)) & 1;
277257
if (signal) ++blocks_sig;
278258
CBlockIndex* current_block = blocks.mine_block(signal);
279-
assert(checker.Condition(current_block) == signal);
259+
assert(checker.Condition(current_block->nVersion) == signal);
280260

281261
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
282262
assert(stats.period == period);
283-
assert(stats.threshold == threshold);
263+
assert(stats.threshold == dep.threshold);
284264
assert(stats.elapsed == period);
285265
assert(stats.count == blocks_sig);
286-
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
266+
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.threshold));
287267

288268
// More interesting is whether the state changed.
289269
const ThresholdState state = checker.GetStateFor(current_block);
@@ -303,33 +283,33 @@ FUZZ_TARGET(versionbits, .init = initialize)
303283
case ThresholdState::DEFINED:
304284
assert(since == 0);
305285
assert(exp_state == ThresholdState::DEFINED);
306-
assert(current_block->GetMedianTimePast() < checker.m_begin);
286+
assert(current_block->GetMedianTimePast() < dep.nStartTime);
307287
break;
308288
case ThresholdState::STARTED:
309-
assert(current_block->GetMedianTimePast() >= checker.m_begin);
289+
assert(current_block->GetMedianTimePast() >= dep.nStartTime);
310290
if (exp_state == ThresholdState::STARTED) {
311-
assert(blocks_sig < threshold);
312-
assert(current_block->GetMedianTimePast() < checker.m_end);
291+
assert(blocks_sig < dep.threshold);
292+
assert(current_block->GetMedianTimePast() < dep.nTimeout);
313293
} else {
314294
assert(exp_state == ThresholdState::DEFINED);
315295
}
316296
break;
317297
case ThresholdState::LOCKED_IN:
318298
if (exp_state == ThresholdState::LOCKED_IN) {
319-
assert(current_block->nHeight + 1 < min_activation);
299+
assert(current_block->nHeight + 1 < dep.min_activation_height);
320300
} else {
321301
assert(exp_state == ThresholdState::STARTED);
322-
assert(blocks_sig >= threshold);
302+
assert(blocks_sig >= dep.threshold);
323303
}
324304
break;
325305
case ThresholdState::ACTIVE:
326-
assert(always_active_test || min_activation <= current_block->nHeight + 1);
306+
assert(always_active_test || dep.min_activation_height <= current_block->nHeight + 1);
327307
assert(exp_state == ThresholdState::ACTIVE || exp_state == ThresholdState::LOCKED_IN);
328308
break;
329309
case ThresholdState::FAILED:
330-
assert(never_active_test || current_block->GetMedianTimePast() >= checker.m_end);
310+
assert(never_active_test || current_block->GetMedianTimePast() >= dep.nTimeout);
331311
if (exp_state == ThresholdState::STARTED) {
332-
assert(blocks_sig < threshold);
312+
assert(blocks_sig < dep.threshold);
333313
} else {
334314
assert(exp_state == ThresholdState::FAILED);
335315
}

src/versionbits_impl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,19 @@ class VersionBitsConditionChecker : public AbstractThresholdConditionChecker {
6767

6868
bool Condition(const CBlockIndex* pindex) const override
6969
{
70-
return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask()) != 0);
70+
return Condition(pindex->nVersion);
7171
}
7272

7373
public:
7474
explicit VersionBitsConditionChecker(const Consensus::BIP9Deployment& dep) : dep{dep} {}
7575
explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : VersionBitsConditionChecker{params.vDeployments[id]} {}
7676

7777
uint32_t Mask() const { return (uint32_t{1}) << dep.bit; }
78+
79+
bool Condition(int32_t nVersion) const
80+
{
81+
return (((nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (nVersion & Mask()) != 0);
82+
}
7883
};
7984

8085
#endif // BITCOIN_VERSIONBITS_IMPL_H

0 commit comments

Comments
 (0)