Showing with 62 additions and 7 deletions.
  1. +2 −0 src/consensus/params.h
  2. +16 −5 src/rpc/blockchain.cpp
  3. +35 −1 src/test/versionbits_tests.cpp
  4. +1 −0 src/validation.cpp
  5. +3 −1 src/versionbits.cpp
  6. +1 −0 src/versionbits.h
  7. +4 −0 test/functional/test_framework/util.py
2 changes: 2 additions & 0 deletions src/consensus/params.h
Expand Up @@ -31,6 +31,8 @@ struct BIP9Deployment {
int64_t nStartTime;
/** Timeout/expiry MedianTime for the deployment attempt. */
int64_t nTimeout;
/** If true STARTED->FAILED will become STARTED->LOCKED_IN */
bool fLockInOnTimeout = false;
};

/**
Expand Down
21 changes: 16 additions & 5 deletions src/rpc/blockchain.cpp
Expand Up @@ -1074,7 +1074,7 @@ static UniValue SoftForkDesc(const std::string &name, int version, CBlockIndex*
return rv;
}

static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
static UniValue VBSoftForkDesc(const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
{
UniValue rv(UniValue::VOBJ);
const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id);
Expand Down Expand Up @@ -1106,13 +1106,13 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse
return rv;
}

void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
void VBSoftForkDescPushBack(UniValue& vb_softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
{
// Deployments with timeout value of 0 are hidden.
// A timeout value of 0 guarantees a softfork will never be activated.
// This is used when softfork codes are merged without specifying the deployment schedule.
if (consensusParams.vDeployments[id].nTimeout > 0)
bip9_softforks.push_back(Pair(name, BIP9SoftForkDesc(consensusParams, id)));
vb_softforks.push_back(Pair(name, VBSoftForkDesc(consensusParams, id)));
}

UniValue getblockchaininfo(const JSONRPCRequest& request)
Expand Down Expand Up @@ -1142,6 +1142,15 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
" },\n"
" }, ...\n"
" ],\n"
" \"bip8_softforks\": { (object) status of BIP8 softforks in progress\n"
" \"xxxx\" : { (string) name of the softfork\n"
" \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n"
" \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
" \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
" \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
" \"since\": xx (numeric) height of the first block to which the status applies\n"
" }\n"
" },\n"
" \"bip9_softforks\": { (object) status of BIP9 softforks in progress\n"
" \"xxxx\" : { (string) name of the softfork\n"
" \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n"
Expand Down Expand Up @@ -1180,13 +1189,15 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
const Consensus::Params& consensusParams = Params().GetConsensus();
CBlockIndex* tip = chainActive.Tip();
UniValue softforks(UniValue::VARR);
UniValue bip8_softforks(UniValue::VOBJ);
UniValue bip9_softforks(UniValue::VOBJ);
softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams));
softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams));
softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams));
BIP9SoftForkDescPushBack(bip9_softforks, "csv", consensusParams, Consensus::DEPLOYMENT_CSV);
BIP9SoftForkDescPushBack(bip9_softforks, "segwit", consensusParams, Consensus::DEPLOYMENT_SEGWIT);
VBSoftForkDescPushBack(bip9_softforks, "csv", consensusParams, Consensus::DEPLOYMENT_CSV);
VBSoftForkDescPushBack(bip9_softforks, "segwit", consensusParams, Consensus::DEPLOYMENT_SEGWIT);
obj.push_back(Pair("softforks", softforks));
obj.push_back(Pair("bip8_softforks", bip8_softforks));
obj.push_back(Pair("bip9_softforks", bip9_softforks));

if (fPruneMode)
Expand Down
36 changes: 35 additions & 1 deletion src/test/versionbits_tests.cpp
Expand Up @@ -21,13 +21,16 @@ class TestConditionChecker : public AbstractThresholdConditionChecker
{
private:
mutable ThresholdConditionCache cache;
bool lock_in_on_timeout = false;

public:
int64_t BeginTime(const Consensus::Params& params) const { return TestTime(10000); }
int64_t EndTime(const Consensus::Params& params) const { return TestTime(20000); }
bool LockInOnTimeout(const Consensus::Params& params) const { return this->lock_in_on_timeout; }
int Period(const Consensus::Params& params) const { return 1000; }
int Threshold(const Consensus::Params& params) const { return 900; }
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const { return (pindex->nVersion & 0x100); }
void SetLockInOnTimeout(int64_t flag) { this->lock_in_on_timeout = flag; }

ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
Expand All @@ -51,6 +54,13 @@ class VersionBitsTester
public:
VersionBitsTester() : num(0) {}

VersionBitsTester& SetCheckerLockInOnTimeoutTest(bool flag) {
for (unsigned int i = 0; i < CHECKERS; i++) {
checker[i].SetLockInOnTimeout(flag);
}
return *this;
}

VersionBitsTester& Reset() {
for (unsigned int i = 0; i < vpblock.size(); i++) {
delete vpblock[i];
Expand Down Expand Up @@ -205,7 +215,31 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
.Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
.Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000);
.Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000)

// DEFINED -> STARTED -> LOCKEDIN -> ACTIVE (mandatory lockin)
.Reset().SetCheckerLockInOnTimeoutTest(true).TestDefined().TestStateSinceHeight(0)
.Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0)
.Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0)
.Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
.Mine(6000, TestTime(20000), 0).TestLockedIn().TestStateSinceHeight(6000)
.Mine(7000, TestTime(20000), 0x100).TestActive().TestStateSinceHeight(7000)

// DEFINED -> STARTED -> LOCKEDIN -> ACTIVE (mandatory lockin but activation by signalling)
.Reset().SetCheckerLockInOnTimeoutTest(true).TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0x200).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(9999) - 1, 0x200).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
.Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
.Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) // 50 old blocks
.Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 900 new blocks
.Mine(2999, TestTime(11999), 0x200).TestStarted().TestStateSinceHeight(2000) // 49 old blocks
.Mine(3000, TestTime(12000), 0x200).TestLockedIn().TestStateSinceHeight(3000) // 1 old block (so 900 out of the past 1000)
.Mine(3999, TestTime(12500), 0).TestLockedIn().TestStateSinceHeight(3000)
.Mine(4000, TestTime(13000), 0).TestActive().TestStateSinceHeight(4000)
;
}

// Sanity checks of version bit deployments
Expand Down
1 change: 1 addition & 0 deletions src/validation.cpp
Expand Up @@ -1451,6 +1451,7 @@ class WarningBitsConditionChecker : public AbstractThresholdConditionChecker

int64_t BeginTime(const Consensus::Params& params) const { return 0; }
int64_t EndTime(const Consensus::Params& params) const { return std::numeric_limits<int64_t>::max(); }
bool LockInOnTimeout(const Consensus::Params& params) const { return std::numeric_limits<bool>::max(); }
int Period(const Consensus::Params& params) const { return params.nMinerConfirmationWindow; }
int Threshold(const Consensus::Params& params) const { return params.nRuleChangeActivationThreshold; }

Expand Down
4 changes: 3 additions & 1 deletion src/versionbits.cpp
Expand Up @@ -26,6 +26,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
int nThreshold = Threshold(params);
int64_t nTimeStart = BeginTime(params);
int64_t nTimeTimeout = EndTime(params);
bool fLockInOnTimeout = LockInOnTimeout(params);

// 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.
if (pindexPrev != NULL) {
Expand Down Expand Up @@ -70,7 +71,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
}
case THRESHOLD_STARTED: {
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
stateNext = THRESHOLD_FAILED;
stateNext = (fLockInOnTimeout == false) ? THRESHOLD_FAILED : THRESHOLD_LOCKED_IN;
break;
}
// We need to count
Expand Down Expand Up @@ -176,6 +177,7 @@ class VersionBitsConditionChecker : public AbstractThresholdConditionChecker {
protected:
int64_t BeginTime(const Consensus::Params& params) const { return params.vDeployments[id].nStartTime; }
int64_t EndTime(const Consensus::Params& params) const { return params.vDeployments[id].nTimeout; }
bool LockInOnTimeout(const Consensus::Params& params) const { return params.vDeployments[id].fLockInOnTimeout; }
int Period(const Consensus::Params& params) const { return params.nMinerConfirmationWindow; }
int Threshold(const Consensus::Params& params) const { return params.nRuleChangeActivationThreshold; }

Expand Down
1 change: 1 addition & 0 deletions src/versionbits.h
Expand Up @@ -55,6 +55,7 @@ class AbstractThresholdConditionChecker {
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 bool LockInOnTimeout(const Consensus::Params& params) const =0;
virtual int Period(const Consensus::Params& params) const =0;
virtual int Threshold(const Consensus::Params& params) const =0;

Expand Down
4 changes: 4 additions & 0 deletions test/functional/test_framework/util.py
Expand Up @@ -610,3 +610,7 @@ def mine_large_block(node, utxos=None):
def get_bip9_status(node, key):
info = node.getblockchaininfo()
return info['bip9_softforks'][key]

def get_bip8_status(node, key):
info = node.getblockchaininfo()
return info['bip8_softforks'][key]