From 50d593c24c48baec8fe8c5955a733eeddd9ccab5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:11:35 +0530 Subject: [PATCH 01/18] fix: annotate `getblockchaininfo[softforks]` as `OBJ_DYN` --- src/rpc/blockchain.cpp | 43 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 505fce3303a96..8f9e55d463013 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1461,31 +1461,34 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (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, "softforks", "status of softforks in progress", + {RPCResult::Type::OBJ_DYN, "softforks", "status of softforks in progress", { - {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""}, - {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)", + {RPCResult::Type::OBJ, "xxxx", "name of the softfork", { - {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::BOOL, "ehf", "returns true for EHF activated forks"}, - {RPCResult::Type::NUM, "ehf_height", /*optional=*/true, "the minimum height when miner's signals for the deployment matter. Below this height miner signaling cannot trigger hard fork lock-in. Not specified for non-EHF forks"}, - {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, - {RPCResult::Type::NUM, "activation_height", "expected activation height for this softfork (only for \"locked_in\" status)"}, - {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::STR, "type", "one of \"buried\", \"bip9\""}, + {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)", { - {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, "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::BOOL, "ehf", "returns true for EHF activated forks"}, + {RPCResult::Type::NUM, "ehf_height", /*optional=*/true, "the minimum height when miner's signals for the deployment matter. Below this height miner signaling cannot trigger hard fork lock-in. Not specified for non-EHF forks"}, + {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, + {RPCResult::Type::NUM, "activation_height", "expected activation height for this softfork (only for \"locked_in\" status)"}, + {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"}, }}, - {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::STR, "warnings", "any network and blockchain warnings"}, }}, From 464ec018d060dc1afa7fe93c0ecd48a7350e2acf Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:22:21 +0530 Subject: [PATCH 02/18] test: drop extra `generate`s from `wallet_signrawtransactionwithwallet` bitcoin#22550 adds assertions to validate CSV/CLTV activation and replaces the `generate()` calls with a helper `generate_to_height()` but bitcoin#22818 activates all forks from block 1 by default, so the `generate_to_height()` calls have been dropped. This leaves us with the `generate()` calls being swapped for assertions. --- test/functional/wallet_signrawtransactionwithwallet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/wallet_signrawtransactionwithwallet.py b/test/functional/wallet_signrawtransactionwithwallet.py index 3127a93a85342..aaf02e7b5e7e0 100755 --- a/test/functional/wallet_signrawtransactionwithwallet.py +++ b/test/functional/wallet_signrawtransactionwithwallet.py @@ -130,7 +130,7 @@ def test_signing_with_csv(self): getcontext().prec = 8 # Make sure CSV is active - self.generate(self.nodes[0], 500) + assert self.nodes[0].getblockchaininfo()['softforks']['csv']['active'] # Create a P2SH script with CSV script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE]) @@ -159,11 +159,11 @@ def test_signing_with_cltv(self): self.nodes[0].walletpassphrase("password", 9999) getcontext().prec = 8 - # Make sure CSV is active - self.generate(self.nodes[0], 1500) + # Make sure CLTV is active + assert self.nodes[0].getblockchaininfo()['softforks']['bip65']['active'] # Create a P2SH script with CLTV - script = CScript([1000, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) + script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) address = script_to_p2sh(script) # Fund that address and make the spend From 0610617e69212fc0490bd448ba56015216c692b2 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 17 Oct 2025 07:57:40 +0530 Subject: [PATCH 03/18] merge bitcoin#23345: Drop unneeded dependencies for bitcoin-wallet tool --- configure.ac | 6 ++++++ src/Makefile.am | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d216ffd04eba3..be073c7089088 100644 --- a/configure.ac +++ b/configure.ac @@ -1502,6 +1502,12 @@ if test "$use_usdt" != "no"; then fi AM_CONDITIONAL([ENABLE_USDT_TRACEPOINTS], [test "$use_usdt" = "yes"]) +if test "$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_util$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nonononononono"; then + use_upnp=no + use_natpmp=no + use_zmq=no +fi + dnl Check for libminiupnpc (optional) if test "$use_upnp" != "no"; then TEMP_CPPFLAGS="$CPPFLAGS" diff --git a/src/Makefile.am b/src/Makefile.am index 598d9c5389523..9303186627fa3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1086,7 +1086,21 @@ dash_wallet_SOURCES = bitcoin-wallet.cpp dash_wallet_CPPFLAGS = $(bitcoin_bin_cppflags) dash_wallet_CXXFLAGS = $(bitcoin_bin_cxxflags) dash_wallet_LDFLAGS = $(bitcoin_bin_ldflags) -dash_wallet_LDADD = $(LIBBITCOIN_WALLET_TOOL) $(bitcoin_bin_ldadd) +dash_wallet_LDADD = \ + $(LIBBITCOIN_WALLET_TOOL) \ + $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UTIL) \ + $(LIBUNIVALUE) \ + $(LIBBITCOIN_CONSENSUS) \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBDASHBLS) \ + $(LIBSECP256K1) \ + $(BACKTRACE_LIB) \ + $(BOOST_LIBS) \ + $(BDB_LIBS) \ + $(SQLITE_LIBS) \ + $(GMP_LIBS) if TARGET_WINDOWS dash_wallet_SOURCES += dash-wallet-res.rc From 150c47d5c943a5982078e828b5c3070a25f56891 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:30:12 +0530 Subject: [PATCH 04/18] merge bitcoin#23508: Add getdeploymentinfo RPC --- doc/release-notes-6901.md | 9 + src/rpc/blockchain.cpp | 205 +++++++++++++----- src/test/fuzz/rpc.cpp | 1 + src/test/fuzz/versionbits.cpp | 35 ++- src/versionbits.cpp | 32 ++- src/versionbits.h | 13 +- test/functional/feature_asset_locks.py | 2 +- test/functional/feature_cltv.py | 2 +- test/functional/feature_dersig.py | 2 +- test/functional/feature_governance.py | 12 +- test/functional/feature_mnehf.py | 5 +- .../feature_new_quorum_type_activation.py | 2 +- test/functional/rpc_blockchain.py | 82 +++++-- test/functional/test_framework/blocktools.py | 2 +- test/functional/test_framework/util.py | 4 +- .../wallet_signrawtransactionwithwallet.py | 4 +- 16 files changed, 291 insertions(+), 121 deletions(-) create mode 100644 doc/release-notes-6901.md diff --git a/doc/release-notes-6901.md b/doc/release-notes-6901.md new file mode 100644 index 0000000000000..cb568546d97f2 --- /dev/null +++ b/doc/release-notes-6901.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. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 8f9e55d463013..9bf93cd1f9247 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1375,7 +1375,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)); @@ -1387,17 +1387,27 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: // 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); } @@ -1407,38 +1417,60 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: if (auto it = signals.find(consensusParams.vDeployments[id].bit); it != signals.end()) { bip9.pushKV("ehf_height", it->second); } - int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id); + bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); + + // BIP9 status + bip9.pushKV("status", get_state_name(current_state)); + int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip->pprev, consensusParams, id); bip9.pushKV("since", since_height); + 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); } - if (ThresholdState::LOCKED_IN == thresholdState) { + if (ThresholdState::LOCKED_IN == current_state) { bip9.pushKV("activation_height", since_height + static_cast(consensusParams.vDeployments[id].nWindowSize)); } - 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 -deprecatedrpc=softforks is removed, drop these */ +UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf_signals, 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", {}, @@ -1461,34 +1493,11 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (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 in progress", + {RPCResult::Type::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks in progress", { {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::BOOL, "ehf", "returns true for EHF activated forks"}, - {RPCResult::Type::NUM, "ehf_height", /*optional=*/true, "the minimum height when miner's signals for the deployment matter. Below this height miner signaling cannot trigger hard fork lock-in. Not specified for non-EHF forks"}, - {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, - {RPCResult::Type::NUM, "activation_height", "expected activation height for this softfork (only for \"locked_in\" status)"}, - {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"}, }}, @@ -1509,8 +1518,6 @@ RPCHelpMan getblockchaininfo() const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; const int height{tip.nHeight}; - const auto ehfSignals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(&tip)}; - UniValue obj(UniValue::VOBJ); if (args.IsArgSet("-devnet")) { obj.pushKV("chain", args.GetDevNetName()); @@ -1539,7 +1546,49 @@ RPCHelpMan getblockchaininfo() } } - const Consensus::Params& consensusParams = Params().GetConsensus(); + if (IsDeprecatedRPCEnabled("softforks")) { + const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(&tip)}; + const Consensus::Params& consensusParams = Params().GetConsensus(); + obj.pushKV("softforks", DeploymentInfo(&tip, ehf_signals, 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::BOOL, "ehf", "returns true for EHF activated forks"}, + {RPCResult::Type::NUM, "ehf_height", /*optional=*/true, "the minimum height when miner's signals for the deployment matter. Below this height miner signaling cannot trigger hard fork lock-in. Not specified for non-EHF forks"}, + {RPCResult::Type::NUM, "activation_height", "expected activation height for this softfork (only for \"locked_in\" status)"}, + {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 CMNHFManager::Signals& ehf_signals, const Consensus::Params& consensusParams) +{ UniValue softforks(UniValue::VOBJ); for (auto deploy : { /* sorted by activation block */ Consensus::DEPLOYMENT_HEIGHTINCB, @@ -1558,18 +1607,63 @@ RPCHelpMan getblockchaininfo() Consensus::DEPLOYMENT_MN_RR, Consensus::DEPLOYMENT_WITHDRAWALS, }) { - SoftForkDescPushBack(&tip, softforks, consensusParams, deploy); + SoftForkDescPushBack(tip, softforks, consensusParams, deploy); } for (auto ehf_deploy : { /* sorted by activation block */ Consensus::DEPLOYMENT_V24, Consensus::DEPLOYMENT_TESTDUMMY }) { - SoftForkDescPushBack(&tip, ehfSignals, softforks, consensusParams, ehf_deploy); + SoftForkDescPushBack(tip, ehf_signals, softforks, consensusParams, ehf_deploy); } - 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 + { + const NodeContext& node = EnsureAnyNodeContext(request.context); + + ChainstateManager& chainman = EnsureChainman(node); + 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(); + const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(tip)}; + + UniValue deploymentinfo(UniValue::VOBJ); + deploymentinfo.pushKV("hash", tip->GetBlockHash().ToString()); + deploymentinfo.pushKV("height", tip->nHeight); + deploymentinfo.pushKV("deployments", DeploymentInfo(tip, ehf_signals, consensusParams)); + return deploymentinfo; + }, }; } @@ -2780,9 +2874,10 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) {"blockchain", &getblockhash}, {"blockchain", &getblockheader}, {"blockchain", &getblockheaders}, - {"blockchain", &getmerkleblocks}, {"blockchain", &getchaintips}, + {"blockchain", &getdeploymentinfo}, {"blockchain", &getdifficulty}, + {"blockchain", &getmerkleblocks}, {"blockchain", &getspecialtxes}, {"blockchain", &gettxout}, {"blockchain", &gettxoutsetinfo}, diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index b95b93425e8e1..ab53f9ceaee3b 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -118,6 +118,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 a0a7369909b9d..5db0854e93e25 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -52,7 +52,7 @@ class TestConditionChecker : public AbstractThresholdConditionChecker 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, m_cache); } + BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, std::vector* signals=nullptr) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindex, dummy_params, m_cache, signals); } bool Condition(int32_t version) const { @@ -221,7 +221,14 @@ FUZZ_TARGET(versionbits, .init = initialize_versionbits) 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); @@ -242,17 +249,25 @@ FUZZ_TARGET(versionbits, .init = initialize_versionbits) 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) { @@ -266,14 +281,12 @@ FUZZ_TARGET(versionbits, .init = initialize_versionbits) 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 f3386d4cfb946..b1dd2b39517fa 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -131,21 +131,25 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* return state; } -BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, ThresholdConditionCache& cache) const +BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, ThresholdConditionCache& cache, std::vector* signalling_blocks) const { BIP9Stats stats = {}; stats.period = Period(params); stats.threshold = Threshold(params, 0); - 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); + } // Re-calculate current threshold + const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period)); int nAttempt{0}; const ThresholdState state = GetStateFor(pindexEndOfPrevPeriod, params, cache); if (state == ThresholdState::STARTED) { @@ -155,14 +159,20 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI stats.threshold = Threshold(params, nAttempt); // 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); @@ -263,10 +273,10 @@ 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) { LOCK(m_mutex); - return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params, m_caches[pos]); + return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindex, params, m_caches[pos], 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 036fba26b4233..46f5bb48d8cae 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -10,6 +10,7 @@ #include #include +#include /** What block version to use for new blocks (pre versionbits) */ static const int32_t VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4; @@ -66,8 +67,10 @@ class AbstractThresholdConditionChecker { virtual int Threshold(const Consensus::Params& params, int nAttempt) 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, ThresholdConditionCache& cache) 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, ThresholdConditionCache& cache, 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; @@ -84,8 +87,10 @@ class VersionBitsCache 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. */ - BIP9Stats Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** 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 + */ + BIP9Stats Statistics(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos, std::vector* signalling_blocks = nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index d482d1061de4e..8f092dcb9a442 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -248,7 +248,7 @@ def run_test(self): self.set_sporks() - assert_equal(self.nodes[0].getblockchaininfo()['softforks']['v20']['active'], True) + assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['v20']['active'], True) for _ in range(2): self.dynamically_add_masternode(evo=True) diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 907811c47250e..0e7ba3f61d7d4 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -99,7 +99,7 @@ def set_test_params(self): 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 604a471c4da0c..4b840cf85df81 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -64,7 +64,7 @@ def create_tx(self, input_txid): 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/feature_governance.py b/test/functional/feature_governance.py index 82025d06d6cdc..ef2f5e7462d41 100755 --- a/test/functional/feature_governance.py +++ b/test/functional/feature_governance.py @@ -24,7 +24,7 @@ def set_test_params(self): self.delay_v20_and_mn_rr(height=160) def check_superblockbudget(self, v20_active): - v20_state = self.nodes[0].getblockchaininfo()["softforks"]["v20"] + v20_state = self.nodes[0].getdeploymentinfo()["deployments"]["v20"] assert_equal(v20_state["active"], v20_active) assert_equal(self.nodes[0].getsuperblockbudget(120), self.expected_old_budget) assert_equal(self.nodes[0].getsuperblockbudget(140), self.expected_old_budget) @@ -95,14 +95,14 @@ def run_test(self): self.bump_mocktime(3) self.generate(self.nodes[0], 3, sync_fun=self.sync_blocks()) assert_equal(self.nodes[0].getblockcount(), 137) - assert_equal(self.nodes[0].getblockchaininfo()["softforks"]["v20"]["active"], False) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["v20"]["active"], False) self.check_superblockbudget(False) self.log.info("Check 2nd superblock before v20") self.bump_mocktime(3) self.generate(self.nodes[0], 3, sync_fun=self.sync_blocks()) assert_equal(self.nodes[0].getblockcount(), 140) - assert_equal(self.nodes[0].getblockchaininfo()["softforks"]["v20"]["active"], False) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["v20"]["active"], False) self.check_superblockbudget(False) self.log.info("Prepare proposals") @@ -221,7 +221,7 @@ def run_test(self): self.bump_mocktime(1) self.generate(self.nodes[0], 1, sync_fun=self.no_op) assert_equal(self.nodes[0].getblockcount(), 150) - assert_equal(self.nodes[0].getblockchaininfo()["softforks"]["v20"]["active"], False) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["v20"]["active"], False) self.check_superblockbudget(False) self.log.info("The 'winner' should submit new trigger and vote for it, but it's isolated so no triggers should be found") @@ -361,7 +361,7 @@ def sync_gov(node): self.bump_mocktime(1) self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks()) assert_equal(self.nodes[0].getblockcount(), 180) - assert_equal(self.nodes[0].getblockchaininfo()["softforks"]["v20"]["active"], True) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["v20"]["active"], True) self.log.info("Mine and check a couple more superblocks") for i in range(2): @@ -375,7 +375,7 @@ def sync_gov(node): self.bump_mocktime(1) self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks()) assert_equal(self.nodes[0].getblockcount(), sb_block_height) - assert_equal(self.nodes[0].getblockchaininfo()["softforks"]["v20"]["active"], True) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["v20"]["active"], True) self.check_superblockbudget(True) self.check_superblock() diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 836821d86c2f2..93bb80cdeefa9 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -162,11 +162,13 @@ def run_test(self): self.check_fork('defined') self.generate(node, 1) + self.generate(node, 1) for _ in range(4 // 2): self.check_fork('started') self.generate(node, 2) + self.generate(node, 1) for i in range(4 // 2): self.check_fork('locked_in') @@ -174,6 +176,7 @@ def run_test(self): if i == 1: self.restart_all_nodes() + self.generate(node, 1) self.check_fork('active') fork_active_blockhash = node.getbestblockhash() @@ -194,7 +197,7 @@ def run_test(self): assert tx_sent_2 in node.getblock(ehf_blockhash_2)['tx'] self.log.info(f"Generate some more block to jump to `started` status") - self.generate(node, 4) + self.generate(node, 5) self.check_fork('started') self.restart_node(0) self.check_fork('started') diff --git a/test/functional/feature_new_quorum_type_activation.py b/test/functional/feature_new_quorum_type_activation.py index 678d3bab66aba..fb1eca98b4e2b 100755 --- a/test/functional/feature_new_quorum_type_activation.py +++ b/test/functional/feature_new_quorum_type_activation.py @@ -22,7 +22,7 @@ def set_test_params(self): def run_test(self): self.log.info(get_bip9_details(self.nodes[0], 'testdummy')) assert_equal(get_bip9_details(self.nodes[0], 'testdummy')['status'], 'defined') - self.generate(self.nodes[0], 9, sync_fun=self.no_op) + self.generate(self.nodes[0], 10, sync_fun=self.no_op) assert_equal(get_bip9_details(self.nodes[0], 'testdummy')['status'], 'started') ql = self.nodes[0].quorum("list") assert_equal(len(ql), 3) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 6ffaaa75f0f90..7619734e0fc1f 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 @@ -92,6 +93,7 @@ def run_test(self): self._test_stopatheight() self._test_waitforblockheight() self._test_getblock() + self._test_getdeploymentinfo() self._test_y2106() assert self.nodes[0].verifychain(4, 0) @@ -130,7 +132,6 @@ def _test_getblockchaininfo(self): 'mediantime', 'pruned', 'size_on_disk', - 'softforks', 'time', 'verificationprogress', 'warnings', @@ -175,19 +176,6 @@ def _test_getblockchaininfo(self): '-stopatheight=207', '-prune=550', '-txindex=0', - '-testactivationheight=bip34@2', - '-testactivationheight=dersig@3', - '-testactivationheight=cltv@4', - '-testactivationheight=csv@5', - '-testactivationheight=bip147@6', - '-testactivationheight=dip0001@10', - '-dip3params=411:511', - '-testactivationheight=dip0008@12', - '-testactivationheight=dip0024@13', - '-testactivationheight=brr@14', - '-testactivationheight=v19@15', - '-testactivationheight=v20@412', # no earlier than DIP0003 - '-testactivationheight=mn_rr@413', ]) res = self.nodes[0].getblockchaininfo() @@ -200,7 +188,14 @@ def _test_getblockchaininfo(self): assert res['automatic_pruning'] 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}, @@ -219,35 +214,74 @@ def _test_getblockchaininfo(self): 'v24': { 'type': 'bip9', 'bip9': { - 'status': 'defined', 'start_time': 0, 'timeout': 9223372036854775807, # "v24" does not have a timeout so is set to the max int64 value - 'since': 0, 'min_activation_height': 0, - 'ehf': True + 'since': 0, + 'status': 'defined', + 'status-next': 'defined', + 'ehf': True, }, - 'active': False}, + 'active': False + }, 'testdummy': { 'type': 'bip9', 'bip9': { - 'status': 'started', 'bit': 28, 'start_time': 0, 'timeout': 9223372036854775807, # testdummy does not have a timeout so is set to the max int64 value + 'min_activation_height': 0, 'since': 144, + 'status': 'started', + 'status-next': status_next, 'statistics': { 'period': 144, 'threshold': 108, - 'elapsed': HEIGHT - 143, - 'count': HEIGHT - 143, + 'elapsed': height - 143, + 'count': height - 143, 'possible': True, }, - 'min_activation_height': 0, 'ehf': False, + 'signalling': '#'*(height-143), }, - 'active': False}, + 'active': False + } + } }) + 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=bip147@6', + '-testactivationheight=dip0001@10', + '-dip3params=411:511', + '-testactivationheight=dip0008@12', + '-testactivationheight=dip0024@13', + '-testactivationheight=brr@14', + '-testactivationheight=v19@15', + '-testactivationheight=v20@412', # no earlier than DIP0003 + '-testactivationheight=mn_rr@413', + ]) + + 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_y2106(self): self.log.info("Check that block timestamps work until year 2106") self.generate(self.nodes[0], 8)[-1] diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index d451ab17359a3..a2d2f05cb1dbc 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -120,7 +120,7 @@ def create_block_with_mnpayments(mninfo, node, vtx=None, mn_payee=None, mn_amoun mn_operator_amount = 0 if mn_amount is None: - v20_info = node.getblockchaininfo()['softforks']['v20'] + v20_info = node.getdeploymentinfo()['deployments']['v20'] mn_amount_total = get_masternode_payment(height, coinbasevalue, v20_info['active']) mn_operator_amount = mn_amount_total * operator_reward // 100 mn_amount = mn_amount_total - mn_operator_amount diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 107bd72cc6c3b..e44dbdf56eaf6 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -505,12 +505,12 @@ def get_chain_conf_names(chain): def get_bip9_details(node, key): """Return extra info about bip9 softfork""" - return node.getblockchaininfo()['softforks'][key]['bip9'] + return node.getdeploymentinfo()['deployments'][key]['bip9'] 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): diff --git a/test/functional/wallet_signrawtransactionwithwallet.py b/test/functional/wallet_signrawtransactionwithwallet.py index aaf02e7b5e7e0..b3f528a1c817d 100755 --- a/test/functional/wallet_signrawtransactionwithwallet.py +++ b/test/functional/wallet_signrawtransactionwithwallet.py @@ -130,7 +130,7 @@ def test_signing_with_csv(self): 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 P2SH script with CSV script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE]) @@ -160,7 +160,7 @@ def test_signing_with_cltv(self): 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 P2SH script with CLTV script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) From 13b81202b7df6852e5e8e899f8f2061e6fa8cadd Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:36:16 +0530 Subject: [PATCH 05/18] rpc: drop `-deprecatedrpc=softforks` requirement. Breaking changes aren't permitted in minor versions, should be reintroduced in v24 merge cycle. --- doc/release-notes-6901.md | 5 ++--- src/rpc/blockchain.cpp | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/doc/release-notes-6901.md b/doc/release-notes-6901.md index cb568546d97f2..a0c0dae9cbf45 100644 --- a/doc/release-notes-6901.md +++ b/doc/release-notes-6901.md @@ -4,6 +4,5 @@ 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. + status in `getblockchaininfo` is currently available but this will be + restricted or removed in a future release. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9bf93cd1f9247..1bdb9b75365cc 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1493,7 +1493,7 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (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", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks in progress", + {RPCResult::Type::OBJ_DYN, "softforks", "Status of softforks in progress", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", RPCHelpForDeployment @@ -1546,11 +1546,10 @@ RPCHelpMan getblockchaininfo() } } - if (IsDeprecatedRPCEnabled("softforks")) { - const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(&tip)}; - const Consensus::Params& consensusParams = Params().GetConsensus(); - obj.pushKV("softforks", DeploymentInfo(&tip, ehf_signals, consensusParams)); - } + // TODO: Must be gated behind -deprecatedrpc=softforks in v24 + const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(&tip)}; + const Consensus::Params& consensusParams = Params().GetConsensus(); + obj.pushKV("softforks", DeploymentInfo(&tip, ehf_signals, consensusParams)); obj.pushKV("warnings", GetWarnings(false).original); return obj; From bebbeb701af80847e4e8b6a178b4712fac9c052f Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:23:27 +0530 Subject: [PATCH 06/18] merge bitcoin#24187: Followups for getdeploymentinfo --- doc/release-notes-6901.md | 6 +++-- src/rpc/blockchain.cpp | 56 +++++++++++++++++++-------------------- src/versionbits.cpp | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/doc/release-notes-6901.md b/doc/release-notes-6901.md index a0c0dae9cbf45..19044a9f29551 100644 --- a/doc/release-notes-6901.md +++ b/doc/release-notes-6901.md @@ -2,7 +2,9 @@ Updated RPCs ------------ - Information on soft fork status has been moved from `getblockchaininfo` - to `getdeploymentinfo` which allows querying soft fork status at any + to the new `getdeploymentinfo` RPC which allows querying soft fork status at any block, rather than just at the chain tip. Inclusion of soft fork status in `getblockchaininfo` is currently available but this will be - restricted or removed in a future release. + restricted or removed in a future release. Note that in either case, the + `status` field now reflects the status of the current block rather than + the next block. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1bdb9b75365cc..e5ffab6d87e73 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1367,7 +1367,7 @@ static RPCHelpMan verifychain() }; } -static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) { // For buried deployments. @@ -1377,17 +1377,17 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& rv.pushKV("type", "buried"); // 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("active", DeploymentActiveAfter(blockindex, params, dep)); rv.pushKV("height", params.DeploymentHeight(dep)); softforks.pushKV(DeploymentName(dep), rv); } -static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std::unordered_map& signals, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unordered_map& signals, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) { // For BIP9 deployments. if (!DeploymentEnabled(consensusParams, id)) return; - if (active_chain_tip == nullptr) return; + if (blockindex == nullptr) return; auto get_state_name = [](const ThresholdState state) -> std::string { switch (state) { @@ -1402,8 +1402,8 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: UniValue bip9(UniValue::VOBJ); - 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 ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id); + const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id); const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state); @@ -1421,7 +1421,7 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: // BIP9 status bip9.pushKV("status", get_state_name(current_state)); - int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip->pprev, consensusParams, id); + int64_t since_height = g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id); bip9.pushKV("since", since_height); bip9.pushKV("status-next", get_state_name(next_state)); @@ -1429,7 +1429,7 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: if (has_signal) { UniValue statsUV(UniValue::VOBJ); std::vector signals; - BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id, &signals); + BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); @@ -1453,7 +1453,7 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, const std: UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); if (ThresholdState::ACTIVE == next_state) { - rv.pushKV("height", g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id)); + rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id)); } rv.pushKV("active", ThresholdState::ACTIVE == next_state); rv.pushKV("bip9", bip9); @@ -1571,9 +1571,9 @@ const std::vector RPCHelpForDeployment{ {RPCResult::Type::NUM, "ehf_height", /*optional=*/true, "the minimum height when miner's signals for the deployment matter. Below this height miner signaling cannot trigger hard fork lock-in. Not specified for non-EHF forks"}, {RPCResult::Type::NUM, "activation_height", "expected activation height for this softfork (only for \"locked_in\" status)"}, {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::STR, "status", "status of deployment at 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::STR, "status-next", "status of deployment at the 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"}, @@ -1586,7 +1586,7 @@ const std::vector RPCHelpForDeployment{ }}, }; -UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf_signals, const Consensus::Params& consensusParams) +UniValue DeploymentInfo(const CBlockIndex* blockindex, const CMNHFManager::Signals& ehf_signals, const Consensus::Params& consensusParams) { UniValue softforks(UniValue::VOBJ); for (auto deploy : { /* sorted by activation block */ @@ -1606,12 +1606,12 @@ UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf Consensus::DEPLOYMENT_MN_RR, Consensus::DEPLOYMENT_WITHDRAWALS, }) { - SoftForkDescPushBack(tip, softforks, consensusParams, deploy); + SoftForkDescPushBack(blockindex, softforks, consensusParams, deploy); } for (auto ehf_deploy : { /* sorted by activation block */ Consensus::DEPLOYMENT_V24, Consensus::DEPLOYMENT_TESTDUMMY }) { - SoftForkDescPushBack(tip, ehf_signals, softforks, consensusParams, ehf_deploy); + SoftForkDescPushBack(blockindex, ehf_signals, softforks, consensusParams, ehf_deploy); } return softforks; } @@ -1620,9 +1620,9 @@ UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf static RPCHelpMan getdeploymentinfo() { return RPCHelpMan{"getdeploymentinfo", - "Returns an object containing various state info regarding soft-forks.", + "Returns an object containing various state info regarding deployments of consensus changes.", { - {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"chain tip"}, "The block hash at which to query fork state"}, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { @@ -1638,29 +1638,29 @@ static RPCHelpMan getdeploymentinfo() { const NodeContext& node = EnsureAnyNodeContext(request.context); - ChainstateManager& chainman = EnsureChainman(node); + const ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + const CChainState& active_chainstate = chainman.ActiveChainstate(); - const CBlockIndex* tip; + const CBlockIndex* blockindex; if (request.params[0].isNull()) { - tip = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(tip); + blockindex = active_chainstate.m_chain.Tip(); + CHECK_NONFATAL(blockindex); } else { - uint256 hash(ParseHashV(request.params[0], "blockhash")); - tip = chainman.m_blockman.LookupBlockIndex(hash); - if (!tip) { + const uint256 hash(ParseHashV(request.params[0], "blockhash")); + blockindex = chainman.m_blockman.LookupBlockIndex(hash); + if (!blockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } const Consensus::Params& consensusParams = Params().GetConsensus(); - const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(tip)}; + const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(blockindex)}; UniValue deploymentinfo(UniValue::VOBJ); - deploymentinfo.pushKV("hash", tip->GetBlockHash().ToString()); - deploymentinfo.pushKV("height", tip->nHeight); - deploymentinfo.pushKV("deployments", DeploymentInfo(tip, ehf_signals, consensusParams)); + deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString()); + deploymentinfo.pushKV("height", blockindex->nHeight); + deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, ehf_signals, consensusParams)); return deploymentinfo; }, }; diff --git a/src/versionbits.cpp b/src/versionbits.cpp index b1dd2b39517fa..ed402d64aeb7e 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -140,7 +140,7 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI if (pindex == nullptr) return stats; - // Find beginning of period + // Find how many blocks are in the current period int blocks_in_period = 1 + (pindex->nHeight % stats.period); // Reset signalling_blocks From 40cfb98d87ac008c220493f430beeb27b2180015 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:26:42 +0530 Subject: [PATCH 07/18] merge bitcoin#24528: rename getdeploymentinfo status-next to status_next --- src/rpc/blockchain.cpp | 4 ++-- test/functional/rpc_blockchain.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index e5ffab6d87e73..4594cd9bd2941 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1423,7 +1423,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unord bip9.pushKV("status", get_state_name(current_state)); int64_t since_height = g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id); bip9.pushKV("since", since_height); - bip9.pushKV("status-next", get_state_name(next_state)); + bip9.pushKV("status_next", get_state_name(next_state)); // BIP9 signalling status, if applicable if (has_signal) { @@ -1573,7 +1573,7 @@ const std::vector RPCHelpForDeployment{ {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"}, {RPCResult::Type::STR, "status", "status of deployment at 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", "status of deployment at the next block"}, + {RPCResult::Type::STR, "status_next", "status of deployment at the 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"}, diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 7619734e0fc1f..d9ca3b0164632 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -219,7 +219,7 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, 'min_activation_height': 0, 'since': 0, 'status': 'defined', - 'status-next': 'defined', + 'status_next': 'defined', 'ehf': True, }, 'active': False @@ -233,7 +233,7 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, 'min_activation_height': 0, 'since': 144, 'status': 'started', - 'status-next': status_next, + 'status_next': status_next, 'statistics': { 'period': 144, 'threshold': 108, From 8e5399b76863bc682e053f6c8f99b8caa140f613 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 16 Mar 2022 09:32:29 +0100 Subject: [PATCH 08/18] merge bitcoin#24579: Fix getblockchaininfo/getdeploymentinfo RPC docs --- src/rpc/blockchain.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 4594cd9bd2941..c26eed4f9d4b3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1493,7 +1493,7 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (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 in progress", + {RPCResult::Type::OBJ_DYN, "softforks", /*optional=*/true, "Status of softforks in progress", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", RPCHelpForDeployment @@ -1503,7 +1503,7 @@ RPCHelpMan getblockchaininfo() }}, RPCExamples{ HelpExampleCli("getblockchaininfo", "") - + HelpExampleRpc("getblockchaininfo", "") + + HelpExampleRpc("getblockchaininfo", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -1582,7 +1582,7 @@ const std::vector RPCHelpForDeployment{ {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 -"}, + {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"}, }}, }; @@ -1628,7 +1628,7 @@ static RPCHelpMan getdeploymentinfo() 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_DYN, "deployments", "", { {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment} }}, } From f9078ae05e6cbed6defc4b496ea4ed5c581bfd5a Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:28:58 +0530 Subject: [PATCH 09/18] partial bitcoin#24595: move g_versionbitscache global to ChainstateManager includes: - 69675ea4e73dcf5e9dd0f94802bd3463e4262081 - 38860f93b680f152fc6fc3d9ae574a4c0659e775 - 5c67e84d37d452e9186a6357e5405fabeff241c7 - deffe0df6c36225bada18603b5a840139f030f2c - 78adef17536edef833a0bfca06b61ce28120e486 --- src/init.cpp | 2 +- src/net_processing.cpp | 6 +- src/rpc/blockchain.cpp | 51 ++++++++------- src/rpc/mining.cpp | 6 +- src/test/blockfilter_index_tests.cpp | 8 +-- src/test/evo_deterministicmns_tests.cpp | 18 +++--- src/test/fuzz/utxo_snapshot.cpp | 2 +- src/test/miner_tests.cpp | 2 +- src/test/util/mining.cpp | 2 +- src/test/util/setup_common.cpp | 8 +-- src/test/validation_block_tests.cpp | 10 +-- src/test/validation_chainstate_tests.cpp | 3 +- src/validation.cpp | 82 ++++++++++++------------ src/validation.h | 38 +++++++++-- 14 files changed, 133 insertions(+), 105 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 6b73e69bb862e..6785353ae3a1c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1936,7 +1936,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { node.mempool = std::make_unique(node.fee_estimator.get(), mempool_check_ratio); - node.chainman = std::make_unique(); + node.chainman = std::make_unique(chainparams); ChainstateManager& chainman = *node.chainman; /** diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 63d5aac9657b0..17f1106ccf7b8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3194,7 +3194,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, bool received_new_header{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash()) == nullptr)}; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders(headers, state, m_chainparams, &pindexLast)) { + if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); return; @@ -3499,7 +3499,7 @@ std::pair static ValidateDSTX(CDeterministicMN void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr& block, bool force_processing) { bool new_block{false}; - m_chainman.ProcessNewBlock(m_chainparams, block, force_processing, &new_block); + m_chainman.ProcessNewBlock(block, force_processing, &new_block); if (new_block) { node.m_last_block_time = GetTime(); } else { @@ -4727,7 +4727,7 @@ void PeerManagerImpl::ProcessMessage( const CBlockIndex *pindex = nullptr; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, m_chainparams, &pindex)) { + if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock"); return; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c26eed4f9d4b3..71a106a6a951d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1367,26 +1367,27 @@ static RPCHelpMan verifychain() }; } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep) { // For buried deployments. - if (!DeploymentEnabled(params, dep)) return; + if (!DeploymentEnabled(chainman, dep)) return; + if (blockindex == nullptr) return; UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); // getdeploymentinfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", DeploymentActiveAfter(blockindex, params, dep)); - rv.pushKV("height", params.DeploymentHeight(dep)); + rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep)); + rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep)); softforks.pushKV(DeploymentName(dep), rv); } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unordered_map& signals, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unordered_map& signals, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id) { // For BIP9 deployments. - if (!DeploymentEnabled(consensusParams, id)) return; + if (!DeploymentEnabled(chainman, id)) return; if (blockindex == nullptr) return; auto get_state_name = [](const ThresholdState state) -> std::string { @@ -1402,26 +1403,26 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unord UniValue bip9(UniValue::VOBJ); - const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id); - const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id); + const ThresholdState next_state = g_versionbitscache.State(blockindex, chainman.GetConsensus(), id); + const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), 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("bit", chainman.GetConsensus().vDeployments[id].bit); } - bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); - bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - bip9.pushKV("ehf", consensusParams.vDeployments[id].useEHF); - if (auto it = signals.find(consensusParams.vDeployments[id].bit); it != signals.end()) { + bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime); + bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout); + bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height); + bip9.pushKV("ehf", chainman.GetConsensus().vDeployments[id].useEHF); + if (auto it = signals.find(chainman.GetConsensus().vDeployments[id].bit); it != signals.end()) { bip9.pushKV("ehf_height", it->second); } - bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); // BIP9 status bip9.pushKV("status", get_state_name(current_state)); - int64_t since_height = g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id); + int64_t since_height = g_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id); bip9.pushKV("since", since_height); bip9.pushKV("status_next", get_state_name(next_state)); @@ -1429,7 +1430,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unord if (has_signal) { UniValue statsUV(UniValue::VOBJ); std::vector signals; - BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals); + BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); @@ -1447,13 +1448,13 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unord bip9.pushKV("signalling", sig); } if (ThresholdState::LOCKED_IN == current_state) { - bip9.pushKV("activation_height", since_height + static_cast(consensusParams.vDeployments[id].nWindowSize)); + bip9.pushKV("activation_height", since_height + static_cast(chainman.GetConsensus().vDeployments[id].nWindowSize)); } UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); if (ThresholdState::ACTIVE == next_state) { - rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id)); + rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id)); } rv.pushKV("active", ThresholdState::ACTIVE == next_state); rv.pushKV("bip9", bip9); @@ -1463,7 +1464,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, const std::unord namespace { /* TODO: when -deprecatedrpc=softforks is removed, drop these */ -UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf_signals, const Consensus::Params& consensusParams); +UniValue DeploymentInfo(const CBlockIndex* tip, const CMNHFManager::Signals& ehf_signals, const ChainstateManager& chainman); extern const std::vector RPCHelpForDeployment; } @@ -1548,8 +1549,7 @@ RPCHelpMan getblockchaininfo() // TODO: Must be gated behind -deprecatedrpc=softforks in v24 const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(&tip)}; - const Consensus::Params& consensusParams = Params().GetConsensus(); - obj.pushKV("softforks", DeploymentInfo(&tip, ehf_signals, consensusParams)); + obj.pushKV("softforks", DeploymentInfo(&tip, ehf_signals, chainman)); obj.pushKV("warnings", GetWarnings(false).original); return obj; @@ -1586,7 +1586,7 @@ const std::vector RPCHelpForDeployment{ }}, }; -UniValue DeploymentInfo(const CBlockIndex* blockindex, const CMNHFManager::Signals& ehf_signals, const Consensus::Params& consensusParams) +UniValue DeploymentInfo(const CBlockIndex* blockindex, const CMNHFManager::Signals& ehf_signals, const ChainstateManager& chainman) { UniValue softforks(UniValue::VOBJ); for (auto deploy : { /* sorted by activation block */ @@ -1606,12 +1606,12 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const CMNHFManager::Signa Consensus::DEPLOYMENT_MN_RR, Consensus::DEPLOYMENT_WITHDRAWALS, }) { - SoftForkDescPushBack(blockindex, softforks, consensusParams, deploy); + SoftForkDescPushBack(blockindex, softforks, chainman, deploy); } for (auto ehf_deploy : { /* sorted by activation block */ Consensus::DEPLOYMENT_V24, Consensus::DEPLOYMENT_TESTDUMMY }) { - SoftForkDescPushBack(blockindex, ehf_signals, softforks, consensusParams, ehf_deploy); + SoftForkDescPushBack(blockindex, ehf_signals, softforks, chainman, ehf_deploy); } return softforks; } @@ -1654,13 +1654,12 @@ static RPCHelpMan getdeploymentinfo() } } - const Consensus::Params& consensusParams = Params().GetConsensus(); const auto ehf_signals{CHECK_NONFATAL(node.mnhf_manager)->GetSignalsStage(blockindex)}; UniValue deploymentinfo(UniValue::VOBJ); deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString()); deploymentinfo.pushKV("height", blockindex->nHeight); - deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, ehf_signals, consensusParams)); + deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, ehf_signals, chainman)); return deploymentinfo; }, }; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 74cb13e40db79..31509de1e60a7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -145,7 +145,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& } std::shared_ptr shared_pblock = std::make_shared(block); - if (!chainman.ProcessNewBlock(chainparams, shared_pblock, true, nullptr)) { + if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } @@ -1040,7 +1040,7 @@ static RPCHelpMan submitblock() bool new_block; auto sc = std::make_shared(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; @@ -1082,7 +1082,7 @@ static RPCHelpMan submitheader() } BlockValidationState state; - chainman.ProcessNewBlockHeaders({h}, state, Params()); + chainman.ProcessNewBlockHeaders({h}, state); if (state.IsValid()) return NullUniValue; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index a94bb2fe74e51..eb7c114f6a639 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -102,7 +102,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, CBlockHeader header = block->GetBlockHeader(); BlockValidationState state; - if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, Params(), &pindex)) { + if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) { return false; } } @@ -174,7 +174,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainA_last_header = last_header; for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; @@ -192,7 +192,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainB_last_header = last_header; for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; @@ -223,7 +223,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // Reorg back to chain A. for (size_t i = 2; i < 4; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } // Check that chain A and B blocks can be retrieved. diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index c67d4dd407258..d32b1a72936ba 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -263,7 +263,7 @@ void FuncDIP3Activation(TestChainSetup& setup) // We start one block before DIP3 activation, so mining a block with a DIP3 transaction should fail auto block = std::make_shared(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate())); - chainman.ProcessNewBlock(Params(), block, true, nullptr); + chainman.ProcessNewBlock(block, true, nullptr); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight); BOOST_REQUIRE(block->GetHash() != chainman.ActiveChain().Tip()->GetBlockHash()); BOOST_REQUIRE(!dmnman.GetListAtChainTip().HasMN(tx.GetHash())); @@ -273,7 +273,7 @@ void FuncDIP3Activation(TestChainSetup& setup) BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1); // Mining a block with a DIP3 transaction should succeed now block = std::make_shared(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip()); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2); BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash()); @@ -301,7 +301,7 @@ void FuncV19Activation(TestChainSetup& setup) int nHeight = chainman.ActiveChain().Height(); auto block = std::make_shared(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)); ++nHeight; BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight); @@ -319,7 +319,7 @@ void FuncV19Activation(TestChainSetup& setup) auto tx_upreg = CreateProUpRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, tx_reg_hash, owner_key, operator_key_new.GetPublicKey(), owner_key.GetPubKey().GetID(), collateralScript, setup.coinbaseKey); block = std::make_shared(setup.CreateBlock({tx_upreg}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)); ++nHeight; BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight); @@ -339,7 +339,7 @@ void FuncV19Activation(TestChainSetup& setup) signing_provider.AddKeyPubKey(collateral_key, collateral_key.GetPubKey()); BOOST_REQUIRE(SignSignature(signing_provider, CTransaction(tx_reg), tx_spend, 0, SIGHASH_ALL)); block = std::make_shared(setup.CreateBlock({tx_spend}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)); ++nHeight; BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight); @@ -639,7 +639,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup) SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey); auto block = std::make_shared(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); setup.m_node.dmnman->UpdatedBlockTip(chainman.ActiveChain().Tip()); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1); BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash()); @@ -785,7 +785,7 @@ void FuncVerifyDB(TestChainSetup& setup) SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey); auto block = std::make_shared(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip()); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1); BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash()); @@ -818,7 +818,7 @@ void FuncVerifyDB(TestChainSetup& setup) auto tx_reg_hash = tx_reg.GetHash(); block = std::make_shared(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip()); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2); BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash()); @@ -830,7 +830,7 @@ void FuncVerifyDB(TestChainSetup& setup) auto proUpRevTx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), collateral_utxos, tx_reg_hash, operatorKey, collateralKey); block = std::make_shared(setup.CreateBlock({proUpRevTx}, coinbase_pk, chainman.ActiveChainstate())); - BOOST_REQUIRE(chainman.ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr)); dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip()); BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 3); BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash()); diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp index a587cf919fb95..a1df97e994939 100644 --- a/src/test/fuzz/utxo_snapshot.cpp +++ b/src/test/fuzz/utxo_snapshot.cpp @@ -57,7 +57,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain) if (fuzzed_data_provider.ConsumeBool()) { for (const auto& block : *g_chain) { BlockValidationState dummy; - bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy, ::Params())}; + bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy)}; Assert(processed); const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))}; Assert(index); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index b84a3a0c8131f..5a1857655e559 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -601,7 +601,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) } } std::shared_ptr shared_pblock = std::make_shared(*pblock); - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr)); pblock->hashPrevBlock = pblock->GetHash(); }; diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index e98eed3f8fd94..491951c377271 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -70,7 +70,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) assert(block->nNonce); } - bool processed{Assert(node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)}; + bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, nullptr)}; assert(processed); return CTxIn{block->vtx[0]->GetHash(), 0}; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 7942e3c2a57f8..ed55a3efba0e9 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -245,6 +245,8 @@ BasicTestingSetup::~BasicTestingSetup() ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::vector& extra_args) : BasicTestingSetup(chainName, extra_args) { + const CChainParams& chainparams = Params(); + // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. m_node.scheduler = std::make_unique(); @@ -256,7 +258,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve m_cache_sizes = CalculateCacheSizes(m_args); - m_node.chainman = std::make_unique(); + m_node.chainman = std::make_unique(chainparams); m_node.chainman->m_blockman.m_block_tree_db = std::make_unique(m_cache_sizes.block_tree_db, true); m_node.mn_metaman = std::make_unique(); @@ -475,11 +477,9 @@ CBlock TestChainSetup::CreateAndProcessBlock( chainstate = &Assert(m_node.chainman)->ActiveChainstate(); } - const CChainParams& chainparams = Params(); auto block = this->CreateBlock(txns, scriptPubKey, *chainstate); - std::shared_ptr shared_pblock = std::make_shared(block); - Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr); + Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr); return block; } diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 99a6109fee68c..492bb3e8294b9 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -99,7 +99,7 @@ std::shared_ptr MinerTestingSetup::FinalizeBlock(std::shared_ptr // submit block header, so that miner can get the block height from the // global state and the node has the topology of the chain BlockValidationState ignored; - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored, Params())); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored)); return pblock; } @@ -156,7 +156,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) bool ignored; // Connect the genesis block and drain any outstanding events - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(Params(), std::make_shared(Params().GenesisBlock()), true, &ignored)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared(Params().GenesisBlock()), true, &ignored)); SyncWithValidationInterfaceQueue(); // subscribe to events (this subscriber will validate event ordering) @@ -178,13 +178,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) FastRandomContext insecure; for (int i = 0; i < 1000; i++) { auto block = blocks[insecure.randrange(blocks.size() - 1)]; - Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); + Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); } // to make sure that eventually we process the full chain - do it here for (auto block : blocks) { if (block->vtx.size() == 1) { - bool processed = Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); + bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); assert(processed); } } @@ -223,7 +223,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { bool ignored; auto ProcessBlock = [&](std::shared_ptr block) -> bool { - return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /*force_processing=*/true, /*new_block=*/&ignored); + return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored); }; // Process all mined blocks diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 928bc2f72154e..69a50d70a9892 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -24,7 +24,8 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, TestingSetup) //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { - ChainstateManager manager; + const CChainParams& chainparams = Params(); + ChainstateManager manager(chainparams); WITH_LOCK(::cs_main, manager.m_blockman.m_block_tree_db = std::make_unique(1 << 20, true)); CTxMemPool& mempool = *Assert(m_node.mempool); diff --git a/src/validation.cpp b/src/validation.cpp index 205cc33c4de68..ac8bd642da7fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -349,7 +348,7 @@ static bool ContextualCheckTransaction(const CTransaction& tx, TxValidationState } // Returns the script flags which should be checked for a given block -static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams); +static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const ChainstateManager& chainman); static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, size_t limit, std::chrono::seconds age) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) @@ -1002,7 +1001,6 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) const CTransaction& tx = *ws.m_ptx; const uint256& hash = ws.m_hash; TxValidationState& state = ws.m_state; - const CChainParams& chainparams = args.m_chainparams; // Check again against the current block tip's script verification // flags to cache our script execution flags. This is, of course, @@ -1019,7 +1017,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus()); + unsigned int currentBlockScriptVerifyFlags{GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), m_active_chainstate.m_chainman)}; if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, ws.m_precomputed_txdata, m_active_chainstate.CoinsTip())) { LogPrintf("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s\n", hash.ToString(), state.ToString()); @@ -1429,7 +1427,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); std::vector coins_to_uncache; - const CChainParams& chainparams = Params(); + const CChainParams& chainparams = active_chainstate.m_params; const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (test_accept) { @@ -1653,7 +1651,7 @@ CChainState::CChainState(CTxMemPool* mempool, m_chain_helper(chain_helper), m_evoDb(evoDb), m_blockman(blockman), - m_params(::Params()), + m_params(chainman.GetParams()), m_chainman(chainman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -2217,7 +2215,7 @@ class WarningBitsConditionChecker : public AbstractThresholdConditionChecker static std::array warningcache GUARDED_BY(cs_main); -static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams) +static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const ChainstateManager& chainman) { unsigned int flags = SCRIPT_VERIFY_NONE; @@ -2226,22 +2224,22 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens flags |= SCRIPT_VERIFY_P2SH; // Enforce the DERSIG (BIP66) rule - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_DERSIG)) { + if (DeploymentActiveAt(*pindex, chainman, Consensus::DEPLOYMENT_DERSIG)) { flags |= SCRIPT_VERIFY_DERSIG; } // Enforce CHECKLOCKTIMEVERIFY (BIP65) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CLTV)) { + if (DeploymentActiveAt(*pindex, chainman, Consensus::DEPLOYMENT_CLTV)) { flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; } // Enforce CHECKSEQUENCEVERIFY (BIP112) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(*pindex, chainman, Consensus::DEPLOYMENT_CSV)) { flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; } // Enforce BIP147 NULLDUMMY - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_BIP147)) { + if (DeploymentActiveAt(*pindex, chainman, Consensus::DEPLOYMENT_BIP147)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } @@ -2412,12 +2410,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Enforce BIP68 (sequence locks) int nLockTimeFlags = 0; - if (DeploymentActiveAt(*pindex, m_params.GetConsensus(), Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) { nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; } // Get the script flags for this block - unsigned int flags = GetBlockScriptFlags(pindex, m_params.GetConsensus()); + unsigned int flags{GetBlockScriptFlags(pindex, m_chainman)}; int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1; LogPrint(BCLog::BENCHMARK, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal); @@ -4052,17 +4050,16 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; // Check proof of work - const Consensus::Params& consensusParams = params.GetConsensus(); - if(Params().NetworkIDString() == CBaseChainParams::MAIN && nHeight <= 68589){ + if (chainman.GetParams().NetworkIDString() == CBaseChainParams::MAIN && nHeight <= 68589){ // architecture issues with DGW v1 and v2) - unsigned int nBitsNext = GetNextWorkRequired(pindexPrev, &block, consensusParams); + unsigned int nBitsNext = GetNextWorkRequired(pindexPrev, &block, chainman.GetConsensus()); double n1 = ConvertBitsToDouble(block.nBits); double n2 = ConvertBitsToDouble(nBitsNext); @@ -4071,8 +4068,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits"); } } else { - if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) + if (block.nBits != GetNextWorkRequired(pindexPrev, &block, chainman.GetConsensus())) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", strprintf("incorrect proof of work at %d", nHeight)); + } } // Check against checkpoints @@ -4080,7 +4078,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). - const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints()); + const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(chainman.GetParams().Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint"); @@ -4096,9 +4094,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", strprintf("block timestamp too far in the future %d %d", block.GetBlockTime(), nAdjustedTime + 2 * 60 * 60)); // Reject blocks with outdated version - if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) || - (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_DERSIG)) || - (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CLTV))) { + if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) || + (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_DERSIG)) || + (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CLTV))) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); } @@ -4112,7 +4110,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { // TODO: validate - why do we need this cs_main ? AssertLockHeld(::cs_main); @@ -4120,7 +4118,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // Enforce BIP113 (Median Time Past). bool enforce_locktime_median_time_past{false}; - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CSV)) { assert(pindexPrev != nullptr); enforce_locktime_median_time_past = true; } @@ -4129,8 +4127,8 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat pindexPrev->GetMedianTimePast() : block.GetBlockTime()}; - bool fDIP0001Active_context = nHeight >= consensusParams.DIP0001Height; - bool fDIP0003Active_context = nHeight >= consensusParams.DIP0003Height; + bool fDIP0001Active_context = nHeight >= chainman.GetConsensus().DIP0001Height; + bool fDIP0003Active_context = nHeight >= chainman.GetConsensus().DIP0003Height; // Size limits unsigned int nMaxBlockSize = MaxBlockSize(fDIP0001Active_context); @@ -4145,7 +4143,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-nonfinal", "non-final transaction"); } TxValidationState tx_state; - if (!ContextualCheckTransaction(*tx, tx_state, consensusParams, pindexPrev)) { + if (!ContextualCheckTransaction(*tx, tx_state, chainman.GetConsensus(), pindexPrev)) { // ContextCheckTransaction() does validation checks than only should // fails as consensus failures. assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS); @@ -4162,7 +4160,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // Enforce rule that the coinbase starts with serialized block height // After DIP3/DIP4 activation, we don't enforce the height in the input script anymore. // The CbTx special transaction payload will then contain the height, which is checked in CheckCbTx - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB) && !fDIP0003Active_context) + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB) && !fDIP0003Active_context) { CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || @@ -4180,7 +4178,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return true; } -bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate @@ -4188,7 +4186,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida // TODO : ENABLE BLOCK CACHE IN SPECIFIC CASES BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; - if (hash != chainparams.GetConsensus().hashGenesisBlock) { + if (hash != GetConsensus().hashGenesisBlock) { if (miSelf != m_blockman.m_block_index.end()) { // Block header is already known. CBlockIndex* pindex = &(miSelf->second); @@ -4205,7 +4203,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida return true; } - if (!CheckBlockHeader(block, hash, state, chainparams.GetConsensus())) { + if (!CheckBlockHeader(block, hash, state, GetConsensus())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -4231,7 +4229,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida return state.Invalid(BlockValidationResult::BLOCK_CHAINLOCK, "bad-prevblk-chainlock"); } - if (!ContextualCheckBlockHeader(block, state, m_blockman, chainparams, pindexPrev, GetAdjustedTime())) { + if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, GetAdjustedTime())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -4311,14 +4309,14 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } // Exposed wrapper for AcceptBlockHeader -bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) +bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& headers, BlockValidationState& state, const CBlockIndex** ppindex) { AssertLockNotHeld(cs_main); { LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - bool accepted{AcceptBlockHeader(header, state, chainparams, &pindex)}; + bool accepted{AcceptBlockHeader(header, state, &pindex)}; ActiveChainstate().CheckBlockIndex(); if (!accepted) { @@ -4332,7 +4330,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& if (NotifyHeaderTip(ActiveChainstate())) { if (ActiveChainstate().IsInitialBlockDownload() && ppindex && *ppindex) { const CBlockIndex& last_accepted{**ppindex}; - const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / chainparams.GetConsensus().nPowTargetSpacing}; + const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / GetConsensus().nPowTargetSpacing}; const double progress{100.0 * last_accepted.nHeight / (last_accepted.nHeight + blocks_left)}; LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", last_accepted.nHeight, progress); } @@ -4353,7 +4351,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - bool accepted_header{m_chainman.AcceptBlockHeader(block, state, m_params, &pindex)}; + bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)}; CheckBlockIndex(); if (!accepted_header) @@ -4393,7 +4391,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block } if (!CheckBlock(block, state, m_params.GetConsensus()) || - !ContextualCheckBlock(block, state, m_params.GetConsensus(), pindex->pprev)) { + !ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_blockman.m_dirty_blockindex.insert(pindex); @@ -4432,7 +4430,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block return true; } -bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr& block, bool force_processing, bool* new_block) +bool ChainstateManager::ProcessNewBlock(const std::shared_ptr& block, bool force_processing, bool* new_block) { AssertLockNotHeld(cs_main); @@ -4450,7 +4448,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s // malleability that cause CheckBlock() to fail; see e.g. CVE-2012-2459 and // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html. Because CheckBlock() is // not very expensive, the anti-DoS benefits of caching failure (of a definitely-invalid block) are not substantial. - bool ret = CheckBlock(*block, state, chainparams.GetConsensus()); + bool ret = CheckBlock(*block, state, GetConsensus()); if (ret) { // Store to disk ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block); @@ -4519,11 +4517,11 @@ bool TestBlockValidity(BlockValidationState& state, auto dbTx = evoDb.BeginTransaction(); // NOTE: CheckBlockHeader is called by CheckBlock - if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) + if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); - if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) + if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString()); if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) return false; @@ -5832,7 +5830,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( } int base_height = snapshot_start_block->nHeight; - auto maybe_au_data = ExpectedAssumeutxo(base_height, ::Params()); + auto maybe_au_data = ExpectedAssumeutxo(base_height, GetParams()); if (!maybe_au_data) { LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized " /* Continued */ diff --git a/src/validation.h b/src/validation.h index f53f098b9a3b3..b2f10d24dee1c 100644 --- a/src/validation.h +++ b/src/validation.h @@ -14,7 +14,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -57,6 +59,9 @@ struct ChainTxData; struct DisconnectedBlockTransactions; struct LockPoints; struct AssumeutxoData; +namespace Consensus { +struct Params; +} // namespace Consensus namespace node { class SnapshotMetadata; } // namespace node @@ -518,6 +523,7 @@ class CChainState node::BlockManager& m_blockman; /** Chain parameters for this chainstate */ + /* TODO: replace with m_chainman.GetParams() */ const CChainParams& m_params; //! The chainstate manager that owns this chainstate. The reference is @@ -899,6 +905,8 @@ class ChainstateManager CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; + const CChainParams& m_chainparams; + //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( CChainState& snapshot_chainstate, @@ -912,11 +920,15 @@ class ChainstateManager bool AcceptBlockHeader( const CBlockHeader& block, BlockValidationState& state, - const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); friend CChainState; public: + explicit ChainstateManager(const CChainParams& chainparams) : m_chainparams{chainparams} { } + + const CChainParams& GetParams() const { return m_chainparams; } + const Consensus::Params& GetConsensus() const { return m_chainparams.GetConsensus(); } + std::thread m_load_block; //! A single BlockManager instance is shared across each constructed //! chainstate to avoid duplicating block metadata. @@ -1031,7 +1043,7 @@ class ChainstateManager * @param[out] new_block A boolean which is set to indicate if the block was first received via this call * @returns If the block was processed, independently of block validity */ - bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlock(const std::shared_ptr& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main); /** * Process incoming block headers. @@ -1041,10 +1053,9 @@ class ChainstateManager * * @param[in] block The block headers themselves * @param[out] state This may be set to an Error state if any error occurred processing them - * @param[in] chainparams The params for the chain we want to connect to * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers */ - bool ProcessNewBlockHeaders(const std::vector& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlockHeaders(const std::vector& block, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); /** * Try to add a transaction to the memory pool. @@ -1068,6 +1079,25 @@ class ChainstateManager ~ChainstateManager(); }; +/** Deployment* info via ChainstateManager */ +template +bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), dep); +} + +template +bool DeploymentActiveAt(const CBlockIndex& index, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAt(index, chainman.GetConsensus(), dep); +} + +template +bool DeploymentEnabled(const ChainstateManager& chainman, DEP dep) +{ + return DeploymentEnabled(chainman.GetConsensus(), dep); +} + /** * Return true if hash can be found in active_chain at nBlockHeight height. * Fills hashRet with found hash, if no nBlockHeight is specified - active_chain.Height() is used. From cd1d25908eb8b892522705cebc9f5059bef250c6 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 22 Oct 2025 00:09:33 +0530 Subject: [PATCH 10/18] merge bitcoin#24410: Split hashing/index `GetUTXOStats` codepaths, decouple from `coinstatsindex` --- src/Makefile.am | 4 +- src/index/coinstatsindex.cpp | 44 +++++++++------- src/index/coinstatsindex.h | 4 +- src/init.cpp | 22 ++++---- src/{node => kernel}/coinstats.cpp | 70 +++++++++++++------------ src/{node => kernel}/coinstats.h | 28 ++++------ src/rpc/blockchain.cpp | 67 ++++++++++++++++------- src/rpc/blockchain.h | 23 +++++++- src/rpc/util.h | 1 - src/test/coinstatsindex_tests.cpp | 14 +++-- src/test/fuzz/coins_view.cpp | 14 ----- src/validation.cpp | 19 ++++--- test/lint/lint-circular-dependencies.py | 3 +- 13 files changed, 174 insertions(+), 139 deletions(-) rename src/{node => kernel}/coinstats.cpp (73%) rename src/{node => kernel}/coinstats.h (75%) diff --git a/src/Makefile.am b/src/Makefile.am index 9303186627fa3..fe2529cfece83 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -259,6 +259,7 @@ BITCOIN_CORE_H = \ instantsend/instantsend.h \ instantsend/lock.h \ instantsend/signing.h \ + kernel/coinstats.h \ key.h \ key_io.h \ limitedmap.h \ @@ -305,7 +306,6 @@ BITCOIN_CORE_H = \ node/caches.h \ node/chainstate.h \ node/coin.h \ - node/coinstats.h \ node/connection_types.h \ node/context.h \ node/eviction.h \ @@ -529,6 +529,7 @@ libbitcoin_node_a_SOURCES = \ instantsend/instantsend.cpp \ instantsend/lock.cpp \ instantsend/signing.cpp \ + kernel/coinstats.cpp \ llmq/blockprocessor.cpp \ llmq/commitment.cpp \ llmq/context.cpp \ @@ -560,7 +561,6 @@ libbitcoin_node_a_SOURCES = \ node/caches.cpp \ node/chainstate.cpp \ node/coin.cpp \ - node/coinstats.cpp \ node/connection_types.cpp \ node/context.cpp \ node/eviction.cpp \ diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp index 6bf43e28f7dba..49c81ba1efbcf 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -14,10 +14,11 @@ #include #include -using node::CCoinsStats; -using node::GetBogoSize; +using kernel::CCoinsStats; +using kernel::GetBogoSize; +using kernel::TxOutSer; + using node::ReadBlockFromDisk; -using node::TxOutSer; using node::UndoReadFromDisk; static constexpr uint8_t DB_BLOCK_HASH{'s'}; @@ -316,28 +317,31 @@ static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVa return db.Read(DBHashKey(block_index->GetBlockHash()), result); } -bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const +std::optional CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const { + CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()}; + stats.index_used = true; + DBVal entry; if (!LookUpOne(*m_db, block_index, entry)) { - return false; + return std::nullopt; } - coins_stats.hashSerialized = entry.muhash; - coins_stats.nTransactionOutputs = entry.transaction_output_count; - coins_stats.nBogoSize = entry.bogo_size; - coins_stats.total_amount = entry.total_amount; - coins_stats.total_subsidy = entry.total_subsidy; - coins_stats.total_unspendable_amount = entry.total_unspendable_amount; - coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; - coins_stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; - coins_stats.total_coinbase_amount = entry.total_coinbase_amount; - coins_stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; - coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30; - coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts; - coins_stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; - - return true; + stats.hashSerialized = entry.muhash; + stats.nTransactionOutputs = entry.transaction_output_count; + stats.nBogoSize = entry.bogo_size; + stats.total_amount = entry.total_amount; + stats.total_subsidy = entry.total_subsidy; + stats.total_unspendable_amount = entry.total_unspendable_amount; + stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; + stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; + stats.total_coinbase_amount = entry.total_coinbase_amount; + stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; + stats.total_unspendables_bip30 = entry.total_unspendables_bip30; + stats.total_unspendables_scripts = entry.total_unspendables_scripts; + stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; + + return stats; } bool CoinStatsIndex::Init() diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index 0eb42b1ca35d6..40b19ca9315c2 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include /** * CoinStatsIndex maintains statistics on the UTXO set. @@ -56,7 +56,7 @@ class CoinStatsIndex final : public BaseIndex explicit CoinStatsIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); // Look up stats for a specific block using CBlockIndex - bool LookUpStats(const CBlockIndex* block_index, node::CCoinsStats& coins_stats) const; + std::optional LookUpStats(const CBlockIndex* block_index) const; }; /// The global UTXO set hash object. diff --git a/src/init.cpp b/src/init.cpp index 6785353ae3a1c..4d7655a13d3ac 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -134,10 +134,10 @@ #include #endif +using kernel::CoinStatsHashType; + using node::CacheSizes; using node::CalculateCacheSizes; -using node::CCoinsStats; -using node::CoinStatsHashType; using node::ChainstateLoadingError; using node::ChainstateLoadVerifyError; using node::DashChainstateSetupClose; @@ -831,15 +831,15 @@ static void PeriodicStats(NodeContext& node) ChainstateManager& chainman = *Assert(node.chainman); const CTxMemPool& mempool = *Assert(node.mempool); const llmq::CInstantSendManager& isman = *Assert(node.llmq_ctx->isman); - CCoinsStats stats{CoinStatsHashType::NONE}; chainman.ActiveChainstate().ForceFlushStateToDisk(); - if (WITH_LOCK(cs_main, return GetUTXOStats(&chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, stats, node.rpc_interruption_point, chainman.ActiveChain().Tip()))) { - ::g_stats_client->gauge("utxoset.tx", stats.nTransactions, 1.0f); - ::g_stats_client->gauge("utxoset.txOutputs", stats.nTransactionOutputs, 1.0f); - ::g_stats_client->gauge("utxoset.dbSizeBytes", stats.nDiskSize, 1.0f); - ::g_stats_client->gauge("utxoset.blockHeight", stats.nHeight, 1.0f); - if (stats.total_amount.has_value()) { - ::g_stats_client->gauge("utxoset.totalAmount", (double)stats.total_amount.value() / (double)COIN, 1.0f); + const auto maybe_stats = WITH_LOCK(::cs_main, return GetUTXOStats(&chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, /*hash_type=*/CoinStatsHashType::NONE, node.rpc_interruption_point, chainman.ActiveChain().Tip(), /*index_requested=*/true)); + if (maybe_stats.has_value()) { + ::g_stats_client->gauge("utxoset.tx", maybe_stats->nTransactions, 1.0f); + ::g_stats_client->gauge("utxoset.txOutputs", maybe_stats->nTransactionOutputs, 1.0f); + ::g_stats_client->gauge("utxoset.dbSizeBytes", maybe_stats->nDiskSize, 1.0f); + ::g_stats_client->gauge("utxoset.blockHeight", maybe_stats->nHeight, 1.0f); + if (maybe_stats->total_amount.has_value()) { + ::g_stats_client->gauge("utxoset.totalAmount", (double)maybe_stats->total_amount.value() / (double)COIN, 1.0f); } } else { // something went wrong diff --git a/src/node/coinstats.cpp b/src/kernel/coinstats.cpp similarity index 73% rename from src/node/coinstats.cpp rename to src/kernel/coinstats.cpp index c31509e153044..8bf24492f07b7 100644 --- a/src/node/coinstats.cpp +++ b/src/kernel/coinstats.cpp @@ -1,14 +1,12 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include +#include #include #include #include -#include #include #include #include @@ -18,7 +16,12 @@ #include -namespace node { +namespace kernel { + +CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash) + : nHeight(block_height), + hashBlock(block_hash) {} + // Database-independent metric indicating the UTXO set size uint64_t GetBogoSize(const CScript& script_pub_key) { @@ -94,24 +97,11 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map -static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function& interruption_point, const CBlockIndex* pindex) +static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function& interruption_point) { std::unique_ptr pcursor(view->Cursor()); assert(pcursor); - if (!pindex) { - LOCK(cs_main); - pindex = blockman.LookupBlockIndex(view->GetBestBlock()); - } - stats.nHeight = Assert(pindex)->nHeight; - stats.hashBlock = pindex->GetBlockHash(); - - // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested - if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) { - stats.index_used = true; - return g_coin_stats_index->LookUpStats(pindex, stats); - } - PrepareHash(hash_obj, stats); uint256 prevkey; @@ -142,25 +132,36 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& FinalizeHash(hash_obj, stats); stats.nDiskSize = view->EstimateSize(); + return true; } -bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function& interruption_point, const CBlockIndex* pindex) +std::optional ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function& interruption_point) { - switch (stats.m_hash_type) { - case(CoinStatsHashType::HASH_SERIALIZED): { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex); - } - case(CoinStatsHashType::MUHASH): { - MuHash3072 muhash; - return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex); - } - case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex); + CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()}; + + bool success = [&]() -> bool { + switch (hash_type) { + case(CoinStatsHashType::HASH_SERIALIZED): { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + return ComputeUTXOStats(view, stats, ss, interruption_point); + } + case(CoinStatsHashType::MUHASH): { + MuHash3072 muhash; + return ComputeUTXOStats(view, stats, muhash, interruption_point); + } + case(CoinStatsHashType::NONE): { + return ComputeUTXOStats(view, stats, nullptr, interruption_point); + } + } // no default case, so the compiler can warn about missing cases + assert(false); + }(); + + if (!success) { + return std::nullopt; } - } // no default case, so the compiler can warn about missing cases - assert(false); + return stats; } // The legacy hash serializes the hashBlock @@ -183,4 +184,5 @@ static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) stats.hashSerialized = out; } static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} -} // namespace node + +} // namespace kernel diff --git a/src/node/coinstats.h b/src/kernel/coinstats.h similarity index 75% rename from src/node/coinstats.h rename to src/kernel/coinstats.h index 4ca9274db35f1..c103966568f23 100644 --- a/src/node/coinstats.h +++ b/src/kernel/coinstats.h @@ -1,10 +1,9 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_NODE_COINSTATS_H -#define BITCOIN_NODE_COINSTATS_H +#ifndef BITCOIN_KERNEL_COINSTATS_H +#define BITCOIN_KERNEL_COINSTATS_H #include #include @@ -21,17 +20,14 @@ namespace node { class BlockManager; } // namespace node -namespace node { -enum class CoinStatsHashType { +namespace kernel { +enum class CoinStatsHashType : uint8_t { HASH_SERIALIZED, MUHASH, NONE, }; struct CCoinsStats { - //! Which hash type to use - const CoinStatsHashType m_hash_type; - int nHeight{0}; uint256 hashBlock{}; uint64_t nTransactions{0}; @@ -45,8 +41,6 @@ struct CCoinsStats { //! The number of coins contained. uint64_t coins_count{0}; - //! Signals if the coinstatsindex should be used (when available). - bool index_requested{true}; //! Signals if the coinstatsindex was used to retrieve the statistics. bool index_used{false}; @@ -71,15 +65,15 @@ struct CCoinsStats { //! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block CAmount total_unspendables_unclaimed_rewards{0}; - CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} + CCoinsStats() = default; + CCoinsStats(int block_height, const uint256& block_hash); }; -//! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, CCoinsStats& stats, const std::function& interruption_point = {}, const CBlockIndex* pindex = nullptr); - uint64_t GetBogoSize(const CScript& script_pub_key); CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin); -} // namespace node -#endif // BITCOIN_NODE_COINSTATS_H +std::optional ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function& interruption_point = {}); +} // namespace kernel + +#endif // BITCOIN_KERNEL_COINSTATS_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 71a106a6a951d..7ad20030b49b5 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -63,9 +63,10 @@ #include #include +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; + using node::BlockManager; -using node::CCoinsStats; -using node::CoinStatsHashType; using node::NodeContext; using node::ReadBlockFromDisk; using node::SnapshotMetadata; @@ -1102,6 +1103,31 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input) } } +std::optional GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, + kernel::CoinStatsHashType hash_type, + const std::function& interruption_point, + const CBlockIndex* pindex, + bool index_requested) +{ + // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested + if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) { + if (pindex) { + return g_coin_stats_index->LookUpStats(pindex); + } else { + CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + return g_coin_stats_index->LookUpStats(block_index); + } + } + + // If the coinstats index isn't requested or is otherwise not usable, the + // pindex should either be null or equal to the view's best block. This is + // because without the coinstats index we can only get coinstats about the + // best block. + CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock()); + + return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point); +} + static RPCHelpMan gettxoutsetinfo() { return RPCHelpMan{"gettxoutsetinfo", @@ -1157,8 +1183,7 @@ static RPCHelpMan gettxoutsetinfo() const CBlockIndex* pindex{nullptr}; const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; - CCoinsStats stats{hash_type}; - stats.index_requested = request.params[2].isNull() || request.params[2].get_bool(); + bool index_requested = request.params[2].isNull() || request.params[2].get_bool(); const NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); @@ -1179,17 +1204,17 @@ static RPCHelpMan gettxoutsetinfo() throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex"); } - if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) { + if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block"); } - if (!stats.index_requested) { + if (!index_requested) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block"); } pindex = ParseHashOrHeight(request.params[1], chainman); } - if (stats.index_requested && g_coin_stats_index) { + if (index_requested && g_coin_stats_index) { if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) { const IndexSummary summary{g_coin_stats_index->GetSummary()}; @@ -1201,7 +1226,9 @@ static RPCHelpMan gettxoutsetinfo() } } - if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { + const std::optional maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested); + if (maybe_stats.has_value()) { + const CCoinsStats& stats = maybe_stats.value(); ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs); @@ -1220,10 +1247,13 @@ static RPCHelpMan gettxoutsetinfo() } else { ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount)); - CCoinsStats prev_stats{hash_type}; - + CCoinsStats prev_stats{}; if (pindex->nHeight > 0) { - GetUTXOStats(coins_view, *blockman, prev_stats, node.rpc_interruption_point, pindex->pprev); + const std::optional maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested); + if (!maybe_prev_stats) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); + } + prev_stats = maybe_prev_stats.value(); } UniValue block_info(UniValue::VOBJ); @@ -2792,7 +2822,7 @@ UniValue CreateUTXOSnapshot( const fs::path& temppath) { std::unique_ptr pcursor; - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; + std::optional maybe_stats; const CBlockIndex* tip; { @@ -2812,19 +2842,20 @@ UniValue CreateUTXOSnapshot( chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) { + maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point); + if (!maybe_stats) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = chainstate.CoinsDB().Cursor(); - tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(stats.hashBlock)); + tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock)); } LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", tip->nHeight, tip->GetBlockHash().ToString(), fs::PathToString(path), fs::PathToString(temppath))); - SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx}; + SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx}; afile << metadata; @@ -2846,11 +2877,11 @@ UniValue CreateUTXOSnapshot( afile.fclose(); UniValue result(UniValue::VOBJ); - result.pushKV("coins_written", stats.coins_count); + result.pushKV("coins_written", maybe_stats->coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.utf8string()); - result.pushKV("txoutset_hash", stats.hashSerialized.ToString()); + result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString()); // Cast required because univalue doesn't have serialization specified for // `unsigned int`, nChainTx's type. result.pushKV("nchaintx", uint64_t{tip->nChainTx}); diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 6438cfcf6a029..a8a744c7d5596 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -8,10 +8,13 @@ #include #include #include +#include #include #include -#include +#include +#include +#include #include extern RecursiveMutex cs_main; @@ -19,7 +22,10 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; class CChainState; -class UniValue; +class CCoinsView; +namespace kernel { +enum class CoinStatsHashType : uint8_t; +} namespace llmq { class CChainLocksHandler; class CInstantSendManager; @@ -29,6 +35,8 @@ class BlockManager; struct NodeContext; } // namespace node +class UniValue; + static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; /** @@ -62,4 +70,15 @@ UniValue CreateUTXOSnapshot( const fs::path& path, const fs::path& tmppath); +/** + * Calculate statistics about the unspent transaction output set + * + * @param[in] index_requested Signals if the coinstatsindex should be used (when available). + */ +std::optional GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, + kernel::CoinStatsHashType hash_type, + const std::function& interruption_point = {}, + const CBlockIndex* pindex = nullptr, + bool index_requested = true); + #endif // BITCOIN_RPC_BLOCKCHAIN_H diff --git a/src/rpc/util.h b/src/rpc/util.h index a9f5d6246fd13..ef19b689377b1 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_RPC_UTIL_H #define BITCOIN_RPC_UTIL_H -#include #include #include #include diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 769935c284e02..e3335373231f9 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -12,8 +12,8 @@ #include -using node::CCoinsStats; -using node::CoinStatsHashType; +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; BOOST_AUTO_TEST_SUITE(coinstatsindex_tests) @@ -21,7 +21,6 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) { CoinStatsIndex coin_stats_index{1 << 20, true}; - CCoinsStats coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* block_index; { LOCK(cs_main); @@ -29,7 +28,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) } // CoinStatsIndex should not be found before it is started. - BOOST_CHECK(!coin_stats_index.LookUpStats(block_index, coin_stats)); + BOOST_CHECK(!coin_stats_index.LookUpStats(block_index)); // BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex // is started. @@ -45,10 +44,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } - BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index, coin_stats)); + BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index)); // Check that CoinStatsIndex updates with new blocks. - coin_stats_index.LookUpStats(block_index, coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(block_index)); const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; std::vector noTxns; @@ -57,13 +56,12 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // Let the CoinStatsIndex to catch up again. BOOST_CHECK(coin_stats_index.BlockUntilSyncedToCurrentChain()); - CCoinsStats new_coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* new_block_index; { LOCK(cs_main); new_block_index = m_node.chainman->ActiveChain().Tip(); } - coin_stats_index.LookUpStats(new_block_index, new_coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index)); BOOST_CHECK(block_index != new_block_index); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index a8ba484f610d4..b8f68fc76e427 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -27,9 +26,6 @@ #include #include -using node::CCoinsStats; -using node::CoinStatsHashType; - namespace { const TestingSetup* g_setup; const Coin EMPTY_COIN{}; @@ -274,16 +270,6 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) return; } (void)GetTransactionSigOpCount(transaction, coins_view_cache, flags); - }, - [&] { - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; - bool expected_code_path = false; - try { - (void)GetUTXOStats(&coins_view_cache, g_setup->m_node.chainman->m_blockman, stats); - } catch (const std::logic_error&) { - expected_code_path = true; - } - assert(expected_code_path); }); } } diff --git a/src/validation.cpp b/src/validation.cpp index ac8bd642da7fb..81501bc00a1cb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -19,10 +19,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -66,12 +66,14 @@ #include #include +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; +using kernel::ComputeUTXOStats; + using node::BlockManager; using node::BlockMap; using node::CBlockIndexHeightOnlyComparator; using node::CBlockIndexWorkComparator; -using node::CCoinsStats; -using node::CoinStatsHashType; using node::DEFAULT_ADDRESSINDEX; using node::DEFAULT_SPENTINDEX; using node::DEFAULT_TIMESTAMPINDEX; @@ -5823,7 +5825,8 @@ bool ChainstateManager::PopulateAndValidateSnapshot( CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return m_blockman.LookupBlockIndex(base_blockhash)); if (!snapshot_start_block) { - // Needed for GetUTXOStats and ExpectedAssumeutxo to determine the height and to avoid a crash when base_blockhash.IsNull() + // Needed for ComputeUTXOStats and ExpectedAssumeutxo to determine the + // height and to avoid a crash when base_blockhash.IsNull() LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n", base_blockhash.ToString()); return false; @@ -5931,22 +5934,22 @@ bool ChainstateManager::PopulateAndValidateSnapshot( assert(coins_cache.GetBestBlock() == base_blockhash); - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ }; // As above, okay to immediately release cs_main here since no other context knows // about the snapshot_chainstate. CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB()); - if (!GetUTXOStats(snapshot_coinsdb, m_blockman, stats, breakpoint_fnc)) { + const std::optional maybe_stats = ComputeUTXOStats(CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, breakpoint_fnc); + if (!maybe_stats.has_value()) { LogPrintf("[snapshot] failed to generate coins stats\n"); return false; } // Assert that the deserialized chainstate contents match the expected assumeutxo value. - if (AssumeutxoHash{stats.hashSerialized} != au_data.hash_serialized) { + if (AssumeutxoHash{maybe_stats->hashSerialized} != au_data.hash_serialized) { LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n", - au_data.hash_serialized.ToString(), stats.hashSerialized.ToString()); + au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString()); return false; } diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 32718063789b9..07dbd89c5d4ec 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -14,13 +14,12 @@ EXPECTED_CIRCULAR_DEPENDENCIES = ( "chainparamsbase -> util/system -> chainparamsbase", "node/blockstorage -> validation -> node/blockstorage", - "index/coinstatsindex -> node/coinstats -> index/coinstatsindex", "policy/fees -> txmempool -> policy/fees", "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel", "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel", "wallet/wallet -> wallet/walletdb -> wallet/wallet", - "node/coinstats -> validation -> node/coinstats", + "kernel/coinstats -> validation -> kernel/coinstats", # Dash "banman -> common/bloom -> evo/assetlocktx -> llmq/quorums -> net -> banman", "chainlock/chainlock -> instantsend/instantsend -> chainlock/chainlock", From 7c3bd1c9b730ede662848e58c49ef89cab7310eb Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 22 Oct 2025 00:08:33 +0530 Subject: [PATCH 11/18] merge bitcoin#15936: Expose settings.json methods to GUI --- src/init.cpp | 1 - src/interfaces/node.h | 29 ++++++++++++++++++++++----- src/node/interfaces.cpp | 40 +++++++++++++++++++++++++++++++++++++ src/test/settings_tests.cpp | 8 ++++---- src/util/settings.cpp | 4 ++++ src/util/settings.h | 6 ++++++ src/util/system.cpp | 35 ++++++++++++++++++++++++++++---- src/util/system.h | 17 +++++++++++++--- 8 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 4d7655a13d3ac..6dfa88bc74486 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -430,7 +430,6 @@ void Shutdown(NodeContext& node) LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } - node.args = nullptr; LogPrintf("%s: done\n", __func__); } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 5a7414b84fa0a..7646fc7f1ea15 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -5,13 +5,14 @@ #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H -#include // For CAmount -#include // For NodeId -#include // For banmap_t -#include // For Network -#include // For ConnectionDirection +#include // For CAmount +#include // For NodeId +#include // For banmap_t +#include // For Network +#include // For ConnectionDirection #include // For SecureString #include +#include // For util::SettingsValue #include #include @@ -193,6 +194,24 @@ class Node //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; + //! Return whether a particular setting in /settings.json is or + //! would be ignored because it is also specified in the command line. + virtual bool isSettingIgnored(const std::string& name) = 0; + + //! Return setting value from /settings.json or dash.conf. + virtual util::SettingsValue getPersistentSetting(const std::string& name) = 0; + + //! Update a setting in /settings.json. + virtual void updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Force a setting value to be applied, overriding any other configuration + //! source, but not being persisted. + virtual void forceSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Clear all settings in /settings.json and store a backup of + //! previous settings in /settings.json.bak. + virtual void resetSettings() = 0; + //! Map port. virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 312378ffa9ea9..07fe07ce2cd79 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -444,6 +444,46 @@ class NodeImpl : public Node } } bool shutdownRequested() override { return ShutdownRequested(); } + bool isSettingIgnored(const std::string& name) override + { + bool ignored = false; + gArgs.LockSettings([&](util::Settings& settings) { + if (auto* options = util::FindKey(settings.command_line_options, name)) { + ignored = !options->empty(); + } + }); + return ignored; + } + util::SettingsValue getPersistentSetting(const std::string& name) override { return gArgs.GetPersistentSetting(name); } + void updateRwSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + gArgs.WriteSettingsFile(); + } + void forceSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.forced_settings.erase(name); + } else { + settings.forced_settings[name] = value; + } + }); + } + void resetSettings() override + { + gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); + gArgs.LockSettings([&](util::Settings& settings) { + settings.rw_settings.clear(); + }); + gArgs.WriteSettingsFile(); + } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(ConnectionDirection flags) override diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp index 2e5eb54247510..ad12c46561064 100644 --- a/src/test/settings_tests.cpp +++ b/src/test/settings_tests.cpp @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(ReadWrite) //! Check settings struct contents against expected json strings. static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val) { - util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false); + util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false, false); util::SettingsValue list_value(util::SettingsValue::VARR); for (const auto& item : GetSettingsList(settings, "section", "name", false)) { list_value.push_back(item); @@ -142,9 +142,9 @@ BOOST_AUTO_TEST_CASE(NullOverride) { util::Settings settings; settings.command_line_options["name"].push_back("value"); - BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false, false).write().c_str()); settings.forced_settings["name"] = {}; - BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false, false).write().c_str()); } // Test different ways settings can be merged, and verify results. This test can @@ -225,7 +225,7 @@ BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) } desc += " || "; - desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write(); + desc += GetSetting(settings, network, name, ignore_default_section_config, /*ignore_nonpersistent=*/false, /*get_chain_name=*/false).write(); desc += " |"; for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) { desc += " "; diff --git a/src/util/settings.cpp b/src/util/settings.cpp index 5e4f80b9aad2b..421b2be4623ce 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -141,6 +141,7 @@ SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name) { SettingsValue result; @@ -176,6 +177,9 @@ SettingsValue GetSetting(const Settings& settings, return; } + // Ignore nonpersistent settings if requested. + if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return; + // Skip negated command line settings. if (skip_negated_command_line && span.last_negated()) return; diff --git a/src/util/settings.h b/src/util/settings.h index 261a0a032f5e5..e97158dc0919d 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -55,12 +55,18 @@ bool WriteSettings(const fs::path& path, //! @param ignore_default_section_config - ignore values in the default section //! of the config file (part before any //! [section] keywords) +//! @param ignore_nonpersistent - ignore non-persistent settings values (forced +//! settings values and values specified on the +//! command line). Only return settings in the +//! read-only config and read-write settings +//! files. //! @param get_chain_name - enable special backwards compatible behavior //! for GetChainName SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name); //! Get combined setting value similar to GetSetting(), except if setting was diff --git a/src/util/system.cpp b/src/util/system.cpp index 95dfa9c6511cf..1b79ef2a18329 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -559,12 +559,15 @@ bool ArgsManager::InitSettings(std::string& error) return true; } -bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const +bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const { fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME); if (settings.empty()) { return false; } + if (backup) { + settings += ".bak"; + } if (filepath) { *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings); } @@ -605,10 +608,10 @@ bool ArgsManager::ReadSettingsFile(std::vector* errors) return true; } -bool ArgsManager::WriteSettingsFile(std::vector* errors) const +bool ArgsManager::WriteSettingsFile(std::vector* errors, bool backup) const { fs::path path, path_tmp; - if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { + if (!GetSettingsPath(&path, /*temp=*/false, backup) || !GetSettingsPath(&path_tmp, /*temp=*/true, backup)) { throw std::logic_error("Attempt to write settings file when dynamic settings are disabled."); } @@ -625,6 +628,13 @@ bool ArgsManager::WriteSettingsFile(std::vector* errors) const return true; } +util::SettingsValue ArgsManager::GetPersistentSetting(const std::string& name) const +{ + LOCK(cs_args); + return util::GetSetting(m_settings, m_network, name, !UseDefaultSection("-" + name), + /*ignore_nonpersistent=*/true, /*get_chain_name=*/false); +} + bool ArgsManager::IsArgNegated(const std::string& strArg) const { return GetSetting(strArg).isFalse(); @@ -633,18 +643,33 @@ bool ArgsManager::IsArgNegated(const std::string& strArg) const std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToString(value, strDefault); +} + +std::string SettingToString(const util::SettingsValue& value, const std::string& strDefault) +{ return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.isNum() ? value.getValStr() : value.get_str(); } int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToInt(value, nDefault); +} + +int64_t SettingToInt(const util::SettingsValue& value, int64_t nDefault) +{ return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.getInt() : LocaleIndependentAtoi(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToBool(value, fDefault); +} + +bool SettingToBool(const util::SettingsValue& value, bool fDefault) +{ return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); } @@ -1086,6 +1111,7 @@ std::string ArgsManager::GetChainName() const LOCK(cs_args); util::SettingsValue value = util::GetSetting(m_settings, /* section= */ "", SettingName(arg), /* ignore_default_section_config= */ false, + /*ignore_nonpersistent=*/false, /* get_chain_name= */ true); return value.isNull() ? false : value.isBool() ? value.get_bool() : (!interpret_bool || InterpretBool(value.get_str())); }; @@ -1154,7 +1180,8 @@ util::SettingsValue ArgsManager::GetSetting(const std::string& arg) const { LOCK(cs_args); return util::GetSetting( - m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), /* get_chain_name= */ false); + m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false); } std::vector ArgsManager::GetSettingsList(const std::string& arg) const diff --git a/src/util/system.h b/src/util/system.h index 53675ccf4cb89..bcf9ba0865cff 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -174,6 +174,10 @@ struct SectionInfo int m_line; }; +std::string SettingToString(const util::SettingsValue&, const std::string&); +int64_t SettingToInt(const util::SettingsValue&, int64_t); +bool SettingToBool(const util::SettingsValue&, bool); + class ArgsManager { public: @@ -468,7 +472,7 @@ class ArgsManager * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ - bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false) const; + bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false, bool backup = false) const; /** * Read settings file. Push errors to vector, or log them if null. @@ -476,9 +480,16 @@ class ArgsManager bool ReadSettingsFile(std::vector* errors = nullptr); /** - * Write settings file. Push errors to vector, or log them if null. + * Write settings file or backup settings file. Push errors to vector, or + * log them if null. + */ + bool WriteSettingsFile(std::vector* errors = nullptr, bool backup = false) const; + + /** + * Get current setting from config file or read/write settings file, + * ignoring nonpersistent command line or forced settings values. */ - bool WriteSettingsFile(std::vector* errors = nullptr) const; + util::SettingsValue GetPersistentSetting(const std::string& name) const; /** * Access settings with lock held. From 7232beda9652d77e887f6b8ca9d4ce398649a602 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 30 Jul 2022 14:27:47 +0200 Subject: [PATCH 12/18] merge bitcoin#25748: Avoid copies in FlatSigningProvider Merge --- src/script/descriptor.cpp | 2 +- src/script/signingprovider.cpp | 19 +++++++------------ src/script/signingprovider.h | 5 +++-- src/test/descriptor_tests.cpp | 4 ++-- src/wallet/rpc/spend.cpp | 2 +- src/wallet/scriptpubkeyman.cpp | 8 ++++---- 6 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index c6d2c5a82a15a..9e8968538c105 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -620,7 +620,7 @@ class DescriptorImpl : public Descriptor assert(outscripts.size() == 1); subscripts.emplace_back(std::move(outscripts[0])); } - out = Merge(std::move(out), std::move(subprovider)); + out.Merge(std::move(subprovider)); std::vector pubkeys; pubkeys.reserve(entries.size()); diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index ab147e55a585e..3f6c9c2ad7c8b 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -55,18 +55,13 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) -{ - FlatSigningProvider ret; - ret.scripts = a.scripts; - ret.scripts.insert(b.scripts.begin(), b.scripts.end()); - ret.pubkeys = a.pubkeys; - ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); - ret.keys = a.keys; - ret.keys.insert(b.keys.begin(), b.keys.end()); - ret.origins = a.origins; - ret.origins.insert(b.origins.begin(), b.origins.end()); - return ret; +FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b) +{ + scripts.merge(b.scripts); + pubkeys.merge(b.pubkeys); + keys.merge(b.keys); + origins.merge(b.origins); + return *this; } bool FillableSigningProvider::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index c231fb0c6544d..f06cb47bb14cf 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SIGNINGPROVIDER_H #define BITCOIN_SCRIPT_SIGNINGPROVIDER_H +#include #include #include #include