aboutsummaryrefslogtreecommitdiff
path: root/src/test/fuzz/versionbits.cpp
blob: e6a19d6e91700f8757f0d38aff5534925e609724 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// Copyright (c) 2020-2021 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 <chain.h>
#include <chainparams.h>
#include <common/args.h>
#include <consensus/params.h>
#include <primitives/block.h>
#include <versionbits.h>

#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>

#include <cstdint>
#include <limits>
#include <memory>
#include <vector>

namespace {
class TestConditionChecker : public AbstractThresholdConditionChecker
{
private:
    mutable ThresholdConditionCache m_cache;
    const Consensus::Params dummy_params{};

public:
    const int64_t m_begin;
    const int64_t m_end;
    const int m_period;
    const int m_threshold;
    const int m_min_activation_height;
    const int m_bit;

    TestConditionChecker(int64_t begin, int64_t end, int period, int threshold, int min_activation_height, int bit)
        : m_begin{begin}, m_end{end}, m_period{period}, m_threshold{threshold}, m_min_activation_height{min_activation_height}, m_bit{bit}
    {
        assert(m_period > 0);
        assert(0 <= m_threshold && m_threshold <= m_period);
        assert(0 <= m_bit && m_bit < 32 && m_bit < VERSIONBITS_NUM_BITS);
        assert(0 <= m_min_activation_height);
    }

    bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); }
    int64_t BeginTime(const Consensus::Params& params) const override { return m_begin; }
    int64_t EndTime(const Consensus::Params& params) const override { return m_end; }
    int Period(const Consensus::Params& params) const override { return m_period; }
    int Threshold(const Consensus::Params& params) const override { return m_threshold; }
    int MinActivationHeight(const Consensus::Params& params) const override { return m_min_activation_height; }

    ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, dummy_params, m_cache); }
    int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, dummy_params, m_cache); }
    BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, std::vector<bool>* signals=nullptr) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params, signals); }

    bool Condition(int32_t version) const
    {
        uint32_t mask = (uint32_t{1}) << m_bit;
        return (((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & mask) != 0);
    }

    bool Condition(const CBlockIndex* pindex) const { return Condition(pindex->nVersion); }
};

/** Track blocks mined for test */
class Blocks
{
private:
    std::vector<std::unique_ptr<CBlockIndex>> m_blocks;
    const uint32_t m_start_time;
    const uint32_t m_interval;
    const int32_t m_signal;
    const int32_t m_no_signal;

public:
    Blocks(uint32_t start_time, uint32_t interval, int32_t signal, int32_t no_signal)
        : m_start_time{start_time}, m_interval{interval}, m_signal{signal}, m_no_signal{no_signal} {}

    size_t size() const { return m_blocks.size(); }

    CBlockIndex* tip() const
    {
        return m_blocks.empty() ? nullptr : m_blocks.back().get();
    }

    CBlockIndex* mine_block(bool signal)
    {
        CBlockHeader header;
        header.nVersion = signal ? m_signal : m_no_signal;
        header.nTime = m_start_time + m_blocks.size() * m_interval;
        header.nBits = 0x1d00ffff;

        auto current_block = std::make_unique<CBlockIndex>(header);
        current_block->pprev = tip();
        current_block->nHeight = m_blocks.size();
        current_block->BuildSkip();

        return m_blocks.emplace_back(std::move(current_block)).get();
    }
};

std::unique_ptr<const CChainParams> g_params;

void initialize()
{
    // this is actually comparatively slow, so only do it once
    g_params = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN);
    assert(g_params != nullptr);
}

constexpr uint32_t MAX_START_TIME = 4102444800; // 2100-01-01

FUZZ_TARGET_INIT(versionbits, initialize)
{
    const CChainParams& params = *g_params;
    const int64_t interval = params.GetConsensus().nPowTargetSpacing;
    assert(interval > 1); // need to be able to halve it
    assert(interval < std::numeric_limits<int32_t>::max());

    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());

    // making period/max_periods larger slows these tests down significantly
    const int period = 32;
    const size_t max_periods = 16;
    const size_t max_blocks = 2 * period * max_periods;

    const int threshold = fuzzed_data_provider.ConsumeIntegralInRange(1, period);
    assert(0 < threshold && threshold <= period); // must be able to both pass and fail threshold!

    // too many blocks at 10min each might cause uint32_t time to overflow if
    // block_start_time is at the end of the range above
    assert(std::numeric_limits<uint32_t>::max() - MAX_START_TIME > interval * max_blocks);

    const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_START_TIME);

    // what values for version will we use to signal / not signal?
    const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
    const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral<int32_t>();

    // select deployment parameters: bit, start time, timeout
    const int bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);

    bool always_active_test = false;
    bool never_active_test = false;
    int64_t start_time;
    int64_t timeout;
    if (fuzzed_data_provider.ConsumeBool()) {
        // pick the timestamp to switch based on a block
        // note states will change *after* these blocks because mediantime lags
        int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));
        int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3));

        start_time = block_start_time + start_block * interval;
        timeout = block_start_time + end_block * interval;

        // allow for times to not exactly match a block
        if (fuzzed_data_provider.ConsumeBool()) start_time += interval / 2;
        if (fuzzed_data_provider.ConsumeBool()) timeout += interval / 2;
    } else {
        if (fuzzed_data_provider.ConsumeBool()) {
            start_time = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
            always_active_test = true;
        } else {
            start_time = Consensus::BIP9Deployment::NEVER_ACTIVE;
            never_active_test = true;
        }
        timeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
    }
    int min_activation = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * max_periods);

    TestConditionChecker checker(start_time, timeout, period, threshold, min_activation, bit);

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

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

    // Now that we have chosen time and versions, setup to mine blocks
    Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);

    /* Strategy:
     *  * we will mine a final period worth of blocks, with
     *    randomised signalling according to a mask
     *  * but before we mine those blocks, we will mine some
     *    randomised number of prior periods; with either all
     *    or no blocks in the period signalling
     *
     * We establish the mask first, then consume "bools" until
     * we run out of fuzz data to work out how many prior periods
     * there are and which ones will signal.
     */

    // establish the mask
    const uint32_t signalling_mask = fuzzed_data_provider.ConsumeIntegral<uint32_t>();

    // mine prior periods
    while (fuzzed_data_provider.remaining_bytes() > 0) { // early exit; no need for LIMITED_WHILE
        // all blocks in these periods either do or don't signal
        bool signal = fuzzed_data_provider.ConsumeBool();
        for (int b = 0; b < period; ++b) {
            blocks.mine_block(signal);
        }

        // don't risk exceeding max_blocks or times may wrap around
        if (blocks.size() + 2 * period > max_blocks) break;
    }
    // NOTE: fuzzed_data_provider may be fully consumed at this point and should not be used further

    // now we mine the final period and check that everything looks sane

    // count the number of signalling blocks
    int blocks_sig = 0;

    // get the info for the first block of the period
    CBlockIndex* prev = blocks.tip();
    const int exp_since = checker.GetStateSinceHeightFor(prev);
    const ThresholdState exp_state = checker.GetStateFor(prev);

    // get statistics from end of previous period, then reset
    BIP9Stats last_stats;
    last_stats.period = period;
    last_stats.threshold = threshold;
    last_stats.count = last_stats.elapsed = 0;
    last_stats.possible = (period >= threshold);
    std::vector<bool> last_signals{};

    int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
    assert(exp_since <= prev_next_height);

    // mine (period-1) blocks and check state
    for (int b = 1; b < period; ++b) {
        const bool signal = (signalling_mask >> (b % 32)) & 1;
        if (signal) ++blocks_sig;

        CBlockIndex* current_block = blocks.mine_block(signal);

        // verify that signalling attempt was interpreted correctly
        assert(checker.Condition(current_block) == signal);

        // state and since don't change within the period
        const ThresholdState state = checker.GetStateFor(current_block);
        const int since = checker.GetStateSinceHeightFor(current_block);
        assert(state == exp_state);
        assert(since == exp_since);

        // check that after mining this block stats change as expected
        std::vector<bool> signals;
        const BIP9Stats stats = checker.GetStateStatisticsFor(current_block, &signals);
        const BIP9Stats stats_no_signals = checker.GetStateStatisticsFor(current_block);
        assert(stats.period == stats_no_signals.period && stats.threshold == stats_no_signals.threshold
               && stats.elapsed == stats_no_signals.elapsed && stats.count == stats_no_signals.count
               && stats.possible == stats_no_signals.possible);

        assert(stats.period == period);
        assert(stats.threshold == threshold);
        assert(stats.elapsed == b);
        assert(stats.count == last_stats.count + (signal ? 1 : 0));
        assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
        last_stats = stats;

        assert(signals.size() == last_signals.size() + 1);
        assert(signals.back() == signal);
        last_signals.push_back(signal);
        assert(signals == last_signals);
    }

    if (exp_state == ThresholdState::STARTED) {
        // double check that stats.possible is sane
        if (blocks_sig >= threshold - 1) assert(last_stats.possible);
    }

    // mine the final block
    bool signal = (signalling_mask >> (period % 32)) & 1;
    if (signal) ++blocks_sig;
    CBlockIndex* current_block = blocks.mine_block(signal);
    assert(checker.Condition(current_block) == signal);

    const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
    assert(stats.period == period);
    assert(stats.threshold == threshold);
    assert(stats.elapsed == period);
    assert(stats.count == blocks_sig);
    assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));

    // More interesting is whether the state changed.
    const ThresholdState state = checker.GetStateFor(current_block);
    const int since = checker.GetStateSinceHeightFor(current_block);

    // since is straightforward:
    assert(since % period == 0);
    assert(0 <= since && since <= current_block->nHeight + 1);
    if (state == exp_state) {
        assert(since == exp_since);
    } else {
        assert(since == current_block->nHeight + 1);
    }

    // state is where everything interesting is
    switch (state) {
    case ThresholdState::DEFINED:
        assert(since == 0);
        assert(exp_state == ThresholdState::DEFINED);
        assert(current_block->GetMedianTimePast() < checker.m_begin);
        break;
    case ThresholdState::STARTED:
        assert(current_block->GetMedianTimePast() >= checker.m_begin);
        if (exp_state == ThresholdState::STARTED) {
            assert(blocks_sig < threshold);
            assert(current_block->GetMedianTimePast() < checker.m_end);
        } else {
            assert(exp_state == ThresholdState::DEFINED);
        }
        break;
    case ThresholdState::LOCKED_IN:
        if (exp_state == ThresholdState::LOCKED_IN) {
            assert(current_block->nHeight + 1 < min_activation);
        } else {
            assert(exp_state == ThresholdState::STARTED);
            assert(blocks_sig >= threshold);
        }
        break;
    case ThresholdState::ACTIVE:
        assert(always_active_test || min_activation <= current_block->nHeight + 1);
        assert(exp_state == ThresholdState::ACTIVE || exp_state == ThresholdState::LOCKED_IN);
        break;
    case ThresholdState::FAILED:
        assert(never_active_test || current_block->GetMedianTimePast() >= checker.m_end);
        if (exp_state == ThresholdState::STARTED) {
            assert(blocks_sig < threshold);
        } else {
            assert(exp_state == ThresholdState::FAILED);
        }
        break;
    default:
        assert(false);
    }

    if (blocks.size() >= period * max_periods) {
        // we chose the timeout (and block times) so that by the time we have this many blocks it's all over
        assert(state == ThresholdState::ACTIVE || state == ThresholdState::FAILED);
    }

    if (always_active_test) {
        // "always active" has additional restrictions
        assert(state == ThresholdState::ACTIVE);
        assert(exp_state == ThresholdState::ACTIVE);
        assert(since == 0);
    } else if (never_active_test) {
        // "never active" does too
        assert(state == ThresholdState::FAILED);
        assert(exp_state == ThresholdState::FAILED);
        assert(since == 0);
    } else {
        // for signalled deployments, the initial state is always DEFINED
        assert(since > 0 || state == ThresholdState::DEFINED);
        assert(exp_since > 0 || exp_state == ThresholdState::DEFINED);
    }
}
} // namespace