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 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 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 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 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* 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 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 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* 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* 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* 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* 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):