mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-10 14:48:46 +02:00
Merge bitcoin/bitcoin#29039: versionbits refactoring
e3014017batest: add IsActiveAfter tests for versionbits (Anthony Towns)60950f77c3versionbits: docstrings for BIP9Info (Anthony Towns)7565563bc7tests: refactor versionbits fuzz test (Anthony Towns)2e4e9b9608tests: refactor versionbits unit test (Anthony Towns)525c00f91bversionbits: Expose VersionBitsConditionChecker via impl header (Anthony Towns)e74a7049b4versionbits: Expose StateName function (Anthony Towns)d00d1ed52cversionbits: Split out internal details into impl header (Anthony Towns)37b9b67a39versionbits: Simplify VersionBitsCache API (Anthony Towns)1198e7d2fdversionbits: Move BIP9 status logic for getblocktemplate to versionbits (Anthony Towns)b1e967c3ecversionbits: Move getdeploymentinfo logic to versionbits (Anthony Towns)3bd32c2055versionbits: Move WarningBits logic from validation to versionbits (Anthony Towns)5da119e5d0versionbits: Change BIP9Stats to uint32_t types (Anthony Towns)a679040ec1consensus/params: Move version bits period/threshold to bip9 param (Anthony Towns)e9d617095dversionbits: Remove params from AbstractThresholdConditionChecker (Anthony Towns)9bc41f1b48versionbits: Use std::array instead of C-style arrays (Anthony Towns) Pull request description: Increases the encapsulation/modularity of the versionbits code, moving more of the logic into the versionbits module rather than having it scattered across validation and rpc code. Updates unit/fuzz tests to test the actual code used rather than just a close approximation of it. ACKs for top commit: achow101: ACKe3014017baTheCharlatan: Re-ACKe3014017badarosior: ACKe3014017baTree-SHA512: 2978db5038354b56fa1dd6aafd511099e9c16504d6a88daeac2ff2702c87bcf3e55a32e2f0a7697e3de76963b68b9d5ede7976ee007e45862fa306911194496d
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#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>
|
||||
@@ -20,47 +21,22 @@
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
class TestConditionChecker : public AbstractThresholdConditionChecker
|
||||
class TestConditionChecker : public VersionBitsConditionChecker
|
||||
{
|
||||
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}
|
||||
TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep}
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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); }
|
||||
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, m_cache); }
|
||||
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, m_cache); }
|
||||
};
|
||||
|
||||
/** Track blocks mined for test */
|
||||
@@ -121,13 +97,10 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
|
||||
// making period/max_periods larger slows these tests down significantly
|
||||
const int period = 32;
|
||||
const uint32_t 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);
|
||||
@@ -137,53 +110,57 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
// 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>();
|
||||
if (ver_nosignal < 0) return; // negative values are uninteresting
|
||||
|
||||
// select deployment parameters: bit, start time, timeout
|
||||
const int bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1);
|
||||
// Now that we have chosen time and versions, setup to mine blocks
|
||||
Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal);
|
||||
|
||||
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));
|
||||
const bool always_active_test = fuzzed_data_provider.ConsumeBool();
|
||||
const bool never_active_test = !always_active_test && fuzzed_data_provider.ConsumeBool();
|
||||
|
||||
start_time = block_start_time + start_block * interval;
|
||||
timeout = block_start_time + end_block * interval;
|
||||
const Consensus::BIP9Deployment dep{[&]() {
|
||||
Consensus::BIP9Deployment dep;
|
||||
dep.period = period;
|
||||
|
||||
// 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;
|
||||
dep.threshold = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, period);
|
||||
assert(0 < dep.threshold && dep.threshold <= dep.period); // must be able to both pass and fail threshold!
|
||||
|
||||
// select deployment parameters: bit, start time, timeout
|
||||
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 {
|
||||
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);
|
||||
// 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));
|
||||
|
||||
TestConditionChecker checker(start_time, timeout, period, threshold, min_activation, bit);
|
||||
dep.nStartTime = block_start_time + start_block * interval;
|
||||
dep.nTimeout = block_start_time + end_block * interval;
|
||||
|
||||
// allow for times to not exactly match a block
|
||||
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);
|
||||
|
||||
// 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
|
||||
@@ -203,7 +180,7 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
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) {
|
||||
for (uint32_t b = 0; b < period; ++b) {
|
||||
blocks.mine_block(signal);
|
||||
}
|
||||
|
||||
@@ -215,7 +192,7 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
// now we mine the final period and check that everything looks sane
|
||||
|
||||
// count the number of signalling blocks
|
||||
int blocks_sig = 0;
|
||||
uint32_t blocks_sig = 0;
|
||||
|
||||
// get the info for the first block of the period
|
||||
CBlockIndex* prev = blocks.tip();
|
||||
@@ -225,23 +202,23 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
// get statistics from end of previous period, then reset
|
||||
BIP9Stats last_stats;
|
||||
last_stats.period = period;
|
||||
last_stats.threshold = threshold;
|
||||
last_stats.threshold = dep.threshold;
|
||||
last_stats.count = last_stats.elapsed = 0;
|
||||
last_stats.possible = (period >= threshold);
|
||||
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);
|
||||
|
||||
// mine (period-1) blocks and check state
|
||||
for (int b = 1; b < period; ++b) {
|
||||
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);
|
||||
|
||||
// verify that signalling attempt was interpreted correctly
|
||||
assert(checker.Condition(current_block) == signal);
|
||||
assert(checker.Condition(current_block->nVersion) == signal);
|
||||
|
||||
// state and since don't change within the period
|
||||
const ThresholdState state = checker.GetStateFor(current_block);
|
||||
@@ -258,10 +235,10 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
&& stats.possible == stats_no_signals.possible);
|
||||
|
||||
assert(stats.period == period);
|
||||
assert(stats.threshold == threshold);
|
||||
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 + threshold));
|
||||
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.threshold));
|
||||
last_stats = stats;
|
||||
|
||||
assert(signals.size() == last_signals.size() + 1);
|
||||
@@ -272,21 +249,21 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
|
||||
if (exp_state == ThresholdState::STARTED) {
|
||||
// double check that stats.possible is sane
|
||||
if (blocks_sig >= threshold - 1) assert(last_stats.possible);
|
||||
if (blocks_sig >= dep.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);
|
||||
assert(checker.Condition(current_block->nVersion) == signal);
|
||||
|
||||
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
|
||||
assert(stats.period == period);
|
||||
assert(stats.threshold == threshold);
|
||||
assert(stats.threshold == dep.threshold);
|
||||
assert(stats.elapsed == period);
|
||||
assert(stats.count == blocks_sig);
|
||||
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
|
||||
assert(stats.possible == (stats.count + period >= stats.elapsed + dep.threshold));
|
||||
|
||||
// More interesting is whether the state changed.
|
||||
const ThresholdState state = checker.GetStateFor(current_block);
|
||||
@@ -306,33 +283,33 @@ FUZZ_TARGET(versionbits, .init = initialize)
|
||||
case ThresholdState::DEFINED:
|
||||
assert(since == 0);
|
||||
assert(exp_state == ThresholdState::DEFINED);
|
||||
assert(current_block->GetMedianTimePast() < checker.m_begin);
|
||||
assert(current_block->GetMedianTimePast() < dep.nStartTime);
|
||||
break;
|
||||
case ThresholdState::STARTED:
|
||||
assert(current_block->GetMedianTimePast() >= checker.m_begin);
|
||||
assert(current_block->GetMedianTimePast() >= dep.nStartTime);
|
||||
if (exp_state == ThresholdState::STARTED) {
|
||||
assert(blocks_sig < threshold);
|
||||
assert(current_block->GetMedianTimePast() < checker.m_end);
|
||||
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 < min_activation);
|
||||
assert(current_block->nHeight + 1 < dep.min_activation_height);
|
||||
} else {
|
||||
assert(exp_state == ThresholdState::STARTED);
|
||||
assert(blocks_sig >= threshold);
|
||||
assert(blocks_sig >= dep.threshold);
|
||||
}
|
||||
break;
|
||||
case ThresholdState::ACTIVE:
|
||||
assert(always_active_test || min_activation <= current_block->nHeight + 1);
|
||||
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() >= checker.m_end);
|
||||
assert(never_active_test || current_block->GetMedianTimePast() >= dep.nTimeout);
|
||||
if (exp_state == ThresholdState::STARTED) {
|
||||
assert(blocks_sig < threshold);
|
||||
assert(blocks_sig < dep.threshold);
|
||||
} else {
|
||||
assert(exp_state == ThresholdState::FAILED);
|
||||
}
|
||||
|
||||
@@ -9,59 +9,48 @@
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/chaintype.h>
|
||||
#include <versionbits.h>
|
||||
#include <versionbits_impl.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
/* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */
|
||||
static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; }
|
||||
|
||||
static std::string StateName(ThresholdState state)
|
||||
{
|
||||
switch (state) {
|
||||
case ThresholdState::DEFINED: return "DEFINED";
|
||||
case ThresholdState::STARTED: return "STARTED";
|
||||
case ThresholdState::LOCKED_IN: return "LOCKED_IN";
|
||||
case ThresholdState::ACTIVE: return "ACTIVE";
|
||||
case ThresholdState::FAILED: return "FAILED";
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
return "";
|
||||
}
|
||||
|
||||
static const Consensus::Params paramsDummy = Consensus::Params();
|
||||
|
||||
class TestConditionChecker : public AbstractThresholdConditionChecker
|
||||
class TestConditionChecker final : public VersionBitsConditionChecker
|
||||
{
|
||||
private:
|
||||
mutable ThresholdConditionCache cache;
|
||||
|
||||
public:
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); }
|
||||
int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); }
|
||||
int Period(const Consensus::Params& params) const override { return 1000; }
|
||||
int Threshold(const Consensus::Params& params) const override { return 900; }
|
||||
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
|
||||
// constructor is implicit to allow for easier initialization of vector<TestConditionChecker>
|
||||
explicit(false) TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep} { }
|
||||
~TestConditionChecker() override = default;
|
||||
|
||||
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
|
||||
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
|
||||
ThresholdState StateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, cache); }
|
||||
int StateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, cache); }
|
||||
void clear() { cache.clear(); }
|
||||
};
|
||||
|
||||
class TestDelayedActivationConditionChecker : public TestConditionChecker
|
||||
namespace {
|
||||
struct Deployments
|
||||
{
|
||||
public:
|
||||
int MinActivationHeight(const Consensus::Params& params) const override { return 15000; }
|
||||
};
|
||||
|
||||
class TestAlwaysActiveConditionChecker : public TestConditionChecker
|
||||
{
|
||||
public:
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; }
|
||||
};
|
||||
|
||||
class TestNeverActiveConditionChecker : public TestConditionChecker
|
||||
{
|
||||
public:
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::NEVER_ACTIVE; }
|
||||
const Consensus::BIP9Deployment normal{
|
||||
.bit = 8,
|
||||
.nStartTime = TestTime(10000),
|
||||
.nTimeout = TestTime(20000),
|
||||
.min_activation_height = 0,
|
||||
.period = 1000,
|
||||
.threshold = 900,
|
||||
};
|
||||
Consensus::BIP9Deployment always, never, delayed;
|
||||
Deployments()
|
||||
{
|
||||
delayed = normal; delayed.min_activation_height = 15000;
|
||||
always = normal; always.nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
|
||||
never = normal; never.nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#define CHECKERS 6
|
||||
|
||||
@@ -71,22 +60,28 @@ class VersionBitsTester
|
||||
// A fake blockchain
|
||||
std::vector<CBlockIndex*> vpblock;
|
||||
|
||||
// Used to automatically set the top bits for manual calls to Mine()
|
||||
const int32_t nVersionBase{0};
|
||||
|
||||
// Setup BIP9Deployment structs for the checkers
|
||||
const Deployments test_deployments;
|
||||
|
||||
// 6 independent checkers for the same bit.
|
||||
// The first one performs all checks, the second only 50%, the third only 25%, etc...
|
||||
// This is to test whether lack of cached information leads to the same results.
|
||||
TestConditionChecker checker[CHECKERS];
|
||||
std::vector<TestConditionChecker> checker{CHECKERS, {test_deployments.normal}};
|
||||
// Another 6 that assume delayed activation
|
||||
TestDelayedActivationConditionChecker checker_delayed[CHECKERS];
|
||||
std::vector<TestConditionChecker> checker_delayed{CHECKERS, {test_deployments.delayed}};
|
||||
// Another 6 that assume always active activation
|
||||
TestAlwaysActiveConditionChecker checker_always[CHECKERS];
|
||||
std::vector<TestConditionChecker> checker_always{CHECKERS, {test_deployments.always}};
|
||||
// Another 6 that assume never active activation
|
||||
TestNeverActiveConditionChecker checker_never[CHECKERS];
|
||||
std::vector<TestConditionChecker> checker_never{CHECKERS, {test_deployments.never}};
|
||||
|
||||
// Test counter (to identify failures)
|
||||
int num{1000};
|
||||
|
||||
public:
|
||||
VersionBitsTester(FastRandomContext& rng) : m_rng{rng} {}
|
||||
explicit VersionBitsTester(FastRandomContext& rng, int32_t nVersionBase=0) : m_rng{rng}, nVersionBase{nVersionBase} { }
|
||||
|
||||
VersionBitsTester& Reset() {
|
||||
// Have each group of tests be counted by the 1000s part, starting at 1000
|
||||
@@ -96,10 +91,10 @@ public:
|
||||
delete vpblock[i];
|
||||
}
|
||||
for (unsigned int i = 0; i < CHECKERS; i++) {
|
||||
checker[i] = TestConditionChecker();
|
||||
checker_delayed[i] = TestDelayedActivationConditionChecker();
|
||||
checker_always[i] = TestAlwaysActiveConditionChecker();
|
||||
checker_never[i] = TestNeverActiveConditionChecker();
|
||||
checker[i].clear();
|
||||
checker_delayed[i].clear();
|
||||
checker_always[i].clear();
|
||||
checker_never[i].clear();
|
||||
}
|
||||
vpblock.clear();
|
||||
return *this;
|
||||
@@ -115,7 +110,7 @@ public:
|
||||
pindex->nHeight = vpblock.size();
|
||||
pindex->pprev = Tip();
|
||||
pindex->nTime = nTime;
|
||||
pindex->nVersion = nVersion;
|
||||
pindex->nVersion = (nVersionBase | nVersion);
|
||||
pindex->BuildSkip();
|
||||
vpblock.push_back(pindex);
|
||||
}
|
||||
@@ -132,10 +127,10 @@ public:
|
||||
const CBlockIndex* tip = Tip();
|
||||
for (int i = 0; i < CHECKERS; i++) {
|
||||
if (m_rng.randbits(i) == 0) {
|
||||
BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(tip) == height, strprintf("Test %i for StateSinceHeight", num));
|
||||
BOOST_CHECK_MESSAGE(checker_delayed[i].GetStateSinceHeightFor(tip) == height_delayed, strprintf("Test %i for StateSinceHeight (delayed)", num));
|
||||
BOOST_CHECK_MESSAGE(checker_always[i].GetStateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (always active)", num));
|
||||
BOOST_CHECK_MESSAGE(checker_never[i].GetStateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (never active)", num));
|
||||
BOOST_CHECK_MESSAGE(checker[i].StateSinceHeightFor(tip) == height, strprintf("Test %i for StateSinceHeight", num));
|
||||
BOOST_CHECK_MESSAGE(checker_delayed[i].StateSinceHeightFor(tip) == height_delayed, strprintf("Test %i for StateSinceHeight (delayed)", num));
|
||||
BOOST_CHECK_MESSAGE(checker_always[i].StateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (always active)", num));
|
||||
BOOST_CHECK_MESSAGE(checker_never[i].StateSinceHeightFor(tip) == 0, strprintf("Test %i for StateSinceHeight (never active)", num));
|
||||
}
|
||||
}
|
||||
num++;
|
||||
@@ -158,10 +153,10 @@ public:
|
||||
const CBlockIndex* pindex = Tip();
|
||||
for (int i = 0; i < CHECKERS; i++) {
|
||||
if (m_rng.randbits(i) == 0) {
|
||||
ThresholdState got = checker[i].GetStateFor(pindex);
|
||||
ThresholdState got_delayed = checker_delayed[i].GetStateFor(pindex);
|
||||
ThresholdState got_always = checker_always[i].GetStateFor(pindex);
|
||||
ThresholdState got_never = checker_never[i].GetStateFor(pindex);
|
||||
ThresholdState got = checker[i].StateFor(pindex);
|
||||
ThresholdState got_delayed = checker_delayed[i].StateFor(pindex);
|
||||
ThresholdState got_always = checker_always[i].StateFor(pindex);
|
||||
ThresholdState got_never = checker_never[i].StateFor(pindex);
|
||||
// nHeight of the next block. If vpblock is empty, the next (ie first)
|
||||
// block should be the genesis block with nHeight == 0.
|
||||
int height = pindex == nullptr ? 0 : pindex->nHeight + 1;
|
||||
@@ -193,7 +188,7 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
|
||||
{
|
||||
for (int i = 0; i < 64; i++) {
|
||||
// DEFINED -> STARTED after timeout reached -> FAILED
|
||||
VersionBitsTester(m_rng).TestDefined().TestStateSinceHeight(0)
|
||||
VersionBitsTester(m_rng, VERSIONBITS_TOP_BITS).TestDefined().TestStateSinceHeight(0)
|
||||
.Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
|
||||
.Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
|
||||
.Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
|
||||
@@ -260,7 +255,8 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
|
||||
}
|
||||
|
||||
struct BlockVersionTest : BasicTestingSetup {
|
||||
/** Check that ComputeBlockVersion will set the appropriate bit correctly */
|
||||
/** Check that ComputeBlockVersion will set the appropriate bit correctly
|
||||
* Also checks IsActiveAfter() behaviour */
|
||||
void check_computeblockversion(VersionBitsCache& versionbitscache, const Consensus::Params& params, Consensus::DeploymentPos dep)
|
||||
{
|
||||
// Clear the cache every time
|
||||
@@ -270,6 +266,12 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
int64_t nStartTime = params.vDeployments[dep].nStartTime;
|
||||
int64_t nTimeout = params.vDeployments[dep].nTimeout;
|
||||
int min_activation_height = params.vDeployments[dep].min_activation_height;
|
||||
uint32_t period = params.vDeployments[dep].period;
|
||||
uint32_t threshold = params.vDeployments[dep].threshold;
|
||||
|
||||
BOOST_REQUIRE(period > 0); // no division by zero, thankyou
|
||||
BOOST_REQUIRE(0 < threshold); // must be able to have a window that doesn't activate
|
||||
BOOST_REQUIRE(threshold < period); // must be able to have a window that does activate
|
||||
|
||||
// should not be any signalling for first block
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS);
|
||||
@@ -278,6 +280,11 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE ||
|
||||
nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE)
|
||||
{
|
||||
if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE) {
|
||||
BOOST_CHECK(versionbitscache.IsActiveAfter(nullptr, params, dep));
|
||||
} else {
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(nullptr, params, dep));
|
||||
}
|
||||
BOOST_CHECK_EQUAL(min_activation_height, 0);
|
||||
BOOST_CHECK_EQUAL(nTimeout, Consensus::BIP9Deployment::NO_TIMEOUT);
|
||||
return;
|
||||
@@ -291,10 +298,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
BOOST_REQUIRE(((1 << bit) & VERSIONBITS_TOP_MASK) == 0);
|
||||
BOOST_REQUIRE(min_activation_height >= 0);
|
||||
// Check min_activation_height is on a retarget boundary
|
||||
BOOST_REQUIRE_EQUAL(min_activation_height % params.nMinerConfirmationWindow, 0U);
|
||||
|
||||
const uint32_t bitmask{versionbitscache.Mask(params, dep)};
|
||||
BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit);
|
||||
BOOST_REQUIRE_EQUAL(min_activation_height % period, 0U);
|
||||
|
||||
// In the first chain, test that the bit is set by CBV until it has failed.
|
||||
// In the second chain, test the bit is set by CBV while STARTED and
|
||||
@@ -311,49 +315,56 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
// since CBlockIndex::nTime is uint32_t we can't represent any
|
||||
// earlier time, so will transition from DEFINED to STARTED at the
|
||||
// end of the first period by mining blocks at nTime == 0
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
lastBlock = firstChain.Mine(period - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
lastBlock = firstChain.Mine(period, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
// then we'll keep mining at nStartTime...
|
||||
} else {
|
||||
// use a time 1s earlier than start time to check we stay DEFINED
|
||||
--nTime;
|
||||
|
||||
// Start generating blocks before nStartTime
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
lastBlock = firstChain.Mine(period, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
|
||||
// Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet.
|
||||
for (uint32_t i = 1; i < params.nMinerConfirmationWindow - 4; i++) {
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
for (uint32_t i = 1; i < period - 4; i++) {
|
||||
lastBlock = firstChain.Mine(period + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
}
|
||||
// Now mine 5 more blocks at the start time -- MTP should not have passed yet, so
|
||||
// CBV should still not yet set the bit.
|
||||
nTime = nStartTime;
|
||||
for (uint32_t i = params.nMinerConfirmationWindow - 4; i <= params.nMinerConfirmationWindow; i++) {
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
for (uint32_t i = period - 4; i <= period; i++) {
|
||||
lastBlock = firstChain.Mine(period + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
}
|
||||
// Next we will advance to the next period and transition to STARTED,
|
||||
}
|
||||
|
||||
lastBlock = firstChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
lastBlock = firstChain.Mine(period * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
// so ComputeBlockVersion should now set the bit,
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
// and should also be using the VERSIONBITS_TOP_BITS.
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
|
||||
// Check that ComputeBlockVersion will set the bit until nTimeout
|
||||
nTime += 600;
|
||||
uint32_t blocksToMine = params.nMinerConfirmationWindow * 2; // test blocks for up to 2 time periods
|
||||
uint32_t nHeight = params.nMinerConfirmationWindow * 3;
|
||||
uint32_t blocksToMine = period * 2; // test blocks for up to 2 time periods
|
||||
uint32_t nHeight = period * 3;
|
||||
// These blocks are all before nTimeout is reached.
|
||||
while (nTime < nTimeout && blocksToMine > 0) {
|
||||
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
blocksToMine--;
|
||||
nTime += 600;
|
||||
nHeight += 1;
|
||||
@@ -365,22 +376,25 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
nTime = nTimeout;
|
||||
|
||||
// finish the last period before we start timing out
|
||||
while (nHeight % params.nMinerConfirmationWindow != 0) {
|
||||
while (nHeight % period != 0) {
|
||||
lastBlock = firstChain.Mine(nHeight+1, nTime - 1, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
nHeight += 1;
|
||||
}
|
||||
|
||||
// FAILED is only triggered at the end of a period, so CBV should be setting
|
||||
// the bit until the period transition.
|
||||
for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) {
|
||||
for (uint32_t i = 0; i < period - 1; i++) {
|
||||
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
nHeight += 1;
|
||||
}
|
||||
// The next block should trigger no longer setting the bit.
|
||||
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
}
|
||||
|
||||
// On a new chain:
|
||||
@@ -390,31 +404,36 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens
|
||||
|
||||
// Mine one period worth of blocks, and check that the bit will be on for the
|
||||
// next period.
|
||||
lastBlock = secondChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
lastBlock = secondChain.Mine(period, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
|
||||
// Mine another period worth of blocks, signaling the new bit.
|
||||
lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip();
|
||||
lastBlock = secondChain.Mine(period * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip();
|
||||
// After one period of setting the bit on each block, it should have locked in.
|
||||
// We keep setting the bit for one more period though, until activation.
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
|
||||
// Now check that we keep mining the block until the end of this period, and
|
||||
// then stop at the beginning of the next period.
|
||||
lastBlock = secondChain.Mine((params.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
lastBlock = secondChain.Mine((period * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
lastBlock = secondChain.Mine(period * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
|
||||
if (lastBlock->nHeight + 1 < min_activation_height) {
|
||||
// check signalling continues while min_activation_height is not reached
|
||||
lastBlock = secondChain.Mine(min_activation_height - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
|
||||
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
// then reach min_activation_height, which was already REQUIRE'd to start a new period
|
||||
lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
|
||||
}
|
||||
|
||||
// Check that we don't signal after activation
|
||||
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
|
||||
BOOST_CHECK(versionbitscache.IsActiveAfter(lastBlock, params, dep));
|
||||
}
|
||||
}; // struct BlockVersionTest
|
||||
|
||||
@@ -434,7 +453,7 @@ BOOST_FIXTURE_TEST_CASE(versionbits_computeblockversion, BlockVersionTest)
|
||||
// not take precedence over STARTED/LOCKED_IN. So all softforks on
|
||||
// the same bit might overlap, even when non-overlapping start-end
|
||||
// times are picked.
|
||||
const uint32_t dep_mask{vbcache.Mask(chainParams->GetConsensus(), dep)};
|
||||
const uint32_t dep_mask{uint32_t{1} << chainParams->GetConsensus().vDeployments[dep].bit};
|
||||
BOOST_CHECK(!(chain_all_vbits & dep_mask));
|
||||
chain_all_vbits |= dep_mask;
|
||||
check_computeblockversion(vbcache, chainParams->GetConsensus(), dep);
|
||||
|
||||
Reference in New Issue
Block a user