Skip to content

Commit a7469bc

Browse files
committed
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.
1 parent 7f15c18 commit a7469bc

File tree

5 files changed

+98
-59
lines changed

5 files changed

+98
-59
lines changed

src/rpc/blockchain.cpp

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,45 +1412,60 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue&
14121412
// For BIP9 deployments.
14131413

14141414
if (!DeploymentEnabled(consensusParams, id)) return;
1415+
if (active_chain_tip == nullptr) return;
1416+
1417+
auto get_state_name = [](const ThresholdState state) -> std::string {
1418+
switch (state) {
1419+
case ThresholdState::DEFINED: return "defined";
1420+
case ThresholdState::STARTED: return "started";
1421+
case ThresholdState::LOCKED_IN: return "locked_in";
1422+
case ThresholdState::ACTIVE: return "active";
1423+
case ThresholdState::FAILED: return "failed";
1424+
}
1425+
return "invalid";
1426+
};
14151427

14161428
UniValue bip9(UniValue::VOBJ);
1417-
const ThresholdState thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id);
1418-
switch (thresholdState) {
1419-
case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
1420-
case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
1421-
case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
1422-
case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
1423-
case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
1424-
}
1425-
const bool has_signal = (ThresholdState::STARTED == thresholdState || ThresholdState::LOCKED_IN == thresholdState);
1429+
1430+
const ThresholdState next_state = g_versionbitscache.State(active_chain_tip, consensusParams, id);
1431+
const ThresholdState current_state = g_versionbitscache.State(active_chain_tip->pprev, consensusParams, id);
1432+
1433+
const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
1434+
1435+
// BIP9 parameters
14261436
if (has_signal) {
14271437
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
14281438
}
14291439
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
14301440
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
1431-
int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id);
1432-
bip9.pushKV("since", since_height);
1441+
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
1442+
1443+
// BIP9 status
1444+
bip9.pushKV("status", get_state_name(current_state));
1445+
bip9.pushKV("since", g_versionbitscache.StateSinceHeight(active_chain_tip->pprev, consensusParams, id));
1446+
bip9.pushKV("status-next", get_state_name(next_state));
1447+
1448+
// BIP9 signalling status, if applicable
14331449
if (has_signal) {
14341450
UniValue statsUV(UniValue::VOBJ);
14351451
BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id);
14361452
statsUV.pushKV("period", statsStruct.period);
14371453
statsUV.pushKV("elapsed", statsStruct.elapsed);
14381454
statsUV.pushKV("count", statsStruct.count);
1439-
if (ThresholdState::LOCKED_IN != thresholdState) {
1455+
if (ThresholdState::LOCKED_IN != current_state) {
14401456
statsUV.pushKV("threshold", statsStruct.threshold);
14411457
statsUV.pushKV("possible", statsStruct.possible);
14421458
}
14431459
bip9.pushKV("statistics", statsUV);
14441460
}
1445-
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
14461461

14471462
UniValue rv(UniValue::VOBJ);
14481463
rv.pushKV("type", "bip9");
1449-
rv.pushKV("bip9", bip9);
1450-
if (ThresholdState::ACTIVE == thresholdState) {
1451-
rv.pushKV("height", since_height);
1464+
if (ThresholdState::ACTIVE == next_state) {
1465+
rv.pushKV("height", g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id));
14521466
}
1453-
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
1467+
rv.pushKV("active", ThresholdState::ACTIVE == next_state);
1468+
rv.pushKV("bip9", bip9);
14541469

14551470
softforks.pushKV(DeploymentName(id), rv);
14561471
}
@@ -1551,14 +1566,17 @@ RPCHelpMan getblockchaininfo()
15511566
namespace {
15521567
const std::vector<RPCResult> RPCHelpForDeployment{
15531568
{RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1569+
{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)"},
1570+
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
15541571
{RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
15551572
{
1556-
{RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
15571573
{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)"},
15581574
{RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
15591575
{RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1560-
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
15611576
{RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1577+
{RPCResult::Type::STR, "status", "bip9 status of specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
1578+
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1579+
{RPCResult::Type::STR, "status-next", "bip9 status of next block"},
15621580
{RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
15631581
{
15641582
{RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
@@ -1568,8 +1586,6 @@ const std::vector<RPCResult> RPCHelpForDeployment{
15681586
{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)"},
15691587
}},
15701588
}},
1571-
{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)"},
1572-
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
15731589
};
15741590

15751591
UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams)

src/test/fuzz/versionbits.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class TestConditionChecker : public AbstractThresholdConditionChecker
5151

5252
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, dummy_params, m_cache); }
5353
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, dummy_params, m_cache); }
54-
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindexPrev, dummy_params); }
54+
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params); }
5555

5656
bool Condition(int32_t version) const
5757
{
@@ -220,7 +220,13 @@ FUZZ_TARGET_INIT(versionbits, initialize)
220220
CBlockIndex* prev = blocks.tip();
221221
const int exp_since = checker.GetStateSinceHeightFor(prev);
222222
const ThresholdState exp_state = checker.GetStateFor(prev);
223-
BIP9Stats last_stats = checker.GetStateStatisticsFor(prev);
223+
224+
// get statistics from end of previous period, then reset
225+
BIP9Stats last_stats;
226+
last_stats.period = period;
227+
last_stats.threshold = threshold;
228+
last_stats.count = last_stats.elapsed = 0;
229+
last_stats.possible = (period >= threshold);
224230

225231
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
226232
assert(exp_since <= prev_next_height);
@@ -241,9 +247,6 @@ FUZZ_TARGET_INIT(versionbits, initialize)
241247
assert(state == exp_state);
242248
assert(since == exp_since);
243249

244-
// GetStateStatistics may crash when state is not STARTED
245-
if (state != ThresholdState::STARTED) continue;
246-
247250
// check that after mining this block stats change as expected
248251
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
249252
assert(stats.period == period);
@@ -265,14 +268,12 @@ FUZZ_TARGET_INIT(versionbits, initialize)
265268
CBlockIndex* current_block = blocks.mine_block(signal);
266269
assert(checker.Condition(current_block) == signal);
267270

268-
// GetStateStatistics is safe on a period boundary
269-
// and has progressed to a new period
270271
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
271272
assert(stats.period == period);
272273
assert(stats.threshold == threshold);
273-
assert(stats.elapsed == 0);
274-
assert(stats.count == 0);
275-
assert(stats.possible == true);
274+
assert(stats.elapsed == period);
275+
assert(stats.count == blocks_sig);
276+
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
276277

277278
// More interesting is whether the state changed.
278279
const ThresholdState state = checker.GetStateFor(current_block);

src/versionbits.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,22 +105,23 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI
105105
stats.period = Period(params);
106106
stats.threshold = Threshold(params);
107107

108-
if (pindex == nullptr)
109-
return stats;
108+
if (pindex == nullptr) return stats;
110109

111110
// Find beginning of period
112-
const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period));
113-
stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight;
111+
int start_height = pindex->nHeight - (pindex->nHeight % stats.period);
114112

115113
// Count from current block to beginning of period
114+
int elapsed = 0;
116115
int count = 0;
117116
const CBlockIndex* currentIndex = pindex;
118-
while (pindexEndOfPrevPeriod->nHeight != currentIndex->nHeight){
119-
if (Condition(currentIndex, params))
120-
count++;
117+
for(;;) {
118+
++elapsed;
119+
if (Condition(currentIndex, params)) ++count;
120+
if (currentIndex->nHeight <= start_height) break;
121121
currentIndex = currentIndex->pprev;
122122
}
123123

124+
stats.elapsed = elapsed;
124125
stats.count = count;
125126
stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count);
126127

@@ -196,9 +197,9 @@ ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Cons
196197
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]);
197198
}
198199

199-
BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
200+
BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos)
200201
{
201-
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params);
202+
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindex, params);
202203
}
203204

204205
int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)

src/versionbits.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class AbstractThresholdConditionChecker {
6464
virtual int Threshold(const Consensus::Params& params) const =0;
6565

6666
public:
67-
/** Returns the numerical statistics of an in-progress BIP9 softfork in the current period */
67+
/** Returns the numerical statistics of an in-progress BIP9 softfork in the period including pindex */
6868
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const;
6969
/** Returns the state for pindex A based on parent pindexPrev B. Applies any state transition if conditions are present.
7070
* Caches state from first block of period. */
@@ -82,8 +82,8 @@ class VersionBitsCache
8282
ThresholdConditionCache m_caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] GUARDED_BY(m_mutex);
8383

8484
public:
85-
/** Get the numerical statistics for a given deployment for the signalling period that includes the block after pindexPrev. */
86-
static BIP9Stats Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos);
85+
/** Get the numerical statistics for a given deployment for the signalling period that includes pindex. */
86+
static BIP9Stats Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos);
8787

8888
static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos);
8989

test/functional/rpc_blockchain.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def run_test(self):
7272
self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete
7373

7474
self._test_getblockchaininfo()
75-
self._test_getdeploymentinfo()
7675
self._test_getchaintxstats()
7776
self._test_gettxoutsetinfo()
7877
self._test_getblockheader()
@@ -81,6 +80,7 @@ def run_test(self):
8180
self._test_stopatheight()
8281
self._test_waitforblockheight()
8382
self._test_getblock()
83+
self._test_getdeploymentinfo()
8484
assert self.nodes[0].verifychain(4, 0)
8585

8686
def mine_chain(self):
@@ -160,11 +160,6 @@ def _test_getblockchaininfo(self):
160160
self.start_node(0, extra_args=[
161161
'-stopatheight=207',
162162
'-prune=550',
163-
'-testactivationheight=bip34@2',
164-
'-testactivationheight=dersig@3',
165-
'-testactivationheight=cltv@4',
166-
'-testactivationheight=csv@5',
167-
'-testactivationheight=segwit@6',
168163
])
169164

170165
res = self.nodes[0].getblockchaininfo()
@@ -178,11 +173,10 @@ def _test_getblockchaininfo(self):
178173
assert_equal(res['prune_target_size'], 576716800)
179174
assert_greater_than(res['size_on_disk'], 0)
180175

181-
def _test_getdeploymentinfo(self):
182-
self.log.info("Test getdeploymentinfo")
176+
def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next):
177+
assert height >= 144 and height <= 287
183178

184-
res = self.nodes[0].getdeploymentinfo()
185-
assert_equal(res, {
179+
assert_equal(gdi_result, {
186180
"deployments": {
187181
'bip34': {'type': 'buried', 'active': True, 'height': 2},
188182
'bip66': {'type': 'buried', 'active': True, 'height': 3},
@@ -192,37 +186,64 @@ def _test_getdeploymentinfo(self):
192186
'testdummy': {
193187
'type': 'bip9',
194188
'bip9': {
195-
'status': 'started',
196189
'bit': 28,
197190
'start_time': 0,
198191
'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value
192+
'min_activation_height': 0,
193+
'status': 'started',
194+
'status-next': status_next,
199195
'since': 144,
200196
'statistics': {
201197
'period': 144,
202198
'threshold': 108,
203-
'elapsed': HEIGHT - 143,
204-
'count': HEIGHT - 143,
199+
'elapsed': height - 143,
200+
'count': height - 143,
205201
'possible': True,
206202
},
207-
'min_activation_height': 0,
208203
},
209204
'active': False
210205
},
211206
'taproot': {
212207
'type': 'bip9',
213208
'bip9': {
214-
'status': 'active',
215209
'start_time': -1,
216210
'timeout': 9223372036854775807,
217-
'since': 0,
218211
'min_activation_height': 0,
212+
'status': 'active',
213+
'status-next': 'active',
214+
'since': 0,
219215
},
220216
'height': 0,
221217
'active': True
222218
}
223219
}
224220
})
225221

222+
def _test_getdeploymentinfo(self):
223+
# Note: continues past -stopatheight height, so must be invoked
224+
# after _test_stopatheight
225+
226+
self.log.info("Test getdeploymentinfo")
227+
self.stop_node(0)
228+
self.start_node(0, extra_args=[
229+
'-testactivationheight=bip34@2',
230+
'-testactivationheight=dersig@3',
231+
'-testactivationheight=cltv@4',
232+
'-testactivationheight=csv@5',
233+
'-testactivationheight=segwit@6',
234+
])
235+
236+
gbci207 = self.nodes[0].getblockchaininfo()
237+
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci207["blocks"], gbci207["bestblockhash"], "started")
238+
239+
# block just prior to lock in
240+
self.generate(self.wallet, 287 - gbci207["blocks"])
241+
gbci287 = self.nodes[0].getblockchaininfo()
242+
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci287["blocks"], gbci287["bestblockhash"], "locked_in")
243+
244+
# calling with an explicit hash works
245+
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(gbci207["bestblockhash"]), gbci207["blocks"], gbci207["bestblockhash"], "started")
246+
226247
def _test_getchaintxstats(self):
227248
self.log.info("Test getchaintxstats")
228249

0 commit comments

Comments
 (0)