diff --git a/contrib/seeds/makeseeds.py b/contrib/seeds/makeseeds.py index 314fb6ea1610..72dc8fb6a3c9 100755 --- a/contrib/seeds/makeseeds.py +++ b/contrib/seeds/makeseeds.py @@ -164,7 +164,7 @@ def main(): mns = filtermulticollateraladdress(mns) mns = filtermultipayoutaddress(mns) # Extract IPs - ips = [parseip(mn['state']['service']) for mn in mns] + ips = [parseip(mn['state']['addresses'][0]) for mn in mns] for onion in onions: parsed = parseip(onion) if parsed is not None: diff --git a/doc/release-notes-6665.md b/doc/release-notes-6665.md new file mode 100644 index 000000000000..efdaea2e482d --- /dev/null +++ b/doc/release-notes-6665.md @@ -0,0 +1,13 @@ +Updated RPCs +------------ + +* The input field `ipAndPort` has been renamed to `coreP2PAddrs`. + * `coreP2PAddrs` can now, in addition to accepting a string, accept an array of strings, subject to validation rules. + +* The key `service` has been deprecated for some RPCs (`decoderawtransaction`, `decodepsbt`, `getblock`, `getrawtransaction`, + `gettransaction`, `masternode status` (only for the `dmnState` key), `protx diff`, `protx listdiff`) and has been replaced + with the field `addresses`. + * The deprecated field can be re-enabled using `-deprecatedrpc=service` but is liable to be removed in future versions + of Dash Core. + * This change does not affect `masternode status` (except for the `dmnState` key) as `service` does not represent a payload + value but the external address advertised by the active masternode. diff --git a/src/Makefile.am b/src/Makefile.am index daa4a2568587..8603d8ed9cf6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -300,6 +300,7 @@ BITCOIN_CORE_H = \ randomenv.h \ rpc/blockchain.h \ rpc/client.h \ + rpc/evo_util.h \ rpc/index_util.h \ rpc/mempool.h \ rpc/mining.h \ @@ -800,6 +801,7 @@ libbitcoin_common_a_SOURCES = \ policy/policy.cpp \ protocol.cpp \ psbt.cpp \ + rpc/evo_util.cpp \ rpc/rawtransaction_util.cpp \ rpc/util.cpp \ saltedhasher.cpp \ diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index d5f5185e4b28..e5125a017a4f 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -1883,7 +1883,10 @@ void CCoinJoinClientSession::GetJsonInfo(UniValue& obj) const assert(mixingMasternode->pdmnState); obj.pushKV("protxhash", mixingMasternode->proTxHash.ToString()); obj.pushKV("outpoint", mixingMasternode->collateralOutpoint.ToStringShort()); - obj.pushKV("service", mixingMasternode->pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + if (m_wallet->chain().rpcEnableDeprecated("service")) { + obj.pushKV("service", mixingMasternode->pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + } + obj.pushKV("addresses", mixingMasternode->pdmnState->netInfo->ToJson()); } obj.pushKV("denomination", ValueFromAmount(CoinJoin::DenominationToAmount(nSessionDenom))); obj.pushKV("state", GetStateString()); diff --git a/src/evo/core_write.cpp b/src/evo/core_write.cpp index 91ffc5c2270f..28ba23342f0c 100644 --- a/src/evo/core_write.cpp +++ b/src/evo/core_write.cpp @@ -67,7 +67,10 @@ ret.pushKV("type", ToUnderlying(nType)); ret.pushKV("collateralHash", collateralOutpoint.hash.ToString()); ret.pushKV("collateralIndex", (int)collateralOutpoint.n); - ret.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + if (IsServiceDeprecatedRPCEnabled()) { + ret.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + } + ret.pushKV("addresses", netInfo->ToJson()); ret.pushKV("ownerAddress", EncodeDestination(PKHash(keyIDOwner))); ret.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) { @@ -114,7 +117,10 @@ ret.pushKV("version", nVersion); ret.pushKV("type", ToUnderlying(nType)); ret.pushKV("proTxHash", proTxHash.ToString()); - ret.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + if (IsServiceDeprecatedRPCEnabled()) { + ret.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + } + ret.pushKV("addresses", netInfo->ToJson()); if (CTxDestination dest; ExtractDestination(scriptOperatorPayout, dest)) { ret.pushKV("operatorPayoutAddress", EncodeDestination(dest)); } diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp index 70a1c139c9c9..6734c9671a27 100644 --- a/src/evo/dmnstate.cpp +++ b/src/evo/dmnstate.cpp @@ -38,7 +38,10 @@ UniValue CDeterministicMNState::ToJson(MnType nType) const { UniValue obj(UniValue::VOBJ); obj.pushKV("version", nVersion); - obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + if (IsServiceDeprecatedRPCEnabled()) { + obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + } + obj.pushKV("addresses", netInfo->ToJson()); obj.pushKV("registeredHeight", nRegisteredHeight); obj.pushKV("lastPaidHeight", nLastPaidHeight); obj.pushKV("consecutivePayments", nConsecutivePayments); @@ -72,7 +75,10 @@ UniValue CDeterministicMNStateDiff::ToJson(MnType nType) const obj.pushKV("version", state.nVersion); } if (fields & Field_netInfo) { - obj.pushKV("service", state.netInfo->GetPrimary().ToStringAddrPort()); + if (IsServiceDeprecatedRPCEnabled()) { + obj.pushKV("service", state.netInfo->GetPrimary().ToStringAddrPort()); + } + obj.pushKV("addresses", state.netInfo->ToJson()); } if (fields & Field_nRegisteredHeight) { obj.pushKV("registeredHeight", state.nRegisteredHeight); diff --git a/src/evo/netinfo.cpp b/src/evo/netinfo.cpp index 76c67404a750..03a258c4f777 100644 --- a/src/evo/netinfo.cpp +++ b/src/evo/netinfo.cpp @@ -11,6 +11,8 @@ #include #include +#include + namespace { static std::unique_ptr g_main_params{nullptr}; static std::once_flag g_main_params_flag; @@ -33,6 +35,19 @@ bool MatchCharsFilter(std::string_view input, std::string_view filter) } } // anonymous namespace +UniValue ArrFromService(const CService& addr) +{ + UniValue obj(UniValue::VARR); + obj.push_back(addr.ToStringAddrPort()); + return obj; +} + +bool IsServiceDeprecatedRPCEnabled() +{ + const auto args = gArgs.GetArgs("-deprecatedrpc"); + return std::find(args.begin(), args.end(), "service") != args.end(); +} + bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const { if (m_type != rhs.m_type) return false; @@ -227,6 +242,11 @@ NetInfoStatus MnNetInfo::Validate() const return ValidateService(GetPrimary()); } +UniValue MnNetInfo::ToJson() const +{ + return ArrFromService(GetPrimary()); +} + std::string MnNetInfo::ToString() const { // Extra padding to account for padding done by the calling function. diff --git a/src/evo/netinfo.h b/src/evo/netinfo.h index 84bb5b5d39c0..27f2ce932895 100644 --- a/src/evo/netinfo.h +++ b/src/evo/netinfo.h @@ -13,6 +13,8 @@ class CService; +class UniValue; + enum class NetInfoStatus : uint8_t { // Managing entries BadInput, @@ -51,6 +53,9 @@ constexpr std::string_view NISToString(const NetInfoStatus code) assert(false); } +/* Identical to IsDeprecatedRPCEnabled("service"). For use outside of RPC code. */ +bool IsServiceDeprecatedRPCEnabled(); + class NetInfoEntry { public: @@ -141,6 +146,7 @@ class NetInfoInterface virtual bool CanStorePlatform() const = 0; virtual bool IsEmpty() const = 0; virtual NetInfoStatus Validate() const = 0; + virtual UniValue ToJson() const = 0; virtual std::string ToString() const = 0; virtual void Clear() = 0; @@ -197,6 +203,7 @@ class MnNetInfo final : public NetInfoInterface bool IsEmpty() const override { return m_addr.IsEmpty(); } bool CanStorePlatform() const override { return false; } NetInfoStatus Validate() const override; + UniValue ToJson() const override; std::string ToString() const override; void Clear() override { m_addr.Clear(); } diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index d6eda245d3b4..d88d74380004 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -4,15 +4,16 @@ #include -#include #include #include +#include #include +#include +#include #include #include #include #include -#include #include #include @@ -80,7 +81,10 @@ UniValue CSimplifiedMNListEntry::ToJson(bool extended) const obj.pushKV("nType", ToUnderlying(nType)); obj.pushKV("proRegTxHash", proRegTxHash.ToString()); obj.pushKV("confirmedHash", confirmedHash.ToString()); - obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + if (IsServiceDeprecatedRPCEnabled()) { + obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + } + obj.pushKV("addresses", netInfo->ToJson()); obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); obj.pushKV("isValid", isValid); diff --git a/src/rpc/coinjoin.cpp b/src/rpc/coinjoin.cpp index 15a36d3c67dc..2a2605ca032d 100644 --- a/src/rpc/coinjoin.cpp +++ b/src/rpc/coinjoin.cpp @@ -435,7 +435,13 @@ static RPCHelpMan getcoinjoininfo() { {RPCResult::Type::STR_HEX, "protxhash", "The ProTxHash of the masternode"}, {RPCResult::Type::STR_HEX, "outpoint", "The outpoint of the masternode"}, - {RPCResult::Type::STR, "service", "The IP address and port of the masternode"}, + {RPCResult::Type::STR, "service", "The IP address and port of the masternode (DEPRECATED, returned only if config option -deprecatedrpc=service is passed)"}, + {RPCResult::Type::ARR, "addresses", "Network addresses of the masternode", + { + { + {RPCResult::Type::STR, "address", ""}, + } + }}, {RPCResult::Type::NUM, "denomination", "The denomination of the mixing session in " + CURRENCY_UNIT + ""}, {RPCResult::Type::STR_HEX, "state", "Current state of the mixing session"}, {RPCResult::Type::NUM, "entries_count", "The number of entries in the mixing session"}, diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index d1f7e7afdf55..a470253691c5 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -91,14 +92,20 @@ static RPCArg GetRpcArg(const std::string& strParamName) "If not specified, payoutAddress is the one that is going to be used.\n" "The private key belonging to this address must be known in your wallet."} }, - {"ipAndPort", - {"ipAndPort", RPCArg::Type::STR, RPCArg::Optional::NO, - "IP and port in the form \"IP:PORT\". Must be unique on the network.\n" - "Can be set to an empty string, which will require a ProUpServTx afterwards."} + {"coreP2PAddrs", + {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, + "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.\n" + "Can be set to an empty string, which will require a ProUpServTx afterwards.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, + }} }, - {"ipAndPort_update", - {"ipAndPort", RPCArg::Type::STR, RPCArg::Optional::NO, - "IP and port in the form \"IP:PORT\". Must be unique on the network."} + {"coreP2PAddrs_update", + {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, + "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, + }} }, {"operatorKey", {"operatorKey", RPCArg::Type::STR, RPCArg::Optional::NO, @@ -230,11 +237,6 @@ static CBLSPublicKey ParseBLSPubKey(const std::string& hexKey, const std::string return pubKey; } -static bool ValidatePlatformPort(const int32_t port) -{ - return port >= 1 && port <= std::numeric_limits::max(); -} - template static void FundSpecialTx(CWallet& wallet, CMutableTransaction& tx, const SpecialTxPayload& payload, const CTxDestination& fundDest) EXCLUSIVE_LOCKS_REQUIRED(!wallet.cs_wallet) @@ -401,7 +403,7 @@ static RPCHelpMan protx_register_fund_wrapper(const bool legacy) + HELP_REQUIRING_PASSPHRASE, { GetRpcArg("collateralAddress"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -452,7 +454,7 @@ static RPCHelpMan protx_register_wrapper(bool legacy) { GetRpcArg("collateralHash"), GetRpcArg("collateralIndex"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -504,7 +506,7 @@ static RPCHelpMan protx_register_prepare_wrapper(const bool legacy) { GetRpcArg("collateralHash"), GetRpcArg("collateralIndex"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -555,7 +557,7 @@ static RPCHelpMan protx_register_fund_evo() HELP_REQUIRING_PASSPHRASE, { GetRpcArg("collateralAddress"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -594,7 +596,7 @@ static RPCHelpMan protx_register_evo() { GetRpcArg("collateralHash"), GetRpcArg("collateralIndex"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -632,7 +634,7 @@ static RPCHelpMan protx_register_prepare_evo() { GetRpcArg("collateralHash"), GetRpcArg("collateralIndex"), - GetRpcArg("ipAndPort"), + GetRpcArg("coreP2PAddrs"), GetRpcArg("ownerAddress"), GetRpcArg("operatorPubKey_register"), GetRpcArg("votingAddress_register"), @@ -713,11 +715,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, paramIdx += 2; } - if (!request.params[paramIdx].get_str().empty()) { - if (auto entryRet = ptx.netInfo->AddEntry(request.params[paramIdx].get_str()); entryRet != NetInfoStatus::Success) { - throw std::runtime_error(strprintf("%s (%s)", NISToString(entryRet), request.params[paramIdx].get_str())); - } - } + ProcessNetInfoCore(ptx, request.params[paramIdx], /*optional=*/true); ptx.keyIDOwner = ParsePubKeyIDFromAddress(request.params[paramIdx + 1].get_str(), "owner address"); ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", use_legacy), use_legacy); @@ -749,17 +747,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, } ptx.platformNodeID.SetHex(request.params[paramIdx + 6].get_str()); - int32_t requestedPlatformP2PPort = ParseInt32V(request.params[paramIdx + 7], "platformP2PPort"); - if (!ValidatePlatformPort(requestedPlatformP2PPort)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "platformP2PPort must be a valid port [1-65535]"); - } - ptx.platformP2PPort = static_cast(requestedPlatformP2PPort); - - int32_t requestedPlatformHTTPPort = ParseInt32V(request.params[paramIdx + 8], "platformHTTPPort"); - if (!ValidatePlatformPort(requestedPlatformHTTPPort)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "platformHTTPPort must be a valid port [1-65535]"); - } - ptx.platformHTTPPort = static_cast(requestedPlatformHTTPPort); + ProcessNetInfoPlatform(ptx, request.params[paramIdx + 7], request.params[paramIdx + 8]); paramIdx += 3; } @@ -931,7 +919,7 @@ static RPCHelpMan protx_update_service() + HELP_REQUIRING_PASSPHRASE, { GetRpcArg("proTxHash"), - GetRpcArg("ipAndPort_update"), + GetRpcArg("coreP2PAddrs_update"), GetRpcArg("operatorKey"), GetRpcArg("operatorPayoutAddress"), GetRpcArg("feeSourceAddress"), @@ -964,7 +952,7 @@ static RPCHelpMan protx_update_service_evo() HELP_REQUIRING_PASSPHRASE, { GetRpcArg("proTxHash"), - GetRpcArg("ipAndPort_update"), + GetRpcArg("coreP2PAddrs_update"), GetRpcArg("operatorKey"), GetRpcArg("platformNodeID"), GetRpcArg("platformP2PPort"), @@ -1018,9 +1006,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques /*is_basic_override=*/dmn->pdmnState->nVersion > ProTxVersion::LegacyBLS); ptx.netInfo = NetInfoInterface::MakeNetInfo(ptx.nVersion); - if (auto entryRet = ptx.netInfo->AddEntry(request.params[1].get_str()); entryRet != NetInfoStatus::Success) { - throw std::runtime_error(strprintf("%s (%s)", NISToString(entryRet), request.params[1].get_str())); - } + ProcessNetInfoCore(ptx, request.params[1], /*optional=*/false); CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey"); @@ -1031,17 +1017,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques } ptx.platformNodeID.SetHex(request.params[paramIdx].get_str()); - int32_t requestedPlatformP2PPort = ParseInt32V(request.params[paramIdx + 1], "platformP2PPort"); - if (!ValidatePlatformPort(requestedPlatformP2PPort)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "platformP2PPort must be a valid port [1-65535]"); - } - ptx.platformP2PPort = static_cast(requestedPlatformP2PPort); - - int32_t requestedPlatformHTTPPort = ParseInt32V(request.params[paramIdx + 2], "platformHTTPPort"); - if (!ValidatePlatformPort(requestedPlatformHTTPPort)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "platformHTTPPort must be a valid port [1-65535]"); - } - ptx.platformHTTPPort = static_cast(requestedPlatformHTTPPort); + ProcessNetInfoPlatform(ptx, request.params[paramIdx + 1], request.params[paramIdx + 2]); paramIdx += 3; } diff --git a/src/rpc/evo_util.cpp b/src/rpc/evo_util.cpp new file mode 100644 index 000000000000..a054beacb6b3 --- /dev/null +++ b/src/rpc/evo_util.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +#include + +template +void ProcessNetInfoCore(T1& ptx, const UniValue& input, const bool optional) +{ + CHECK_NONFATAL(ptx.netInfo); + + if (input.isStr()) { + const std::string& entry = input.get_str(); + if (entry.empty()) { + if (!optional) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Empty param for coreP2PAddrs not allowed"); + } + return; // Nothing to do + } + if (auto entryRet = ptx.netInfo->AddEntry(entry); entryRet != NetInfoStatus::Success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("Error setting coreP2PAddrs[0] to '%s' (%s)", entry, NISToString(entryRet))); + } + return; // Parsing complete + } + + if (input.isArray()) { + const UniValue& entries = input.get_array(); + if (entries.empty()) { + if (!optional) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Empty params for coreP2PAddrs not allowed"); + } + return; // Nothing to do + } + for (size_t idx{0}; idx < entries.size(); idx++) { + const UniValue& entry_uv{entries[idx]}; + if (!entry_uv.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("Invalid param for coreP2PAddrs[%d], must be string", idx)); + } + const std::string& entry = entry_uv.get_str(); + if (entry.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("Invalid param for coreP2PAddrs[%d], cannot be empty string", idx)); + } + if (auto entryRet = ptx.netInfo->AddEntry(entry); entryRet != NetInfoStatus::Success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Error setting coreP2PAddrs[%d] to '%s' (%s)", idx, + entry, NISToString(entryRet))); + } + } + return; // Parsing complete + } + + // Invalid input + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid param for coreP2PAddrs, must be string or array"); +} +template void ProcessNetInfoCore(CProRegTx& ptx, const UniValue& input, const bool optional); +template void ProcessNetInfoCore(CProUpServTx& ptx, const UniValue& input, const bool optional); + +template +void ProcessNetInfoPlatform(T1& ptx, const UniValue& input_p2p, const UniValue& input_http) +{ + CHECK_NONFATAL(ptx.netInfo); + + auto process_field = [](uint16_t& target, const UniValue& input, const std::string& field_name) { + if (!input.isNum() && !input.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid param for %s, must be number", field_name)); + } + if (int32_t port{ParseInt32V(input, field_name)}; port >= 1 && port <= std::numeric_limits::max()) { + target = static_cast(port); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid port [1-65535]", field_name)); + } + }; + process_field(ptx.platformP2PPort, input_p2p, "platformP2PPort"); + process_field(ptx.platformHTTPPort, input_http, "platformHTTPPort"); +} +template void ProcessNetInfoPlatform(CProRegTx& ptx, const UniValue& input_p2p, const UniValue& input_http); +template void ProcessNetInfoPlatform(CProUpServTx& ptx, const UniValue& input_p2p, const UniValue& input_http); diff --git a/src/rpc/evo_util.h b/src/rpc/evo_util.h new file mode 100644 index 000000000000..de06acc36028 --- /dev/null +++ b/src/rpc/evo_util.h @@ -0,0 +1,16 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_EVO_UTIL_H +#define BITCOIN_RPC_EVO_UTIL_H + +class UniValue; + +template +void ProcessNetInfoCore(T1& ptx, const UniValue& input, const bool optional); + +template +void ProcessNetInfoPlatform(T1& ptx, const UniValue& input_p2p, const UniValue& input_http); + +#endif // BITCOIN_RPC_EVO_UTIL_H diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 7248938326d0..354c532a8bcf 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -623,7 +623,10 @@ static RPCHelpMan masternodelist_helper(bool is_composite) strOutpoint.find(strFilter) == std::string::npos) return; UniValue objMN(UniValue::VOBJ); objMN.pushKV("proTxHash", dmn.proTxHash.ToString()); - objMN.pushKV("address", dmn.pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + if (IsDeprecatedRPCEnabled("service")) { + objMN.pushKV("address", dmn.pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + } + objMN.pushKV("addresses", dmn.pdmnState->netInfo->ToJson()); objMN.pushKV("payee", payeeStr); objMN.pushKV("status", dmnToStatus(dmn)); objMN.pushKV("type", std::string(GetMnType(dmn.nType).description)); diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp index 64ceaa059c3b..c8bfd6cd0128 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -206,7 +206,10 @@ static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_ const auto& dmn = quorum->members[i]; UniValue mo(UniValue::VOBJ); mo.pushKV("proTxHash", dmn->proTxHash.ToString()); - mo.pushKV("service", dmn->pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + if (IsDeprecatedRPCEnabled("service")) { + mo.pushKV("service", dmn->pdmnState->netInfo->GetPrimary().ToStringAddrPort()); + } + mo.pushKV("addresses", dmn->pdmnState->netInfo->ToJson()); mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.ToString()); mo.pushKV("valid", quorum->qc->validMembers[i]); if (quorum->qc->validMembers[i]) { diff --git a/test/functional/feature_dip3_deterministicmns.py b/test/functional/feature_dip3_deterministicmns.py index 698a3f39765f..7494f6536d1e 100755 --- a/test/functional/feature_dip3_deterministicmns.py +++ b/test/functional/feature_dip3_deterministicmns.py @@ -272,13 +272,13 @@ def update_mn_payee(self, mn: MasternodeInfo, payee): def test_protx_update_service(self, mn: MasternodeInfo): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) - mn.update_service(self.nodes[0], submit=True, ipAndPort=f'127.0.0.2:{mn.nodePort}') + mn.update_service(self.nodes[0], submit=True, coreP2PAddrs=[f'127.0.0.2:{mn.nodePort}']) self.generate(self.nodes[0], 1) for node in self.nodes: protx_info = node.protx('info', mn.proTxHash) mn_list = node.masternode('list') - assert_equal(protx_info['state']['service'], '127.0.0.2:%d' % mn.nodePort) - assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.nodePort) + assert_equal(protx_info['state']['addresses'][0], '127.0.0.2:%d' % mn.nodePort) + assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['addresses'][0], '127.0.0.2:%d' % mn.nodePort) # undo mn.update_service(self.nodes[0], submit=True) diff --git a/test/functional/rpc_netinfo.py b/test/functional/rpc_netinfo.py new file mode 100755 index 000000000000..ba1951b820d7 --- /dev/null +++ b/test/functional/rpc_netinfo.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test network information fields across RPCs.""" + +from test_framework.util import ( + assert_equal +) +from test_framework.script import ( + hash160 +) +from test_framework.test_framework import ( + BitcoinTestFramework, + MasternodeInfo, + p2p_port +) +from test_framework.test_node import TestNode + +from _decimal import Decimal +from random import randint + +# See CMainParams in src/chainparams.cpp +DEFAULT_PORT_MAINNET_CORE_P2P = 9999 +# See CRegTestParams in src/chainparams.cpp +DEFAULT_PORT_PLATFORM_P2P = 22200 +DEFAULT_PORT_PLATFORM_HTTP = 22201 + +class Node: + mn: MasternodeInfo + node: TestNode + platform_nodeid: str = "" + + def __init__(self, node: TestNode, is_evo: bool): + self.mn = MasternodeInfo(evo=is_evo, legacy=False) + self.mn.generate_addresses(node) + self.mn.set_node(node.index) + self.mn.set_params(nodePort=p2p_port(node.index)) + self.node = node + + def generate_collateral(self, test: BitcoinTestFramework): + assert self.mn.nodeIdx is not None + + while self.node.getbalance() < self.mn.get_collateral_value(): + test.bump_mocktime(1) + test.generate(self.node, 10, sync_fun=test.no_op) + + collateral_txid = self.node.sendmany("", {self.mn.collateral_address: self.mn.get_collateral_value(), self.mn.fundsAddr: 1}) + self.mn.bury_tx(test, self.mn.nodeIdx, collateral_txid, 1) + collateral_vout = self.mn.get_collateral_vout(self.node, collateral_txid) + self.mn.set_params(collateral_txid=collateral_txid, collateral_vout=collateral_vout) + + def is_mn_visible(self, _protx_hash = None) -> bool: + protx_hash = _protx_hash or self.mn.proTxHash + mn_list = self.node.masternodelist() + mn_visible = False + for mn_entry in mn_list: + dmn = mn_list.get(mn_entry) + if dmn['proTxHash'] == protx_hash: + assert_equal(dmn['type'], "Evo" if self.mn.evo else "Regular") + mn_visible = True + return mn_visible + + def register_mn(self, test: BitcoinTestFramework, submit: bool, addrs_core_p2p, addrs_platform_p2p = None, addrs_platform_http = None, code = None, msg = None) -> str: + assert self.mn.nodeIdx is not None + + if self.mn.evo and (not addrs_platform_http or not addrs_platform_p2p): + raise AssertionError("EvoNode but platformP2PPort and platformHTTPPort not specified") + + # Evonode-specific fields are ignored if regular masternode + self.platform_nodeid = hash160(b'%d' % randint(1, 65535)).hex() + protx_output = self.mn.register(self.node, submit=submit, coreP2PAddrs=addrs_core_p2p, operator_reward=0, + platform_node_id=self.platform_nodeid, platform_p2p_port=addrs_platform_p2p, + platform_http_port=addrs_platform_http, expected_assert_code=code, expected_assert_msg=msg) + + # If we expected error, make sure the transaction didn't succeed + if code and msg: + assert protx_output is None + return "" + else: + assert protx_output is not None + if not submit: + return "" + + # Bury ProTx transaction and check if masternode is online + self.mn.set_params(proTxHash=protx_output, operator_reward=0) + self.mn.bury_tx(test, self.mn.nodeIdx, protx_output, 1) + assert_equal(self.is_mn_visible(), True) + + test.log.debug(f"Registered {'Evo' if self.mn.evo else 'regular'} masternode with collateral_txid={self.mn.collateral_txid}, " + f"collateral_vout={self.mn.collateral_vout}, provider_txid={self.mn.proTxHash}") + + test.restart_node(self.mn.nodeIdx, extra_args=self.node.extra_args + [f'-masternodeblsprivkey={self.mn.keyOperator}']) + return self.mn.proTxHash + + def update_mn(self, test: BitcoinTestFramework, addrs_core_p2p, addrs_platform_p2p = None, addrs_platform_http = None) -> str: + assert self.mn.nodeIdx is not None + + if self.mn.evo and (not addrs_platform_http or not addrs_platform_p2p): + raise AssertionError("EvoNode but platformP2PPort and platformHTTPPort not specified") + + # Evonode-specific fields are ignored if regular masternode + protx_output = self.mn.update_service(self.node, submit=True, coreP2PAddrs=addrs_core_p2p, platform_node_id=self.platform_nodeid, + platform_p2p_port=addrs_platform_p2p, platform_http_port=addrs_platform_http) + assert protx_output is not None + + self.mn.bury_tx(test, self.mn.nodeIdx, protx_output, 1) + assert_equal(self.is_mn_visible(), True) + + test.log.debug(f"Updated {'Evo' if self.mn.evo else 'regular'} masternode with collateral_txid={self.mn.collateral_txid}, " + f"collateral_vout={self.mn.collateral_vout}, provider_txid={self.mn.proTxHash}") + return protx_output + + def destroy_mn(self, test: BitcoinTestFramework): + assert self.mn.nodeIdx is not None + + # Get UTXO from address used to pay fees, generate new addresses + address_funds_unspent = self.node.listunspent(0, 99999, [self.mn.fundsAddr])[0] + address_funds_value = address_funds_unspent['amount'] + self.mn.generate_addresses(self.node, True) + + # Create transaction to spend old collateral and fee change + raw_tx = self.node.createrawtransaction([ + { 'txid': self.mn.collateral_txid, 'vout': self.mn.collateral_vout }, + { 'txid': address_funds_unspent['txid'], 'vout': address_funds_unspent['vout'] } + ], [ + {self.mn.collateral_address: float(self.mn.get_collateral_value())}, + {self.mn.fundsAddr: float(address_funds_value - Decimal(0.001))} + ]) + raw_tx = self.node.signrawtransactionwithwallet(raw_tx)['hex'] + + # Send that transaction, resulting txid is new collateral + new_collateral_txid = self.node.sendrawtransaction(raw_tx) + self.mn.bury_tx(test, self.mn.nodeIdx, new_collateral_txid, 1) + new_collateral_vout = self.mn.get_collateral_vout(self.node, new_collateral_txid) + self.mn.set_params(proTxHash="", collateral_txid=new_collateral_txid, collateral_vout=new_collateral_vout) + + # Old masternode entry should be dead + assert_equal(self.is_mn_visible(self.mn.proTxHash), False) + test.log.debug(f"Destroyed {'Evo' if self.mn.evo else 'regular'} masternode with collateral_txid={self.mn.collateral_txid}, " + f"collateral_vout={self.mn.collateral_vout}, provider_txid={self.mn.proTxHash}") + + test.restart_node(self.mn.nodeIdx, extra_args=self.node.extra_args) + +class NetInfoTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [ + ["-dip3params=2:2"], + ["-deprecatedrpc=service", "-dip3params=2:2"] + ] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def check_netinfo_fields(self, val, core_p2p_port: int): + assert_equal(val[0], f"127.0.0.1:{core_p2p_port}") + + def run_test(self): + self.node_evo: Node = Node(self.nodes[0], True) + self.node_evo.generate_collateral(self) + + self.node_simple: TestNode = self.nodes[1] + + self.log.info("Test input validation for masternode address fields") + self.test_validation_common() + self.test_validation_legacy() + + self.log.info("Test output masternode address fields for consistency") + self.test_deprecation() + + def test_validation_common(self): + # Arrays of addresses with invalid inputs get refused + self.node_evo.register_mn(self, False, [[f"127.0.0.1:{self.node_evo.mn.nodePort}"]], + DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP, + -8, "Invalid param for coreP2PAddrs[0], must be string") + self.node_evo.register_mn(self, False, [f"127.0.0.1:{self.node_evo.mn.nodePort}", ""], + DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP, + -8, "Invalid param for coreP2PAddrs[1], cannot be empty string") + self.node_evo.register_mn(self, False, [f"127.0.0.1:{self.node_evo.mn.nodePort}", self.node_evo.mn.nodePort], + DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP, + -8, "Invalid param for coreP2PAddrs[1], must be string") + + # platformP2PPort and platformHTTPPort must be within acceptable range (i.e. a valid port number) + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", "0", DEFAULT_PORT_PLATFORM_HTTP, + -8, "platformP2PPort must be a valid port [1-65535]") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", "65536", DEFAULT_PORT_PLATFORM_HTTP, + -8, "platformP2PPort must be a valid port [1-65535]") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, "0", + -8, "platformHTTPPort must be a valid port [1-65535]") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, "65536", + -8, "platformHTTPPort must be a valid port [1-65535]") + + def test_validation_legacy(self): + # Using mainnet P2P port gets refused + self.node_evo.register_mn(self, False, f"127.0.0.1:{DEFAULT_PORT_MAINNET_CORE_P2P}", + DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP, + -8, f"Error setting coreP2PAddrs[0] to '127.0.0.1:{DEFAULT_PORT_MAINNET_CORE_P2P}' (invalid port)") + + # Arrays of addresses are recognized by coreP2PAddrs (but get refused for too many entries) + self.node_evo.register_mn(self, False, [f"127.0.0.1:{self.node_evo.mn.nodePort}", f"127.0.0.2:{self.node_evo.mn.nodePort}"], + DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP, + -8, f"Error setting coreP2PAddrs[1] to '127.0.0.2:{self.node_evo.mn.nodePort}' (too many entries)") + + # platformP2PPort and platformHTTPPort doesn't accept non-numeric inputs + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", f"127.0.0.1:{DEFAULT_PORT_PLATFORM_P2P}", DEFAULT_PORT_PLATFORM_HTTP, + -8, f"platformP2PPort must be a 32bit integer (not '127.0.0.1:{DEFAULT_PORT_PLATFORM_P2P}')") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", [f"127.0.0.1:{DEFAULT_PORT_PLATFORM_P2P}"], DEFAULT_PORT_PLATFORM_HTTP, + -8, "Invalid param for platformP2PPort, must be number") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, f"127.0.0.1:{DEFAULT_PORT_PLATFORM_HTTP}", + -8, f"platformHTTPPort must be a 32bit integer (not '127.0.0.1:{DEFAULT_PORT_PLATFORM_HTTP}')") + self.node_evo.register_mn(self, False, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, [f"127.0.0.1:{DEFAULT_PORT_PLATFORM_HTTP}"], + -8, "Invalid param for platformHTTPPort, must be number") + + def test_deprecation(self): + # netInfo is represented with JSON in CProRegTx, CProUpServTx, CDeterministicMNState and CSimplifiedMNListEntry, + # so we need to test calls that rely on these underlying implementations. Start by collecting RPC responses. + self.log.info("Collect JSON RPC responses from node") + + # CProRegTx::ToJson() <- TxToUniv() <- TxToJSON() <- getrawtransaction + proregtx_hash = self.node_evo.register_mn(self, True, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP) + proregtx_rpc = self.node_evo.node.getrawtransaction(proregtx_hash, True) + + # CDeterministicMNState::ToJson() <- CDeterministicMN::pdmnState <- masternode_status + masternode_status = self.node_evo.node.masternode('status') + + # Generate deprecation-disabled response to avoid having to re-create a masternode again later on + self.restart_node(self.node_evo.mn.nodeIdx, extra_args=self.node_evo.node.extra_args + + [f'-masternodeblsprivkey={self.node_evo.mn.keyOperator}', '-deprecatedrpc=service']) + self.connect_nodes(self.node_evo.mn.nodeIdx, self.node_simple.index) # Needed as restarts don't reconnect nodes + masternode_status_depr = self.node_evo.node.masternode('status') + + # Stop actively running the masternode so we can issue a CProUpServTx (and enable the deprecation) + self.restart_node(self.node_evo.mn.nodeIdx, extra_args=self.node_evo.node.extra_args) + self.connect_nodes(self.node_evo.mn.nodeIdx, self.node_simple.index) # Needed as restarts don't reconnect nodes + + # CProUpServTx::ToJson() <- TxToUniv() <- TxToJSON() <- getrawtransaction + proupservtx_hash = self.node_evo.update_mn(self, f"127.0.0.1:{self.node_evo.mn.nodePort+1}", DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP) + proupservtx_rpc = self.node_evo.node.getrawtransaction(proupservtx_hash, True) + + # We need to update *twice*, the first time to incorrect values and the second time, back to correct values. + # This is to make sure that the fields we need to check against are reflected in the diff. + proupservtx_hash = self.node_evo.update_mn(self, f"127.0.0.1:{self.node_evo.mn.nodePort}", DEFAULT_PORT_PLATFORM_P2P, DEFAULT_PORT_PLATFORM_HTTP) + proupservtx_rpc = self.node_evo.node.getrawtransaction(proupservtx_hash, True) + + # CSimplifiedMNListEntry::ToJson() <- CSimplifiedMNListDiff::mnList <- CSimplifiedMNListDiff::ToJson() <- protx_diff + masternode_active_height: int = masternode_status['dmnState']['registeredHeight'] + protx_diff_rpc = self.node_evo.node.protx('diff', masternode_active_height - 1, masternode_active_height) + + # CDeterministicMNStateDiff::ToJson() <- CDeterministicMNListDiff::updatedMns <- protx_listdiff + proupservtx_height = proupservtx_rpc['height'] + protx_listdiff_rpc = self.node_evo.node.protx('listdiff', proupservtx_height - 1, proupservtx_height) + + self.log.info("Test RPCs return an 'addresses' field") + assert "addresses" in proregtx_rpc['proRegTx'].keys() + assert "addresses" in masternode_status['dmnState'].keys() + assert "addresses" in proupservtx_rpc['proUpServTx'].keys() + assert "addresses" in protx_diff_rpc['mnList'][0].keys() + assert "addresses" in protx_listdiff_rpc['updatedMNs'][0][proregtx_hash].keys() + + self.log.info("Test 'addresses' report correctly") + self.check_netinfo_fields(proregtx_rpc['proRegTx']['addresses'], self.node_evo.mn.nodePort) + self.check_netinfo_fields(masternode_status['dmnState']['addresses'], self.node_evo.mn.nodePort) + self.check_netinfo_fields(proupservtx_rpc['proUpServTx']['addresses'], self.node_evo.mn.nodePort) + self.check_netinfo_fields(protx_diff_rpc['mnList'][0]['addresses'], self.node_evo.mn.nodePort) + self.check_netinfo_fields(protx_listdiff_rpc['updatedMNs'][0][proregtx_hash]['addresses'], self.node_evo.mn.nodePort) + + self.log.info("Test RPCs by default no longer return a 'service' field") + assert "service" not in proregtx_rpc['proRegTx'].keys() + assert "service" not in masternode_status['dmnState'].keys() + assert "service" not in proupservtx_rpc['proUpServTx'].keys() + assert "service" not in protx_diff_rpc['mnList'][0].keys() + assert "service" not in protx_listdiff_rpc['updatedMNs'][0][proregtx_hash].keys() + # "service" in "masternode status" is exempt from the deprecation as the primary address is + # relevant on the host node as opposed to expressing payload information in most other RPCs. + assert "service" in masternode_status.keys() + + self.node_evo.destroy_mn(self) # Shut down previous masternode + self.connect_nodes(self.node_evo.mn.nodeIdx, self.node_simple.index) # Needed as restarts don't reconnect nodes + + self.log.info("Collect RPC responses from node with -deprecatedrpc=service") + + # Re-use chain activity from earlier + proregtx_rpc = self.node_simple.getrawtransaction(proregtx_hash, True) + proupservtx_rpc = self.node_simple.getrawtransaction(proupservtx_hash, True) + protx_diff_rpc = self.node_simple.protx('diff', masternode_active_height - 1, masternode_active_height) + masternode_status = masternode_status_depr # Pull in response generated from earlier + protx_listdiff_rpc = self.node_simple.protx('listdiff', proupservtx_height - 1, proupservtx_height) + + self.log.info("Test RPCs return 'service' with -deprecatedrpc=service") + assert "service" in proregtx_rpc['proRegTx'].keys() + assert "service" in masternode_status['dmnState'].keys() + assert "service" in proupservtx_rpc['proUpServTx'].keys() + assert "service" in protx_diff_rpc['mnList'][0].keys() + assert "service" in protx_listdiff_rpc['updatedMNs'][0][proregtx_hash].keys() + +if __name__ == "__main__": + NetInfoTest().main() diff --git a/test/functional/rpc_quorum.py b/test/functional/rpc_quorum.py index 40da55c4a776..202f0374c9d3 100755 --- a/test/functional/rpc_quorum.py +++ b/test/functional/rpc_quorum.py @@ -28,7 +28,7 @@ def run_test(self): mn: MasternodeInfo = self.mninfo[idx] for member in quorum_info["members"]: if member["proTxHash"] == mn.proTxHash: - assert_equal(member["service"], f'127.0.0.1:{mn.nodePort}') + assert_equal(member['addresses'][0], f'127.0.0.1:{mn.nodePort}') if __name__ == '__main__': RPCMasternodeTest().main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index d6dbdfd8c487..eb1f8d6e1b22 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -23,7 +23,7 @@ import time from concurrent.futures import ThreadPoolExecutor -from typing import List, Optional +from typing import List, Optional, Union from .address import ADDRESS_BCRT1_P2SH_OP_TRUE from .authproxy import JSONRPCException from test_framework.masternodes import check_banned, check_punished @@ -1216,7 +1216,7 @@ def get_node(self, test: BitcoinTestFramework) -> TestNode: return test.nodes[self.nodeIdx] def register(self, node: TestNode, submit: bool, collateral_txid: Optional[str] = None, collateral_vout: Optional[int] = None, - ipAndPort: Optional[str] = None, ownerAddr: Optional[str] = None, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, + coreP2PAddrs: Union[str, List[str], None] = None, ownerAddr: Optional[str] = None, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, operator_reward: Optional[int] = None, rewards_address: Optional[str] = None, fundsAddr: Optional[str] = None, platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, platform_http_port: Optional[int] = None, expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: @@ -1236,7 +1236,7 @@ def register(self, node: TestNode, submit: bool, collateral_txid: Optional[str] args = [ collateral_txid or self.collateral_txid, collateral_vout or self.collateral_vout, - ipAndPort or f'127.0.0.1:{self.nodePort}', + coreP2PAddrs or [f'127.0.0.1:{self.nodePort}'], ownerAddr or self.ownerAddr, pubKeyOperator or self.pubKeyOperator, votingAddr or self.votingAddr, @@ -1271,7 +1271,7 @@ def register(self, node: TestNode, submit: bool, collateral_txid: Optional[str] return ret - def register_fund(self, node: TestNode, submit: bool, collateral_address: Optional[str] = None, ipAndPort: Optional[str] = None, + def register_fund(self, node: TestNode, submit: bool, collateral_address: Optional[str] = None, coreP2PAddrs: Union[str, List[str], None] = None, ownerAddr: Optional[str] = None, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, operator_reward: Optional[int] = None, rewards_address: Optional[str] = None, fundsAddr: Optional[str] = None, platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, platform_http_port: Optional[int] = None, @@ -1299,7 +1299,7 @@ def register_fund(self, node: TestNode, submit: bool, collateral_address: Option # Common arguments shared between regular masternodes and EvoNodes args = [ collateral_address or self.collateral_address, - ipAndPort or f'127.0.0.1:{self.nodePort}', + coreP2PAddrs or [f'127.0.0.1:{self.nodePort}'], ownerAddr or self.ownerAddr, pubKeyOperator or self.pubKeyOperator, votingAddr or self.votingAddr, @@ -1410,7 +1410,7 @@ def update_registrar(self, node: TestNode, submit: bool, pubKeyOperator: Optiona return ret - def update_service(self, node: TestNode, submit: bool, ipAndPort: Optional[str] = None, platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, + def update_service(self, node: TestNode, submit: bool, coreP2PAddrs: Union[str, List[str], None] = None, platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, platform_http_port: Optional[int] = None, address_operator: Optional[str] = None, fundsAddr: Optional[str] = None, expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): @@ -1442,7 +1442,7 @@ def update_service(self, node: TestNode, submit: bool, ipAndPort: Optional[str] # Common arguments shared between regular masternodes and EvoNodes args = [ self.proTxHash, - ipAndPort or f'127.0.0.1:{self.nodePort}', + coreP2PAddrs or [f'127.0.0.1:{self.nodePort}'], self.keyOperator, ] address_funds = fundsAddr or self.fundsAddr @@ -1643,11 +1643,11 @@ def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None mn.bury_tx(self, genIdx=0, txid=collateral_txid, depth=1) collateral_vout = mn.get_collateral_vout(self.nodes[0], collateral_txid) - ipAndPort = '127.0.0.1:%d' % node_p2p_port + coreP2PAddrs = ['127.0.0.1:%d' % node_p2p_port] operatorReward = idx # platform_node_id, platform_p2p_port and platform_http_port are ignored for regular masternodes - protx_result = mn.register(self.nodes[0], submit=True, collateral_txid=collateral_txid, collateral_vout=collateral_vout, ipAndPort=ipAndPort, operator_reward=operatorReward, + protx_result = mn.register(self.nodes[0], submit=True, collateral_txid=collateral_txid, collateral_vout=collateral_vout, coreP2PAddrs=coreP2PAddrs, operator_reward=operatorReward, platform_node_id=platform_node_id, platform_p2p_port=platform_p2p_port, platform_http_port=platform_http_port) assert protx_result is not None @@ -1709,16 +1709,16 @@ def prepare_masternode(self, idx): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) port = p2p_port(len(self.nodes) + idx) - ipAndPort = '127.0.0.1:%d' % port + coreP2PAddrs = ['127.0.0.1:%d' % port] operatorReward = idx submit = (idx % 4) < 2 if register_fund: - protx_result = mn.register_fund(self.nodes[0], submit=submit, ipAndPort=ipAndPort, operator_reward=operatorReward) + protx_result = mn.register_fund(self.nodes[0], submit=submit, coreP2PAddrs=coreP2PAddrs, operator_reward=operatorReward) else: self.generate(self.nodes[0], 1, sync_fun=self.no_op) - protx_result = mn.register(self.nodes[0], submit=submit, collateral_txid=txid, collateral_vout=collateral_vout, ipAndPort=ipAndPort, + protx_result = mn.register(self.nodes[0], submit=submit, collateral_txid=txid, collateral_vout=collateral_vout, coreP2PAddrs=coreP2PAddrs, operator_reward=operatorReward) if submit: proTxHash = protx_result @@ -1730,7 +1730,7 @@ def prepare_masternode(self, idx): if operatorReward > 0: self.generate(self.nodes[0], 1, sync_fun=self.no_op) operatorPayoutAddress = self.nodes[0].getnewaddress() - mn.update_service(self.nodes[0], submit=True, ipAndPort=ipAndPort, address_operator=operatorPayoutAddress) + mn.update_service(self.nodes[0], submit=True, coreP2PAddrs=coreP2PAddrs, address_operator=operatorPayoutAddress) self.mninfo.append(mn) self.log.info("Prepared MN %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (idx, txid, collateral_vout, proTxHash)) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 963d281ed235..1f1bae53c762 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -233,6 +233,7 @@ 'p2p_addrfetch.py', 'rpc_net.py --v1transport', 'rpc_net.py --v2transport', + 'rpc_netinfo.py', 'wallet_keypool.py --legacy-wallet', 'wallet_keypool_hd.py --legacy-wallet', 'wallet_keypool_hd.py --descriptors', diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index a12d04314384..d7567f67035d 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -24,6 +24,7 @@ src/qt/governancelist.* src/qt/masternodelist.* src/rpc/coinjoin.cpp src/rpc/evo.cpp +src/rpc/evo_util.* src/rpc/governance.cpp src/rpc/masternode.cpp src/rpc/quorums.cpp