diff options
author | MarcoFalke <falke.marco@gmail.com> | 2022-01-28 08:45:43 +0100 |
---|---|---|
committer | MarcoFalke <falke.marco@gmail.com> | 2022-01-28 08:46:03 +0100 |
commit | d4e92d843650b0480b86d15ce46ed02b6fd9b5be (patch) | |
tree | d3a995005a565a172fc67cae68fbdc97831d035c | |
parent | a4f7c41271edd28e2b2bf870174d4db155830c97 (diff) | |
parent | a3809228917b8f750090c8bfec8e283391dbb524 (diff) | |
download | bitcoin-d4e92d843650b0480b86d15ce46ed02b6fd9b5be.tar.xz |
Merge bitcoin/bitcoin#23508: Add getdeploymentinfo RPC
a3809228917b8f750090c8bfec8e283391dbb524 Release notes for getdeploymentinfo rpc (Anthony Towns)
240cad09baefcf363cce36a4b2795122adfce27f rpc: getdeploymentinfo: include signalling info (Anthony Towns)
376c0c6dae2bebbb3e1352377e71fb1996d09f64 rpc: getdeploymentinfo: include block hash/height (Anthony Towns)
a7469bcd35692d56f57e91b3f21d30855bdf6531 rpc: getdeploymentinfo: change stats to always refer to current period (Anthony Towns)
7f15c1841b98de6931a7ac68e16635a05d3e96cf rpc: getdeploymentinfo: allow specifying a blockhash other than tip (Anthony Towns)
fd826130a0a4e67fdc26f8064f4ecb4ff79b3333 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 a3809228917b8f750090c8bfec8e283391dbb524
Sjors:
tACK a3809228917b8f750090c8bfec8e283391dbb524
fjahr:
tACK a3809228917b8f750090c8bfec8e283391dbb524
Tree-SHA512: 7417d733b47629f229c5128586569909250481a3e94356c52fe67a03fd42cd81745246e384b98c4115fb61587714c879e4bc3e5f5c74407d9f8f6773472a33cb
-rw-r--r-- | doc/release-notes-23508.md | 9 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 186 | ||||
-rw-r--r-- | src/test/fuzz/rpc.cpp | 1 | ||||
-rw-r--r-- | src/test/fuzz/versionbits.cpp | 35 | ||||
-rw-r--r-- | src/versionbits.cpp | 31 | ||||
-rw-r--r-- | src/versionbits.h | 12 | ||||
-rwxr-xr-x | test/functional/feature_cltv.py | 2 | ||||
-rwxr-xr-x | test/functional/feature_dersig.py | 2 | ||||
-rwxr-xr-x | test/functional/rpc_blockchain.py | 57 | ||||
-rwxr-xr-x | test/functional/rpc_signrawtransaction.py | 4 | ||||
-rw-r--r-- | test/functional/test_framework/util.py | 2 |
11 files changed, 250 insertions, 91 deletions
diff --git a/doc/release-notes-23508.md b/doc/release-notes-23508.md new file mode 100644 index 0000000000..098654e00b --- /dev/null +++ b/doc/release-notes-23508.md @@ -0,0 +1,9 @@ +Updated RPCs +------------ + +- Information on soft fork status has been moved from `getblockchaininfo` + to `getdeploymentinfo` which allows querying soft fork status at any + block, rather than just at the chain tip. Inclusion of soft fork + status in `getblockchaininfo` can currently be restored using the + configuration `-deprecatedrpc=softforks`, but this will be removed in + a future release. (#23508) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 5f5db967c7..f4930cd839 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1438,7 +1438,7 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); - // getblockchaininfo reports the softfork as active from when the chain height is + // getdeploymentinfo reports the softfork as active from when the chain height is // one below the activation height rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep)); rv.pushKV("height", params.DeploymentHeight(dep)); @@ -1450,51 +1450,82 @@ 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); + std::vector<bool> signals; + BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id, &signals); 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); + + std::string sig; + sig.reserve(signals.size()); + for (const bool s : signals) { + sig.push_back(s ? '#' : '-'); + } + bip9.pushKV("signalling", sig); } - 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); } +namespace { +/* TODO: when -dprecatedrpc=softforks is removed, drop these */ +UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams); +extern const std::vector<RPCResult> RPCHelpForDeployment; +} + +// used by rest.cpp:rest_chaininfo, so cannot be static RPCHelpMan getblockchaininfo() { + /* TODO: from v24, remove -deprecatedrpc=softforks */ return RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, @@ -1516,31 +1547,11 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"}, {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"}, {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"}, - {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks", + {RPCResult::Type::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", - { - {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, - {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::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"}, - {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"}, - {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"}, - {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, - {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"}, - }}, + RPCHelpForDeployment + }, }}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, @@ -1588,7 +1599,45 @@ RPCHelpMan getblockchaininfo() } } - const Consensus::Params& consensusParams = Params().GetConsensus(); + if (IsDeprecatedRPCEnabled("softforks")) { + const Consensus::Params& consensusParams = Params().GetConsensus(); + obj.pushKV("softforks", DeploymentInfo(tip, consensusParams)); + } + + obj.pushKV("warnings", GetWarnings(false).original); + return obj; +}, + }; +} + +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::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, "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"}, + {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"}, + {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"}, + {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, + {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::STR, "signalling", "indicates blocks that signalled with a # and blocks that did not with a -"}, + }}, +}; + +UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams) +{ UniValue softforks(UniValue::VOBJ); SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); @@ -1597,11 +1646,53 @@ RPCHelpMan getblockchaininfo() SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); - obj.pushKV("softforks", softforks); + return softforks; +} +} // anon namespace - obj.pushKV("warnings", GetWarnings(false).original); - return obj; -}, +static RPCHelpMan getdeploymentinfo() +{ + return RPCHelpMan{"getdeploymentinfo", + "Returns an object containing various state info regarding soft-forks.", + { + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"chain tip"}, "The block hash at which to query fork state"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "hash", "requested block hash (or tip)"}, + {RPCResult::Type::NUM, "height", "requested block height (or tip)"}, + {RPCResult::Type::OBJ, "deployments", "", { + {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment} + }}, + } + }, + RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + ChainstateManager& chainman = EnsureAnyChainman(request.context); + LOCK(cs_main); + CChainState& active_chainstate = chainman.ActiveChainstate(); + + const CBlockIndex* tip; + if (request.params[0].isNull()) { + tip = active_chainstate.m_chain.Tip(); + CHECK_NONFATAL(tip); + } else { + uint256 hash(ParseHashV(request.params[0], "blockhash")); + tip = chainman.m_blockman.LookupBlockIndex(hash); + if (!tip) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + } + + const Consensus::Params& consensusParams = Params().GetConsensus(); + + UniValue deploymentinfo(UniValue::VOBJ); + deploymentinfo.pushKV("hash", tip->GetBlockHash().ToString()); + deploymentinfo.pushKV("height", tip->nHeight); + deploymentinfo.pushKV("deployments", DeploymentInfo(tip, consensusParams)); + return deploymentinfo; + }, }; } @@ -2751,6 +2842,7 @@ static const CRPCCommand commands[] = { "blockchain", &getblockheader, }, { "blockchain", &getchaintips, }, { "blockchain", &getdifficulty, }, + { "blockchain", &getdeploymentinfo, }, { "blockchain", &getmempoolancestors, }, { "blockchain", &getmempooldescendants, }, { "blockchain", &getmempoolentry, }, diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 73bfb7ff55..03a84b697d 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -120,6 +120,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "getchaintips", "getchaintxstats", "getconnectioncount", + "getdeploymentinfo", "getdescriptorinfo", "getdifficulty", "getindexinfo", diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index cf95c0b9bf..95eb71099d 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -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, std::vector<bool>* signals=nullptr) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params, signals); } bool Condition(int32_t version) const { @@ -220,7 +220,14 @@ 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); + std::vector<bool> last_signals{}; int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1); assert(exp_since <= prev_next_height); @@ -241,17 +248,25 @@ 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); + std::vector<bool> signals; + const BIP9Stats stats = checker.GetStateStatisticsFor(current_block, &signals); + const BIP9Stats stats_no_signals = checker.GetStateStatisticsFor(current_block); + assert(stats.period == stats_no_signals.period && stats.threshold == stats_no_signals.threshold + && stats.elapsed == stats_no_signals.elapsed && stats.count == stats_no_signals.count + && stats.possible == stats_no_signals.possible); + assert(stats.period == period); assert(stats.threshold == threshold); assert(stats.elapsed == b); assert(stats.count == last_stats.count + (signal ? 1 : 0)); assert(stats.possible == (stats.count + period >= stats.elapsed + threshold)); last_stats = stats; + + assert(signals.size() == last_signals.size() + 1); + assert(signals.back() == signal); + last_signals.push_back(signal); + assert(signals == last_signals); } if (exp_state == ThresholdState::STARTED) { @@ -265,14 +280,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); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index a9264e6116..36815fba17 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -98,29 +98,38 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* return state; } -BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const +BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, std::vector<bool>* signalling_blocks) const { BIP9Stats stats = {}; 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 blocks_in_period = 1 + (pindex->nHeight % stats.period); + + // Reset signalling_blocks + if (signalling_blocks) { + signalling_blocks->assign(blocks_in_period, false); + } // 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++; + do { + ++elapsed; + --blocks_in_period; + if (Condition(currentIndex, params)) { + ++count; + if (signalling_blocks) signalling_blocks->at(blocks_in_period) = true; + } currentIndex = currentIndex->pprev; - } + } while(blocks_in_period > 0); + stats.elapsed = elapsed; stats.count = count; stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count); @@ -196,9 +205,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, std::vector<bool>* signalling_blocks) { - return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params); + return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindex, params, signalling_blocks); } int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) diff --git a/src/versionbits.h b/src/versionbits.h index 25ddd6fa5d..1b3fa11e61 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -64,8 +64,10 @@ 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 */ - BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const; + /** 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<bool>* 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; @@ -82,8 +84,10 @@ 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. + * 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<bool>* signalling_blocks = nullptr); static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 7fd0d0140b..9d32749a08 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -92,7 +92,7 @@ class BIP65Test(BitcoinTestFramework): self.rpc_timeout = 480 def test_cltv_info(self, *, is_active): - assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip65'], { + assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['bip65'], { "active": is_active, "height": CLTV_HEIGHT, "type": "buried", diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index f35ce7e0c9..9a46839969 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -60,7 +60,7 @@ class BIP66Test(BitcoinTestFramework): return self.miniwallet.create_self_transfer(utxo_to_spend=utxo_to_spend)['tx'] def test_dersig_info(self, *, is_active): - assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip66'], + assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['bip66'], { "active": is_active, "height": DERSIG_HEIGHT, diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 4dd4899f74..2d96ba74b5 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -6,6 +6,7 @@ Test the following RPCs: - getblockchaininfo + - getdeploymentinfo - getchaintxstats - gettxoutsetinfo - getblockheader @@ -79,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): @@ -115,7 +117,6 @@ class BlockchainTest(BitcoinTestFramework): 'mediantime', 'pruned', 'size_on_disk', - 'softforks', 'time', 'verificationprogress', 'warnings', @@ -159,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() @@ -177,7 +173,13 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(res['prune_target_size'], 576716800) assert_greater_than(res['size_on_disk'], 0) - assert_equal(res['softforks'], { + def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next): + assert height >= 144 and height <= 287 + + assert_equal(gdi_result, { + "hash": blockhash, + "height": height, + "deployments": { 'bip34': {'type': 'buried', 'active': True, 'height': 2}, 'bip66': {'type': 'buried', 'active': True, 'height': 3}, 'bip65': {'type': 'buried', 'active': True, 'height': 4}, @@ -186,36 +188,65 @@ 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, + 'signalling': '#'*(height-143), }, '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 } + } }) + 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") diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index e648040278..a2091b4ece 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -270,7 +270,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): getcontext().prec = 8 # Make sure CSV is active - assert self.nodes[0].getblockchaininfo()['softforks']['csv']['active'] + assert self.nodes[0].getdeploymentinfo()['deployments']['csv']['active'] # Create a P2WSH script with CSV script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP]) @@ -305,7 +305,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): getcontext().prec = 8 # Make sure CLTV is active - assert self.nodes[0].getblockchaininfo()['softforks']['bip65']['active'] + assert self.nodes[0].getdeploymentinfo()['deployments']['bip65']['active'] # Create a P2WSH script with CLTV script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP]) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 195af14914..dabde13bf1 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -438,7 +438,7 @@ def delete_cookie_file(datadir, chain): def softfork_active(node, key): """Return whether a softfork is active.""" - return node.getblockchaininfo()['softforks'][key]['active'] + return node.getdeploymentinfo()['deployments'][key]['active'] def set_node_times(nodes, t): |