From f81c56edd5ef5c997841184f0bcc0ac253eb7e80 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 19 Aug 2026 05:08:51 +0700 Subject: [PATCH 1/5] chore: restore alphabetical order in non-backported.txt PR 7600 inserted src/interfaces/providertx.h before the src/instantsend globs; instantsend sorts before interfaces. --- test/util/data/non-backported.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index fcd48d3a87c9..9ef8b5e20873 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -25,9 +25,9 @@ src/index/spent*.cpp src/index/spent*.h src/index/timestamp*.cpp src/index/timestamp*.h -src/interfaces/providertx.h src/instantsend/*.cpp src/instantsend/*.h +src/interfaces/providertx.h src/llmq/*.cpp src/llmq/*.h src/masternode/*.cpp From 4288bb71e522c858d6a311a871b4f74330ab3c35 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 19 Aug 2026 04:51:48 +0700 Subject: [PATCH 2/5] refactor(evo): deduplicate provider fee-source fallback cascade UpdateService() and Revoke() carried the same fee_source -> scriptOperatorPayout -> first-owner-payout resolution, differing only in which operator payout script feeds it and the error text when nothing is found. Express the cascade once as ResolveFeeSource(), keeping both error messages verbatim. --- src/evo/providertx_service.cpp | 44 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/evo/providertx_service.cpp b/src/evo/providertx_service.cpp index accdb7d25be3..1361cdfb76b4 100644 --- a/src/evo/providertx_service.cpp +++ b/src/evo/providertx_service.cpp @@ -265,6 +265,23 @@ PayoutResult BuildPayouts(const std::vector& payouts) return result; } +std::optional ResolveFeeSource(const std::optional& fee_source, + const CScript& operator_payout, const CDeterministicMNState& dmn_state, + std::string missing_error, CTxDestination& fund_destination) +{ + if (fee_source) { + fund_destination = *fee_source; + } else if (!operator_payout.empty()) { + ExtractDestination(operator_payout, fund_destination); + } else { + const auto owner_payouts{GetOwnerPayouts(dmn_state)}; + if (owner_payouts.empty() || !ExtractDestination(owner_payouts.front().scriptPayout, fund_destination)) { + return Error(ProviderTxErrorCode::INVALID_PARAMETER, std::move(missing_error)); + } + } + return std::nullopt; +} + template void UpdateInputsHash(const CMutableTransaction& tx, Payload& payload) { @@ -675,16 +692,10 @@ ProviderTxResult UpdateService(node::NodeContext& node, Wa : dmn->pdmnState->scriptOperatorPayout; CTxDestination fund_destination; - if (request.fee_source) { - fund_destination = *request.fee_source; - } else if (!payload.scriptOperatorPayout.empty()) { - ExtractDestination(payload.scriptOperatorPayout, fund_destination); - } else { - const auto owner_payouts{GetOwnerPayouts(*dmn->pdmnState)}; - if (owner_payouts.empty() || !ExtractDestination(owner_payouts.front().scriptPayout, fund_destination)) { - return Error(ProviderTxErrorCode::INVALID_PARAMETER, - "masternode has no default fee source; specify feeSourceAddress"); - } + if (auto error{ResolveFeeSource(request.fee_source, payload.scriptOperatorPayout, *dmn->pdmnState, + "masternode has no default fee source; specify feeSourceAddress", + fund_destination)}) { + return *error; } CMutableTransaction tx; @@ -798,16 +809,9 @@ ProviderTxResult Revoke(node::NodeContext& node, Wallet& w payload.nReason = request.reason; CTxDestination fund_destination; - if (request.fee_source) { - fund_destination = *request.fee_source; - } else if (!dmn->pdmnState->scriptOperatorPayout.empty()) { - ExtractDestination(dmn->pdmnState->scriptOperatorPayout, fund_destination); - } else { - const auto owner_payouts{GetOwnerPayouts(*dmn->pdmnState)}; - if (owner_payouts.empty() || !ExtractDestination(owner_payouts.front().scriptPayout, fund_destination)) { - return Error(ProviderTxErrorCode::INVALID_PARAMETER, - "No payout or fee source addresses found, can't revoke"); - } + if (auto error{ResolveFeeSource(request.fee_source, dmn->pdmnState->scriptOperatorPayout, *dmn->pdmnState, + "No payout or fee source addresses found, can't revoke", fund_destination)}) { + return *error; } CMutableTransaction tx; From 86b1475c6afed8b555f40548eae4fafe57b216e5 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 19 Aug 2026 04:50:44 +0700 Subject: [PATCH 3/5] fix: correct 'collaterall' and 'anonumous' typos from PR 7600 The misspelled collateral message appears in user-facing RPC errors from both the RPC pre-check and the provider service; no test asserts the misspelling. --- src/evo/providertx_service.cpp | 2 +- src/rpc/evo.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evo/providertx_service.cpp b/src/evo/providertx_service.cpp index 1361cdfb76b4..47961aba49d5 100644 --- a/src/evo/providertx_service.cpp +++ b/src/evo/providertx_service.cpp @@ -453,7 +453,7 @@ RegistrationResult BuildRegistration(node::NodeContext& node, Wallet& wallet, if (const auto* collateral{std::get_if(&request.collateral)}) { if (!IsValidDestination(collateral->destination)) { return Error(ProviderTxErrorCode::INVALID_ADDRESS_OR_KEY, - strprintf("invalid collaterall address: %s", EncodeDestination(collateral->destination))); + strprintf("invalid collateral address: %s", EncodeDestination(collateral->destination))); } tx.vout.emplace_back(GetMnType(request.type).collat_amount, GetScriptForDestination(collateral->destination)); } else { diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 285cf857beab..bf1ab82535fa 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -469,7 +469,7 @@ enum class ProTxRegisterAction Fund, Prepare, }; -} // anonumous namespace +} // anonymous namespace static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, const bool specific_legacy_bls_scheme, @@ -769,7 +769,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, CTxDestination collateral_destination{DecodeDestination(request.params[paramIdx].get_str())}; if (!IsValidDestination(collateral_destination)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str())); + strprintf("invalid collateral address: %s", request.params[paramIdx].get_str())); } typed_request.collateral = interfaces::FundProviderCollateral{collateral_destination}; paramIdx++; From 1bb24f4435716d18610c85e9d376bd301d02e722 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 19 Aug 2026 04:47:23 +0700 Subject: [PATCH 4/5] refactor(rpc): collapse protx result unwrapping into UnwrapOrThrow() All six protx service call sites repeated the same get_if/throw/get sequence to turn a ProviderTxResult into its success value. One template helper next to ThrowProviderTxError() expresses it once. --- src/rpc/evo.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index bf1ab82535fa..f0d5f0cae911 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -392,6 +392,13 @@ static std::string SubmissionToString(const interfaces::ProviderTxSubmission& su return submission.submitted ? submission.tx->GetHash().GetHex() : EncodeHexTx(*submission.tx); } +template +static T UnwrapOrThrow(interfaces::ProviderTxResult result) +{ + if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); + return std::get(std::move(result)); +} + static std::vector ParseCoreNetInfo(const UniValue& input, bool optional) { if (input.isStr()) { @@ -838,18 +845,14 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, auto wallet_interface{MakeWalletInterface(node, pwallet)}; if (action == ProTxRegisterAction::Prepare) { - auto result{evo::provider::PrepareRegistration(node, *wallet_interface, typed_request)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - const auto& prepared{std::get(result)}; + const auto prepared{UnwrapOrThrow(evo::provider::PrepareRegistration(node, *wallet_interface, typed_request))}; UniValue response{UniValue::VOBJ}; response.pushKV("tx", EncodeHexTx(*prepared.tx)); response.pushKV("collateralAddress", EncodeDestination(prepared.collateral_address)); response.pushKV("signMessage", prepared.sign_message); return response; } - auto result{evo::provider::Register(node, *wallet_interface, typed_request)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - return SubmissionToString(std::get(result)); + return SubmissionToString(UnwrapOrThrow(evo::provider::Register(node, *wallet_interface, typed_request))); } static RPCHelpMan protx_register_submit() @@ -883,10 +886,8 @@ static RPCHelpMan protx_register_submit() } auto wallet_interface{MakeWalletInterface(node, wallet)}; - auto result{evo::provider::SubmitRegistration(node, *wallet_interface, MakeTransactionRef(std::move(tx)), - *opt_vchSig)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - return SubmissionToString(std::get(result)); + return SubmissionToString(UnwrapOrThrow(evo::provider::SubmitRegistration( + node, *wallet_interface, MakeTransactionRef(std::move(tx)), *opt_vchSig))); }, }; } @@ -1007,9 +1008,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques } auto wallet_interface{MakeWalletInterface(node, wallet)}; - auto result{evo::provider::UpdateService(node, *wallet_interface, typed_request)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - return SubmissionToString(std::get(result)); + return SubmissionToString(UnwrapOrThrow(evo::provider::UpdateService(node, *wallet_interface, typed_request))); } static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_scheme) @@ -1083,9 +1082,8 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_ } auto wallet_interface{MakeWalletInterface(node, wallet)}; - auto result{evo::provider::UpdateRegistrar(node, *wallet_interface, typed_request)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - return SubmissionToString(std::get(result)); + return SubmissionToString( + UnwrapOrThrow(evo::provider::UpdateRegistrar(node, *wallet_interface, typed_request))); }, }; } @@ -1154,9 +1152,7 @@ static RPCHelpMan protx_revoke() } auto wallet_interface{MakeWalletInterface(node, pwallet)}; - auto result{evo::provider::Revoke(node, *wallet_interface, typed_request)}; - if (const auto* error{std::get_if(&result)}) ThrowProviderTxError(*error); - return SubmissionToString(std::get(result)); + return SubmissionToString(UnwrapOrThrow(evo::provider::Revoke(node, *wallet_interface, typed_request))); }, }; } From 29e84fe22db0c23e60c35d4177834491f81f4460 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 19 Aug 2026 04:48:42 +0700 Subject: [PATCH 5/5] refactor(rpc): deduplicate protx feeSourceAddress parsing Four protx commands repeated the same seven-line decode/validate/assign unit for the optional fee source parameter. Fold it into ParseFeeSource(), preserving the exact error message. --- src/rpc/evo.cpp | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index f0d5f0cae911..fbe5d0e046b6 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #ifdef ENABLE_WALLET @@ -399,6 +400,16 @@ static T UnwrapOrThrow(interfaces::ProviderTxResult result) return std::get(std::move(result)); } +static std::optional ParseFeeSource(const UniValue& param) +{ + if (param.isNull()) return std::nullopt; + CTxDestination fee_source{DecodeDestination(param.get_str())}; + if (!IsValidDestination(fee_source)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + param.get_str()); + } + return fee_source; +} + static std::vector ParseCoreNetInfo(const UniValue& input, bool optional) { if (input.isStr()) { @@ -830,14 +841,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, paramIdx += 3; } - if (!request.params[paramIdx + 6].isNull()) { - CTxDestination fund_destination{DecodeDestination(request.params[paramIdx + 6].get_str())}; - if (!IsValidDestination(fund_destination)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - std::string("Invalid Dash address: ") + request.params[paramIdx + 6].get_str()); - } - typed_request.fee_source = fund_destination; - } + typed_request.fee_source = ParseFeeSource(request.params[paramIdx + 6]); if ((action == ProTxRegisterAction::External || action == ProTxRegisterAction::Fund) && !request.params[paramIdx + 7].isNull()) { typed_request.submit = ParseBoolV(request.params[paramIdx + 7], "submit"); @@ -995,14 +999,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques } typed_request.operator_payout = payout_destination; } - if (!request.params[paramIdx + 1].isNull()) { - CTxDestination fee_source{DecodeDestination(request.params[paramIdx + 1].get_str())}; - if (!IsValidDestination(fee_source)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - std::string("Invalid Dash address: ") + request.params[paramIdx + 1].get_str()); - } - typed_request.fee_source = fee_source; - } + typed_request.fee_source = ParseFeeSource(request.params[paramIdx + 1]); if (!request.params[paramIdx + 2].isNull()) { typed_request.submit = ParseBoolV(request.params[paramIdx + 2], "submit"); } @@ -1069,14 +1066,7 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_ } typed_request.payouts = ParsePayouts(request.params[3], "payouts"); } - if (!request.params[4].isNull()) { - CTxDestination fee_source{DecodeDestination(request.params[4].get_str())}; - if (!IsValidDestination(fee_source)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - std::string("Invalid Dash address: ") + request.params[4].get_str()); - } - typed_request.fee_source = fee_source; - } + typed_request.fee_source = ParseFeeSource(request.params[4]); if (!request.params[5].isNull()) { typed_request.submit = ParseBoolV(request.params[5], "submit"); } @@ -1139,14 +1129,7 @@ static RPCHelpMan protx_revoke() } typed_request.reason = static_cast(nReason); } - if (!request.params[3].isNull()) { - CTxDestination fee_source{DecodeDestination(request.params[3].get_str())}; - if (!IsValidDestination(fee_source)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - std::string("Invalid Dash address: ") + request.params[3].get_str()); - } - typed_request.fee_source = fee_source; - } + typed_request.fee_source = ParseFeeSource(request.params[3]); if (!request.params[4].isNull()) { typed_request.submit = ParseBoolV(request.params[4], "submit"); }