diff --git a/contrib/completions/fish/dash-cli.fish b/contrib/completions/fish/dash-cli.fish
index 9a059d5f4ed4..8353506726a9 100644
--- a/contrib/completions/fish/dash-cli.fish
+++ b/contrib/completions/fish/dash-cli.fish
@@ -9,14 +9,20 @@ function __fish_dash_cli_get_commands_helper
return
end
- # Strip help cmd from token to avoid duplication errors
- set --local cmd (string match --invert --regex -- '^help$' $cmd)
- # Strip -stdin* options to avoid waiting for input while we fetch completions
- # TODO: this appears to be broken when run as tab completion (requires ctrl+c to exit)
- set --local cmd (string match --invert --regex -- '^-stdin.*$' $cmd)
+ # Only keep the connection options given before the RPC command. Never pass
+ # on the typed command or its arguments: they would be executed on every Tab
+ # (e.g. `sendtoaddress
` would send funds).
+ # -rpcwait* is dropped so Tab doesn't hang while the node is down.
+ set --local base $cmd[1]
+ for arg in $cmd[2..-1]
+ string match --quiet --regex -- '^-' $arg; or break
+ if string match --quiet --regex -- '^--?(conf|datadir|testnet|regtest|devnet|chain|rpc(?!wait))' $arg
+ set --append base $arg
+ end
+ end
# Match, format and return commands
- for command in ($cmd help 2>&1 | string match --invert -r '^\=\=.*' | string match --invert -r '^\\s*$')
+ for command in ($base help 2>&1 | string match --invert -r '^\=\=.*' | string match --invert -r '^\\s*$')
echo $command
end
end
@@ -60,9 +66,9 @@ function __fish_dash_cli_get_options
set --local options
if set -q _flag_nofiles
- set --append options ($cmd -help 2>&1 | string match -r '^ -.*' | string replace -r ' -' '-' | string replace -r '=.*' '=' | string match --invert -r '^.*=$')
+ set --append options ($cmd[1] -help 2>&1 | string match -r '^ -.*' | string replace -r ' -' '-' | string replace -r '=.*' '=' | string match --invert -r '^.*=$')
else
- set --append options ($cmd -help 2>&1 | string match -r '^ -.*' | string replace -r ' -' '-' | string replace -r '=.*' '=' | string match -r '^.*=$')
+ set --append options ($cmd[1] -help 2>&1 | string match -r '^ -.*' | string replace -r ' -' '-' | string replace -r '=.*' '=' | string match -r '^.*=$')
end
for option in $options
diff --git a/doc/release-notes-7300.md b/doc/release-notes-7300.md
index a9d63a16eaf2..bbae7e647268 100644
--- a/doc/release-notes-7300.md
+++ b/doc/release-notes-7300.md
@@ -3,6 +3,14 @@ P2P and network changes
* Startup now fails if any configured `-bind`, `-whitebind` or the implicit Tor
onion-service bind (`127.0.0.1:9996` by default) cannot be set up. Dash Core
- v23 and earlier started as long as at least one bind succeeded. Use
- `-bind=:=onion` to move the onion bind, or `-listenonion=0` to
- drop it, if the default address is not available on your system.
+ v23 and earlier started as long as at least one bind succeeded.
+
+* Nodes configured with `-bind` but no specific `-bind==onion` now
+ refuse to start when `-listenonion` is enabled. This includes nodes without
+ Tor configured, since `-listenonion` is enabled by default when listening.
+ Shared binds cannot distinguish Tor-forwarded connections from direct
+ connections, which can grant Tor peers unintended IP-based whitelist
+ permissions. Nodes without `-bind`, including those using only `-whitebind`,
+ continue to get the default onion target. Users should add a specific
+ `-bind==onion` to accept incoming Tor connections, or set
+ `-listenonion=0` to disable automatic onion service creation.
diff --git a/doc/release-notes-7779.md b/doc/release-notes-7779.md
new file mode 100644
index 000000000000..6358edcfd0d5
--- /dev/null
+++ b/doc/release-notes-7779.md
@@ -0,0 +1,6 @@
+Updated RPCs
+------------
+
+- `protx listdiff` reports the complete `addresses` object, `platform_p2p` and `platform_https` included,
+ when the core P2P address of an EvoNode on legacy (pre-extended) addresses changes, so its Platform
+ ports can be followed without `-deprecatedrpc=service`. (#7779)
diff --git a/doc/release-notes-7807.md b/doc/release-notes-7807.md
new file mode 100644
index 000000000000..ce06a893ef4b
--- /dev/null
+++ b/doc/release-notes-7807.md
@@ -0,0 +1,20 @@
+Consensus (v24, not yet activated on mainnet or testnet)
+---------------------------------------------------------
+
+- EvoNodes cannot register with shared collateral or have more than one owner payout until
+ the new `evo_shares` deployment activates. An EvoNode ProRegTx with a share table is
+ rejected with `bad-protx-shares-evo`, and an EvoNode ProRegTx or ProUpRegTx with more than
+ one owner payout is rejected with `bad-protx-payouts-evo`. Regular masternodes are not
+ affected.
+- `evo_shares` (version bit 14) is a masternode-activated deployment with the same parameters
+ as `v24`. This release follows it but its masternodes do not sign it, so it stays inactive
+ until a later release signs it once Platform supports EvoNode shares and payouts.
+- The consent digest that share owners sign for a shared EvoNode registration also covers the
+ EvoNode's `platformNodeID`. The digest of a regular shared registration is unchanged.
+
+New RPCs
+--------
+
+- `protx shared_register_prepare_evo` creates an unsigned shared EvoNode registration. It
+ takes the arguments of `protx shared_register_prepare` followed by `platformNodeID`,
+ `platformP2PAddrs` and `platformHTTPSAddrs`, and fails until `evo_shares` is active.
diff --git a/doc/tor.md b/doc/tor.md
index 3cd32aee785f..d617a58e510d 100644
--- a/doc/tor.md
+++ b/doc/tor.md
@@ -83,6 +83,9 @@ it requires a Tor connection to work. It can be explicitly disabled with
`-listenonion=0`. If it is not disabled, it can be configured using the
`-torcontrol` and `-torpassword` settings.
+When enabled with an explicit `-bind`, the automatic service requires a non-wildcard onion bind such as `-bind=127.0.0.1:=onion`.
+Without `-bind`, Dash Core adds the default onion bind.
+
To see verbose Tor information in the dashd debug log, pass `-debug=tor`.
### Control Port
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 901491876a69..c46bc0a738b8 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -218,6 +218,15 @@ class CMainParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_V24].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].bit = 14;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; // TODO: same as DEPLOYMENT_V24
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; // TODO: same as DEPLOYMENT_V24
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nWindowSize = 4032;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdStart = 3226; // 80% of 4032
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdMin = 2420; // 60% of 4032
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nFalloffCoeff = 5; // this corresponds to 10 periods
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].useEHF = true;
+
consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000ba7225adc3153945d14d"); // 2487500
consensus.defaultAssumeValid = uint256S("0x00000000000000119fe42827219e0686d3f7b494ae65f823194c740c5dbab492"); // 2487500
@@ -418,6 +427,15 @@ class CTestNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_V24].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].bit = 14;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nStartTime = 1790208000; // Thursday, September 24, 2026 0:00:00
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nWindowSize = 100;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdStart = 80; // 80% of 100
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdMin = 60; // 60% of 100
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nFalloffCoeff = 5; // this corresponds to 10 periods
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].useEHF = true;
+
consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000036c8f738da818d2"); // 1400000
consensus.defaultAssumeValid = uint256S("0x000000541a23f9db7411cddbe50f9f1ebd4aa7108ebdcad62214753f648c0239"); // 1400000
@@ -591,6 +609,15 @@ class CDevNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_V24].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].bit = 14;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nStartTime = 1751328000; // July 1, 2025
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nWindowSize = 120;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdStart = 96; // 80% of 120
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdMin = 72; // 60% of 120
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nFalloffCoeff = 5; // this corresponds to 10 periods
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].useEHF = true;
+
consensus.nMinimumChainWork = uint256{};
consensus.defaultAssumeValid = uint256{};
@@ -826,6 +853,15 @@ class CRegTestParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_V24].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].bit = 14;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nStartTime = 0;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nWindowSize = 250;
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdStart = 250 / 5 * 4; // 80% of window size
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nThresholdMin = 250 / 5 * 3; // 60% of window size
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].nFalloffCoeff = 5; // this corresponds to 10 periods
+ consensus.vDeployments[Consensus::DEPLOYMENT_EVO_SHARES].useEHF = true;
+
consensus.nMinimumChainWork = uint256{};
consensus.defaultAssumeValid = uint256{};
diff --git a/src/coinjoin/common.h b/src/coinjoin/common.h
index 3a3b3177bc90..8765c4633f84 100644
--- a/src/coinjoin/common.h
+++ b/src/coinjoin/common.h
@@ -120,15 +120,23 @@ constexpr CAmount GetMaxCollateralAmount() { return GetCollateralAmount() * 4; }
// Promotion/demotion constants (post-V24 feature)
constexpr int PROMOTION_RATIO = 10; // 10 smaller denomination coins = 1 larger denomination coin
constexpr int GAP_DIVISOR = 5; // Deficit gap required to trigger promotion/demotion, as 1/N of the goal
+/**
+ * Smallest gap that stops a conversion from making the opposite conversion worthwhile. A
+ * conversion needs the receiving deficit to exceed the other by more than the gap, and it moves
+ * the two deficits by at most PROMOTION_RATIO and 1, so reversing it right away would need
+ * 2 * gap + 1 <= PROMOTION_RATIO. Any gap with 2 * gap + 1 > PROMOTION_RATIO rules that out.
+ */
+constexpr int MIN_GAP_THRESHOLD = (PROMOTION_RATIO + 1) / 2;
/**
* How far behind the other denomination a denomination has to be before converting is worth it;
* the gap is what keeps promotion and demotion from oscillating. It is a fraction of the goal
* rather than a constant because the largest gap two denominations can show is the goal itself:
* a constant of 10 was unsatisfiable at MIN_COINJOIN_DENOMS_GOAL (also 10) and silently
- * disabled the feature there. At the default goal of 50 this still yields 10.
+ * disabled the feature there. At the default goal of 50 this still yields 10; below a goal of 25
+ * it is floored at MIN_GAP_THRESHOLD.
*/
-constexpr int GetGapThreshold(int nGoal) { return std::max(nGoal / GAP_DIVISOR, 1); }
+constexpr int GetGapThreshold(int nGoal) { return std::max(nGoal / GAP_DIVISOR, MIN_GAP_THRESHOLD); }
/**
* Which side(s) of the session denomination a participant occupies. A standard entry mixes at
diff --git a/src/consensus/params.h b/src/consensus/params.h
index 20c20531abcb..7a6f724b8e48 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -42,6 +42,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_
enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_V24, // Deployment of doubling withdrawal limit, extended addresses
+ DEPLOYMENT_EVO_SHARES, // Deployment of EvoNode shared collateral and multiple owner payouts
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp
index 856403e32889..594bf6615a08 100644
--- a/src/deploymentinfo.cpp
+++ b/src/deploymentinfo.cpp
@@ -15,6 +15,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
/*.name =*/"v24",
/*.gbt_force =*/true,
},
+ {
+ /*.name =*/"evo_shares",
+ /*.gbt_force =*/true,
+ },
};
std::string DeploymentName(Consensus::BuriedDeployment dep)
diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp
index 4037fa8bf302..c10b312a441a 100644
--- a/src/evo/providertx.cpp
+++ b/src/evo/providertx.cpp
@@ -278,9 +278,6 @@ bool CProRegTx::IsTriviallyValid(TxValidationState& state) const
}
if (IsShared()) {
- if (nType != MnType::Regular) {
- return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-shares-evo");
- }
// The collateral must be internal; the funding inputs and outputs are covered by the consent digest
if (!collateralOutpoint.hash.IsNull()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-shares-external");
@@ -359,6 +356,9 @@ uint256 CProRegTx::MakeSharedRegConsentHash(const CTransaction& tx) const
hw << nMode;
hw << NetInfoSerWrapper(const_cast&>(netInfo),
nVersion >= ProTxVersion::ExtAddr);
+ if (nType == MnType::Evo) {
+ hw << platformNodeID;
+ }
hw << keyIDVoting;
hw << CBLSLazyPublicKeyVersionWrapper(const_cast(pubKeyOperator),
nVersion == ProTxVersion::LegacyBLS);
diff --git a/src/evo/providertx_service.cpp b/src/evo/providertx_service.cpp
index 3d3f6ed5f439..aa9426567183 100644
--- a/src/evo/providertx_service.cpp
+++ b/src/evo/providertx_service.cpp
@@ -99,6 +99,14 @@ uint16_t CurrentProviderTxVersion(node::NodeContext& node, std::optional b
return DeploymentToProtxVersion(chainman.ActiveChain().Tip(), chainman, basic_override);
}
+bool EvoSharesActive(node::NodeContext& node)
+{
+ AssertLockNotHeld(::cs_main);
+ auto& chainman{*Assert(node.chainman)};
+ LOCK(::cs_main);
+ return DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman, Consensus::DEPLOYMENT_EVO_SHARES);
+}
+
struct ChainSnapshot {
uint16_t provider_tx_version{0};
CDeterministicMNCPtr dmn;
@@ -255,6 +263,17 @@ std::optional ValidateNetworkFields(const ProTx& payload, bool
return std::nullopt;
}
+template
+std::optional ApplyPlatformNodeId(ProTx& payload, const std::optional& platform_node_id)
+{
+ if (payload.nType != MnType::Evo) return std::nullopt;
+ if (!platform_node_id) {
+ return Error(ProviderTxErrorCode::INVALID_PARAMETER, "platformNodeID must be specified for an EvoNode");
+ }
+ payload.platformNodeID = *platform_node_id;
+ return std::nullopt;
+}
+
using PayoutResult = std::variant;
PayoutResult BuildPayouts(const std::vector& payouts)
@@ -360,9 +379,8 @@ std::optional Preflight(node::NodeContext& node, const CTransac
}
TxValidationState state;
- const bool is_v24_active{DeploymentActiveAfter(tip, chainman, Consensus::DEPLOYMENT_V24)};
- if (!chain_helper.special_tx->CheckSpecialTx(tx, tip, is_v24_active, chainman.ActiveChainstate().CoinsTip(), true,
- state)) {
+ if (!chain_helper.special_tx->CheckSpecialTx(tx, tip, GetSpecialTxRules(tip, chainman),
+ chainman.ActiveChainstate().CoinsTip(), true, state)) {
return Error(ProviderTxErrorCode::CONSENSUS_REJECTED, state.ToString(), state.GetRejectReason());
}
return std::nullopt;
@@ -515,12 +533,7 @@ RegistrationResult BuildRegistration(node::NodeContext& node, Wallet& wallet,
return Error(ProviderTxErrorCode::INVALID_PARAMETER, "payouts array requires provider transaction version 3");
}
- if (request.type == MnType::Evo) {
- if (!request.platform_node_id) {
- return Error(ProviderTxErrorCode::INVALID_PARAMETER, "platformNodeID must be specified for an EvoNode");
- }
- payload.platformNodeID = *request.platform_node_id;
- }
+ if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true,
/*check_platform_node_id=*/true)}) {
return *error;
@@ -822,12 +835,7 @@ ProviderTxResult UpdateService(node::NodeContext& node, Wa
if (auto error{ApplyNetInfo(payload, request.net_info, request.type == MnType::Evo, /*optional=*/false)}) {
return *error;
}
- if (request.type == MnType::Evo) {
- if (!request.platform_node_id) {
- return Error(ProviderTxErrorCode::INVALID_PARAMETER, "platformNodeID must be specified for an EvoNode");
- }
- payload.platformNodeID = *request.platform_node_id;
- }
+ if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/false,
/*check_platform_node_id=*/true)}) {
return *error;
@@ -992,12 +1000,20 @@ ProviderTxResult Revoke(node::NodeContext& node, Wallet& w
ProviderTxResult PrepareSharedRegistration(node::NodeContext& node,
const SharedRegistrationRequest& request)
{
+ if (!IsValidMnType(request.type)) {
+ return Error(ProviderTxErrorCode::INVALID_PARAMETER, "invalid masternode type");
+ }
+ if (request.type == MnType::Evo && !EvoSharesActive(node)) {
+ return Error(ProviderTxErrorCode::INVALID_PARAMETER,
+ "EvoNode shared registration requires the evo_shares deployment to be active");
+ }
+
CMutableTransaction tx{request.funding_tx};
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_REGISTER;
CProRegTx payload;
- payload.nType = MnType::Regular;
+ payload.nType = request.type;
payload.nVersion = CurrentProviderTxVersion(node);
if (payload.nVersion < ProTxVersion::ExtAddr) {
return Error(ProviderTxErrorCode::INVALID_PARAMETER,
@@ -1023,7 +1039,13 @@ ProviderTxResult PrepareSharedRegistration(node::Nod
share.reward ? GetScriptForDestination(*share.reward) : CScript{}, share.owner);
}
- if (auto error{ApplyNetInfo(payload, request.net_info, /*platform=*/false, /*optional=*/true)}) return *error;
+ if (auto error{ApplyNetInfo(payload, request.net_info, /*platform=*/payload.nType == MnType::Evo, /*optional=*/true)}) {
+ return *error;
+ }
+ if (auto error{ApplyPlatformNodeId(payload, request.platform_node_id)}) return *error;
+ if (auto error{ValidateNetworkFields(payload, /*allow_empty=*/true, /*check_platform_node_id=*/true)}) {
+ return *error;
+ }
if (!request.operator_key.IsValid()) {
return Error(ProviderTxErrorCode::INVALID_PARAMETER, "operator BLS address must be a valid BLS public key");
diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp
index be1f0f18175c..28f59ec6ced5 100644
--- a/src/evo/specialtxman.cpp
+++ b/src/evo/specialtxman.cpp
@@ -215,7 +215,7 @@ bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const
}
bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransaction& tx,
- const CBlockIndex* pindexPrev, bool is_v24_active,
+ const CBlockIndex* pindexPrev, SpecialTxRules rules,
const CCoinsViewCache& view, const std::optional& indexes,
bool check_sigs, TxValidationState& state)
{
@@ -231,26 +231,25 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa
try {
switch (tx.nType) {
case TRANSACTION_PROVIDER_REGISTER:
- return CheckProRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, rules, state, check_sigs);
case TRANSACTION_PROVIDER_UPDATE_SERVICE:
- return CheckProUpServTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProUpServTx(tx, pindexPrev, m_dmnman, m_consensus_params, rules.v24, state, check_sigs);
case TRANSACTION_PROVIDER_UPDATE_REGISTRAR:
- return CheckProUpRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProUpRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, rules, state, check_sigs);
case TRANSACTION_PROVIDER_UPDATE_REVOKE:
- return CheckProUpRevTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProUpRevTx(tx, pindexPrev, m_dmnman, m_consensus_params, rules.v24, state, check_sigs);
case TRANSACTION_PROVIDER_DISSOLVE:
- return CheckProDisTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProDisTx(tx, pindexPrev, m_dmnman, m_consensus_params, rules.v24, state, check_sigs);
case TRANSACTION_PROVIDER_UPDATE_SHARE:
- return CheckProUpShareTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs);
+ return CheckProUpShareTx(tx, pindexPrev, m_dmnman, m_consensus_params, rules.v24, state, check_sigs);
case TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR:
- return CheckProUpSharedRegTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state,
- check_sigs);
+ return CheckProUpSharedRegTx(tx, pindexPrev, m_dmnman, m_consensus_params, rules.v24, state, check_sigs);
case TRANSACTION_COINBASE: {
if (!tx.IsCoinBase()) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid");
}
if (const auto opt_cbTx = GetTxPayload(tx)) {
- return CheckCbTx(*opt_cbTx, pindexPrev, is_v24_active, state);
+ return CheckCbTx(*opt_cbTx, pindexPrev, rules.v24, state);
} else {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-payload");
}
@@ -261,10 +260,10 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa
return chain ? CheckMNHFTx(m_blockman, m_qman, *chain, tx, pindexPrev, state) :
CheckMNHFTx(m_blockman, m_qman, tx, pindexPrev, state);
case TRANSACTION_ASSET_LOCK:
- return CheckAssetLockTx(tx, state, is_v24_active);
+ return CheckAssetLockTx(tx, state, rules.v24);
case TRANSACTION_ASSET_UNLOCK:
- return chain ? CheckAssetUnlockTx(m_blockman, m_qman, *chain, tx, pindexPrev, indexes, is_v24_active, state) :
- CheckAssetUnlockTx(m_blockman, m_qman, tx, pindexPrev, indexes, is_v24_active, state);
+ return chain ? CheckAssetUnlockTx(m_blockman, m_qman, *chain, tx, pindexPrev, indexes, rules.v24, state)
+ : CheckAssetUnlockTx(m_blockman, m_qman, tx, pindexPrev, indexes, rules.v24, state);
}
} catch (const std::exception& e) {
LogPrintf("%s -- failed: %s\n", __func__, e.what());
@@ -274,11 +273,11 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-check");
}
-bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active,
+bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, SpecialTxRules rules,
const CCoinsViewCache& view, bool check_sigs, TxValidationState& state)
{
AssertLockHeld(::cs_main);
- return CheckSpecialTxInner(nullptr, tx, pindexPrev, is_v24_active, view, std::nullopt, check_sigs, state);
+ return CheckSpecialTxInner(nullptr, tx, pindexPrev, rules, view, std::nullopt, check_sigs, state);
}
static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std::vector& members,
@@ -796,7 +795,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul
}
bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block,
- const CBlockIndex* pindex, bool is_v24_active,
+ const CBlockIndex* pindex, SpecialTxRules rules,
const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck,
bool fCheckCbTxMerkleRoots, BlockValidationState& state,
MNListUpdates& updatesRet)
@@ -821,7 +820,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const
}
if (opt_cbTx = GetTxPayload(*tx); opt_cbTx) {
TxValidationState tx_state;
- if (!CheckCbTx(*opt_cbTx, pindex->pprev, is_v24_active, tx_state)) {
+ if (!CheckCbTx(*opt_cbTx, pindex->pprev, rules.v24, tx_state)) {
assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS ||
tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
@@ -854,7 +853,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const
for (size_t i = 0; i < block.vtx.size(); ++i) {
// The shared-collateral creation rule applies to every transaction, including the
// coinbase and non-special transactions, which CheckSpecialTxInner never sees
- if (is_v24_active) {
+ if (rules.v24) {
if (TxValidationState tx_state; !CheckSharedCollateralTemplateOutputs(*block.vtx[i], tx_state)) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
strprintf("Shared collateral check failed (tx hash %s) %s",
@@ -869,8 +868,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const
TxValidationState tx_state;
// At this moment CheckSpecialTx() may fail by 2 possible ways:
// consensus failures and "TX_BAD_SPECIAL"
- if (!CheckSpecialTxInner(&chain, *ptr_tx, pindex->pprev, is_v24_active, view, indexes, fCheckCbTxMerkleRoots,
- tx_state)) {
+ if (!CheckSpecialTxInner(&chain, *ptr_tx, pindex->pprev, rules, view, indexes, fCheckCbTxMerkleRoots, tx_state)) {
assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage()));
@@ -906,7 +904,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const
CDeterministicMNList mn_list;
if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) {
- if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list)) {
+ if (!BuildNewListFromBlock(block, pindex->pprev, rules.v24, view, true, state, mn_list)) {
// pass the state returned by the function above
return false;
}
@@ -1148,20 +1146,29 @@ static bool IsVersionChangeValid(const uint16_t state_version, const uint16_t tx
}
bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman,
- const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active,
+ const CCoinsViewCache& view, const Consensus::Params& consensus_params, SpecialTxRules rules,
TxValidationState& state, bool check_sigs)
{
- const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state);
+ const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, rules.v24, state);
if (!opt_ptx) {
// pass the state returned by the function above
return false;
}
// No longer allow legacy scheme masternode registration
- if (is_v24_active && opt_ptx->nVersion < ProTxVersion::BasicBLS) {
+ if (rules.v24 && opt_ptx->nVersion < ProTxVersion::BasicBLS) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-disallowed");
}
+ if (opt_ptx->nType == MnType::Evo && !rules.evo_shares) {
+ if (opt_ptx->IsShared()) {
+ return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-shares-evo");
+ }
+ if (GetOwnerPayouts(*opt_ptx).size() > 1) {
+ return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payouts-evo");
+ }
+ }
+
// It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be
// issues later. If any of both is set, it must be valid however
if (!CheckProviderNetworkFields(opt_ptx->netInfo, opt_ptx->nType, opt_ptx->nVersion, &opt_ptx->platformNodeID,
@@ -1262,7 +1269,7 @@ bool CheckProRegTx(const CTransaction& tx, gsl::not_null pin
// proves ownership of the operator key, so that gap lets anyone claim a masternode's key.
// Nothing is excluded here: a duplicate key is never allowed, even for a ProTx replacing an
// existing masternode.
- if (is_v24_active && mnList.HasOperatorKeyUnderAnyScheme(opt_ptx->pubKeyOperator.Get(), /*self=*/uint256())) {
+ if (rules.v24 && mnList.HasOperatorKeyUnderAnyScheme(opt_ptx->pubKeyOperator.Get(), /*self=*/uint256())) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key");
}
@@ -1419,10 +1426,10 @@ bool CheckProUpServTx(const CTransaction& tx, gsl::not_null
}
bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman,
- const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active,
+ const CCoinsViewCache& view, const Consensus::Params& consensus_params, SpecialTxRules rules,
TxValidationState& state, bool check_sigs)
{
- const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state);
+ const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, rules.v24, state);
if (!opt_ptx) {
// pass the state returned by the function above
return false;
@@ -1440,7 +1447,7 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-shared-mn");
}
- if (!IsVersionChangeValid(dmn->pdmnState->nVersion, opt_ptx->nVersion, is_v24_active, state)) {
+ if (!IsVersionChangeValid(dmn->pdmnState->nVersion, opt_ptx->nVersion, rules.v24, state)) {
// pass the state returned by the function above
return false;
}
@@ -1450,7 +1457,7 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p
// if that target slot is already held by another masternode -- under either encoding -- so the
// re-key in UpdateMN() cannot collide and throw out of block assembly. Scoped to those two cases
// so a pre-existing cross-scheme pair's non-migrating routine update is not blocked.
- if (is_v24_active) {
+ if (rules.v24) {
const bool key_changed{!(opt_ptx->pubKeyOperator == dmn->pdmnState->pubKeyOperator)};
const bool migrating{IsSchemeMigration(dmn->pdmnState->nVersion, opt_ptx->nVersion)};
if ((key_changed || migrating) &&
@@ -1473,6 +1480,9 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p
const auto owner_payouts = GetOwnerPayouts(*opt_ptx);
if (!IsPayoutListTriviallyValid(owner_payouts, dmn->pdmnState->keyIDOwner, opt_ptx->keyIDVoting, state)) return false;
+ if (dmn->nType == MnType::Evo && !rules.evo_shares && owner_payouts.size() > 1) {
+ return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payouts-evo");
+ }
Coin coin;
if (!view.GetCoin(dmn->collateralOutpoint, coin) || coin.IsSpent()) {
diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h
index cb900c749d89..c90858d22062 100644
--- a/src/evo/specialtxman.h
+++ b/src/evo/specialtxman.h
@@ -44,6 +44,14 @@ namespace node {
class BlockManager;
} // namespace node
+/** Activation state of the deployments that special transaction validation depends on, evaluated by the
+ * caller for the block after pindexPrev (see GetSpecialTxRules() in validation.h) */
+struct SpecialTxRules {
+ bool v24{false};
+ //! EvoNodes may register with shared collateral and have multiple owner payouts
+ bool evo_shares{false};
+};
+
class CSpecialTxProcessor
{
private:
@@ -77,13 +85,14 @@ class CSpecialTxProcessor
{
}
- bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active,
+ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, SpecialTxRules rules,
const CCoinsViewCache& view, bool check_sigs, TxValidationState& state)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block, const CBlockIndex* pindex,
- bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck,
- bool fCheckCbTxMerkleRoots, BlockValidationState& state,
- MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block,
+ const CBlockIndex* pindex, SpecialTxRules rules, const CCoinsViewCache& view,
+ CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots,
+ BlockValidationState& state, MNListUpdates& updatesRet)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, MNListUpdates& updatesRet)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
@@ -101,7 +110,7 @@ class CSpecialTxProcessor
private:
bool CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev,
- bool is_v24_active, const CCoinsViewCache& view, const std::optional& indexes,
+ SpecialTxRules rules, const CCoinsViewCache& view, const std::optional& indexes,
bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx,
CAmount blockSubsidy, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
@@ -123,13 +132,13 @@ bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const
const chainlock::Chainlocks& chainlocks, BlockValidationState& state);
bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman,
- const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active,
+ const CCoinsViewCache& view, const Consensus::Params& consensus_params, SpecialTxRules rules,
TxValidationState& state, bool check_sigs);
bool CheckProUpServTx(const CTransaction& tx, gsl::not_null pindexPrev,
CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active,
TxValidationState& state, bool check_sigs);
bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman,
- const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active,
+ const CCoinsViewCache& view, const Consensus::Params& consensus_params, SpecialTxRules rules,
TxValidationState& state, bool check_sigs);
bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null pindexPrev,
CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active,
diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp
index bc15e1424c00..6e34c5c56e95 100644
--- a/src/governance/governance.cpp
+++ b/src/governance/governance.cpp
@@ -427,13 +427,17 @@ void CGovernanceManager::CheckAndRemove()
// IF DELETE=TRUE, THEN CLEAN THE MESS UP!
- const auto nTimeSinceDeletion = nNow - std::chrono::seconds{pObj->GetDeletionTime()};
+ // Read the flags before the deletion time: another thread can mark the object for deletion
+ // at any point, and a flag read after a zero deletion time would skip GOVERNANCE_DELETION_DELAY.
+ const bool fCachedDelete{pObj->IsSetCachedDelete()};
+ const bool fExpired{pObj->IsSetExpired()};
+ const int64_t nDeletionTime{pObj->GetDeletionTime()};
+ const auto nTimeSinceDeletion = nNow - std::chrono::seconds{nDeletionTime};
LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- Checking object for deletion: %s, deletion time = %d, time since deletion = %d, delete flag = %d, expired flag = %d\n",
- strHash, pObj->GetDeletionTime(), nTimeSinceDeletion.count(), pObj->IsSetCachedDelete(), pObj->IsSetExpired());
+ strHash, nDeletionTime, nTimeSinceDeletion.count(), fCachedDelete, fExpired);
- if ((pObj->IsSetCachedDelete() || pObj->IsSetExpired()) &&
- (nTimeSinceDeletion >= GOVERNANCE_DELETION_DELAY)) {
+ if ((fCachedDelete || fExpired) && (nTimeSinceDeletion >= GOVERNANCE_DELETION_DELAY)) {
LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- erase obj %s\n", nHash.ToString());
m_mn_metaman.RemoveGovernanceObject(pObj->GetHash());
diff --git a/src/governance/object.cpp b/src/governance/object.cpp
index 240e1378ebfd..9a5eb49880fb 100644
--- a/src/governance/object.cpp
+++ b/src/governance/object.cpp
@@ -356,19 +356,20 @@ CGovernanceObject::CGovernanceObject(const uint256& nHashParentIn, int nRevision
CGovernanceObject::CGovernanceObject(const CGovernanceObject& other) :
cs(),
m_obj{other.m_obj},
- nDeletionTime(other.nDeletionTime),
fCachedLocalValidity(other.fCachedLocalValidity),
strLocalValidityError(other.strLocalValidityError),
- fCachedFunding(other.fCachedFunding),
- fCachedValid(other.fCachedValid),
- fCachedDelete(other.fCachedDelete),
- fCachedEndorsed(other.fCachedEndorsed),
- fDirtyCache(other.fDirtyCache),
- fExpired(other.fExpired),
- fUnparsable(other.fUnparsable),
- mapCurrentMNVotes(other.mapCurrentMNVotes),
- fileVotes(other.fileVotes)
+ fUnparsable(other.fUnparsable)
{
+ LOCK(other.cs);
+ nDeletionTime = other.nDeletionTime;
+ fCachedFunding = other.fCachedFunding;
+ fCachedValid = other.fCachedValid;
+ fCachedDelete = other.fCachedDelete;
+ fCachedEndorsed = other.fCachedEndorsed;
+ fDirtyCache = other.fDirtyCache;
+ fExpired = other.fExpired;
+ mapCurrentMNVotes = other.mapCurrentMNVotes;
+ fileVotes = other.fileVotes;
}
bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateChecksEnabled,
@@ -614,10 +615,11 @@ UniValue CGovernanceObject::GetStateJson(const ChainstateManager& chainman, cons
std::string strError;
ret.pushKV(local_valid_key, WITH_LOCK(::cs_main, return IsValidLocally(tip_mn_list, chainman, strError, /*fCheckCollateral=*/false)));
ret.pushKV("IsValidReason", strError.c_str());
- ret.pushKV("fCachedValid", IsSetCachedValid());
- ret.pushKV("fCachedFunding", IsSetCachedFunding());
- ret.pushKV("fCachedDelete", IsSetCachedDelete());
- ret.pushKV("fCachedEndorsed", IsSetCachedEndorsed());
+ LOCK(cs);
+ ret.pushKV("fCachedValid", fCachedValid);
+ ret.pushKV("fCachedFunding", fCachedFunding);
+ ret.pushKV("fCachedDelete", fCachedDelete);
+ ret.pushKV("fCachedEndorsed", fCachedEndorsed);
return ret;
}
@@ -877,7 +879,7 @@ bool CGovernanceObject::IsCollateralValid(const ChainstateManager& chainman, std
int CGovernanceObject::CountMatchingVotes(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
{
- LOCK(cs);
+ AssertLockHeld(cs);
int nCount = 0;
for (const auto& [outpoint, recVote] : mapCurrentMNVotes) {
@@ -911,19 +913,19 @@ int CGovernanceObject::GetAbsoluteNoCount(const CDeterministicMNList& tip_mn_lis
int CGovernanceObject::GetYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
{
AssertLockNotHeld(cs);
- return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_YES);
+ return WITH_LOCK(cs, return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_YES));
}
int CGovernanceObject::GetNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
{
AssertLockNotHeld(cs);
- return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_NO);
+ return WITH_LOCK(cs, return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_NO));
}
int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
{
AssertLockNotHeld(cs);
- return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN);
+ return WITH_LOCK(cs, return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN));
}
CGovernanceObject::UniqueVoterCount CGovernanceObject::GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
@@ -973,27 +975,25 @@ void CGovernanceObject::UpdateSentinelVariables(const CDeterministicMNList& tip_
int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10);
int nAbsDeleteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, (2 * nWeightedMnCount) / 3);
- // SET SENTINEL FLAGS TO FALSE
-
- fCachedFunding = false;
- fCachedValid = true; //default to valid
- fCachedEndorsed = false;
- fDirtyCache = false;
+ // COUNT VOTES AND SET ALL SENTINEL FLAGS IN ONE CRITICAL SECTION
- // SET SENTINEL FLAGS TO TRUE IF MINIMUM SUPPORT LEVELS ARE REACHED
- // ARE ANY OF THESE FLAGS CURRENTLY ACTIVATED?
+ LOCK(cs);
+ const auto absolute_yes_count = [&](vote_signal_enum_t signal) EXCLUSIVE_LOCKS_REQUIRED(cs) {
+ return CountMatchingVotes(tip_mn_list, signal, VOTE_OUTCOME_YES) -
+ CountMatchingVotes(tip_mn_list, signal, VOTE_OUTCOME_NO);
+ };
+ const auto absolute_no_count = [&](vote_signal_enum_t signal) EXCLUSIVE_LOCKS_REQUIRED(cs) {
+ return CountMatchingVotes(tip_mn_list, signal, VOTE_OUTCOME_NO) -
+ CountMatchingVotes(tip_mn_list, signal, VOTE_OUTCOME_YES);
+ };
- if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = true;
- if ((GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_DELETE) >= nAbsDeleteReq) && !fCachedDelete) {
- fCachedDelete = true;
- LOCK(cs);
- if (nDeletionTime == 0) {
- nDeletionTime = GetTime().count();
- }
+ fDirtyCache = false;
+ fCachedFunding = absolute_yes_count(VOTE_SIGNAL_FUNDING) >= nAbsVoteReq;
+ if (absolute_yes_count(VOTE_SIGNAL_DELETE) >= nAbsDeleteReq) {
+ MarkForDeletion(GetTime().count());
}
- if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true;
-
- if (GetAbsoluteNoCount(tip_mn_list, VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false;
+ fCachedEndorsed = absolute_yes_count(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq;
+ fCachedValid = absolute_no_count(VOTE_SIGNAL_VALID) < nAbsVoteReq;
}
namespace governance {
diff --git a/src/governance/object.h b/src/governance/object.h
index 66faf23351fd..d967c42d04a8 100644
--- a/src/governance/object.h
+++ b/src/governance/object.h
@@ -157,21 +157,21 @@ class CGovernanceObject
// VARIOUS FLAGS FOR OBJECT / SET VIA MASTERNODE VOTING
/// true == minimum network support has been reached for this object to be funded (doesn't mean it will for sure though)
- bool fCachedFunding{false};
+ bool fCachedFunding GUARDED_BY(cs){false};
/// true == minimum network has been reached flagging this object as a valid and understood governance object (e.g, the serialized data is correct format, etc)
- bool fCachedValid{true};
+ bool fCachedValid GUARDED_BY(cs){true};
/// true == minimum network support has been reached saying this object should be deleted from the system entirely
- bool fCachedDelete{false};
+ bool fCachedDelete GUARDED_BY(cs){false};
/** true == minimum network support has been reached flagging this object as endorsed by an elected representative body
* (e.g. business review board / technical review board /etc)
*/
- bool fCachedEndorsed{false};
+ bool fCachedEndorsed GUARDED_BY(cs){false};
/// object was updated and cached values should be updated soon
- bool fDirtyCache{true};
+ bool fDirtyCache GUARDED_BY(cs){true};
/// Object is no longer of interest
bool fExpired GUARDED_BY(cs){false};
@@ -192,11 +192,11 @@ class CGovernanceObject
CGovernanceObject(deserialize_type, Stream& s) { s >> *this; }
// Getters
- bool IsSetCachedFunding() const { return fCachedFunding; }
- bool IsSetCachedValid() const { return fCachedValid; }
- bool IsSetCachedDelete() const { return fCachedDelete; }
- bool IsSetCachedEndorsed() const { return fCachedEndorsed; }
- bool IsSetDirtyCache() const { return fDirtyCache; }
+ bool IsSetCachedFunding() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { return WITH_LOCK(cs, return fCachedFunding); }
+ bool IsSetCachedValid() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { return WITH_LOCK(cs, return fCachedValid); }
+ bool IsSetCachedDelete() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { return WITH_LOCK(cs, return fCachedDelete); }
+ bool IsSetCachedEndorsed() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { return WITH_LOCK(cs, return fCachedEndorsed); }
+ bool IsSetDirtyCache() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { return WITH_LOCK(cs, return fDirtyCache); }
bool IsSetExpired() const EXCLUSIVE_LOCKS_REQUIRED(!cs)
{
return WITH_LOCK(cs, return fExpired);
@@ -250,11 +250,8 @@ class CGovernanceObject
void PrepareDeletion(int64_t nDeletionTime_) EXCLUSIVE_LOCKS_REQUIRED(!cs)
{
- fCachedDelete = true;
LOCK(cs);
- if (nDeletionTime == 0) {
- nDeletionTime = nDeletionTime_;
- }
+ MarkForDeletion(nDeletionTime_);
}
CAmount GetMinCollateralFee() const;
@@ -266,9 +263,6 @@ class CGovernanceObject
// GET VOTE COUNT FOR SIGNAL
- int CountMatchingVotes(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
- EXCLUSIVE_LOCKS_REQUIRED(!cs);
-
int GetAbsoluteYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
EXCLUSIVE_LOCKS_REQUIRED(!cs);
int GetAbsoluteNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
@@ -350,6 +344,18 @@ class CGovernanceObject
// Returns deleted vote hashes.
std::set RemoveInvalidVotes(const CDeterministicMNList& tip_mn_list, const COutPoint& mnOutpoint)
EXCLUSIVE_LOCKS_REQUIRED(!cs);
+
+private:
+ int CountMatchingVotes(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn,
+ vote_outcome_enum_t eVoteOutcomeIn) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+
+ void MarkForDeletion(int64_t nDeletionTime_) EXCLUSIVE_LOCKS_REQUIRED(cs)
+ {
+ fCachedDelete = true;
+ if (nDeletionTime == 0) {
+ nDeletionTime = nDeletionTime_;
+ }
+ }
};
namespace governance {
diff --git a/src/governance/votedb.cpp b/src/governance/votedb.cpp
index 5c9b3ef99072..da0ee869e8cf 100644
--- a/src/governance/votedb.cpp
+++ b/src/governance/votedb.cpp
@@ -20,6 +20,15 @@ CGovernanceObjectVoteFile::CGovernanceObjectVoteFile(const CGovernanceObjectVote
RebuildIndex();
}
+CGovernanceObjectVoteFile& CGovernanceObjectVoteFile::operator=(const CGovernanceObjectVoteFile& other)
+{
+ if (this != &other) {
+ listVotes = vote_l_t{other.listVotes};
+ RebuildIndex();
+ }
+ return *this;
+}
+
void CGovernanceObjectVoteFile::AddVote(const CGovernanceVote& vote)
{
uint256 nHash = vote.GetHash();
diff --git a/src/governance/votedb.h b/src/governance/votedb.h
index 5032d6317001..15c39f5c7efd 100644
--- a/src/governance/votedb.h
+++ b/src/governance/votedb.h
@@ -44,6 +44,8 @@ class CGovernanceObjectVoteFile
CGovernanceObjectVoteFile(const CGovernanceObjectVoteFile& other);
+ CGovernanceObjectVoteFile& operator=(const CGovernanceObjectVoteFile& other);
+
/**
* Add a vote to the file
*/
diff --git a/src/init.cpp b/src/init.cpp
index 4683a409b123..aa4d77b639ed 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -653,7 +653,8 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-addnode=", strprintf("Add a node to connect to and attempt to keep the connection open (see the addnode RPC help for more info). This option can be specified multiple times to add multiple nodes; connections are limited to %u at a time and are counted separately from the -maxconnections limit.", MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-allowprivatenet", strprintf("Allow RFC1918 addresses to be relayed and connected to (default: %u)", DEFAULT_ALLOWPRIVATENET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, devnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), devnetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, devnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion). "
+ "The default onion bind is added only when no -bind is specified. When -listenonion is enabled and -bind is specified, a non-wildcard =onion bind is required, and wildcard =onion binds are rejected.", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), devnetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1439,6 +1440,13 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
}
+ // Governance needs the transaction index to look up proposal collateral transactions
+ if (!args.GetBoolArg("-disablegovernance", !DEFAULT_GOVERNANCE_ENABLE) &&
+ !args.GetBoolArg("-txindex", DEFAULT_TXINDEX) && chainparams.NetworkIDString() != CBaseChainParams::REGTEST) {
+ return InitError(_("Transaction index can't be disabled with governance validation enabled. Either start with "
+ "-disablegovernance command line switch or enable transaction index."));
+ }
+
if (args.GetBoolArg("-disablegovernance", !DEFAULT_GOVERNANCE_ENABLE)) {
InitWarning(_("You are starting with governance validation disabled.") +
(fPruneMode ?
@@ -2675,17 +2683,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
}
- CService onion_service_target;
- if (!connOptions.onion_binds.empty()) {
- onion_service_target = connOptions.onion_binds.front();
- } else if (!connOptions.vBinds.empty()) {
- onion_service_target = connOptions.vBinds.front();
- } else {
- onion_service_target = DefaultOnionServiceTarget();
- connOptions.onion_binds.push_back(onion_service_target);
+ if (connOptions.onion_binds.empty() && connOptions.vBinds.empty()) {
+ connOptions.onion_binds.push_back(DefaultOnionServiceTarget());
}
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
+ if (connOptions.onion_binds.empty()) {
+ return InitError(_("The automatic Tor onion service requires a dedicated onion bind. Use a specific address such as -bind=127.0.0.1:=onion, or disable the service with -listenonion=0."));
+ }
+ if (std::ranges::any_of(connOptions.onion_binds, [](auto& b) { return b.IsBindAny(); })) {
+ return InitError(_("The automatic Tor onion service cannot use a wildcard onion bind because the Tor daemon wouldn't be able to forward incoming connections to us. Use a specific address such as -bind=127.0.0.1:=onion, or disable the service with -listenonion=0."));
+ }
+ const CService& onion_service_target{connOptions.onion_binds[0]};
if (connOptions.onion_binds.size() > 1) {
InitWarning(strprintf(_("More than one onion bind address is provided. Using %s "
"for the automatically created Tor onion service."),
diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp
index 921b7291014e..bf5749a174a3 100644
--- a/src/instantsend/signing.cpp
+++ b/src/instantsend/signing.cpp
@@ -211,8 +211,8 @@ bool InstantSendSigner::CheckCanLockAssetUnlock(const CTransaction& tx, bool pri
const CBlockIndex* tip = chainstate.m_chain.Tip();
// Minable in the next block: inside its height window and signed by a recent quorum
TxValidationState state;
- const bool is_v24_active{DeploymentActiveAfter(tip, m_chainman, Consensus::DEPLOYMENT_V24)};
- if (!chainstate.ChainHelper().special_tx->CheckSpecialTx(tx, tip, is_v24_active, chainstate.CoinsTip(),
+ if (!chainstate.ChainHelper().special_tx->CheckSpecialTx(tx, tip, GetSpecialTxRules(tip, m_chainman),
+ chainstate.CoinsTip(),
/*check_sigs=*/true, state)) {
return log_refusal(state.ToString());
}
diff --git a/src/interfaces/providertx.h b/src/interfaces/providertx.h
index 63decede3568..d23c71dee4c2 100644
--- a/src/interfaces/providertx.h
+++ b/src/interfaces/providertx.h
@@ -153,9 +153,12 @@ struct SharedShareSpec {
//! Turns a funding transaction that already holds every participant's inputs and change
//! into an unsigned shared ProRegTx. Needs no wallet: nothing is funded or signed.
struct SharedRegistrationRequest {
+ MnType type{MnType::Regular};
CMutableTransaction funding_tx;
std::vector shares;
ProviderNetInfo net_info;
+ //! Required for an EvoNode
+ std::optional platform_node_id;
CBLSPublicKey operator_key;
CKeyID voting_key;
uint16_t operator_reward{0};
diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp
index a5e26f80f0fd..fc8563fdc197 100644
--- a/src/llmq/ehf_signals.cpp
+++ b/src/llmq/ehf_signals.cpp
@@ -33,16 +33,31 @@ CEHFSignalsHandler::~CEHFSignalsHandler()
sigman.UnregisterRecoveredSigsListener(this);
}
+static bool IsSignalWindowOpen(const Consensus::BIP9Deployment& deployment, int64_t time_past)
+{
+ return deployment.nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE && deployment.nStartTime <= time_past &&
+ time_past <= deployment.nTimeout;
+}
+
void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
{
if (!DeploymentActiveAfter(pindexNew, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) return;
const auto ehfSignals = m_chainman.ActiveChainstate().ChainHelper().ehf_manager->GetSignalsStage(pindexNew);
- for (const auto& deployment : Params().GetConsensus().vDeployments) {
+ const int64_t time_past = pindexNew->GetMedianTimePast();
+ for (int i = 0; i < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++i) {
+ const auto pos{static_cast(i)};
+ const auto& deployment{Params().GetConsensus().vDeployments[pos]};
// Skip deployments that do not use dip0023
if (!deployment.useEHF) continue;
+ // v24 follows evo_shares without signing it; a later release signs it once Platform supports
+ // EvoNode shares and payouts
+ if (pos == Consensus::DEPLOYMENT_EVO_SHARES) continue;
+ // IsValidMNActivation() is permissive for validation (it accepts bits of NEVER_ACTIVE deployments
+ // and bits outside of any window), so only sign inside the deployment's own start/timeout window
+ if (!IsSignalWindowOpen(deployment, time_past)) continue;
// Try to sign only activable deployments that haven't been mined yet
- if (ehfSignals.find(deployment.bit) == ehfSignals.end() && Params().IsValidMNActivation(deployment.bit, pindexNew->GetMedianTimePast())) {
+ if (ehfSignals.find(deployment.bit) == ehfSignals.end() && Params().IsValidMNActivation(deployment.bit, time_past)) {
trySignEHFSignal(deployment.bit, pindexNew);
}
}
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index 3a64cb5d942c..7009839ab8bc 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -378,8 +378,7 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, unsigned int packageSigOp
// - safe TXs in regard to ChainLocks
bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) const
{
- const bool is_v24_active{
- DeploymentActiveAfter(m_chainstate.m_chain.Tip(), m_chainstate.m_chainman, Consensus::DEPLOYMENT_V24)};
+ const SpecialTxRules special_tx_rules{GetSpecialTxRules(m_chainstate.m_chain.Tip(), m_chainstate.m_chainman)};
for (CTxMemPool::txiter it : package) {
if (!IsFinalTx(it->GetTx(), nHeight, m_lock_time_cutoff)) {
return false;
@@ -396,7 +395,7 @@ bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& packa
// signature regardless, which is cheap enough here given how rare MNHF signals are.
if (it->GetTx().IsSpecialTxVersion()) {
TxValidationState tx_state;
- if (!m_chain_helper.special_tx->CheckSpecialTx(it->GetTx(), m_chainstate.m_chain.Tip(), is_v24_active,
+ if (!m_chain_helper.special_tx->CheckSpecialTx(it->GetTx(), m_chainstate.m_chain.Tip(), special_tx_rules,
m_chainstate.CoinsTip(), /*check_sigs=*/false, tx_state)) {
return false;
}
@@ -406,8 +405,7 @@ bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& packa
// normal transaction creating or spending a template output can sit in the mempool across
// v24 activation on a node accepting nonstandard transactions, and would poison every
// template through the same TestBlockValidity path described above.
- if (DeploymentActiveAfter(m_chainstate.m_chain.Tip(), m_chainstate.m_chainman,
- Consensus::DEPLOYMENT_V24)) {
+ if (special_tx_rules.v24) {
TxValidationState tx_state;
if (!CheckSharedCollateralSpends(it->GetTx(), m_chainstate.CoinsTip(), tx_state) ||
!CheckSharedCollateralTemplateOutputs(it->GetTx(), tx_state)) {
@@ -511,7 +509,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele
// This map with signals is used only to find duplicates
auto signals = m_chain_helper.ehf_manager->GetSignalsStage(pindexPrev);
- const bool is_v24_active{DeploymentActiveAfter(pindexPrev, m_chainstate.m_chainman, Consensus::DEPLOYMENT_V24)};
+ const SpecialTxRules special_tx_rules{GetSpecialTxRules(pindexPrev, m_chainstate.m_chainman)};
// mapModifiedTx will store sorted packages after they are modified
// because some of their txs are already in the block
@@ -651,7 +649,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele
// producing an invalid template.
if (IsAssetUnlockWithStableTxid(tx)) {
TxValidationState state;
- if (!m_chain_helper.special_tx->CheckSpecialTx(tx, m_chainstate.m_chain.Tip(), is_v24_active,
+ if (!m_chain_helper.special_tx->CheckSpecialTx(tx, m_chainstate.m_chain.Tip(), special_tx_rules,
m_chainstate.CoinsTip(), /*check_sigs=*/true, state)) {
LogPrintf("%s: package tx %s skipped, asset unlock instance not currently minable: %s\n", __func__,
tx.GetHash().ToString(), state.ToString());
diff --git a/src/node/sync_manager.cpp b/src/node/sync_manager.cpp
index 4eb1598e284f..2f11b9f3cb31 100644
--- a/src/node/sync_manager.cpp
+++ b/src/node/sync_manager.cpp
@@ -83,7 +83,7 @@ int SyncManager::RequestGovernanceObjectVotes(const std::vector& vNodesC
static std::map> mapAskedRecently GUARDED_BY(cs_recently);
LOCK(cs_recently);
- auto [vTriggerObjHashes, vOtherObjHashes] = m_gov_manager.FetchGovernanceObjectVotes(nMaxObjRequestsPerNode, nNow,
+ auto [vTriggerObjHashes, vOtherObjHashes] = m_gov_manager.FetchGovernanceObjectVotes(nPeersPerHashMax, nNow,
mapAskedRecently);
if (vTriggerObjHashes.empty() && vOtherObjHashes.empty()) return -2;
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 2ba214b74d19..fdc2c4f149b7 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -181,10 +181,10 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
}
-bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata)
+bool PSBTInputSignedAndVerified(const PartiallySignedTransaction& psbt, unsigned int input_index, const PrecomputedTransactionData* txdata)
{
CTxOut utxo;
- assert(psbt.inputs.size() >= input_index);
+ assert(input_index < psbt.inputs.size());
const PSBTInput& input = psbt.inputs[input_index];
if (input.non_witness_utxo) {
diff --git a/src/psbt.h b/src/psbt.h
index 4777289c9114..a79a8f3a1456 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -869,7 +869,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
bool PSBTInputSigned(const PSBTInput& input);
/** Checks whether a PSBTInput is already signed by doing script verification using final fields. */
-bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata);
+bool PSBTInputSignedAndVerified(const PartiallySignedTransaction& psbt, unsigned int input_index, const PrecomputedTransactionData* txdata);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed.
*
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 734a45a6c9d8..61c4e825ea43 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -119,7 +119,12 @@ static QString GetLangTerritory()
if(!lang_territory_qsettings.isEmpty())
lang_territory = lang_territory_qsettings;
// 3) -lang command line argument
- lang_territory = QString::fromStdString(gArgs.GetArg("-lang", lang_territory.toStdString()));
+ try {
+ lang_territory = QString::fromStdString(gArgs.GetArg("-lang", lang_territory.toStdString()));
+ } catch (const std::exception&) {
+ // Keep the language selected above if "lang" in settings.json has an
+ // unexpected type. OptionsModel::Init reports it as an error.
+ }
return lang_territory;
}
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index e0f5adc3a690..12e077381cb7 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -204,8 +204,20 @@ void OptionsModel::addOverriddenOption(const std::string &option)
// Writes all missing QSettings with their default values
bool OptionsModel::Init(bilingual_str& error)
{
+ // Handles exceptions thrown by univalue that can happen if settings in
+ // settings.json don't have the expected types.
+ const auto setting_error = [&](const std::string& setting, const std::exception& e) {
+ error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what());
+ error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString();
+ return false;
+ };
+
// Initialize display settings from stored settings.
- language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), ""));
+ try {
+ language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), ""));
+ } catch (const std::exception& e) {
+ return setting_error("lang", e);
+ }
checkAndMigrate();
@@ -256,9 +268,13 @@ bool OptionsModel::Init(bilingual_str& error)
addOverriddenOption("-font-family");
}
if (GUIUtil::fontsLoaded()) {
- const QString font_name = QString::fromStdString(
- SettingToString(node().getPersistentSetting("font-family"), ""));
- GUIUtil::setActiveFont(font_name);
+ try {
+ const QString font_name = QString::fromStdString(
+ SettingToString(node().getPersistentSetting("font-family"), ""));
+ GUIUtil::setActiveFont(font_name);
+ } catch (const std::exception& e) {
+ return setting_error("font-family", e);
+ }
}
// Font Scale
@@ -266,7 +282,11 @@ bool OptionsModel::Init(bilingual_str& error)
addOverriddenOption("-font-scale");
}
if (GUIUtil::fontsLoaded()) {
- GUIUtil::setFontScale(SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::defaultFontScale()));
+ try {
+ GUIUtil::setFontScale(SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::defaultFontScale()));
+ } catch (const std::exception& e) {
+ return setting_error("font-scale", e);
+ }
}
// Font Weight (Normal)
@@ -276,11 +296,17 @@ bool OptionsModel::Init(bilingual_str& error)
const bool override_family{isOptionOverridden("-font-family")};
if (GUIUtil::fontsLoaded()) {
- // If font was overridden by CLI but weight wasn't, use the font's default weight
const int default_arg = GUIUtil::defaultWeightArg(GUIUtil::FontWeight::Normal);
- const int arg = (!override_family || isOptionOverridden("-font-weight-normal"))
- ? SettingToInt(node().getPersistentSetting("font-weight-normal"), default_arg)
- : default_arg;
+ // Read the stored value even when it is not used below, so that a
+ // malformed one is reported here rather than thrown later via gArgs.
+ int setting_arg;
+ try {
+ setting_arg = SettingToInt(node().getPersistentSetting("font-weight-normal"), default_arg);
+ } catch (const std::exception& e) {
+ return setting_error("font-weight-normal", e);
+ }
+ // If font was overridden by CLI but weight wasn't, use the font's default weight
+ const int arg = (!override_family || isOptionOverridden("-font-weight-normal")) ? setting_arg : default_arg;
if (!GUIUtil::setWeightFromArg(GUIUtil::FontWeight::Normal, arg)) {
node().forceSetting("font-weight-normal", default_arg);
GUIUtil::setWeightFromArg(GUIUtil::FontWeight::Normal, default_arg);
@@ -292,11 +318,17 @@ bool OptionsModel::Init(bilingual_str& error)
addOverriddenOption("-font-weight-bold");
}
if (GUIUtil::fontsLoaded()) {
- // If font was overridden by CLI but weight wasn't, use the font's default weight
const int default_arg = GUIUtil::defaultWeightArg(GUIUtil::FontWeight::Bold);
- const int arg = (!override_family || isOptionOverridden("-font-weight-bold"))
- ? SettingToInt(node().getPersistentSetting("font-weight-bold"), default_arg)
- : default_arg;
+ // Read the stored value even when it is not used below, so that a
+ // malformed one is reported here rather than thrown later via gArgs.
+ int setting_arg;
+ try {
+ setting_arg = SettingToInt(node().getPersistentSetting("font-weight-bold"), default_arg);
+ } catch (const std::exception& e) {
+ return setting_error("font-weight-bold", e);
+ }
+ // If font was overridden by CLI but weight wasn't, use the font's default weight
+ const int arg = (!override_family || isOptionOverridden("-font-weight-bold")) ? setting_arg : default_arg;
if (!GUIUtil::setWeightFromArg(GUIUtil::FontWeight::Bold, arg)) {
node().forceSetting("font-weight-bold", default_arg);
GUIUtil::setWeightFromArg(GUIUtil::FontWeight::Bold, default_arg);
@@ -361,11 +393,7 @@ bool OptionsModel::Init(bilingual_str& error)
try {
getOption(option);
} catch (const std::exception& e) {
- // This handles exceptions thrown by univalue that can happen if
- // settings in settings.json don't have the expected types.
- error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what());
- error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString();
- return false;
+ return setting_error(setting, e);
}
}
diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp
index fba2d2f118f6..626b5d1079d1 100644
--- a/src/qt/test/optiontests.cpp
+++ b/src/qt/test/optiontests.cpp
@@ -101,6 +101,61 @@ void OptionTests::integerGetArgBug()
gArgs.WriteSettingsFile();
}
+void OptionTests::invalidFontSetting()
+{
+ if (QApplication::platformName() == "minimal") {
+ QSKIP("AppTests cannot initialize fonts with the 'minimal' platform plugin.");
+ }
+
+ // Font settings are only read once loadFonts() has run, which AppTests does.
+ QVERIFY2(GUIUtil::fontsLoaded(),
+ "GUIUtil::loadFonts() must succeed in AppTests::appTests() before OptionTests run.");
+
+ for (const std::string setting : {"font-family", "font-scale", "font-weight-normal", "font-weight-bold"}) {
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.rw_settings[setting] = UniValue{UniValue::VARR};
+ });
+ bilingual_str error;
+ QVERIFY(!OptionsModel{m_node}.Init(error));
+ QCOMPARE(QString::fromStdString(error.original),
+ QString::fromStdString(strprintf("Could not read setting \"%s\", JSON value of type array is not of expected type string.", setting)));
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.rw_settings.erase(setting);
+ });
+ }
+
+ // With -font-family on the command line the stored weights are not used, but
+ // dash-qt still reads them through gArgs later, so they must be reported too.
+ for (const std::string setting : {"font-weight-normal", "font-weight-bold"}) {
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.command_line_options["font-family"] = {UniValue{GUIUtil::defaultFontFamily().toStdString()}};
+ settings.rw_settings[setting] = UniValue{UniValue::VARR};
+ });
+ bilingual_str error;
+ QVERIFY(!OptionsModel{m_node}.Init(error));
+ QCOMPARE(QString::fromStdString(error.original),
+ QString::fromStdString(strprintf("Could not read setting \"%s\", JSON value of type array is not of expected type string.", setting)));
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.command_line_options.erase("font-family");
+ settings.rw_settings.erase(setting);
+ });
+ }
+}
+
+void OptionTests::invalidLangSetting()
+{
+ // A "lang" value with an unexpected type in settings.json must be reported
+ // through the error string like other settings instead of throwing out of
+ // OptionsModel::Init.
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.rw_settings["lang"] = UniValue{UniValue::VOBJ};
+ });
+ bilingual_str error;
+ QVERIFY(!OptionsModel{m_node}.Init(error));
+ QCOMPARE(QString::fromStdString(error.original),
+ QString{"Could not read setting \"lang\", JSON value of type object is not of expected type string."});
+}
+
void OptionTests::parametersInteraction()
{
// Test that the bug https://github.com/bitcoin-core/gui/issues/567 does not resurface.
diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h
index 89976d97d0b3..2cee85ab5903 100644
--- a/src/qt/test/optiontests.h
+++ b/src/qt/test/optiontests.h
@@ -21,6 +21,8 @@ private Q_SLOTS:
void init(); // called before each test function execution.
void migrateSettings();
void integerGetArgBug();
+ void invalidFontSetting();
+ void invalidLangSetting();
void parametersInteraction();
void extractFilter();
void effectivePointSize();
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 6c2a5efc689f..3b82e308a89d 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1685,6 +1685,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const CMNHFManager::Signa
}
for (auto ehf_deploy : { /* sorted by activation block */
Consensus::DEPLOYMENT_V24,
+ Consensus::DEPLOYMENT_EVO_SHARES,
Consensus::DEPLOYMENT_TESTDUMMY }) {
SoftForkDescPushBack(blockindex, ehf_signals, softforks, chainman, ehf_deploy);
}
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 1cba059e4732..260e3875ec50 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -325,6 +325,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "protx shared_register_prepare", 3, "coreP2PAddrs", true },
{ "protx shared_register_prepare", 7, "earlyPeriodBlocks" },
{ "protx shared_register_prepare", 8, "earlyPenalty" },
+ { "protx shared_register_prepare_evo", 2, "shares" },
+ { "protx shared_register_prepare_evo", 3, "coreP2PAddrs", true },
+ { "protx shared_register_prepare_evo", 7, "earlyPeriodBlocks" },
+ { "protx shared_register_prepare_evo", 8, "earlyPenalty" },
+ { "protx shared_register_prepare_evo", 10, "platformP2PAddrs", true },
+ { "protx shared_register_prepare_evo", 11, "platformHTTPSAddrs", true },
{ "protx revoke", 3, "reason" },
{ "protx revoke", 5, "submit" },
{ "protx shared_combine", 2, "signatures" },
diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp
index ffb820e119ce..8db3f4f93e89 100644
--- a/src/rpc/evo.cpp
+++ b/src/rpc/evo.cpp
@@ -563,6 +563,16 @@ static interfaces::ProviderPlatformEndpoints ParsePlatformNetInfo(const UniValue
strprintf("Invalid param for %s, must be a valid port [1-65535]", field_name));
}
+static uint160 ParsePlatformNodeID(const UniValue& value)
+{
+ if (!IsHex(value.get_str())) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
+ }
+ uint160 platform_node_id;
+ platform_node_id.SetHex(value.get_str());
+ return platform_node_id;
+}
+
// forward declaration
namespace {
enum class ProTxRegisterAction
@@ -912,12 +922,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
typed_request.payouts = ParsePayouts(request.params[paramIdx + 5], "payouts");
if (mnType == MnType::Evo) {
- if (!IsHex(request.params[paramIdx + 6].get_str())) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
- }
- uint160 platform_node_id;
- platform_node_id.SetHex(request.params[paramIdx + 6].get_str());
- typed_request.platform_node_id = platform_node_id;
+ typed_request.platform_node_id = ParsePlatformNodeID(request.params[paramIdx + 6]);
typed_request.net_info.platform_p2p = ParsePlatformNetInfo(request.params[paramIdx + 7], "platformP2PAddrs",
capabilities.extended_addresses);
typed_request.net_info.platform_https = ParsePlatformNetInfo(request.params[paramIdx + 8], "platformHTTPSAddrs",
@@ -1062,12 +1067,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques
size_t paramIdx{3};
if (mnType == MnType::Evo) {
const auto capabilities{evo::provider::GetCapabilities(node)};
- if (!IsHex(request.params[paramIdx].get_str())) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
- }
- uint160 platform_node_id;
- platform_node_id.SetHex(request.params[paramIdx].get_str());
- typed_request.platform_node_id = platform_node_id;
+ typed_request.platform_node_id = ParsePlatformNodeID(request.params[paramIdx]);
typed_request.net_info.platform_p2p = ParsePlatformNetInfo(request.params[paramIdx + 1], "platformP2PAddrs",
capabilities.extended_addresses);
typed_request.net_info.platform_https = ParsePlatformNetInfo(request.params[paramIdx + 2], "platformHTTPSAddrs",
@@ -1959,12 +1959,18 @@ static RPCHelpMan protx_listdiff()
UniValue jupdatedMNs(UniValue::VARR);
for(const auto& [internal_id, stateDiff] : mnDiff.updatedMNs) {
- auto dmn = baseBlockMNList.GetMNByInternalId(internal_id);
+ auto dmn = blockMNList.GetMNByInternalId(internal_id);
// BuildDiff will construct itself with MNs that we already have knowledge
// of, meaning that fetch operations should never fail.
CHECK_NONFATAL(dmn);
+ UniValue diff_json{stateDiff.ToJson(dmn->nType)};
+ if ((stateDiff.fields & CDeterministicMNStateDiff::Field_netInfo) && !dmn->pdmnState->netInfo->IsEmpty()) {
+ // A legacy EvoNode's Platform addresses pair its core P2P address with the Platform ports,
+ // which the diff doesn't carry unless they changed too
+ diff_json.pushKV("addresses", GetNetInfoWithLegacyFields(*dmn->pdmnState, dmn->nType));
+ }
UniValue obj(UniValue::VOBJ);
- obj.pushKV(dmn->proTxHash.ToString(), stateDiff.ToJson(dmn->nType));
+ obj.pushKV(dmn->proTxHash.ToString(), diff_json);
jupdatedMNs.push_back(obj);
}
ret.pushKV("updatedMNs", jupdatedMNs);
@@ -2150,26 +2156,10 @@ static RPCHelpMan evodb_repair()
}
#ifdef ENABLE_WALLET
-static RPCHelpMan protx_shared_register_prepare()
+static RPCHelpMan protx_shared_register_prepare_wrapper(const MnType mn_type)
{
- return RPCHelpMan{"protx shared_register_prepare",
- "\nCreates an unsigned shared masternode registration (a version 3 ProRegTx with a collateral share\n"
- "table) by appending the shared-collateral output and payload to a caller-supplied funding\n"
- "transaction. The funding transaction must already contain every participant's contribution inputs\n"
- "and any change outputs; the consent digest binds all of them. Every share owner must sign the\n"
- "returned consent hash via \"protx shared_sign\"; combine with \"protx shared_combine\", then have the\n"
- "funding inputs signed (signrawtransactionwithwallet) and broadcast with sendrawtransaction.\n"
- "\nIMPORTANT: consent is required from each share in the table, not from each funding input; an input\n"
- "is not tied to any share. Before signing its funding inputs, each participant should check the\n"
- "share table (\"proRegTx\" in decoderawtransaction, or \"terms\" from shared_sign) for its own share,\n"
- "with its own amount and addresses. Signing inputs into a table without that share gives the\n"
- "contribution to the other shares.\n"
- "\nIMPORTANT: once the registration confirms, every participant should create a zero-penalty\n"
- "standby dissolution (\"protx shared_dissolve false false\") and store\n"
- "the returned hex with their refund-key backup, separately from the share owner key. It becomes\n"
- "valid when the early period ends (or immediately when no early period applies), then never expires;\n"
- "broadcasting it after that point recovers the participant's principal without cooperation.\n",
- {
+ const bool evo{mn_type == MnType::Evo};
+ std::vector args{
{"fundingTx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The serialized funding transaction with all participants' inputs and change outputs."},
{"shares", RPCArg::Type::ARR, RPCArg::Optional::NO, "The collateral share table, in consensus-significant order. Amounts must sum to the collateral.",
{
@@ -2189,7 +2179,46 @@ static RPCHelpMan protx_shared_register_prepare()
RPCArgOptions{.skip_type_check = true}},
{"earlyPeriodBlocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "Length in blocks of the early period during which unilateral dissolution is penalized (up to 420480)."},
{"earlyPenalty", RPCArg::Type::NUM, RPCArg::Optional::NO, "Penalty in duffs for unilateral dissolution during the early period (must be below the smallest share, and zero when earlyPeriodBlocks is zero)."},
- },
+ };
+ if (evo) {
+ args.push_back(GetRpcArg("platformNodeID"));
+ args.push_back({"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
+ "Array of addresses in the form \"ADDR:PORT\" used by Platform for peer-to-peer connection.\n"
+ "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
+ },
+ RPCArgOptions{.skip_type_check = true}});
+ args.push_back({"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
+ "Array of addresses in the form \"ADDR:PORT\" used by Platform for their HTTPS API.\n"
+ "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
+ },
+ RPCArgOptions{.skip_type_check = true}});
+ }
+ const std::string example_args{"\"fundingTx\" \"[...]\" \"1.2.3.4:1234\" \"operatorPubKey\" \"" + EXAMPLE_ADDRESS[1] +
+ "\" 0 10000 5000000000"};
+ return RPCHelpMan{evo ? "protx shared_register_prepare_evo" : "protx shared_register_prepare",
+ std::string{evo ? "\nSame as \"protx shared_register_prepare\", but for an EvoNode. EvoNodes can only register with\n"
+ "shared collateral once the evo_shares deployment is active.\n" : ""} +
+ "\nCreates an unsigned shared masternode registration (a version 3 ProRegTx with a collateral share\n"
+ "table) by appending the shared-collateral output and payload to a caller-supplied funding\n"
+ "transaction. The funding transaction must already contain every participant's contribution inputs\n"
+ "and any change outputs; the consent digest binds all of them. Every share owner must sign the\n"
+ "returned consent hash via \"protx shared_sign\"; combine with \"protx shared_combine\", then have the\n"
+ "funding inputs signed (signrawtransactionwithwallet) and broadcast with sendrawtransaction.\n"
+ "\nIMPORTANT: consent is required from each share in the table, not from each funding input; an input\n"
+ "is not tied to any share. Before signing its funding inputs, each participant should check the\n"
+ "share table (\"proRegTx\" in decoderawtransaction, or \"terms\" from shared_sign) for its own share,\n"
+ "with its own amount and addresses. Signing inputs into a table without that share gives the\n"
+ "contribution to the other shares.\n"
+ "\nIMPORTANT: once the registration confirms, every participant should create a zero-penalty\n"
+ "standby dissolution (\"protx shared_dissolve false false\") and store\n"
+ "the returned hex with their refund-key backup, separately from the share owner key. It becomes\n"
+ "valid when the early period ends (or immediately when no early period applies), then never expires;\n"
+ "broadcasting it after that point recovers the participant's principal without cooperation.\n",
+ args,
RPCResult{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "tx", "The serialized unsigned shared ProRegTx"},
@@ -2198,12 +2227,14 @@ static RPCHelpMan protx_shared_register_prepare()
CProRegTx::GetJsonHelp("terms", /*optional=*/false),
{RPCResult::Type::STR, "warning", /*optional=*/true, "Present when earlyPenalty is zero: any participant can force an early exit at no cost beyond the transaction fee"},
}},
- RPCExamples{HelpExampleCli("protx", "shared_register_prepare \"fundingTx\" \"[...]\" \"1.2.3.4:1234\" \"operatorPubKey\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 10000 5000000000")},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ RPCExamples{evo ? HelpExampleCli("protx", "shared_register_prepare_evo " + example_args + " \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" \"[\\\"1.2.3.4:22821\\\"]\" \"[\\\"1.2.3.4:22822\\\"]\"")
+ : HelpExampleCli("protx", "shared_register_prepare " + example_args)},
+ [mn_type](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureAnyNodeContext(request.context);
interfaces::SharedRegistrationRequest typed_request;
+ typed_request.type = mn_type;
if (!DecodeHexTx(typed_request.funding_tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "funding transaction not deserializable");
}
@@ -2233,6 +2264,14 @@ static RPCHelpMan protx_shared_register_prepare()
typed_request.early_period_blocks = static_cast(earlyPeriodBlocks);
typed_request.early_penalty = request.params[7].getInt();
+ if (mn_type == MnType::Evo) {
+ typed_request.platform_node_id = ParsePlatformNodeID(request.params[8]);
+ typed_request.net_info.platform_p2p = ParsePlatformNetInfo(request.params[9], "platformP2PAddrs",
+ /*extended_addresses=*/true);
+ typed_request.net_info.platform_https = ParsePlatformNetInfo(request.params[10], "platformHTTPSAddrs",
+ /*extended_addresses=*/true);
+ }
+
const auto prepared{UnwrapOrThrow(evo::provider::PrepareSharedRegistration(node, typed_request))};
UniValue ret(UniValue::VOBJ);
ret.pushKV("tx", EncodeHexTx(*prepared.tx));
@@ -2247,6 +2286,16 @@ static RPCHelpMan protx_shared_register_prepare()
};
}
+static RPCHelpMan protx_shared_register_prepare()
+{
+ return protx_shared_register_prepare_wrapper(MnType::Regular);
+}
+
+static RPCHelpMan protx_shared_register_prepare_evo()
+{
+ return protx_shared_register_prepare_wrapper(MnType::Evo);
+}
+
static RPCHelpMan protx_shared_dissolve_prepare()
{
return RPCHelpMan{"protx shared_dissolve_prepare",
@@ -2311,6 +2360,7 @@ static RPCHelpMan protx_help()
" update_registrar_legacy - (DEPRECATED) Create ProUpRegTx by parsing BLS using the legacy scheme, then send it to network\n"
" revoke - Create and send ProUpRevTx to network\n"
" shared_register_prepare - Create an unsigned shared masternode ProTx\n"
+ " shared_register_prepare_evo - Create an unsigned shared masternode ProTx for an EvoNode\n"
" shared_sign - Sign a shared masternode transaction with this wallet's share owner keys\n"
" shared_combine - Combine share owner signatures into a shared masternode transaction\n"
" shared_dissolve - Create, sign and send a unilateral ProDisTx\n"
@@ -2442,6 +2492,7 @@ Span GetWalletEvoRPCCommands()
{"evo", &protx_update_registrar},
{"evo", &protx_revoke},
{"evo", &protx_shared_register_prepare},
+ {"evo", &protx_shared_register_prepare_evo},
{"evo", &protx_shared_sign},
{"evo", &protx_shared_combine},
{"evo", &protx_shared_dissolve},
diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp
index 67e9da8de518..2100cbca2668 100644
--- a/src/rpc/quorums.cpp
+++ b/src/rpc/quorums.cpp
@@ -387,7 +387,7 @@ static RPCHelpMan quorum_dkgstatus()
if (node.active_ctx) {
int quorumHeight = tipHeight - (tipHeight % llmq_params.dkgInterval) + quorumIndex;
if (quorumHeight <= tipHeight) {
- const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return chainman.ActiveChain()[quorumHeight]);
+ const CBlockIndex* pQuorumBaseBlockIndex = pindexTip->GetAncestor(quorumHeight);
obj.pushKV("pQuorumBaseBlockIndex", pQuorumBaseBlockIndex->nHeight);
obj.pushKV("quorumHash", pQuorumBaseBlockIndex->GetBlockHash().ToString());
obj.pushKV("pindexTip", pindexTip->nHeight);
@@ -427,7 +427,8 @@ static RPCHelpMan quorum_dkgstatus()
}
LOCK(cs_main);
- std::optional> vfqc = llmq_ctx.quorum_block_processor->GetMineableCommitments(llmq_params, tipHeight + 1);
+ std::optional> vfqc =
+ llmq_ctx.quorum_block_processor->GetMineableCommitments(llmq_params, chainman.ActiveChain().Height() + 1);
if (vfqc.has_value()) {
for (const auto& fqc : vfqc.value()) {
minableCommitments.push_back(fqc.ToJson());
diff --git a/src/test/coinjoin_inouts_tests.cpp b/src/test/coinjoin_inouts_tests.cpp
index ee7b268831f6..9bf2f803869b 100644
--- a/src/test/coinjoin_inouts_tests.cpp
+++ b/src/test/coinjoin_inouts_tests.cpp
@@ -19,6 +19,7 @@
#include