Coverage Report

Created: 2026-06-09 19:51

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