#include <chain.h>
#include <chainparams.h>
#include <consensus/params.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <util/chaintype.h>
#include <versionbits.h>
#include <versionbits_impl.h>
#include <boost/test/unit_test.hpp>
static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; }
class TestConditionChecker final : public VersionBitsConditionChecker
{
private:
mutable ThresholdConditionCache cache;
public:
explicit(false) TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep} { }
~TestConditionChecker() override = default;
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(); }
};
namespace {
struct Deployments
{
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
class VersionBitsTester
{
FastRandomContext& m_rng;
std::vector<CBlockIndex*> vpblock;
const int32_t nVersionBase{0};
const Deployments test_deployments;
std::vector<TestConditionChecker> checker{CHECKERS, {test_deployments.normal}};
std::vector<TestConditionChecker> checker_delayed{CHECKERS, {test_deployments.delayed}};
std::vector<TestConditionChecker> checker_always{CHECKERS, {test_deployments.always}};
std::vector<TestConditionChecker> checker_never{CHECKERS, {test_deployments.never}};
int num{1000};
public:
explicit VersionBitsTester(FastRandomContext& rng, int32_t nVersionBase=0) : m_rng{rng}, nVersionBase{nVersionBase} { }
VersionBitsTester& Reset() {
num = num - (num % 1000) + 1000;
for (unsigned int i = 0; i < vpblock.size(); i++) {
delete vpblock[i];
}
for (unsigned int i = 0; i < CHECKERS; i++) {
checker[i].clear();
checker_delayed[i].clear();
checker_always[i].clear();
checker_never[i].clear();
}
vpblock.clear();
return *this;
}
~VersionBitsTester() {
Reset();
}
VersionBitsTester& Mine(unsigned int height, int32_t nTime, int32_t nVersion) {
while (vpblock.size() < height) {
CBlockIndex* pindex = new CBlockIndex();
pindex->nHeight = vpblock.size();
pindex->pprev = Tip();
pindex->nTime = nTime;
pindex->nVersion = (nVersionBase | nVersion);
pindex->BuildSkip();
vpblock.push_back(pindex);
}
return *this;
}
VersionBitsTester& TestStateSinceHeight(int height)
{
return TestStateSinceHeight(height, height);
}
VersionBitsTester& TestStateSinceHeight(int height, int height_delayed)
{
const CBlockIndex* tip = Tip();
for (int i = 0; i < CHECKERS; i++) {
if (m_rng.randbits(i) == 0) {
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++;
return *this;
}
VersionBitsTester& TestState(ThresholdState exp)
{
return TestState(exp, exp);
}
VersionBitsTester& TestState(ThresholdState exp, ThresholdState exp_delayed)
{
if (exp != exp_delayed) {
BOOST_CHECK_EQUAL(exp, ThresholdState::ACTIVE);
BOOST_CHECK_EQUAL(exp_delayed, ThresholdState::LOCKED_IN);
}
const CBlockIndex* pindex = Tip();
for (int i = 0; i < CHECKERS; i++) {
if (m_rng.randbits(i) == 0) {
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);
int height = pindex == nullptr ? 0 : pindex->nHeight + 1;
BOOST_CHECK_MESSAGE(got == exp, strprintf("Test %i for %s height %d (got %s)", num, StateName(exp), height, StateName(got)));
BOOST_CHECK_MESSAGE(got_delayed == exp_delayed, strprintf("Test %i for %s height %d (got %s; delayed case)", num, StateName(exp_delayed), height, StateName(got_delayed)));
BOOST_CHECK_MESSAGE(got_always == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE height %d (got %s; always active case)", num, height, StateName(got_always)));
BOOST_CHECK_MESSAGE(got_never == ThresholdState::FAILED, strprintf("Test %i for FAILED height %d (got %s; never active case)", num, height, StateName(got_never)));
}
}
num++;
return *this;
}
VersionBitsTester& TestDefined() { return TestState(ThresholdState::DEFINED); }
VersionBitsTester& TestStarted() { return TestState(ThresholdState::STARTED); }
VersionBitsTester& TestLockedIn() { return TestState(ThresholdState::LOCKED_IN); }
VersionBitsTester& TestActive() { return TestState(ThresholdState::ACTIVE); }
VersionBitsTester& TestFailed() { return TestState(ThresholdState::FAILED); }
VersionBitsTester& TestActiveDelayed() { return TestState(ThresholdState::ACTIVE, ThresholdState::LOCKED_IN); }
CBlockIndex* Tip() { return vpblock.empty() ? nullptr : vpblock.back(); }
};
BOOST_FIXTURE_TEST_SUITE(versionbits_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(versionbits_test)
{
for (int i = 0; i < 64; i++) {
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)
.Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0) .Mine(1000, TestTime(20000), 0).TestStarted().TestStateSinceHeight(1000) .Mine(1999, TestTime(30001), 0).TestStarted().TestStateSinceHeight(1000)
.Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(2000) .Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(2000)
.Mine(4000, TestTime(30006), 0x100).TestFailed().TestStateSinceHeight(2000)
.Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) .Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) .Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) .Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) .Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)
.Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) .Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) .Mine(3000, TestTime(30000), 0x100).TestLockedIn().TestStateSinceHeight(3000) .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
.Mine(14333, TestTime(30003), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
.Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
.Reset().TestDefined()
.Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) .Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) .Mine(2999, TestTime(19999), 0x200).TestStarted().TestStateSinceHeight(2000) .Mine(3000, TestTime(29999), 0x200).TestLockedIn().TestStateSinceHeight(3000) .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000) .Mine(14333, TestTime(30003), 0).TestActiveDelayed().TestStateSinceHeight(4000, 3000)
.Mine(15000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
.Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000, 15000)
.Reset().TestDefined().TestStateSinceHeight(0)
.Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0)
.Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0)
.Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5999, TestTime(20000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
.Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000)
.Mine(24000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000) ;
}
}
struct BlockVersionTest : BasicTestingSetup {
void check_computeblockversion(VersionBitsCache& versionbitscache, const Consensus::Params& params, Consensus::DeploymentPos dep)
{
versionbitscache.Clear();
int64_t bit = params.vDeployments[dep].bit;
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); BOOST_REQUIRE(0 < threshold); BOOST_REQUIRE(threshold < period);
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS);
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;
}
BOOST_REQUIRE(nStartTime < nTimeout);
BOOST_REQUIRE(nStartTime >= 0);
BOOST_REQUIRE(nTimeout <= std::numeric_limits<uint32_t>::max() || nTimeout == Consensus::BIP9Deployment::NO_TIMEOUT);
BOOST_REQUIRE(0 <= bit && bit < 32);
BOOST_REQUIRE(((1 << bit) & VERSIONBITS_TOP_MASK) == 0);
BOOST_REQUIRE(min_activation_height >= 0);
BOOST_REQUIRE_EQUAL(min_activation_height % period, 0U);
VersionBitsTester firstChain{m_rng}, secondChain{m_rng};
int64_t nTime = nStartTime;
const CBlockIndex *lastBlock = nullptr;
if (nTime == 0) {
lastBlock = firstChain.Mine(period - 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));
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));
} else {
--nTime;
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));
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));
}
nTime = nStartTime;
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));
}
}
lastBlock = firstChain.Mine(period * 3, 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));
nTime += 600;
uint32_t blocksToMine = period * 2; uint32_t nHeight = period * 3;
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;
}
if (nTimeout != Consensus::BIP9Deployment::NO_TIMEOUT) {
nTime = nTimeout;
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;
}
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;
}
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));
}
nTime = nStartTime;
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));
lastBlock = secondChain.Mine(period * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip();
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
BOOST_CHECK(!versionbitscache.IsActiveAfter(lastBlock, params, dep));
lastBlock = secondChain.Mine((period * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0);
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) {
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));
lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
}
BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0);
BOOST_CHECK(versionbitscache.IsActiveAfter(lastBlock, params, dep));
}
};
BOOST_FIXTURE_TEST_CASE(versionbits_computeblockversion, BlockVersionTest)
{
VersionBitsCache vbcache;
for (const auto& chain_type: {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST}) {
const auto chainParams = CreateChainParams(*m_node.args, chain_type);
uint32_t chain_all_vbits{0};
for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++i) {
const auto dep = static_cast<Consensus::DeploymentPos>(i);
const uint32_t dep_mask{uint32_t{1} << chainParams->GetConsensus().vDeployments[dep].bit};
if (chain_type != ChainType::REGTEST && dep == Consensus::DEPLOYMENT_CTV) {
continue;
}
BOOST_CHECK(!(chain_all_vbits & dep_mask));
chain_all_vbits |= dep_mask;
check_computeblockversion(vbcache, chainParams->GetConsensus(), dep);
}
}
{
ArgsManager args;
args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999"); const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY);
}
{
ArgsManager args;
args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999:403200"); const auto chainParams = CreateChainParams(args, ChainType::REGTEST);
check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY);
}
}
BOOST_AUTO_TEST_SUITE_END()