#include <chain.h>
#include <chainparams.h>
#include <common/args.h>
#include <consensus/params.h>
#include <primitives/block.h>
#include <util/chaintype.h>
#include <versionbits.h>
#include <versionbits_impl.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 VersionBitsConditionChecker
{
private:
mutable ThresholdConditionCache m_cache;
public:
TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep}
{
assert(dep.period > 0);
assert(dep.threshold <= dep.period);
assert(0 <= dep.bit && dep.bit < 32 && dep.bit < VERSIONBITS_NUM_BITS);
assert(0 <= dep.min_activation_height);
}
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, m_cache); }
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, m_cache); }
};
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()
{
g_params = CreateChainParams(ArgsManager{}, ChainType::MAIN);
assert(g_params != nullptr);
}
constexpr uint32_t MAX_START_TIME = 4102444800;
FUZZ_TARGET(versionbits, .init = initialize)
{
const CChainParams& params = *g_params;
const int64_t interval = params.GetConsensus().nPowTargetSpacing;
assert(interval > 1); assert(interval < std::numeric_limits<int32_t>::max());
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const uint32_t period = 32;
const size_t max_periods = 16;
const size_t max_blocks = 2 * period * max_periods;
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);
const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral<int32_t>();
if (ver_nosignal < 0) return;
Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);
const bool always_active_test = fuzzed_data_provider.ConsumeBool();
const bool never_active_test = !always_active_test && fuzzed_data_provider.ConsumeBool();
const Consensus::BIP9Deployment dep{[&]() {
Consensus::BIP9Deployment dep;
dep.period = period;
dep.threshold = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, period);
assert(0 < dep.threshold && dep.threshold <= dep.period);
dep.bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);
if (always_active_test) {
dep.nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
dep.nTimeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
} else if (never_active_test) {
dep.nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
dep.nTimeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral<int64_t>();
} else {
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));
dep.nStartTime = block_start_time + start_block * interval;
dep.nTimeout = block_start_time + end_block * interval;
if (fuzzed_data_provider.ConsumeBool()) dep.nStartTime += interval / 2;
if (fuzzed_data_provider.ConsumeBool()) dep.nTimeout += interval / 2;
}
dep.min_activation_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * max_periods);
return dep;
}()};
TestConditionChecker checker(dep);
if (!checker.Condition(ver_signal)) return;
if (checker.Condition(ver_nosignal)) return;
assert(ver_signal > 0);
assert(ver_signal >= VERSIONBITS_LAST_OLD_BLOCK_VERSION);
const uint32_t signalling_mask = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
while (fuzzed_data_provider.remaining_bytes() > 0) { bool signal = fuzzed_data_provider.ConsumeBool();
for (uint32_t b = 0; b < period; ++b) {
blocks.mine_block(signal);
}
if (blocks.size() + 2 * period > max_blocks) break;
}
uint32_t blocks_sig = 0;
CBlockIndex* prev = blocks.tip();
const int exp_since = checker.GetStateSinceHeightFor(prev);
const ThresholdState exp_state = checker.GetStateFor(prev);
BIP9Stats last_stats;
last_stats.period = period;
last_stats.threshold = dep.threshold;
last_stats.count = last_stats.elapsed = 0;
last_stats.possible = (period >= dep.threshold);
std::vector<bool> last_signals{};
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
assert(exp_since <= prev_next_height);
for (uint32_t b = 1; b < period; ++b) {
const bool signal = (signalling_mask >> (b % 32)) & 1;
if (signal) ++blocks_sig;
CBlockIndex* current_block = blocks.mine_block(signal);
assert(checker.Condition(current_block->nVersion) == signal);
const ThresholdState state = checker.GetStateFor(current_block);
const int since = checker.GetStateSinceHeightFor(current_block);
assert(state == exp_state);
assert(since == exp_since);
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 == dep.threshold);
assert(stats.elapsed == b);
assert(stats.count == last_stats.count + (signal ? 1 : 0));
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.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) {
if (blocks_sig >= dep.threshold - 1) assert(last_stats.possible);
}
bool signal = (signalling_mask >> (period % 32)) & 1;
if (signal) ++blocks_sig;
CBlockIndex* current_block = blocks.mine_block(signal);
assert(checker.Condition(current_block->nVersion) == signal);
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
assert(stats.period == period);
assert(stats.threshold == dep.threshold);
assert(stats.elapsed == period);
assert(stats.count == blocks_sig);
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.threshold));
const ThresholdState state = checker.GetStateFor(current_block);
const int since = checker.GetStateSinceHeightFor(current_block);
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);
}
switch (state) {
case ThresholdState::DEFINED:
assert(since == 0);
assert(exp_state == ThresholdState::DEFINED);
assert(current_block->GetMedianTimePast() < dep.nStartTime);
break;
case ThresholdState::STARTED:
assert(current_block->GetMedianTimePast() >= dep.nStartTime);
if (exp_state == ThresholdState::STARTED) {
assert(blocks_sig < dep.threshold);
assert(current_block->GetMedianTimePast() < dep.nTimeout);
} else {
assert(exp_state == ThresholdState::DEFINED);
}
break;
case ThresholdState::LOCKED_IN:
if (exp_state == ThresholdState::LOCKED_IN) {
assert(current_block->nHeight + 1 < dep.min_activation_height);
} else {
assert(exp_state == ThresholdState::STARTED);
assert(blocks_sig >= dep.threshold);
}
break;
case ThresholdState::ACTIVE:
assert(always_active_test || dep.min_activation_height <= 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() >= dep.nTimeout);
if (exp_state == ThresholdState::STARTED) {
assert(blocks_sig < dep.threshold);
} else {
assert(exp_state == ThresholdState::FAILED);
}
break;
default:
assert(false);
}
if (blocks.size() >= period * max_periods) {
assert(state == ThresholdState::ACTIVE || state == ThresholdState::FAILED);
}
if (always_active_test) {
assert(state == ThresholdState::ACTIVE);
assert(exp_state == ThresholdState::ACTIVE);
assert(since == 0);
} else if (never_active_test) {
assert(state == ThresholdState::FAILED);
assert(exp_state == ThresholdState::FAILED);
assert(since == 0);
} else {
assert(since > 0 || state == ThresholdState::DEFINED);
assert(exp_since > 0 || exp_state == ThresholdState::DEFINED);
}
}
}