mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 18:09:47 +01:00
rpc: getdeploymentinfo: change stats to always refer to current period
On a period boundary, getdeploymentinfo (and previously getblockchaininfo) would report the status and statistics for the next block rather than the current block. Change this to always report the status/statistics of the current block, but add status-next to report the status for the next block.
This commit is contained in:
parent
7f15c1841b
commit
a7469bcd35
@ -1412,45 +1412,60 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue&
|
||||
// For BIP9 deployments.
|
||||
|
||||
if (!DeploymentEnabled(consensusParams, id)) return;
|
||||
if (active_chain_tip == 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 thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id);
|
||||
switch (thresholdState) {
|
||||
case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
|
||||
case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
|
||||
case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
|
||||
case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
|
||||
case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
|
||||
}
|
||||
const bool has_signal = (ThresholdState::STARTED == thresholdState || ThresholdState::LOCKED_IN == thresholdState);
|
||||
|
||||
const ThresholdState next_state = g_versionbitscache.State(active_chain_tip, consensusParams, id);
|
||||
const ThresholdState current_state = g_versionbitscache.State(active_chain_tip->pprev, consensusParams, id);
|
||||
|
||||
const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
|
||||
|
||||
// BIP9 parameters
|
||||
if (has_signal) {
|
||||
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
|
||||
}
|
||||
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
|
||||
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
|
||||
int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id);
|
||||
bip9.pushKV("since", since_height);
|
||||
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
|
||||
|
||||
// BIP9 status
|
||||
bip9.pushKV("status", get_state_name(current_state));
|
||||
bip9.pushKV("since", g_versionbitscache.StateSinceHeight(active_chain_tip->pprev, consensusParams, id));
|
||||
bip9.pushKV("status-next", get_state_name(next_state));
|
||||
|
||||
// BIP9 signalling status, if applicable
|
||||
if (has_signal) {
|
||||
UniValue statsUV(UniValue::VOBJ);
|
||||
BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id);
|
||||
statsUV.pushKV("period", statsStruct.period);
|
||||
statsUV.pushKV("elapsed", statsStruct.elapsed);
|
||||
statsUV.pushKV("count", statsStruct.count);
|
||||
if (ThresholdState::LOCKED_IN != thresholdState) {
|
||||
if (ThresholdState::LOCKED_IN != current_state) {
|
||||
statsUV.pushKV("threshold", statsStruct.threshold);
|
||||
statsUV.pushKV("possible", statsStruct.possible);
|
||||
}
|
||||
bip9.pushKV("statistics", statsUV);
|
||||
}
|
||||
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
|
||||
|
||||
UniValue rv(UniValue::VOBJ);
|
||||
rv.pushKV("type", "bip9");
|
||||
rv.pushKV("bip9", bip9);
|
||||
if (ThresholdState::ACTIVE == thresholdState) {
|
||||
rv.pushKV("height", since_height);
|
||||
if (ThresholdState::ACTIVE == next_state) {
|
||||
rv.pushKV("height", g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id));
|
||||
}
|
||||
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
|
||||
rv.pushKV("active", ThresholdState::ACTIVE == next_state);
|
||||
rv.pushKV("bip9", bip9);
|
||||
|
||||
softforks.pushKV(DeploymentName(id), rv);
|
||||
}
|
||||
@ -1551,14 +1566,17 @@ RPCHelpMan getblockchaininfo()
|
||||
namespace {
|
||||
const std::vector<RPCResult> RPCHelpForDeployment{
|
||||
{RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
|
||||
{RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
|
||||
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
|
||||
{RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
|
||||
{
|
||||
{RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
|
||||
{RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"},
|
||||
{RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
|
||||
{RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
|
||||
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
|
||||
{RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
|
||||
{RPCResult::Type::STR, "status", "bip9 status of specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
|
||||
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
|
||||
{RPCResult::Type::STR, "status-next", "bip9 status of next block"},
|
||||
{RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
|
||||
{
|
||||
{RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
|
||||
@ -1568,8 +1586,6 @@ const std::vector<RPCResult> RPCHelpForDeployment{
|
||||
{RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
|
||||
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
|
||||
};
|
||||
|
||||
UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams)
|
||||
|
@ -51,7 +51,7 @@ public:
|
||||
|
||||
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* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindexPrev, dummy_params); }
|
||||
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params); }
|
||||
|
||||
bool Condition(int32_t version) const
|
||||
{
|
||||
@ -220,7 +220,13 @@ FUZZ_TARGET_INIT(versionbits, initialize)
|
||||
CBlockIndex* prev = blocks.tip();
|
||||
const int exp_since = checker.GetStateSinceHeightFor(prev);
|
||||
const ThresholdState exp_state = checker.GetStateFor(prev);
|
||||
BIP9Stats last_stats = checker.GetStateStatisticsFor(prev);
|
||||
|
||||
// get statistics from end of previous period, then reset
|
||||
BIP9Stats last_stats;
|
||||
last_stats.period = period;
|
||||
last_stats.threshold = threshold;
|
||||
last_stats.count = last_stats.elapsed = 0;
|
||||
last_stats.possible = (period >= threshold);
|
||||
|
||||
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
|
||||
assert(exp_since <= prev_next_height);
|
||||
@ -241,9 +247,6 @@ FUZZ_TARGET_INIT(versionbits, initialize)
|
||||
assert(state == exp_state);
|
||||
assert(since == exp_since);
|
||||
|
||||
// GetStateStatistics may crash when state is not STARTED
|
||||
if (state != ThresholdState::STARTED) continue;
|
||||
|
||||
// check that after mining this block stats change as expected
|
||||
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
|
||||
assert(stats.period == period);
|
||||
@ -265,14 +268,12 @@ FUZZ_TARGET_INIT(versionbits, initialize)
|
||||
CBlockIndex* current_block = blocks.mine_block(signal);
|
||||
assert(checker.Condition(current_block) == signal);
|
||||
|
||||
// GetStateStatistics is safe on a period boundary
|
||||
// and has progressed to a new period
|
||||
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
|
||||
assert(stats.period == period);
|
||||
assert(stats.threshold == threshold);
|
||||
assert(stats.elapsed == 0);
|
||||
assert(stats.count == 0);
|
||||
assert(stats.possible == true);
|
||||
assert(stats.elapsed == period);
|
||||
assert(stats.count == blocks_sig);
|
||||
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
|
||||
|
||||
// More interesting is whether the state changed.
|
||||
const ThresholdState state = checker.GetStateFor(current_block);
|
||||
|
@ -105,22 +105,23 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI
|
||||
stats.period = Period(params);
|
||||
stats.threshold = Threshold(params);
|
||||
|
||||
if (pindex == nullptr)
|
||||
return stats;
|
||||
if (pindex == nullptr) return stats;
|
||||
|
||||
// Find beginning of period
|
||||
const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period));
|
||||
stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight;
|
||||
int start_height = pindex->nHeight - (pindex->nHeight % stats.period);
|
||||
|
||||
// Count from current block to beginning of period
|
||||
int elapsed = 0;
|
||||
int count = 0;
|
||||
const CBlockIndex* currentIndex = pindex;
|
||||
while (pindexEndOfPrevPeriod->nHeight != currentIndex->nHeight){
|
||||
if (Condition(currentIndex, params))
|
||||
count++;
|
||||
for(;;) {
|
||||
++elapsed;
|
||||
if (Condition(currentIndex, params)) ++count;
|
||||
if (currentIndex->nHeight <= start_height) break;
|
||||
currentIndex = currentIndex->pprev;
|
||||
}
|
||||
|
||||
stats.elapsed = elapsed;
|
||||
stats.count = count;
|
||||
stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count);
|
||||
|
||||
@ -196,9 +197,9 @@ ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Cons
|
||||
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]);
|
||||
}
|
||||
|
||||
BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
|
||||
BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos)
|
||||
{
|
||||
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params);
|
||||
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindex, params);
|
||||
}
|
||||
|
||||
int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
|
||||
|
@ -64,7 +64,7 @@ protected:
|
||||
virtual int Threshold(const Consensus::Params& params) const =0;
|
||||
|
||||
public:
|
||||
/** Returns the numerical statistics of an in-progress BIP9 softfork in the current period */
|
||||
/** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex */
|
||||
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) 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. */
|
||||
@ -82,8 +82,8 @@ private:
|
||||
ThresholdConditionCache m_caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] GUARDED_BY(m_mutex);
|
||||
|
||||
public:
|
||||
/** Get the numerical statistics for a given deployment for the signalling period that includes the block after pindexPrev. */
|
||||
static BIP9Stats Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos);
|
||||
/** Get the numerical statistics for a given deployment for the signalling period that includes pindex. */
|
||||
static BIP9Stats Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos);
|
||||
|
||||
static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos);
|
||||
|
||||
|
@ -72,7 +72,6 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete
|
||||
|
||||
self._test_getblockchaininfo()
|
||||
self._test_getdeploymentinfo()
|
||||
self._test_getchaintxstats()
|
||||
self._test_gettxoutsetinfo()
|
||||
self._test_getblockheader()
|
||||
@ -81,6 +80,7 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
self._test_stopatheight()
|
||||
self._test_waitforblockheight()
|
||||
self._test_getblock()
|
||||
self._test_getdeploymentinfo()
|
||||
assert self.nodes[0].verifychain(4, 0)
|
||||
|
||||
def mine_chain(self):
|
||||
@ -160,11 +160,6 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
self.start_node(0, extra_args=[
|
||||
'-stopatheight=207',
|
||||
'-prune=550',
|
||||
'-testactivationheight=bip34@2',
|
||||
'-testactivationheight=dersig@3',
|
||||
'-testactivationheight=cltv@4',
|
||||
'-testactivationheight=csv@5',
|
||||
'-testactivationheight=segwit@6',
|
||||
])
|
||||
|
||||
res = self.nodes[0].getblockchaininfo()
|
||||
@ -178,11 +173,10 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
assert_equal(res['prune_target_size'], 576716800)
|
||||
assert_greater_than(res['size_on_disk'], 0)
|
||||
|
||||
def _test_getdeploymentinfo(self):
|
||||
self.log.info("Test getdeploymentinfo")
|
||||
def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next):
|
||||
assert height >= 144 and height <= 287
|
||||
|
||||
res = self.nodes[0].getdeploymentinfo()
|
||||
assert_equal(res, {
|
||||
assert_equal(gdi_result, {
|
||||
"deployments": {
|
||||
'bip34': {'type': 'buried', 'active': True, 'height': 2},
|
||||
'bip66': {'type': 'buried', 'active': True, 'height': 3},
|
||||
@ -192,30 +186,32 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
'testdummy': {
|
||||
'type': 'bip9',
|
||||
'bip9': {
|
||||
'status': 'started',
|
||||
'bit': 28,
|
||||
'start_time': 0,
|
||||
'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value
|
||||
'min_activation_height': 0,
|
||||
'status': 'started',
|
||||
'status-next': status_next,
|
||||
'since': 144,
|
||||
'statistics': {
|
||||
'period': 144,
|
||||
'threshold': 108,
|
||||
'elapsed': HEIGHT - 143,
|
||||
'count': HEIGHT - 143,
|
||||
'elapsed': height - 143,
|
||||
'count': height - 143,
|
||||
'possible': True,
|
||||
},
|
||||
'min_activation_height': 0,
|
||||
},
|
||||
'active': False
|
||||
},
|
||||
'taproot': {
|
||||
'type': 'bip9',
|
||||
'bip9': {
|
||||
'status': 'active',
|
||||
'start_time': -1,
|
||||
'timeout': 9223372036854775807,
|
||||
'since': 0,
|
||||
'min_activation_height': 0,
|
||||
'status': 'active',
|
||||
'status-next': 'active',
|
||||
'since': 0,
|
||||
},
|
||||
'height': 0,
|
||||
'active': True
|
||||
@ -223,6 +219,31 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
}
|
||||
})
|
||||
|
||||
def _test_getdeploymentinfo(self):
|
||||
# Note: continues past -stopatheight height, so must be invoked
|
||||
# after _test_stopatheight
|
||||
|
||||
self.log.info("Test getdeploymentinfo")
|
||||
self.stop_node(0)
|
||||
self.start_node(0, extra_args=[
|
||||
'-testactivationheight=bip34@2',
|
||||
'-testactivationheight=dersig@3',
|
||||
'-testactivationheight=cltv@4',
|
||||
'-testactivationheight=csv@5',
|
||||
'-testactivationheight=segwit@6',
|
||||
])
|
||||
|
||||
gbci207 = self.nodes[0].getblockchaininfo()
|
||||
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci207["blocks"], gbci207["bestblockhash"], "started")
|
||||
|
||||
# block just prior to lock in
|
||||
self.generate(self.wallet, 287 - gbci207["blocks"])
|
||||
gbci287 = self.nodes[0].getblockchaininfo()
|
||||
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci287["blocks"], gbci287["bestblockhash"], "locked_in")
|
||||
|
||||
# calling with an explicit hash works
|
||||
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(gbci207["bestblockhash"]), gbci207["blocks"], gbci207["bestblockhash"], "started")
|
||||
|
||||
def _test_getchaintxstats(self):
|
||||
self.log.info("Test getchaintxstats")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user