Skip to content

Commit d4e92d8

Browse files
author
MarcoFalke
committed
Merge bitcoin#23508: Add getdeploymentinfo RPC
a380922 Release notes for getdeploymentinfo rpc (Anthony Towns) 240cad0 rpc: getdeploymentinfo: include signalling info (Anthony Towns) 376c0c6 rpc: getdeploymentinfo: include block hash/height (Anthony Towns) a7469bc rpc: getdeploymentinfo: change stats to always refer to current period (Anthony Towns) 7f15c18 rpc: getdeploymentinfo: allow specifying a blockhash other than tip (Anthony Towns) fd82613 rpc: move softfork info from getblockchaininfo to getdeploymentinfo (Anthony Towns) Pull request description: The aim of this PR is to improve the ability to monitor soft fork status. It first moves the softfork section from getblockchaininfo into a new RPC named getdeploymentinfo, which is then also able to query the status of forks at an arbitrary block rather than only at the tip. In addition, bip9 status is changed to indicate the status of the given block, rather than just for the next block, and an additional field is included to indicate whether each block in the signalling period signaled. ACKs for top commit: laanwj: Code review and lightly tested ACK a380922 Sjors: tACK a380922 fjahr: tACK a380922 Tree-SHA512: 7417d733b47629f229c5128586569909250481a3e94356c52fe67a03fd42cd81745246e384b98c4115fb61587714c879e4bc3e5f5c74407d9f8f6773472a33cb
2 parents a4f7c41 + a380922 commit d4e92d8

11 files changed

+250
-91
lines changed

doc/release-notes-23508.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Updated RPCs
2+
------------
3+
4+
- Information on soft fork status has been moved from `getblockchaininfo`
5+
to `getdeploymentinfo` which allows querying soft fork status at any
6+
block, rather than just at the chain tip. Inclusion of soft fork
7+
status in `getblockchaininfo` can currently be restored using the
8+
configuration `-deprecatedrpc=softforks`, but this will be removed in
9+
a future release. (#23508)

src/rpc/blockchain.cpp

+139-47
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,7 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue&
14381438

14391439
UniValue rv(UniValue::VOBJ);
14401440
rv.pushKV("type", "buried");
1441-
// getblockchaininfo reports the softfork as active from when the chain height is
1441+
// getdeploymentinfo reports the softfork as active from when the chain height is
14421442
// one below the activation height
14431443
rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep));
14441444
rv.pushKV("height", params.DeploymentHeight(dep));
@@ -1450,51 +1450,82 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue&
14501450
// For BIP9 deployments.
14511451

14521452
if (!DeploymentEnabled(consensusParams, id)) return;
1453+
if (active_chain_tip == nullptr) return;
1454+
1455+
auto get_state_name = [](const ThresholdState state) -> std::string {
1456+
switch (state) {
1457+
case ThresholdState::DEFINED: return "defined";
1458+
case ThresholdState::STARTED: return "started";
1459+
case ThresholdState::LOCKED_IN: return "locked_in";
1460+
case ThresholdState::ACTIVE: return "active";
1461+
case ThresholdState::FAILED: return "failed";
1462+
}
1463+
return "invalid";
1464+
};
14531465

14541466
UniValue bip9(UniValue::VOBJ);
1455-
const ThresholdState thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id);
1456-
switch (thresholdState) {
1457-
case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
1458-
case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
1459-
case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
1460-
case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
1461-
case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
1462-
}
1463-
const bool has_signal = (ThresholdState::STARTED == thresholdState || ThresholdState::LOCKED_IN == thresholdState);
1467+
1468+
const ThresholdState next_state = g_versionbitscache.State(active_chain_tip, consensusParams, id);
1469+
const ThresholdState current_state = g_versionbitscache.State(active_chain_tip->pprev, consensusParams, id);
1470+
1471+
const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
1472+
1473+
// BIP9 parameters
14641474
if (has_signal) {
14651475
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
14661476
}
14671477
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
14681478
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
1469-
int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id);
1470-
bip9.pushKV("since", since_height);
1479+
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
1480+
1481+
// BIP9 status
1482+
bip9.pushKV("status", get_state_name(current_state));
1483+
bip9.pushKV("since", g_versionbitscache.StateSinceHeight(active_chain_tip->pprev, consensusParams, id));
1484+
bip9.pushKV("status-next", get_state_name(next_state));
1485+
1486+
// BIP9 signalling status, if applicable
14711487
if (has_signal) {
14721488
UniValue statsUV(UniValue::VOBJ);
1473-
BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id);
1489+
std::vector<bool> signals;
1490+
BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id, &signals);
14741491
statsUV.pushKV("period", statsStruct.period);
14751492
statsUV.pushKV("elapsed", statsStruct.elapsed);
14761493
statsUV.pushKV("count", statsStruct.count);
1477-
if (ThresholdState::LOCKED_IN != thresholdState) {
1494+
if (ThresholdState::LOCKED_IN != current_state) {
14781495
statsUV.pushKV("threshold", statsStruct.threshold);
14791496
statsUV.pushKV("possible", statsStruct.possible);
14801497
}
14811498
bip9.pushKV("statistics", statsUV);
1499+
1500+
std::string sig;
1501+
sig.reserve(signals.size());
1502+
for (const bool s : signals) {
1503+
sig.push_back(s ? '#' : '-');
1504+
}
1505+
bip9.pushKV("signalling", sig);
14821506
}
1483-
bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height);
14841507

14851508
UniValue rv(UniValue::VOBJ);
14861509
rv.pushKV("type", "bip9");
1487-
rv.pushKV("bip9", bip9);
1488-
if (ThresholdState::ACTIVE == thresholdState) {
1489-
rv.pushKV("height", since_height);
1510+
if (ThresholdState::ACTIVE == next_state) {
1511+
rv.pushKV("height", g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id));
14901512
}
1491-
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
1513+
rv.pushKV("active", ThresholdState::ACTIVE == next_state);
1514+
rv.pushKV("bip9", bip9);
14921515

14931516
softforks.pushKV(DeploymentName(id), rv);
14941517
}
14951518

1519+
namespace {
1520+
/* TODO: when -dprecatedrpc=softforks is removed, drop these */
1521+
UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams);
1522+
extern const std::vector<RPCResult> RPCHelpForDeployment;
1523+
}
1524+
1525+
// used by rest.cpp:rest_chaininfo, so cannot be static
14961526
RPCHelpMan getblockchaininfo()
14971527
{
1528+
/* TODO: from v24, remove -deprecatedrpc=softforks */
14981529
return RPCHelpMan{"getblockchaininfo",
14991530
"Returns an object containing various state info regarding blockchain processing.\n",
15001531
{},
@@ -1516,31 +1547,11 @@ RPCHelpMan getblockchaininfo()
15161547
{RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
15171548
{RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
15181549
{RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
1519-
{RPCResult::Type::OBJ_DYN, "softforks", "status of softforks",
1550+
{RPCResult::Type::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks",
15201551
{
15211552
{RPCResult::Type::OBJ, "xxxx", "name of the softfork",
1522-
{
1523-
{RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1524-
{RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
1525-
{
1526-
{RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
1527-
{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)"},
1528-
{RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
1529-
{RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1530-
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1531-
{RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1532-
{RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
1533-
{
1534-
{RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
1535-
{RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
1536-
{RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
1537-
{RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
1538-
{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)"},
1539-
}},
1540-
}},
1541-
{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)"},
1542-
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
1543-
}},
1553+
RPCHelpForDeployment
1554+
},
15441555
}},
15451556
{RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
15461557
}},
@@ -1588,7 +1599,45 @@ RPCHelpMan getblockchaininfo()
15881599
}
15891600
}
15901601

1591-
const Consensus::Params& consensusParams = Params().GetConsensus();
1602+
if (IsDeprecatedRPCEnabled("softforks")) {
1603+
const Consensus::Params& consensusParams = Params().GetConsensus();
1604+
obj.pushKV("softforks", DeploymentInfo(tip, consensusParams));
1605+
}
1606+
1607+
obj.pushKV("warnings", GetWarnings(false).original);
1608+
return obj;
1609+
},
1610+
};
1611+
}
1612+
1613+
namespace {
1614+
const std::vector<RPCResult> RPCHelpForDeployment{
1615+
{RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1616+
{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)"},
1617+
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
1618+
{RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
1619+
{
1620+
{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)"},
1621+
{RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
1622+
{RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1623+
{RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1624+
{RPCResult::Type::STR, "status", "bip9 status of specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
1625+
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1626+
{RPCResult::Type::STR, "status-next", "bip9 status of next block"},
1627+
{RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
1628+
{
1629+
{RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
1630+
{RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
1631+
{RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
1632+
{RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
1633+
{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)"},
1634+
}},
1635+
{RPCResult::Type::STR, "signalling", "indicates blocks that signalled with a # and blocks that did not with a -"},
1636+
}},
1637+
};
1638+
1639+
UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams)
1640+
{
15921641
UniValue softforks(UniValue::VOBJ);
15931642
SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB);
15941643
SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG);
@@ -1597,11 +1646,53 @@ RPCHelpMan getblockchaininfo()
15971646
SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT);
15981647
SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
15991648
SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT);
1600-
obj.pushKV("softforks", softforks);
1649+
return softforks;
1650+
}
1651+
} // anon namespace
16011652

1602-
obj.pushKV("warnings", GetWarnings(false).original);
1603-
return obj;
1604-
},
1653+
static RPCHelpMan getdeploymentinfo()
1654+
{
1655+
return RPCHelpMan{"getdeploymentinfo",
1656+
"Returns an object containing various state info regarding soft-forks.",
1657+
{
1658+
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"chain tip"}, "The block hash at which to query fork state"},
1659+
},
1660+
RPCResult{
1661+
RPCResult::Type::OBJ, "", "", {
1662+
{RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
1663+
{RPCResult::Type::NUM, "height", "requested block height (or tip)"},
1664+
{RPCResult::Type::OBJ, "deployments", "", {
1665+
{RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
1666+
}},
1667+
}
1668+
},
1669+
RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") },
1670+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1671+
{
1672+
ChainstateManager& chainman = EnsureAnyChainman(request.context);
1673+
LOCK(cs_main);
1674+
CChainState& active_chainstate = chainman.ActiveChainstate();
1675+
1676+
const CBlockIndex* tip;
1677+
if (request.params[0].isNull()) {
1678+
tip = active_chainstate.m_chain.Tip();
1679+
CHECK_NONFATAL(tip);
1680+
} else {
1681+
uint256 hash(ParseHashV(request.params[0], "blockhash"));
1682+
tip = chainman.m_blockman.LookupBlockIndex(hash);
1683+
if (!tip) {
1684+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1685+
}
1686+
}
1687+
1688+
const Consensus::Params& consensusParams = Params().GetConsensus();
1689+
1690+
UniValue deploymentinfo(UniValue::VOBJ);
1691+
deploymentinfo.pushKV("hash", tip->GetBlockHash().ToString());
1692+
deploymentinfo.pushKV("height", tip->nHeight);
1693+
deploymentinfo.pushKV("deployments", DeploymentInfo(tip, consensusParams));
1694+
return deploymentinfo;
1695+
},
16051696
};
16061697
}
16071698

@@ -2751,6 +2842,7 @@ static const CRPCCommand commands[] =
27512842
{ "blockchain", &getblockheader, },
27522843
{ "blockchain", &getchaintips, },
27532844
{ "blockchain", &getdifficulty, },
2845+
{ "blockchain", &getdeploymentinfo, },
27542846
{ "blockchain", &getmempoolancestors, },
27552847
{ "blockchain", &getmempooldescendants, },
27562848
{ "blockchain", &getmempoolentry, },

src/test/fuzz/rpc.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
120120
"getchaintips",
121121
"getchaintxstats",
122122
"getconnectioncount",
123+
"getdeploymentinfo",
123124
"getdescriptorinfo",
124125
"getdifficulty",
125126
"getindexinfo",

src/test/fuzz/versionbits.cpp

+24-11
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, std::vector<bool>* signals=nullptr) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params, signals); }
5555

5656
bool Condition(int32_t version) const
5757
{
@@ -220,7 +220,14 @@ 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);
230+
std::vector<bool> last_signals{};
224231

225232
int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1);
226233
assert(exp_since <= prev_next_height);
@@ -241,17 +248,25 @@ FUZZ_TARGET_INIT(versionbits, initialize)
241248
assert(state == exp_state);
242249
assert(since == exp_since);
243250

244-
// GetStateStatistics may crash when state is not STARTED
245-
if (state != ThresholdState::STARTED) continue;
246-
247251
// check that after mining this block stats change as expected
248-
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
252+
std::vector<bool> signals;
253+
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block, &signals);
254+
const BIP9Stats stats_no_signals = checker.GetStateStatisticsFor(current_block);
255+
assert(stats.period == stats_no_signals.period && stats.threshold == stats_no_signals.threshold
256+
&& stats.elapsed == stats_no_signals.elapsed && stats.count == stats_no_signals.count
257+
&& stats.possible == stats_no_signals.possible);
258+
249259
assert(stats.period == period);
250260
assert(stats.threshold == threshold);
251261
assert(stats.elapsed == b);
252262
assert(stats.count == last_stats.count + (signal ? 1 : 0));
253263
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
254264
last_stats = stats;
265+
266+
assert(signals.size() == last_signals.size() + 1);
267+
assert(signals.back() == signal);
268+
last_signals.push_back(signal);
269+
assert(signals == last_signals);
255270
}
256271

257272
if (exp_state == ThresholdState::STARTED) {
@@ -265,14 +280,12 @@ FUZZ_TARGET_INIT(versionbits, initialize)
265280
CBlockIndex* current_block = blocks.mine_block(signal);
266281
assert(checker.Condition(current_block) == signal);
267282

268-
// GetStateStatistics is safe on a period boundary
269-
// and has progressed to a new period
270283
const BIP9Stats stats = checker.GetStateStatisticsFor(current_block);
271284
assert(stats.period == period);
272285
assert(stats.threshold == threshold);
273-
assert(stats.elapsed == 0);
274-
assert(stats.count == 0);
275-
assert(stats.possible == true);
286+
assert(stats.elapsed == period);
287+
assert(stats.count == blocks_sig);
288+
assert(stats.possible == (stats.count + period >= stats.elapsed + threshold));
276289

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

0 commit comments

Comments
 (0)