Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
51b145c
Merge #7778: test: use single-member in functional tests: feature_mne…
PastaPastaPasta Oct 3, 2026
7d1a90d
Merge #7791: fix(net): request object votes from nPeersPerHashMax peers
PastaPastaPasta Oct 4, 2026
40de493
Merge #7781: fix(rpc): use a consistent tip height in quorum dkgstatus
PastaPastaPasta Oct 4, 2026
73f6ea2
Merge #7789: fix(qt): report malformed lang/font settings instead of …
PastaPastaPasta Oct 4, 2026
a1d7411
Merge #7779: fix!: report legacy evonode Platform addresses when list…
PastaPastaPasta Oct 4, 2026
89d44c8
Merge #7795: backport: bitcoin#34272, partial bitcoin#31650 (pass PSB…
PastaPastaPasta Oct 4, 2026
d9f6592
Merge #7783: fix: stop fish completion from re-running the typed dash…
PastaPastaPasta Oct 4, 2026
2d1735d
Merge #7794: fix(wallet): floor the denomination gap threshold so reb…
PastaPastaPasta Oct 4, 2026
a3b0f3c
Merge #7785: fix: restore the txindex requirement when governance val…
PastaPastaPasta Oct 5, 2026
700c195
Merge #7787: fix: only sign EHF signals inside the deployment's start…
PastaPastaPasta Oct 5, 2026
250a3cd
Merge #7790: fix: guard cached governance object flags with the objec…
PastaPastaPasta Oct 5, 2026
70626a0
Merge #7793: backport: partial bitcoin#28868 (wallet: reload wallet i…
PastaPastaPasta Oct 6, 2026
4eb82f6
Merge #7786: fix(net): keep a dedicated onion listener when -bind is …
PastaPastaPasta Oct 6, 2026
57a8f07
Merge #7796: backport: bitcoin#26720 (remaining), partial bitcoin#295…
PastaPastaPasta Oct 6, 2026
ca69801
Merge #7803: fix: return non-zero amount of accounts after sethdseed
PastaPastaPasta Oct 6, 2026
57fd2d9
Merge #7776: fix: keep a pending ProUpServTx across a registrar updat…
PastaPastaPasta Oct 6, 2026
fbe6526
Merge #7807: feat(consensus): hold EvoNode shares and multiple payout…
PastaPastaPasta Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions contrib/completions/fish/dash-cli.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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 <address> <amount> <Tab>` 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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 11 additions & 3 deletions doc/release-notes-7300.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<addr>:<port>=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=<addr:port>=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=<addr:port>=onion` to accept incoming Tor connections, or set
`-listenonion=0` to disable automatic onion service creation.
6 changes: 6 additions & 0 deletions doc/release-notes-7779.md
Original file line number Diff line number Diff line change
@@ -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)
20 changes: 20 additions & 0 deletions doc/release-notes-7807.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions doc/tor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<port>=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
Expand Down
36 changes: 36 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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{};

Expand Down Expand Up @@ -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{};

Expand Down
12 changes: 10 additions & 2 deletions src/coinjoin/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -359,6 +356,9 @@ uint256 CProRegTx::MakeSharedRegConsentHash(const CTransaction& tx) const
hw << nMode;
hw << NetInfoSerWrapper(const_cast<std::shared_ptr<NetInfoInterface>&>(netInfo),
nVersion >= ProTxVersion::ExtAddr);
if (nType == MnType::Evo) {
hw << platformNodeID;
}
hw << keyIDVoting;
hw << CBLSLazyPublicKeyVersionWrapper(const_cast<CBLSLazyPublicKey&>(pubKeyOperator),
nVersion == ProTxVersion::LegacyBLS);
Expand Down
56 changes: 39 additions & 17 deletions src/evo/providertx_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ uint16_t CurrentProviderTxVersion(node::NodeContext& node, std::optional<bool> 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;
Expand Down Expand Up @@ -255,6 +263,17 @@ std::optional<ProviderTxError> ValidateNetworkFields(const ProTx& payload, bool
return std::nullopt;
}

template <typename ProTx>
std::optional<ProviderTxError> ApplyPlatformNodeId(ProTx& payload, const std::optional<uint160>& 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<MasternodePayoutShares, ProviderTxError>;

PayoutResult BuildPayouts(const std::vector<ProviderPayout>& payouts)
Expand Down Expand Up @@ -360,9 +379,8 @@ std::optional<ProviderTxError> 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -822,12 +835,7 @@ ProviderTxResult<ProviderTxSubmission> 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;
Expand Down Expand Up @@ -992,12 +1000,20 @@ ProviderTxResult<ProviderTxSubmission> Revoke(node::NodeContext& node, Wallet& w
ProviderTxResult<PreparedSharedRegistration> 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,
Expand All @@ -1023,7 +1039,13 @@ ProviderTxResult<PreparedSharedRegistration> 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");
Expand Down
Loading
Loading