From 9bc41f1b48b2e0cc6abf9714e860a29989d7809c Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Thu, 7 Dec 2023 09:20:03 +1000 Subject: [PATCH 01/15] versionbits: Use std::array instead of C-style arrays --- src/consensus/params.h | 3 ++- src/deploymentinfo.cpp | 14 +++++++------- src/deploymentinfo.h | 3 ++- src/versionbits.h | 3 ++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/consensus/params.h b/src/consensus/params.h index dd29b9408e2..32f14c25e84 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -104,7 +105,7 @@ struct Params { */ uint32_t nRuleChangeActivationThreshold; uint32_t nMinerConfirmationWindow; - BIP9Deployment vDeployments[MAX_VERSION_BITS_DEPLOYMENTS]; + std::array vDeployments; /** Proof of work parameters */ uint256 powLimit; bool fPowAllowMinDifficultyBlocks; diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 185a7dcb54c..0931ec77d37 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -8,14 +8,14 @@ #include -const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { - { - /*.name =*/ "testdummy", - /*.gbt_force =*/ true, +const std::array VersionBitsDeploymentInfo{ + VBDeploymentInfo{ + .name = "testdummy", + .gbt_force = true, }, - { - /*.name =*/ "taproot", - /*.gbt_force =*/ true, + VBDeploymentInfo{ + .name = "taproot", + .gbt_force = true, }, }; diff --git a/src/deploymentinfo.h b/src/deploymentinfo.h index 72ba297ea0b..b6ca4450f41 100644 --- a/src/deploymentinfo.h +++ b/src/deploymentinfo.h @@ -7,6 +7,7 @@ #include +#include #include #include @@ -17,7 +18,7 @@ struct VBDeploymentInfo { bool gbt_force; }; -extern const VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; +extern const std::array VersionBitsDeploymentInfo; std::string DeploymentName(Consensus::BuriedDeployment dep); diff --git a/src/versionbits.h b/src/versionbits.h index 09313d2054a..8d2cd0f43d4 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -8,6 +8,7 @@ #include #include +#include #include /** What block version to use for new blocks (pre versionbits) */ @@ -81,7 +82,7 @@ class VersionBitsCache { private: Mutex m_mutex; - ThresholdConditionCache m_caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] GUARDED_BY(m_mutex); + std::array m_caches GUARDED_BY(m_mutex); public: /** Get the numerical statistics for a given deployment for the signalling period that includes pindex. From e9d617095d4ce9525a4337d33624cac9d6b4abe6 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Thu, 7 Dec 2023 10:17:28 +1000 Subject: [PATCH 02/15] versionbits: Remove params from AbstractThresholdConditionChecker For an abstract class, specifying parameters in detail serves no point; and for the concrete implementation, changing the consensus parameters between invocations doesn't make sense. So simplify the class by removing the consensus params from the method arguments, and just make it a member variable in the concrete object where needed. This also allows dropping dummy parameters from the unit/fuzz tests. --- src/test/fuzz/versionbits.cpp | 20 +++++------ src/test/versionbits_tests.cpp | 22 ++++++------ src/validation.cpp | 16 ++++----- src/versionbits.cpp | 61 +++++++++++++++++----------------- src/versionbits.h | 18 +++++----- 5 files changed, 66 insertions(+), 71 deletions(-) diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index c1b0f552ea0..f52c17cffc1 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -24,7 +24,6 @@ class TestConditionChecker : public AbstractThresholdConditionChecker { private: mutable ThresholdConditionCache m_cache; - const Consensus::Params dummy_params{}; public: const int64_t m_begin; @@ -43,24 +42,21 @@ public: 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; } + bool Condition(const CBlockIndex* pindex) const override { return Condition(pindex->nVersion); } + int64_t BeginTime() const override { return m_begin; } + int64_t EndTime() const override { return m_end; } + int Period() const override { return m_period; } + int Threshold() const override { return m_threshold; } + int MinActivationHeight() 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* signals=nullptr) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params, signals); } + ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, m_cache); } + int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, m_cache); } 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 */ diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 29240a45f09..a59dfdad40f 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -27,40 +27,38 @@ static std::string StateName(ThresholdState state) return ""; } -static const Consensus::Params paramsDummy = Consensus::Params(); - class TestConditionChecker : public AbstractThresholdConditionChecker { 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); } + int64_t BeginTime() const override { return TestTime(10000); } + int64_t EndTime() const override { return TestTime(20000); } + int Period() const override { return 1000; } + int Threshold() const override { return 900; } + bool Condition(const CBlockIndex* pindex) const override { return (pindex->nVersion & 0x100); } - 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 GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, cache); } + int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, cache); } }; class TestDelayedActivationConditionChecker : public TestConditionChecker { public: - int MinActivationHeight(const Consensus::Params& params) const override { return 15000; } + int MinActivationHeight() const override { return 15000; } }; class TestAlwaysActiveConditionChecker : public TestConditionChecker { public: - int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; } + int64_t BeginTime() 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; } + int64_t BeginTime() const override { return Consensus::BIP9Deployment::NEVER_ACTIVE; } }; #define CHECKERS 6 diff --git a/src/validation.cpp b/src/validation.cpp index 3f774fd0a1e..c9d5e866866 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2376,17 +2376,17 @@ private: public: explicit WarningBitsConditionChecker(const ChainstateManager& chainman, int bit) : m_chainman{chainman}, m_bit(bit) {} - int64_t BeginTime(const Consensus::Params& params) const override { return 0; } - int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits::max(); } - int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } - int Threshold(const Consensus::Params& params) const override { return params.nRuleChangeActivationThreshold; } + int64_t BeginTime() const override { return 0; } + int64_t EndTime() const override { return std::numeric_limits::max(); } + int Period() const override { return m_chainman.GetConsensus().nMinerConfirmationWindow; } + int Threshold() const override { return m_chainman.GetConsensus().nRuleChangeActivationThreshold; } - bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override + bool Condition(const CBlockIndex* pindex) const override { - return pindex->nHeight >= params.MinBIP9WarningHeight && + return pindex->nHeight >= m_chainman.GetConsensus().MinBIP9WarningHeight && ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && ((pindex->nVersion >> m_bit) & 1) != 0 && - ((m_chainman.m_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> m_bit) & 1) == 0; + ((m_chainman.m_versionbitscache.ComputeBlockVersion(pindex->pprev, m_chainman.GetConsensus()) >> m_bit) & 1) == 0; } }; @@ -3029,7 +3029,7 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew) const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(m_chainman, bit); - ThresholdState state = checker.GetStateFor(pindex, m_chainman.GetConsensus(), m_chainman.m_warningcache.at(bit)); + ThresholdState state = checker.GetStateFor(pindex, m_chainman.m_warningcache.at(bit)); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); if (state == ThresholdState::ACTIVE) { diff --git a/src/versionbits.cpp b/src/versionbits.cpp index fa9d1fe9c99..958bb76abbb 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -6,13 +6,13 @@ #include #include -ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const +ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const { - int nPeriod = Period(params); - int nThreshold = Threshold(params); - int min_activation_height = MinActivationHeight(params); - int64_t nTimeStart = BeginTime(params); - int64_t nTimeTimeout = EndTime(params); + int nPeriod = Period(); + int nThreshold = Threshold(); + int min_activation_height = MinActivationHeight(); + int64_t nTimeStart = BeginTime(); + int64_t nTimeTimeout = EndTime(); // Check if this deployment is always active. if (nTimeStart == Consensus::BIP9Deployment::ALWAYS_ACTIVE) { @@ -68,7 +68,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* const CBlockIndex* pindexCount = pindexPrev; int count = 0; for (int i = 0; i < nPeriod; i++) { - if (Condition(pindexCount, params)) { + if (Condition(pindexCount)) { count++; } pindexCount = pindexCount->pprev; @@ -99,12 +99,12 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* return state; } -BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, std::vector* signalling_blocks) const +BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, std::vector* signalling_blocks) const { BIP9Stats stats = {}; - stats.period = Period(params); - stats.threshold = Threshold(params); + stats.period = Period(); + stats.threshold = Threshold(); if (pindex == nullptr) return stats; @@ -123,7 +123,7 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI do { ++elapsed; --blocks_in_period; - if (Condition(currentIndex, params)) { + if (Condition(currentIndex)) { ++count; if (signalling_blocks) signalling_blocks->at(blocks_in_period) = true; } @@ -137,21 +137,21 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI return stats; } -int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const +int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const { - int64_t start_time = BeginTime(params); + int64_t start_time = BeginTime(); if (start_time == Consensus::BIP9Deployment::ALWAYS_ACTIVE || start_time == Consensus::BIP9Deployment::NEVER_ACTIVE) { return 0; } - const ThresholdState initialState = GetStateFor(pindexPrev, params, cache); + const ThresholdState initialState = GetStateFor(pindexPrev, cache); // BIP 9 about state DEFINED: "The genesis block is by definition in this state for each deployment." if (initialState == ThresholdState::DEFINED) { return 0; } - const int nPeriod = Period(params); + const int nPeriod = Period(); // A block's state is always the same as that of the first of its period, so it is computed based on a pindexPrev whose height equals a multiple of nPeriod - 1. // To ease understanding of the following height calculation, it helps to remember that @@ -163,7 +163,7 @@ int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* const CBlockIndex* previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); - while (previousPeriodParent != nullptr && GetStateFor(previousPeriodParent, params, cache) == initialState) { + while (previousPeriodParent != nullptr && GetStateFor(previousPeriodParent, cache) == initialState) { pindexPrev = previousPeriodParent; previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); } @@ -179,23 +179,24 @@ namespace */ class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { private: + const Consensus::Params& params; const Consensus::DeploymentPos id; protected: - int64_t BeginTime(const Consensus::Params& params) const override { return params.vDeployments[id].nStartTime; } - int64_t EndTime(const Consensus::Params& params) const override { return params.vDeployments[id].nTimeout; } - int MinActivationHeight(const Consensus::Params& params) const override { return params.vDeployments[id].min_activation_height; } - int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } - int Threshold(const Consensus::Params& params) const override { return params.nRuleChangeActivationThreshold; } + int64_t BeginTime() const override { return params.vDeployments[id].nStartTime; } + int64_t EndTime() const override { return params.vDeployments[id].nTimeout; } + int MinActivationHeight() const override { return params.vDeployments[id].min_activation_height; } + int Period() const override { return params.nMinerConfirmationWindow; } + int Threshold() const override { return params.nRuleChangeActivationThreshold; } - bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override + bool Condition(const CBlockIndex* pindex) const override { - return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask(params)) != 0); + return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask()) != 0); } public: - explicit VersionBitsConditionChecker(Consensus::DeploymentPos id_) : id(id_) {} - uint32_t Mask(const Consensus::Params& params) const { return (uint32_t{1}) << params.vDeployments[id].bit; } + explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : params{params}, id{id} {} + uint32_t Mask() const { return (uint32_t{1}) << params.vDeployments[id].bit; } }; } // namespace @@ -203,23 +204,23 @@ public: ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(m_mutex); - return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]); + return VersionBitsConditionChecker(params, pos).GetStateFor(pindexPrev, m_caches[pos]); } BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos, std::vector* signalling_blocks) { - return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindex, params, signalling_blocks); + return VersionBitsConditionChecker(params, pos).GetStateStatisticsFor(pindex, signalling_blocks); } int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(m_mutex); - return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, m_caches[pos]); + return VersionBitsConditionChecker(params, pos).GetStateSinceHeightFor(pindexPrev, m_caches[pos]); } uint32_t VersionBitsCache::Mask(const Consensus::Params& params, Consensus::DeploymentPos pos) { - return VersionBitsConditionChecker(pos).Mask(params); + return VersionBitsConditionChecker(params, pos).Mask(); } int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) @@ -229,7 +230,7 @@ int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, con for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { Consensus::DeploymentPos pos = static_cast(i); - ThresholdState state = VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]); + ThresholdState state = VersionBitsConditionChecker(params, pos).GetStateFor(pindexPrev, m_caches[pos]); if (state == ThresholdState::LOCKED_IN || state == ThresholdState::STARTED) { nVersion |= Mask(params, pos); } diff --git a/src/versionbits.h b/src/versionbits.h index 8d2cd0f43d4..2f334081ab4 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -57,23 +57,23 @@ struct BIP9Stats { */ class AbstractThresholdConditionChecker { protected: - virtual bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const =0; - virtual int64_t BeginTime(const Consensus::Params& params) const =0; - virtual int64_t EndTime(const Consensus::Params& params) const =0; - virtual int MinActivationHeight(const Consensus::Params& params) const { return 0; } - virtual int Period(const Consensus::Params& params) const =0; - virtual int Threshold(const Consensus::Params& params) const =0; + virtual bool Condition(const CBlockIndex* pindex) const =0; + virtual int64_t BeginTime() const =0; + virtual int64_t EndTime() const =0; + virtual int MinActivationHeight() const { return 0; } + virtual int Period() const =0; + virtual int Threshold() const =0; public: /** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex * If provided, signalling_blocks is set to true/false based on whether each block in the period signalled */ - BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, std::vector* signalling_blocks = nullptr) const; + BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, std::vector* signalling_blocks = nullptr) const; /** Returns the state for pindex A based on parent pindexPrev B. Applies any state transition if conditions are present. * Caches state from first block of period. */ - ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; + ThresholdState GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; /** Returns the height since when the ThresholdState has started for pindex A based on parent pindexPrev B, all blocks of a period share the same */ - int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; + int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; }; /** BIP 9 allows multiple softforks to be deployed in parallel. We cache From a679040ec19ef17f3f03988a52207f1c03af701e Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Thu, 7 Dec 2023 12:16:22 +1000 Subject: [PATCH 03/15] consensus/params: Move version bits period/threshold to bip9 param Rather than having the rule change period/threshold be constant for all potential deployments on a chain, have it be specific to the deployment itself. This both matches history (BIP 9 specified a 2016 block period and 1916 block threshold; BIP 91 specified a 336 block period and 269 block threshold; and BIP 341 specified a 2016 block period and 1815 block threshold), and allows the code to be simplified, as only the BIP9Deployment structure is needed, not the full Consensus::Params structure. --- src/consensus/params.h | 15 +++++++------ src/kernel/chainparams.cpp | 31 +++++++++++++++++--------- src/test/versionbits_tests.cpp | 40 +++++++++++++++++++--------------- src/validation.cpp | 16 ++++++++++++-- src/versionbits.cpp | 19 ++++++++-------- 5 files changed, 76 insertions(+), 45 deletions(-) diff --git a/src/consensus/params.h b/src/consensus/params.h index 32f14c25e84..6344349b866 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -53,6 +53,14 @@ struct BIP9Deployment { * boundary. */ int min_activation_height{0}; + /** Period of blocks to check signalling in (usually retarget period, ie params.DifficultyAdjustmentInterval()) */ + uint32_t period{2016}; + /** + * Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, + * which is also used for BIP9 deployments. + * Examples: 1916 for 95%, 1512 for testchains. + */ + uint32_t threshold{1916}; /** Constant for nTimeout very far in the future. */ static constexpr int64_t NO_TIMEOUT = std::numeric_limits::max(); @@ -98,13 +106,6 @@ struct Params { /** Don't warn about unknown BIP 9 activations below this height. * This prevents us from warning about the CSV and segwit activations. */ int MinBIP9WarningHeight; - /** - * Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, - * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. - * Examples: 1916 for 95%, 1512 for testchains. - */ - uint32_t nRuleChangeActivationThreshold; - uint32_t nMinerConfirmationWindow; std::array vDeployments; /** Proof of work parameters */ uint256 powLimit; diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 349e14a5d6e..7e1c31b1b50 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -104,18 +104,20 @@ public: consensus.fPowAllowMinDifficultyBlocks = false; consensus.enforce_BIP94 = false; consensus.fPowNoRetargeting = false; - consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016 - consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1815; // 90% + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016; // Deployment of Taproot (BIPs 340-342) consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1619222400; // April 24th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 709632; // Approximately November 12th, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 1815; // 90% + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 2016; consensus.nMinimumChainWork = uint256{"000000000000000000000000000000000000000088e186b70e0862c193ec44d6"}; consensus.defaultAssumeValid = uint256{"000000000000000000011c5890365bdbe5d25b97ce0057589acaef4f1a57263f"}; // 856760 @@ -228,18 +230,20 @@ public: consensus.fPowAllowMinDifficultyBlocks = true; consensus.enforce_BIP94 = false; consensus.fPowNoRetargeting = false; - consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains - consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1512; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016; // Deployment of Taproot (BIPs 340-342) consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1619222400; // April 24th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 1512; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 2016; consensus.nMinimumChainWork = uint256{"000000000000000000000000000000000000000000000f209695166be8b61fa9"}; consensus.defaultAssumeValid = uint256{"000000000000000465b1a66c9f386308e8c75acef9201f3f577811da09fc90ad"}; // 2873500 @@ -327,18 +331,21 @@ public: consensus.fPowAllowMinDifficultyBlocks = true; consensus.enforce_BIP94 = true; consensus.fPowNoRetargeting = false; - consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains - consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1512; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016; // Deployment of Taproot (BIPs 340-342) consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 1512; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 2016; consensus.nMinimumChainWork = uint256{"00000000000000000000000000000000000000000000005faa15d02e6202f3ba"}; consensus.defaultAssumeValid = uint256{"000000005be348057db991fa5d89fe7c4695b667cfb311391a8db374b6f681fd"}; // 39550 @@ -465,20 +472,22 @@ public: consensus.fPowAllowMinDifficultyBlocks = false; consensus.enforce_BIP94 = false; consensus.fPowNoRetargeting = false; - consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016 - consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256{"00000377ae000000000000000000000000000000000000000000000000000000"}; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 1815; // 90% + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 2016; // Activation of Taproot (BIPs 340-342) consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 1815; // 90% + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 2016; // message start is defined as the first 4 bytes of the sha256d of the block script HashWriter h{}; @@ -544,18 +553,20 @@ public: consensus.fPowAllowMinDifficultyBlocks = true; consensus.enforce_BIP94 = opts.enforce_bip94; consensus.fPowNoRetargeting = true; - consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains - consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016) consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].threshold = 108; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].period = 144; // Faster than normal for regtest (144 instead of 2016) consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].threshold = 108; // 75% + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].period = 144; consensus.nMinimumChainWork = uint256{}; consensus.defaultAssumeValid = uint256{}; diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index a59dfdad40f..19f75c57bb0 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -268,6 +268,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); @@ -289,7 +295,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); + BOOST_REQUIRE_EQUAL(min_activation_height % period, 0U); const uint32_t bitmask{versionbitscache.Mask(params, dep)}; BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit); @@ -309,9 +315,9 @@ 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(); + lastBlock = firstChain.Mine(period, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then we'll keep mining at nStartTime... } else { @@ -319,25 +325,25 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens --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); // 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); } // 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); } // 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. @@ -345,8 +351,8 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens // 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(); @@ -363,7 +369,7 @@ 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); nHeight += 1; @@ -371,7 +377,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens // 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); nHeight += 1; @@ -388,20 +394,20 @@ 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); // Mine another period worth of blocks, signaling the new bit. - lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<nHeight + 1 < min_activation_height) { // check signalling continues while min_activation_height is not reached diff --git a/src/validation.cpp b/src/validation.cpp index c9d5e866866..95bffd15a82 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2378,8 +2378,20 @@ public: int64_t BeginTime() const override { return 0; } int64_t EndTime() const override { return std::numeric_limits::max(); } - int Period() const override { return m_chainman.GetConsensus().nMinerConfirmationWindow; } - int Threshold() const override { return m_chainman.GetConsensus().nRuleChangeActivationThreshold; } + int Period() const override { + if (m_chainman.GetParams().IsTestChain()) { + return m_chainman.GetConsensus().DifficultyAdjustmentInterval(); + } else { + return 2016; + } + } + int Threshold() const override { + if (m_chainman.GetParams().IsTestChain()) { + return m_chainman.GetConsensus().DifficultyAdjustmentInterval() * 3 / 4; // 75% for test nets per BIP9 suggestion + } else { + return 1815; // 90% threshold used in BIP 341 + } + } bool Condition(const CBlockIndex* pindex) const override { diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 958bb76abbb..e986427bd65 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -179,15 +179,14 @@ namespace */ class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { private: - const Consensus::Params& params; - const Consensus::DeploymentPos id; + const Consensus::BIP9Deployment& dep; protected: - int64_t BeginTime() const override { return params.vDeployments[id].nStartTime; } - int64_t EndTime() const override { return params.vDeployments[id].nTimeout; } - int MinActivationHeight() const override { return params.vDeployments[id].min_activation_height; } - int Period() const override { return params.nMinerConfirmationWindow; } - int Threshold() const override { return params.nRuleChangeActivationThreshold; } + int64_t BeginTime() const override { return dep.nStartTime; } + int64_t EndTime() const override { return dep.nTimeout; } + int MinActivationHeight() const override { return dep.min_activation_height; } + int Period() const override { return dep.period; } + int Threshold() const override { return dep.threshold; } bool Condition(const CBlockIndex* pindex) const override { @@ -195,8 +194,10 @@ protected: } public: - explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : params{params}, id{id} {} - uint32_t Mask() const { return (uint32_t{1}) << params.vDeployments[id].bit; } + explicit VersionBitsConditionChecker(const Consensus::BIP9Deployment& dep) : dep{dep} {} + explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : VersionBitsConditionChecker{params.vDeployments[id]} {} + + uint32_t Mask() const { return (uint32_t{1}) << dep.bit; } }; } // namespace From 5da119e5d0e61f0b583f0fe21b9a00ee815a3e46 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 17 Dec 2023 19:02:26 +1000 Subject: [PATCH 04/15] versionbits: Change BIP9Stats to uint32_t types --- src/test/fuzz/versionbits.cpp | 10 +++++----- src/versionbits.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index f52c17cffc1..8b30d1796c0 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -117,11 +117,11 @@ 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); + const uint32_t 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 @@ -199,7 +199,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); } @@ -211,7 +211,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(); @@ -230,7 +230,7 @@ FUZZ_TARGET(versionbits, .init = initialize) 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; diff --git a/src/versionbits.h b/src/versionbits.h index 2f334081ab4..db76eadfa01 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -41,13 +41,13 @@ typedef std::map ThresholdConditionCache; /** Display status of an in-progress BIP9 softfork */ struct BIP9Stats { /** Length of blocks of the BIP9 signalling period */ - int period; + uint32_t period; /** Number of blocks with the version bit set required to activate the softfork */ - int threshold; + uint32_t threshold; /** Number of blocks elapsed since the beginning of the current period */ - int elapsed; + uint32_t elapsed; /** Number of blocks with the version bit set since the beginning of the current period */ - int count; + uint32_t count; /** False if there are not enough blocks left in this period to pass activation threshold */ bool possible; }; From 3bd32c20550e69688a4ff02409fb34b9a637b9c4 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Mon, 20 Jan 2025 12:51:46 +1000 Subject: [PATCH 05/15] versionbits: Move WarningBits logic from validation to versionbits --- src/validation.cpp | 56 +++++------------------------------- src/validation.h | 2 -- src/versionbits.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++--- src/versionbits.h | 8 ++++++ 4 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 95bffd15a82..ca7d252b0e2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2364,44 +2364,6 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } -/** - * Threshold condition checker that triggers when unknown versionbits are seen on the network. - */ -class WarningBitsConditionChecker : public AbstractThresholdConditionChecker -{ -private: - const ChainstateManager& m_chainman; - int m_bit; - -public: - explicit WarningBitsConditionChecker(const ChainstateManager& chainman, int bit) : m_chainman{chainman}, m_bit(bit) {} - - int64_t BeginTime() const override { return 0; } - int64_t EndTime() const override { return std::numeric_limits::max(); } - int Period() const override { - if (m_chainman.GetParams().IsTestChain()) { - return m_chainman.GetConsensus().DifficultyAdjustmentInterval(); - } else { - return 2016; - } - } - int Threshold() const override { - if (m_chainman.GetParams().IsTestChain()) { - return m_chainman.GetConsensus().DifficultyAdjustmentInterval() * 3 / 4; // 75% for test nets per BIP9 suggestion - } else { - return 1815; // 90% threshold used in BIP 341 - } - } - - bool Condition(const CBlockIndex* pindex) const override - { - return pindex->nHeight >= m_chainman.GetConsensus().MinBIP9WarningHeight && - ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && - ((pindex->nVersion >> m_bit) & 1) != 0 && - ((m_chainman.m_versionbitscache.ComputeBlockVersion(pindex->pprev, m_chainman.GetConsensus()) >> m_bit) & 1) == 0; - } -}; - static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const ChainstateManager& chainman) { const Consensus::Params& consensusparams = chainman.GetConsensus(); @@ -3038,17 +3000,13 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew) std::vector warning_messages; if (!m_chainman.IsInitialBlockDownload()) { - const CBlockIndex* pindex = pindexNew; - for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { - WarningBitsConditionChecker checker(m_chainman, bit); - ThresholdState state = checker.GetStateFor(pindex, m_chainman.m_warningcache.at(bit)); - if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { - const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); - if (state == ThresholdState::ACTIVE) { - m_chainman.GetNotifications().warningSet(kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED, warning); - } else { - warning_messages.push_back(warning); - } + auto bits = m_chainman.m_versionbitscache.CheckUnknownActivations(pindexNew, m_chainman.GetParams()); + for (auto [bit, active] : bits) { + const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); + if (active) { + m_chainman.GetNotifications().warningSet(kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED, warning); + } else { + warning_messages.push_back(warning); } } } diff --git a/src/validation.h b/src/validation.h index e2ff5925c58..68b23caa8dd 100644 --- a/src/validation.h +++ b/src/validation.h @@ -935,8 +935,6 @@ private: /** Most recent headers presync progress update, for rate-limiting. */ std::chrono::time_point m_last_presync_update GUARDED_BY(::cs_main) {}; - std::array m_warningcache GUARDED_BY(::cs_main); - //! Return true if a chainstate is considered usable. //! //! This is false when a background validation chainstate has completed its diff --git a/src/versionbits.cpp b/src/versionbits.cpp index e986427bd65..6869189879c 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -3,9 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include +using enum ThresholdState; + ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const { int nPeriod = Period(); @@ -224,22 +227,28 @@ uint32_t VersionBitsCache::Mask(const Consensus::Params& params, Consensus::Depl return VersionBitsConditionChecker(params, pos).Mask(); } -int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) +static int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params, std::array& caches) { - LOCK(m_mutex); int32_t nVersion = VERSIONBITS_TOP_BITS; for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { Consensus::DeploymentPos pos = static_cast(i); - ThresholdState state = VersionBitsConditionChecker(params, pos).GetStateFor(pindexPrev, m_caches[pos]); + VersionBitsConditionChecker checker(params, pos); + ThresholdState state = checker.GetStateFor(pindexPrev, caches[pos]); if (state == ThresholdState::LOCKED_IN || state == ThresholdState::STARTED) { - nVersion |= Mask(params, pos); + nVersion |= checker.Mask(); } } return nVersion; } +int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) +{ + LOCK(m_mutex); + return ::ComputeBlockVersion(pindexPrev, params, m_caches); +} + void VersionBitsCache::Clear() { LOCK(m_mutex); @@ -247,3 +256,55 @@ void VersionBitsCache::Clear() m_caches[d].clear(); } } + +namespace { +/** + * Threshold condition checker that triggers when unknown versionbits are seen on the network. + */ +class WarningBitsConditionChecker : public AbstractThresholdConditionChecker +{ +private: + const Consensus::Params& m_params; + std::array& m_caches; + int m_bit; + int period{2016}; + int threshold{1815}; // 90% threshold used in BIP 341 + +public: + explicit WarningBitsConditionChecker(const CChainParams& chainparams, std::array& caches, int bit) + : m_params{chainparams.GetConsensus()}, m_caches{caches}, m_bit(bit) + { + if (chainparams.IsTestChain()) { + period = chainparams.GetConsensus().DifficultyAdjustmentInterval(); + threshold = period * 3 / 4; // 75% for test nets per BIP9 suggestion + } + } + + int64_t BeginTime() const override { return 0; } + int64_t EndTime() const override { return std::numeric_limits::max(); } + int Period() const override { return period; } + int Threshold() const override { return threshold; } + + bool Condition(const CBlockIndex* pindex) const override + { + return pindex->nHeight >= m_params.MinBIP9WarningHeight && + ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && + ((pindex->nVersion >> m_bit) & 1) != 0 && + ((::ComputeBlockVersion(pindex->pprev, m_params, m_caches) >> m_bit) & 1) == 0; + } +}; +} // anonymous namespace + +std::vector> VersionBitsCache::CheckUnknownActivations(const CBlockIndex* pindex, const CChainParams& chainparams) +{ + LOCK(m_mutex); + std::vector> result; + for (int bit = 0; bit < VERSIONBITS_NUM_BITS; ++bit) { + WarningBitsConditionChecker checker(chainparams, m_caches, bit); + ThresholdState state = checker.GetStateFor(pindex, m_warning_caches.at(bit)); + if (state == ACTIVE || state == LOCKED_IN) { + result.emplace_back(bit, state == ACTIVE); + } + } + return result; +} diff --git a/src/versionbits.h b/src/versionbits.h index db76eadfa01..d61bd5f226c 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -11,6 +11,8 @@ #include #include +class CChainParams; + /** What block version to use for new blocks (pre versionbits) */ static const int32_t VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4; /** What bits to set in version for versionbits blocks */ @@ -82,6 +84,7 @@ class VersionBitsCache { private: Mutex m_mutex; + std::array m_warning_caches GUARDED_BY(m_mutex); std::array m_caches GUARDED_BY(m_mutex); public: @@ -102,6 +105,11 @@ public: */ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Check for unknown activations + * Returns a vector containing the bit number used for signalling and a bool + * indicating the deployment is likely to be ACTIVE, rather than merely LOCKED_IN. */ + std::vector> CheckUnknownActivations(const CBlockIndex* pindex, const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); }; From b1e967c3ec92738affb22d3b58483ebcdd8dfea2 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Fri, 8 Dec 2023 14:20:19 +1000 Subject: [PATCH 06/15] versionbits: Move getdeploymentinfo logic to versionbits Rather than having the RPC code have knowledge about how BIP9 is implemented, create a reporting function in the versionbits code, and limit the RPC code to coverting the result of that into Univalue/JSON. --- src/rpc/blockchain.cpp | 66 ++++++++++++++++-------------------------- src/versionbits.cpp | 41 ++++++++++++++++++++++++++ src/versionbits.h | 24 +++++++++++---- 3 files changed, 85 insertions(+), 46 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 89e01bbf7fe..69204b3184c 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1215,58 +1215,41 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id) { // For BIP9 deployments. - if (!DeploymentEnabled(chainman, id)) return; if (blockindex == nullptr) return; - auto get_state_name = [](const ThresholdState state) -> std::string { - 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"; - } - return "invalid"; - }; - UniValue bip9(UniValue::VOBJ); - - const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id); - const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id); - - const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state); + BIP9Info info{chainman.m_versionbitscache.Info(*blockindex, chainman.GetConsensus(), id)}; + const auto& depparams{chainman.GetConsensus().vDeployments[id]}; // BIP9 parameters - if (has_signal) { - bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit); + if (info.stats.has_value()) { + bip9.pushKV("bit", depparams.bit); } - bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime); - bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout); - bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height); + bip9.pushKV("start_time", depparams.nStartTime); + bip9.pushKV("timeout", depparams.nTimeout); + bip9.pushKV("min_activation_height", depparams.min_activation_height); // BIP9 status - bip9.pushKV("status", get_state_name(current_state)); - bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id)); - bip9.pushKV("status_next", get_state_name(next_state)); + bip9.pushKV("status", info.current_state); + bip9.pushKV("since", info.since); + bip9.pushKV("status_next", info.next_state); // BIP9 signalling status, if applicable - if (has_signal) { + if (info.stats.has_value()) { UniValue statsUV(UniValue::VOBJ); - std::vector signals; - BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals); - statsUV.pushKV("period", statsStruct.period); - statsUV.pushKV("elapsed", statsStruct.elapsed); - statsUV.pushKV("count", statsStruct.count); - if (ThresholdState::LOCKED_IN != current_state) { - statsUV.pushKV("threshold", statsStruct.threshold); - statsUV.pushKV("possible", statsStruct.possible); + statsUV.pushKV("period", info.stats->period); + statsUV.pushKV("elapsed", info.stats->elapsed); + statsUV.pushKV("count", info.stats->count); + if (info.stats->threshold > 0 || info.stats->possible) { + statsUV.pushKV("threshold", info.stats->threshold); + statsUV.pushKV("possible", info.stats->possible); } bip9.pushKV("statistics", std::move(statsUV)); std::string sig; - sig.reserve(signals.size()); - for (const bool s : signals) { + sig.reserve(info.signalling_blocks.size()); + for (const bool s : info.signalling_blocks) { sig.push_back(s ? '#' : '-'); } bip9.pushKV("signalling", sig); @@ -1274,12 +1257,13 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); - if (ThresholdState::ACTIVE == next_state) { - rv.pushKV("height", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id)); + bool is_active = false; + if (info.active_since.has_value()) { + rv.pushKV("height", *info.active_since); + is_active = (*info.active_since <= blockindex->nHeight + 1); } - rv.pushKV("active", ThresholdState::ACTIVE == next_state); - rv.pushKV("bip9", std::move(bip9)); - + rv.pushKV("active", is_active); + rv.pushKV("bip9", bip9); softforks.pushKV(DeploymentName(id), std::move(rv)); } diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 6869189879c..f54f8f6a787 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -9,6 +9,18 @@ using enum ThresholdState; +static std::string StateName(ThresholdState state) +{ + switch (state) { + case DEFINED: return "defined"; + case STARTED: return "started"; + case LOCKED_IN: return "locked_in"; + case ACTIVE: return "active"; + case FAILED: return "failed"; + } + return "invalid"; +} + ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const { int nPeriod = Period(); @@ -205,6 +217,35 @@ public: } // namespace +BIP9Info VersionBitsCache::Info(const CBlockIndex& block_index, const Consensus::Params& params, Consensus::DeploymentPos id) +{ + BIP9Info result; + + const auto current_state = State(block_index.pprev, params, id); + result.current_state = StateName(current_state); + result.since = StateSinceHeight(block_index.pprev, params, id); + + const auto next_state = State(&block_index, params, id); + result.next_state = StateName(next_state); + + const bool has_signal = (STARTED == current_state || LOCKED_IN == current_state); + if (has_signal) { + result.stats.emplace(Statistics(&block_index, params, id, &result.signalling_blocks)); + if (LOCKED_IN == current_state) { + result.stats->threshold = 0; + result.stats->possible = false; + } + } + + if (current_state == ACTIVE) { + result.active_since = result.since; + } else if (next_state == ACTIVE) { + result.active_since = block_index.nHeight + 1; + } + + return result; +} + ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(m_mutex); diff --git a/src/versionbits.h b/src/versionbits.h index d61bd5f226c..b435d313821 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -10,6 +10,8 @@ #include #include +#include +#include class CChainParams; @@ -43,15 +45,25 @@ typedef std::map ThresholdConditionCache; /** Display status of an in-progress BIP9 softfork */ struct BIP9Stats { /** Length of blocks of the BIP9 signalling period */ - uint32_t period; + uint32_t period{0}; /** Number of blocks with the version bit set required to activate the softfork */ - uint32_t threshold; + uint32_t threshold{0}; /** Number of blocks elapsed since the beginning of the current period */ - uint32_t elapsed; + uint32_t elapsed{0}; /** Number of blocks with the version bit set since the beginning of the current period */ - uint32_t count; + uint32_t count{0}; /** False if there are not enough blocks left in this period to pass activation threshold */ - bool possible; + bool possible{false}; +}; + +/** Detailed status of an enabled BIP9 deployment */ +struct BIP9Info { + int since{0}; + std::string current_state{}; + std::string next_state{}; + std::optional stats; + std::vector signalling_blocks; + std::optional active_since; }; /** @@ -95,6 +107,8 @@ public: static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); + BIP9Info Info(const CBlockIndex& block_index, const Consensus::Params& params, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); From 1198e7d2fd665bf2bc49fd26773d4fd5fbc2b716 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sat, 9 Dec 2023 08:09:36 +1000 Subject: [PATCH 07/15] versionbits: Move BIP9 status logic for getblocktemplate to versionbits Rather than having the RPC code have knowledge about how BIP9 is implemented, create a reporting function in the versionbits code, and limit the RPC code to coverting the result of that into the appropriate output for getblocktemplate. --- src/rpc/mining.cpp | 70 +++++++++++++++++++-------------------------- src/versionbits.cpp | 32 +++++++++++++++++++++ src/versionbits.h | 11 +++++++ 3 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 3b05f84eee4..4bd4e1d4737 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -565,10 +565,10 @@ static UniValue BIP22ValidationResult(const BlockValidationState& state) return "valid?"; } -static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { - const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos]; - std::string s = vbinfo.name; - if (!vbinfo.gbt_force) { +static std::string gbt_force_name(const std::string& name, bool gbt_force) +{ + std::string s{name}; + if (!gbt_force) { s.insert(s.begin(), '!'); } return s; @@ -898,45 +898,33 @@ static RPCHelpMan getblocktemplate() } UniValue vbavailable(UniValue::VOBJ); - for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { - Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); - ThresholdState state = chainman.m_versionbitscache.State(pindexPrev, consensusParams, pos); - switch (state) { - case ThresholdState::DEFINED: - case ThresholdState::FAILED: - // Not exposed to GBT at all - break; - case ThresholdState::LOCKED_IN: - // Ensure bit is set in block version - block.nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); - [[fallthrough]]; - case ThresholdState::STARTED: - { - const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos]; - vbavailable.pushKV(gbt_vb_name(pos), consensusParams.vDeployments[pos].bit); - if (setClientRules.find(vbinfo.name) == setClientRules.end()) { - if (!vbinfo.gbt_force) { - // If the client doesn't support this, don't indicate it in the [default] version - block.nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); - } - } - break; - } - case ThresholdState::ACTIVE: - { - // Add to rules only - const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos]; - aRules.push_back(gbt_vb_name(pos)); - if (setClientRules.find(vbinfo.name) == setClientRules.end()) { - // Not supported by the client; make sure it's safe to proceed - if (!vbinfo.gbt_force) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Support for '%s' rule requires explicit client support", vbinfo.name)); - } - } - break; - } + const auto gbtstatus = chainman.m_versionbitscache.GBTStatus(*pindexPrev, consensusParams); + + for (const auto& [name, info] : gbtstatus.signalling) { + vbavailable.pushKV(gbt_force_name(name, info.gbt_force), info.bit); + if (!info.gbt_force && !setClientRules.count(name)) { + // If the client doesn't support this, don't indicate it in the [default] version + block.nVersion &= ~info.mask; } } + + for (const auto& [name, info] : gbtstatus.locked_in) { + block.nVersion |= info.mask; + vbavailable.pushKV(gbt_force_name(name, info.gbt_force), info.bit); + if (!info.gbt_force && !setClientRules.count(name)) { + // If the client doesn't support this, don't indicate it in the [default] version + block.nVersion &= ~info.mask; + } + } + + for (const auto& [name, info] : gbtstatus.active) { + aRules.push_back(gbt_force_name(name, info.gbt_force)); + if (!info.gbt_force && !setClientRules.count(name)) { + // Not supported by the client; make sure it's safe to proceed + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Support for '%s' rule requires explicit client support", name)); + } + } + result.pushKV("version", block.nVersion); result.pushKV("rules", std::move(aRules)); result.pushKV("vbavailable", std::move(vbavailable)); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index f54f8f6a787..0b416884a67 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -246,6 +247,37 @@ BIP9Info VersionBitsCache::Info(const CBlockIndex& block_index, const Consensus: return result; } +BIP9GBTStatus VersionBitsCache::GBTStatus(const CBlockIndex& block_index, const Consensus::Params& params) +{ + BIP9GBTStatus result; + + LOCK(m_mutex); + for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { + auto pos = static_cast(i); + VersionBitsConditionChecker checker(params, pos); + ThresholdState state = checker.GetStateFor(&block_index, m_caches[pos]); + const VBDeploymentInfo& vbdepinfo = VersionBitsDeploymentInfo[pos]; + BIP9GBTStatus::Info gbtinfo{.bit=params.vDeployments[pos].bit, .mask=checker.Mask(), .gbt_force=vbdepinfo.gbt_force}; + + switch (state) { + case DEFINED: + case FAILED: + // Not exposed to GBT + break; + case STARTED: + result.signalling.try_emplace(vbdepinfo.name, gbtinfo); + break; + case LOCKED_IN: + result.locked_in.try_emplace(vbdepinfo.name, gbtinfo); + break; + case ACTIVE: + result.active.try_emplace(vbdepinfo.name, gbtinfo); + break; + } + } + return result; +} + ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(m_mutex); diff --git a/src/versionbits.h b/src/versionbits.h index b435d313821..c9fefad9693 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -66,6 +66,15 @@ struct BIP9Info { std::optional active_since; }; +struct BIP9GBTStatus { + struct Info { + int bit; + uint32_t mask; + bool gbt_force; + }; + std::map> signalling, locked_in, active; +}; + /** * Abstract class that implements BIP9-style threshold logic, and caches results. */ @@ -109,6 +118,8 @@ public: BIP9Info Info(const CBlockIndex& block_index, const Consensus::Params& params, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + BIP9GBTStatus GBTStatus(const CBlockIndex& block_index, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); From 37b9b67a39554465104c9cf1a74690f40019dbad Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sat, 9 Dec 2023 10:13:38 +1000 Subject: [PATCH 08/15] versionbits: Simplify VersionBitsCache API Replaces State() (which returned ACTIVE/STARTED/etc) with IsActiveAfter() which just returns a bool, as this was all State was actually used for. Drops Mask(), which was only used in tests and can be replaced with `1<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); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 0b416884a67..8cd5815bcec 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -222,16 +222,23 @@ BIP9Info VersionBitsCache::Info(const CBlockIndex& block_index, const Consensus: { BIP9Info result; - const auto current_state = State(block_index.pprev, params, id); - result.current_state = StateName(current_state); - result.since = StateSinceHeight(block_index.pprev, params, id); + VersionBitsConditionChecker checker(params, id); - const auto next_state = State(&block_index, params, id); + ThresholdState current_state, next_state; + + { + LOCK(m_mutex); + current_state = checker.GetStateFor(block_index.pprev, m_caches[id]); + next_state = checker.GetStateFor(&block_index, m_caches[id]); + result.since = checker.GetStateSinceHeightFor(block_index.pprev, m_caches[id]); + } + + result.current_state = StateName(current_state); result.next_state = StateName(next_state); const bool has_signal = (STARTED == current_state || LOCKED_IN == current_state); if (has_signal) { - result.stats.emplace(Statistics(&block_index, params, id, &result.signalling_blocks)); + result.stats.emplace(checker.GetStateStatisticsFor(&block_index, &result.signalling_blocks)); if (LOCKED_IN == current_state) { result.stats->threshold = 0; result.stats->possible = false; @@ -278,26 +285,10 @@ BIP9GBTStatus VersionBitsCache::GBTStatus(const CBlockIndex& block_index, const return result; } -ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) +bool VersionBitsCache::IsActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(m_mutex); - return VersionBitsConditionChecker(params, pos).GetStateFor(pindexPrev, m_caches[pos]); -} - -BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos, std::vector* signalling_blocks) -{ - return VersionBitsConditionChecker(params, pos).GetStateStatisticsFor(pindex, signalling_blocks); -} - -int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(m_mutex); - return VersionBitsConditionChecker(params, pos).GetStateSinceHeightFor(pindexPrev, m_caches[pos]); -} - -uint32_t VersionBitsCache::Mask(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - return VersionBitsConditionChecker(params, pos).Mask(); + return ThresholdState::ACTIVE == VersionBitsConditionChecker(params, pos).GetStateFor(pindexPrev, m_caches[pos]); } static int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params, std::array& caches) diff --git a/src/versionbits.h b/src/versionbits.h index c9fefad9693..732e073228a 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -109,25 +109,14 @@ private: std::array m_caches GUARDED_BY(m_mutex); public: - /** Get the numerical statistics for a given deployment for the signalling period that includes pindex. - * If provided, signalling_blocks is set to true/false based on whether each block in the period signalled - */ - static BIP9Stats Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos, std::vector* signalling_blocks = nullptr); - - static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); - BIP9Info Info(const CBlockIndex& block_index, const Consensus::Params& params, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); BIP9GBTStatus GBTStatus(const CBlockIndex& block_index, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ - ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + bool IsActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - /** Get the block height at which the BIP9 deployment switched into the state for the block after pindexPrev. */ - int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - - /** Determine what nVersion a new block should use - */ + /** Determine what nVersion a new block should use */ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Check for unknown activations From d00d1ed52c8ee95eeed665d68d6715a694bd4c1f Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sat, 9 Dec 2023 10:14:07 +1000 Subject: [PATCH 09/15] versionbits: Split out internal details into impl header --- src/test/fuzz/versionbits.cpp | 1 + src/test/versionbits_tests.cpp | 1 + src/versionbits.cpp | 1 + src/versionbits.h | 38 +------------------ src/versionbits_impl.h | 49 +++++++++++++++++++++++++ test/lint/lint-circular-dependencies.py | 1 + 6 files changed, 55 insertions(+), 36 deletions(-) create mode 100644 src/versionbits_impl.h diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index 8b30d1796c0..10c88fc7872 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 19cb3a27547..be9aa0ee884 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 8cd5815bcec..22d206f9af4 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using enum ThresholdState; diff --git a/src/versionbits.h b/src/versionbits.h index 732e073228a..2bb64acfa24 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -24,18 +24,8 @@ static const int32_t VERSIONBITS_TOP_MASK = 0xE0000000UL; /** Total bits available for versionbits */ static const int32_t VERSIONBITS_NUM_BITS = 29; -/** BIP 9 defines a finite-state-machine to deploy a softfork in multiple stages. - * State transitions happen during retarget period if conditions are met - * In case of reorg, transitions can go backward. Without transition, state is - * inherited between periods. All blocks of a period share the same state. - */ -enum class ThresholdState { - DEFINED, // First state that each softfork starts out as. The genesis block is by definition in this state for each deployment. - STARTED, // For blocks past the starttime. - LOCKED_IN, // For at least one retarget period after the first retarget period with STARTED blocks of which at least threshold have the associated bit set in nVersion, until min_activation_height is reached. - ACTIVE, // For all blocks after the LOCKED_IN retarget period (final state) - FAILED, // For all blocks once the first retarget period after the timeout time is hit, if LOCKED_IN wasn't already reached (final state) -}; +/** Opaque type for BIP9 state. See versionbits_impl.h for details. */ +enum class ThresholdState : uint8_t; // A map that gives the state for blocks whose height is a multiple of Period(). // The map is indexed by the block's parent, however, so all keys in the map @@ -75,30 +65,6 @@ struct BIP9GBTStatus { std::map> signalling, locked_in, active; }; -/** - * Abstract class that implements BIP9-style threshold logic, and caches results. - */ -class AbstractThresholdConditionChecker { -protected: - virtual bool Condition(const CBlockIndex* pindex) const =0; - virtual int64_t BeginTime() const =0; - virtual int64_t EndTime() const =0; - virtual int MinActivationHeight() const { return 0; } - virtual int Period() const =0; - virtual int Threshold() const =0; - -public: - /** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex - * If provided, signalling_blocks is set to true/false based on whether each block in the period signalled - */ - BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, std::vector* signalling_blocks = nullptr) const; - /** Returns the state for pindex A based on parent pindexPrev B. Applies any state transition if conditions are present. - * Caches state from first block of period. */ - ThresholdState GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; - /** Returns the height since when the ThresholdState has started for pindex A based on parent pindexPrev B, all blocks of a period share the same */ - int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; -}; - /** BIP 9 allows multiple softforks to be deployed in parallel. We cache * per-period state for every one of them. */ class VersionBitsCache diff --git a/src/versionbits_impl.h b/src/versionbits_impl.h new file mode 100644 index 00000000000..686f0e616cc --- /dev/null +++ b/src/versionbits_impl.h @@ -0,0 +1,49 @@ +// Copyright (c) 2016-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_VERSIONBITS_IMPL_H +#define BITCOIN_VERSIONBITS_IMPL_H + +#include +#include +#include + +/** BIP 9 defines a finite-state-machine to deploy a softfork in multiple stages. + * State transitions happen during retarget period if conditions are met + * In case of reorg, transitions can go backward. Without transition, state is + * inherited between periods. All blocks of a period share the same state. + */ +enum class ThresholdState : uint8_t { + DEFINED, // First state that each softfork starts out as. The genesis block is by definition in this state for each deployment. + STARTED, // For blocks past the starttime. + LOCKED_IN, // For at least one retarget period after the first retarget period with STARTED blocks of which at least threshold have the associated bit set in nVersion, until min_activation_height is reached. + ACTIVE, // For all blocks after the LOCKED_IN retarget period (final state) + FAILED, // For all blocks once the first retarget period after the timeout time is hit, if LOCKED_IN wasn't already reached (final state) +}; + +/** + * Abstract class that implements BIP9-style threshold logic, and caches results. + */ +class AbstractThresholdConditionChecker { +protected: + virtual bool Condition(const CBlockIndex* pindex) const =0; + virtual int64_t BeginTime() const =0; + virtual int64_t EndTime() const =0; + virtual int MinActivationHeight() const { return 0; } + virtual int Period() const =0; + virtual int Threshold() const =0; + +public: + /** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex + * If provided, signalling_blocks is set to true/false based on whether each block in the period signalled + */ + BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, std::vector* signalling_blocks = nullptr) const; + /** Returns the state for pindex A based on parent pindexPrev B. Applies any state transition if conditions are present. + * Caches state from first block of period. */ + ThresholdState GetStateFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; + /** Returns the height since when the ThresholdState has started for pindex A based on parent pindexPrev B, all blocks of a period share the same */ + int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; +}; + +#endif // BITCOIN_VERSIONBITS_IMPL_H diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 6f9a633807a..9554e560650 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -21,6 +21,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES = ( "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel", "wallet/wallet -> wallet/walletdb -> wallet/wallet", "kernel/coinstats -> validation -> kernel/coinstats", + "versionbits -> versionbits_impl -> versionbits", # Temporary, removed in followup https://github.com/bitcoin/bitcoin/pull/24230 "index/base -> node/context -> net_processing -> index/blockfilterindex -> index/base", From e74a7049b477d1853191ded75fdf25024a6e233f Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sat, 9 Dec 2023 10:15:15 +1000 Subject: [PATCH 10/15] versionbits: Expose StateName function Rather than essentially duplicating StateName in the unit tests, expose it via the impl header. --- src/test/versionbits_tests.cpp | 12 ------------ src/versionbits.cpp | 2 +- src/versionbits_impl.h | 3 +++ 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index be9aa0ee884..bace58da9f6 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -16,18 +16,6 @@ /* 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 ""; -} - class TestConditionChecker : public AbstractThresholdConditionChecker { private: diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 22d206f9af4..62c0e754b96 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -11,7 +11,7 @@ using enum ThresholdState; -static std::string StateName(ThresholdState state) +std::string StateName(ThresholdState state) { switch (state) { case DEFINED: return "defined"; diff --git a/src/versionbits_impl.h b/src/versionbits_impl.h index 686f0e616cc..b2526a0b4ec 100644 --- a/src/versionbits_impl.h +++ b/src/versionbits_impl.h @@ -22,6 +22,9 @@ enum class ThresholdState : uint8_t { FAILED, // For all blocks once the first retarget period after the timeout time is hit, if LOCKED_IN wasn't already reached (final state) }; +/** Get a string with the state name */ +std::string StateName(ThresholdState state); + /** * Abstract class that implements BIP9-style threshold logic, and caches results. */ From 525c00f91bb27d0f2a1b2e5532aebec7fac97d3a Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 17 Dec 2023 18:44:58 +1000 Subject: [PATCH 11/15] versionbits: Expose VersionBitsConditionChecker via impl header --- src/versionbits.cpp | 30 ------------------------------ src/versionbits_impl.h | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 62c0e754b96..0aac33ac6f3 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -189,36 +189,6 @@ int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* return pindexPrev->nHeight + 1; } -namespace -{ -/** - * Class to implement versionbits logic. - */ -class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { -private: - const Consensus::BIP9Deployment& dep; - -protected: - int64_t BeginTime() const override { return dep.nStartTime; } - int64_t EndTime() const override { return dep.nTimeout; } - int MinActivationHeight() const override { return dep.min_activation_height; } - int Period() const override { return dep.period; } - int Threshold() const override { return dep.threshold; } - - bool Condition(const CBlockIndex* pindex) const override - { - return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask()) != 0); - } - -public: - explicit VersionBitsConditionChecker(const Consensus::BIP9Deployment& dep) : dep{dep} {} - explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : VersionBitsConditionChecker{params.vDeployments[id]} {} - - uint32_t Mask() const { return (uint32_t{1}) << dep.bit; } -}; - -} // namespace - BIP9Info VersionBitsCache::Info(const CBlockIndex& block_index, const Consensus::Params& params, Consensus::DeploymentPos id) { BIP9Info result; diff --git a/src/versionbits_impl.h b/src/versionbits_impl.h index b2526a0b4ec..a2a62d77ba2 100644 --- a/src/versionbits_impl.h +++ b/src/versionbits_impl.h @@ -49,4 +49,30 @@ public: int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, ThresholdConditionCache& cache) const; }; +/** + * Class to implement versionbits logic. + */ +class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { +private: + const Consensus::BIP9Deployment& dep; + +protected: + int64_t BeginTime() const override { return dep.nStartTime; } + int64_t EndTime() const override { return dep.nTimeout; } + int MinActivationHeight() const override { return dep.min_activation_height; } + int Period() const override { return dep.period; } + int Threshold() const override { return dep.threshold; } + + bool Condition(const CBlockIndex* pindex) const override + { + return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask()) != 0); + } + +public: + explicit VersionBitsConditionChecker(const Consensus::BIP9Deployment& dep) : dep{dep} {} + explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : VersionBitsConditionChecker{params.vDeployments[id]} {} + + uint32_t Mask() const { return (uint32_t{1}) << dep.bit; } +}; + #endif // BITCOIN_VERSIONBITS_IMPL_H From 2e4e9b9608c722aaf767638e9dba498d8dc3e772 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 17 Dec 2023 21:12:32 +1000 Subject: [PATCH 12/15] tests: refactor versionbits unit test Base the unit test directly on `VersionBitsConditionChecker`, slightly improving coverage, in particular adding coverage for the the logic regarding setting the TOP_BITS. --- src/test/versionbits_tests.cpp | 92 ++++++++++++++++++---------------- src/versionbits_impl.h | 2 + 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index bace58da9f6..bcacac025d6 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -16,39 +16,41 @@ /* 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; } -class TestConditionChecker : public AbstractThresholdConditionChecker +class TestConditionChecker final : public VersionBitsConditionChecker { private: mutable ThresholdConditionCache cache; public: - int64_t BeginTime() const override { return TestTime(10000); } - int64_t EndTime() const override { return TestTime(20000); } - int Period() const override { return 1000; } - int Threshold() const override { return 900; } - bool Condition(const CBlockIndex* pindex) const override { return (pindex->nVersion & 0x100); } + // constructor is implicit to allow for easier initialization of vector + explicit(false) TestConditionChecker(const Consensus::BIP9Deployment& dep) : VersionBitsConditionChecker{dep} { } + ~TestConditionChecker() override = default; - ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, cache); } - int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, 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 override { return 15000; } -}; - -class TestAlwaysActiveConditionChecker : public TestConditionChecker -{ -public: - int64_t BeginTime() const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; } -}; - -class TestNeverActiveConditionChecker : public TestConditionChecker -{ -public: - int64_t BeginTime() 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 @@ -58,22 +60,28 @@ class VersionBitsTester // A fake blockchain std::vector 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 checker{CHECKERS, {test_deployments.normal}}; // Another 6 that assume delayed activation - TestDelayedActivationConditionChecker checker_delayed[CHECKERS]; + std::vector checker_delayed{CHECKERS, {test_deployments.delayed}}; // Another 6 that assume always active activation - TestAlwaysActiveConditionChecker checker_always[CHECKERS]; + std::vector checker_always{CHECKERS, {test_deployments.always}}; // Another 6 that assume never active activation - TestNeverActiveConditionChecker checker_never[CHECKERS]; + std::vector 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 @@ -83,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; @@ -102,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); } @@ -119,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++; @@ -145,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; @@ -180,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) diff --git a/src/versionbits_impl.h b/src/versionbits_impl.h index a2a62d77ba2..57dc2699088 100644 --- a/src/versionbits_impl.h +++ b/src/versionbits_impl.h @@ -38,6 +38,8 @@ protected: virtual int Threshold() const =0; public: + virtual ~AbstractThresholdConditionChecker() = default; + /** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex * If provided, signalling_blocks is set to true/false based on whether each block in the period signalled */ From 7565563bc7a5bb98ebf03a7d6881912a74d3f302 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Sun, 17 Dec 2023 19:07:17 +1000 Subject: [PATCH 13/15] tests: refactor versionbits fuzz test Test `VersionBitsConditionChecker` behaviour directly, rather than reimplementing it, thus slightly improving fuzz test coverage of the real code. --- src/test/fuzz/versionbits.cpp | 138 +++++++++++++++------------------- src/versionbits_impl.h | 7 +- 2 files changed, 65 insertions(+), 80 deletions(-) diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index 10c88fc7872..5a7722d8708 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -21,43 +21,22 @@ #include namespace { -class TestConditionChecker : public AbstractThresholdConditionChecker +class TestConditionChecker : public VersionBitsConditionChecker { private: mutable ThresholdConditionCache m_cache; 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 override { return Condition(pindex->nVersion); } - int64_t BeginTime() const override { return m_begin; } - int64_t EndTime() const override { return m_end; } - int Period() const override { return m_period; } - int Threshold() const override { return m_threshold; } - int MinActivationHeight() const override { return m_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); } - - 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); - } }; /** Track blocks mined for test */ @@ -122,9 +101,6 @@ FUZZ_TARGET(versionbits, .init = initialize) const size_t max_periods = 16; const size_t max_blocks = 2 * period * max_periods; - const uint32_t 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::max() - MAX_START_TIME > interval * max_blocks); @@ -134,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(); const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral(); + if (ver_nosignal < 0) return; // negative values are uninteresting - // select deployment parameters: bit, start time, timeout - const int bit = fuzzed_data_provider.ConsumeIntegralInRange(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(0, period * (max_periods - 3)); - int end_block = fuzzed_data_provider.ConsumeIntegralInRange(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(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(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(); + } 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(); } else { - start_time = Consensus::BIP9Deployment::NEVER_ACTIVE; - never_active_test = true; - } - timeout = fuzzed_data_provider.ConsumeBool() ? Consensus::BIP9Deployment::NO_TIMEOUT : fuzzed_data_provider.ConsumeIntegral(); - } - int min_activation = fuzzed_data_provider.ConsumeIntegralInRange(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(0, period * (max_periods - 3)); + int end_block = fuzzed_data_provider.ConsumeIntegralInRange(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(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 @@ -222,9 +202,9 @@ 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 last_signals{}; int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1); @@ -238,7 +218,7 @@ FUZZ_TARGET(versionbits, .init = initialize) 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); @@ -255,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); @@ -269,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); @@ -303,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); } diff --git a/src/versionbits_impl.h b/src/versionbits_impl.h index 57dc2699088..84f19ebc244 100644 --- a/src/versionbits_impl.h +++ b/src/versionbits_impl.h @@ -67,7 +67,7 @@ protected: bool Condition(const CBlockIndex* pindex) const override { - return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask()) != 0); + return Condition(pindex->nVersion); } public: @@ -75,6 +75,11 @@ public: explicit VersionBitsConditionChecker(const Consensus::Params& params, Consensus::DeploymentPos id) : VersionBitsConditionChecker{params.vDeployments[id]} {} uint32_t Mask() const { return (uint32_t{1}) << dep.bit; } + + bool Condition(int32_t nVersion) const + { + return (((nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (nVersion & Mask()) != 0); + } }; #endif // BITCOIN_VERSIONBITS_IMPL_H From 60950f77c35e54e2884cfc14ab67623f3e325099 Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Fri, 14 Mar 2025 13:38:53 +1000 Subject: [PATCH 14/15] versionbits: docstrings for BIP9Info --- src/versionbits.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/versionbits.h b/src/versionbits.h index 2bb64acfa24..b3d82e2aac6 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -48,11 +48,17 @@ struct BIP9Stats { /** Detailed status of an enabled BIP9 deployment */ struct BIP9Info { + /** Height at which current_state started */ int since{0}; + /** String representing the current state */ std::string current_state{}; + /** String representing the next block's state */ std::string next_state{}; + /** For states where signalling is applicable, information about the signalling */ std::optional stats; + /** Which blocks signalled; empty if signalling is not applicable */ std::vector signalling_blocks; + /** Height at which the deployment is active, if known. May be in the future. */ std::optional active_since; }; From e3014017bacff42d8d69f3061ce1ee621aaa450a Mon Sep 17 00:00:00 2001 From: Anthony Towns Date: Fri, 14 Mar 2025 13:58:08 +1000 Subject: [PATCH 15/15] test: add IsActiveAfter tests for versionbits --- src/test/versionbits_tests.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index bcacac025d6..9de57393282 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -255,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 @@ -279,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; @@ -311,8 +317,10 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens // end of the first period by mining blocks at 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)); // then we'll keep mining at nStartTime... } else { // use a time 1s earlier than start time to check we stay DEFINED @@ -321,11 +329,13 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens // Start generating blocks before nStartTime 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 < 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. @@ -333,6 +343,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens 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, } @@ -342,6 +353,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens 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; @@ -352,6 +364,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens 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; @@ -366,6 +379,7 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens 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; } @@ -374,11 +388,13 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens 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,29 +406,34 @@ void check_computeblockversion(VersionBitsCache& versionbitscache, const Consens // next period. 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(period * 2, nTime, VERSIONBITS_TOP_BITS | (1<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