diff --git a/src/Makefile.am b/src/Makefile.am index 0a91b742a016..d3839cf03a4b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -192,6 +192,7 @@ BITCOIN_CORE_H = \ evo/evodb.h \ evo/mnauth.h \ evo/mnhftx.h \ + evo/netinfo.h \ evo/providertx.h \ evo/simplifiedmns.h \ evo/specialtx.h \ @@ -776,6 +777,7 @@ libbitcoin_common_a_SOURCES = \ core_write.cpp \ deploymentinfo.cpp \ evo/core_write.cpp \ + evo/netinfo.cpp \ governance/common.cpp \ init/common.cpp \ key.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 0791df6d2257..57765a51c155 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -113,6 +113,7 @@ BITCOIN_TESTS =\ test/evo_deterministicmns_tests.cpp \ test/evo_islock_tests.cpp \ test/evo_mnhf_tests.cpp \ + test/evo_netinfo_tests.cpp \ test/evo_simplifiedmns_tests.cpp \ test/evo_trivialvalidation.cpp \ test/evo_utils_tests.cpp \ diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 8ae8a666c30a..b8713a5b67bc 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -186,7 +186,7 @@ void CCoinJoinClientSession::ProcessMessage(CNode& peer, CChainState& active_cha if (!m_mn_sync.IsBlockchainSynced()) return; if (!mixingMasternode) return; - if (mixingMasternode->pdmnState->addr != peer.addr) return; + if (mixingMasternode->pdmnState->netInfo.GetPrimary() != peer.addr) return; if (msg_type == NetMsgType::DSSTATUSUPDATE) { CCoinJoinStatusUpdate psssup; @@ -1106,7 +1106,7 @@ bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, m_clientman.AddUsedMasternode(dsq.masternodeOutpoint); - if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->addr)) { + if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo.GetPrimary())) { WalletCJLogPrint(m_wallet, /* Continued */ "CCoinJoinClientSession::JoinExistingQueue -- skipping connection, masternode=%s\n", dmn->proTxHash.ToString()); continue; @@ -1178,7 +1178,7 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon continue; } - if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->addr)) { + if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo.GetPrimary())) { WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- skipping connection, masternode=%s\n", dmn->proTxHash.ToString()); nTries++; @@ -1218,7 +1218,7 @@ bool CCoinJoinClientSession::ProcessPendingDsaRequest(CConnman& connman) CService mn_addr; if (auto dmn = m_dmnman.GetListAtChainTip().GetMN(pendingDsaRequest.GetProTxHash())) { - mn_addr = Assert(dmn->pdmnState)->addr; + mn_addr = Assert(dmn->pdmnState)->netInfo.GetPrimary(); } else { WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- cannot find address to connect, masternode=%s\n", __func__, pendingDsaRequest.GetProTxHash().ToString()); @@ -1820,7 +1820,7 @@ void CCoinJoinClientSession::RelayIn(const CCoinJoinEntry& entry, CConnman& conn { if (!mixingMasternode) return; - connman.ForNode(mixingMasternode->pdmnState->addr, [&entry, &connman, this](CNode* pnode) { + connman.ForNode(mixingMasternode->pdmnState->netInfo.GetPrimary(), [&entry, &connman, this](CNode* pnode) { WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::RelayIn -- found master, relaying message to %s\n", pnode->addr.ToStringAddrPort()); CNetMsgMaker msgMaker(pnode->GetCommonVersion()); @@ -1876,7 +1876,7 @@ 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->addr.ToStringAddrPort()); + obj.pushKV("service", mixingMasternode->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); } 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 b2a2cdbd7f0a..b851cc075c3c 100644 --- a/src/evo/core_write.cpp +++ b/src/evo/core_write.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ ret.pushKV("type", ToUnderlying(nType)); ret.pushKV("collateralHash", collateralOutpoint.hash.ToString()); ret.pushKV("collateralIndex", (int)collateralOutpoint.n); - ret.pushKV("service", addr.ToStringAddrPort()); + ret.pushKV("service", netInfo.GetPrimary().ToStringAddrPort()); ret.pushKV("ownerAddress", EncodeDestination(PKHash(keyIDOwner))); ret.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) { @@ -113,7 +114,7 @@ ret.pushKV("version", nVersion); ret.pushKV("type", ToUnderlying(nType)); ret.pushKV("proTxHash", proTxHash.ToString()); - ret.pushKV("service", addr.ToStringAddrPort()); + ret.pushKV("service", netInfo.GetPrimary().ToStringAddrPort()); if (CTxDestination dest; ExtractDestination(scriptOperatorPayout, dest)) { ret.pushKV("operatorPayoutAddress", EncodeDestination(dest)); } diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 546f4ad292c8..ea789de75b59 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -470,10 +470,12 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTota throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate collateralOutpoint=%s", __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort()))); } - if (dmn->pdmnState->addr != CService() && !AddUniqueProperty(*dmn, dmn->pdmnState->addr)) { - mnUniquePropertyMap = mnUniquePropertyMapSaved; - throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s", __func__, - dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringAddrPort()))); + for (const CService& entry : dmn->pdmnState->netInfo.GetEntries()) { + if (!AddUniqueProperty(*dmn, entry)) { + mnUniquePropertyMap = mnUniquePropertyMapSaved; + throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s", __func__, + dmn->proTxHash.ToString(), entry.ToStringAddrPort()))); + } } if (!AddUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) { mnUniquePropertyMap = mnUniquePropertyMapSaved; @@ -511,10 +513,28 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s // Using this temporary map as a checkpoint to roll back to in case of any issues. decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap; - if (!UpdateUniqueProperty(*dmn, oldState->addr, pdmnState->addr)) { + const auto updateNetInfo = [&]() { + if (oldState->netInfo != pdmnState->netInfo) { + // We track each individual entry in netInfo as opposed to netInfo itself (preventing us from + // using UpdateUniqueProperty()), so we need to successfully purge all old entries and insert + // new entries to successfully update. + for (const CService& old_entry : oldState->netInfo.GetEntries()) { + if (!DeleteUniqueProperty(*dmn, old_entry)) { + return strprintf("internal error"); // This shouldn't be possible + } + } + for (const CService& new_entry : pdmnState->netInfo.GetEntries()) { + if (!AddUniqueProperty(*dmn, new_entry)) { + return strprintf("duplicate (%s)", new_entry.ToStringAddrPort()); + } + } + } + return strprintf(""); + }(); + if (!updateNetInfo.empty()) { mnUniquePropertyMap = mnUniquePropertyMapSaved; - throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate address=%s", __func__, - oldDmn.proTxHash.ToString(), pdmnState->addr.ToStringAddrPort()))); + throw(std::runtime_error(strprintf("%s: Can't update masternode %s with addresses, reason=%s", __func__, + oldDmn.proTxHash.ToString(), updateNetInfo))); } if (!UpdateUniqueProperty(*dmn, oldState->keyIDOwner, pdmnState->keyIDOwner)) { mnUniquePropertyMap = mnUniquePropertyMapSaved; @@ -571,10 +591,12 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash) throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a collateralOutpoint=%s", __func__, proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort()))); } - if (dmn->pdmnState->addr != CService() && !DeleteUniqueProperty(*dmn, dmn->pdmnState->addr)) { - mnUniquePropertyMap = mnUniquePropertyMapSaved; - throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a address=%s", __func__, - proTxHash.ToString(), dmn->pdmnState->addr.ToStringAddrPort()))); + for (const CService& entry : dmn->pdmnState->netInfo.GetEntries()) { + if (!DeleteUniqueProperty(*dmn, entry)) { + mnUniquePropertyMap = mnUniquePropertyMapSaved; + throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with an address=%s", __func__, + proTxHash.ToString(), entry.ToStringAddrPort()))); + } } if (!DeleteUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) { mnUniquePropertyMap = mnUniquePropertyMapSaved; @@ -789,8 +811,10 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no } } - if (newList.HasUniqueProperty(proTx.addr)) { - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-addr"); + for (const CService& entry : proTx.netInfo.GetEntries()) { + if (newList.HasUniqueProperty(entry)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry"); + } } if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator)) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key"); @@ -800,7 +824,7 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no auto dmnState = std::make_shared(proTx); dmnState->nRegisteredHeight = nHeight; - if (proTx.addr == CService()) { + if (proTx.netInfo.IsEmpty()) { // start in banned pdmnState as we need to wait for a ProUpServTx dmnState->BanIfNotBanned(nHeight); } @@ -818,8 +842,11 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload"); } - if (newList.HasUniqueProperty(opt_proTx->addr) && newList.GetUniquePropertyMN(opt_proTx->addr)->proTxHash != opt_proTx->proTxHash) { - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-addr"); + for (const CService& entry : opt_proTx->netInfo.GetEntries()) { + if (newList.HasUniqueProperty(entry) && + newList.GetUniquePropertyMN(entry)->proTxHash != opt_proTx->proTxHash) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry"); + } } auto dmn = newList.GetMN(opt_proTx->proTxHash); @@ -834,7 +861,7 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no } auto newState = std::make_shared(*dmn->pdmnState); - newState->addr = opt_proTx->addr; + newState->netInfo = opt_proTx->netInfo; newState->scriptOperatorPayout = opt_proTx->scriptOperatorPayout; if (opt_proTx->nType == MnType::Evo) { newState->platformNodeID = opt_proTx->platformNodeID; @@ -1177,28 +1204,15 @@ void CDeterministicMNManager::CleanupCache(int nHeight) template static bool CheckService(const ProTx& proTx, TxValidationState& state) { - if (!proTx.addr.IsValid()) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-ipaddr"); - } - if (Params().RequireRoutableExternalIP() && !proTx.addr.IsRoutable()) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-ipaddr"); - } - - // TODO: use real args here - static int mainnetDefaultPort = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN)->GetDefaultPort(); - if (Params().NetworkIDString() == CBaseChainParams::MAIN) { - if (proTx.addr.GetPort() != mainnetDefaultPort) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-ipaddr-port"); - } - } else if (proTx.addr.GetPort() == mainnetDefaultPort) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-ipaddr-port"); - } - - if (!proTx.addr.IsIPv4()) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-ipaddr"); - } - - return true; + switch (proTx.netInfo.Validate()) { + case NetInfoStatus::BadInput: + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo"); + case NetInfoStatus::BadPort: + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-port"); + case NetInfoStatus::Success: + return true; + } // no default case, so the compiler can warn about missing cases + assert(false); } template @@ -1233,9 +1247,8 @@ static bool CheckPlatformFields(const ProTx& proTx, TxValidationState& state) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port"); } - if (proTx.platformP2PPort == proTx.platformHTTPPort || - proTx.platformP2PPort == proTx.addr.GetPort() || - proTx.platformHTTPPort == proTx.addr.GetPort()) { + if (proTx.platformP2PPort == proTx.platformHTTPPort || proTx.platformP2PPort == proTx.netInfo.GetPrimary().GetPort() || + proTx.platformHTTPPort == proTx.netInfo.GetPrimary().GetPort()) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-dup-ports"); } @@ -1300,7 +1313,7 @@ bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl: // 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 (opt_ptx->addr != CService() && !CheckService(*opt_ptx, state)) { + if (!opt_ptx->netInfo.IsEmpty() && !CheckService(*opt_ptx, state)) { // pass the state returned by the function above return false; } @@ -1360,8 +1373,11 @@ bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl: auto mnList = dmnman.GetListForBlock(pindexPrev); // only allow reusing of addresses when it's for the same collateral (which replaces the old MN) - if (mnList.HasUniqueProperty(opt_ptx->addr) && mnList.GetUniquePropertyMN(opt_ptx->addr)->collateralOutpoint != collateralOutpoint) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-addr"); + for (const CService& entry : opt_ptx->netInfo.GetEntries()) { + if (mnList.HasUniqueProperty(entry) && + mnList.GetUniquePropertyMN(entry)->collateralOutpoint != collateralOutpoint) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry"); + } } // never allow duplicate keys, even if this ProTx would replace an existing MN @@ -1430,8 +1446,10 @@ bool CheckProUpServTx(CDeterministicMNManager& dmnman, const CTransaction& tx, g } // don't allow updating to addresses already used by other MNs - if (mnList.HasUniqueProperty(opt_ptx->addr) && mnList.GetUniquePropertyMN(opt_ptx->addr)->proTxHash != opt_ptx->proTxHash) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-addr"); + for (const CService& entry : opt_ptx->netInfo.GetEntries()) { + if (mnList.HasUniqueProperty(entry) && mnList.GetUniquePropertyMN(entry)->proTxHash != opt_ptx->proTxHash) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry"); + } } // don't allow updating to platformNodeIds already used by other EvoNodes diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 76375e2d0695..386d98a23063 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -394,13 +394,17 @@ class CDeterministicMNList template [[nodiscard]] uint256 GetUniquePropertyHash(const T& v) const { - static_assert(!std::is_same(), "GetUniquePropertyHash cannot be templated against CBLSPublicKey"); +#define DMNL_NO_TEMPLATE(name) \ + static_assert(!std::is_same_v, name>, "GetUniquePropertyHash cannot be templated against " #name) + DMNL_NO_TEMPLATE(CBLSPublicKey); + DMNL_NO_TEMPLATE(MnNetInfo); +#undef DMNL_NO_TEMPLATE return ::SerializeHash(v); } template [[nodiscard]] bool AddUniqueProperty(const CDeterministicMN& dmn, const T& v) { - static const T nullValue; + static const T nullValue{}; if (v == nullValue) { return false; } @@ -420,7 +424,7 @@ class CDeterministicMNList template [[nodiscard]] bool DeleteUniqueProperty(const CDeterministicMN& dmn, const T& oldValue) { - static const T nullValue; + static const T nullValue{}; if (oldValue == nullValue) { return false; } @@ -443,7 +447,7 @@ class CDeterministicMNList if (oldValue == newValue) { return true; } - static const T nullValue; + static const T nullValue{}; if (oldValue != nullValue && !DeleteUniqueProperty(dmn, oldValue)) { return false; diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp index 8943640b8db3..df09e2d4611c 100644 --- a/src/evo/dmnstate.cpp +++ b/src/evo/dmnstate.cpp @@ -27,19 +27,19 @@ std::string CDeterministicMNState::ToString() const return strprintf("CDeterministicMNState(nVersion=%d, nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, " "nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, nRevocationReason=%d, " - "ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, addr=%s, payoutAddress=%s, " - "operatorPayoutAddress=%s)", + "ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, payoutAddress=%s, " + "operatorPayoutAddress=%s)\n" + " %s", nVersion, nRegisteredHeight, nLastPaidHeight, nPoSePenalty, nPoSeRevivedHeight, nPoSeBanHeight, nRevocationReason, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), - EncodeDestination(PKHash(keyIDVoting)), addr.ToStringAddrPort(), payoutAddress, - operatorPayoutAddress); + EncodeDestination(PKHash(keyIDVoting)), payoutAddress, operatorPayoutAddress, netInfo.ToString()); } UniValue CDeterministicMNState::ToJson(MnType nType) const { UniValue obj(UniValue::VOBJ); obj.pushKV("version", nVersion); - obj.pushKV("service", addr.ToStringAddrPort()); + obj.pushKV("service", netInfo.GetPrimary().ToStringAddrPort()); obj.pushKV("registeredHeight", nRegisteredHeight); obj.pushKV("lastPaidHeight", nLastPaidHeight); obj.pushKV("consecutivePayments", nConsecutivePayments); @@ -72,8 +72,8 @@ UniValue CDeterministicMNStateDiff::ToJson(MnType nType) const if (fields & Field_nVersion) { obj.pushKV("version", state.nVersion); } - if (fields & Field_addr) { - obj.pushKV("service", state.addr.ToStringAddrPort()); + if (fields & Field_netInfo) { + obj.pushKV("service", state.netInfo.GetPrimary().ToStringAddrPort()); } if (fields & Field_nRegisteredHeight) { obj.pushKV("registeredHeight", state.nRegisteredHeight); diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index ec8a767ed76b..49a3b4745d55 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -54,7 +54,7 @@ class CDeterministicMNState CKeyID keyIDOwner; CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; - CService addr; + MnNetInfo netInfo; CScript scriptPayout; CScript scriptOperatorPayout; @@ -69,7 +69,7 @@ class CDeterministicMNState keyIDOwner(proTx.keyIDOwner), pubKeyOperator(proTx.pubKeyOperator), keyIDVoting(proTx.keyIDVoting), - addr(proTx.addr), + netInfo(proTx.netInfo), scriptPayout(proTx.scriptPayout), platformNodeID(proTx.platformNodeID), platformP2PPort(proTx.platformP2PPort), @@ -100,7 +100,7 @@ class CDeterministicMNState READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), obj.nVersion == ProTxVersion::LegacyBLS)); READWRITE( obj.keyIDVoting, - obj.addr, + obj.netInfo, obj.scriptPayout, obj.scriptOperatorPayout, obj.platformNodeID, @@ -112,7 +112,7 @@ class CDeterministicMNState { nVersion = ProTxVersion::LegacyBLS; pubKeyOperator = CBLSLazyPublicKey(); - addr = CService(); + netInfo.Clear(); scriptOperatorPayout = CScript(); nRevocationReason = CProUpRevTx::REASON_NOT_SPECIFIED; platformNodeID = uint160(); @@ -166,7 +166,7 @@ class CDeterministicMNStateDiff Field_keyIDOwner = 0x0100, Field_pubKeyOperator = 0x0200, Field_keyIDVoting = 0x0400, - Field_addr = 0x0800, + Field_netInfo = 0x0800, Field_scriptPayout = 0x1000, Field_scriptOperatorPayout = 0x2000, Field_nConsecutivePayments = 0x4000, @@ -198,7 +198,7 @@ class CDeterministicMNStateDiff DMN_STATE_MEMBER(keyIDOwner), DMN_STATE_MEMBER(pubKeyOperator), DMN_STATE_MEMBER(keyIDVoting), - DMN_STATE_MEMBER(addr), + DMN_STATE_MEMBER(netInfo), DMN_STATE_MEMBER(scriptPayout), DMN_STATE_MEMBER(scriptOperatorPayout), DMN_STATE_MEMBER(nConsecutivePayments), diff --git a/src/evo/netinfo.cpp b/src/evo/netinfo.cpp new file mode 100644 index 000000000000..ed5fc19053db --- /dev/null +++ b/src/evo/netinfo.cpp @@ -0,0 +1,83 @@ +// 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 + +namespace { +static std::unique_ptr g_main_params{nullptr}; +static std::once_flag g_main_params_flag; + +bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; } +const CChainParams& MainParams() +{ + // TODO: use real args here + std::call_once(g_main_params_flag, + [&]() { g_main_params = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN); }); + return *Assert(g_main_params); +} +} // anonymous namespace + +NetInfoStatus MnNetInfo::ValidateService(const CService& service) +{ + if (!service.IsValid()) { + return NetInfoStatus::BadInput; + } + if (!service.IsIPv4()) { + return NetInfoStatus::BadInput; + } + if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) { + return NetInfoStatus::BadInput; + } + + const auto default_port_main = MainParams().GetDefaultPort(); + if (IsNodeOnMainnet() && service.GetPort() != default_port_main) { + // Must use mainnet port on mainnet + return NetInfoStatus::BadPort; + } else if (service.GetPort() == default_port_main) { + // Using mainnet port prohibited outside of mainnet + return NetInfoStatus::BadPort; + } + + return NetInfoStatus::Success; +} + +NetInfoStatus MnNetInfo::AddEntry(const std::string& input) +{ + if (auto service = Lookup(input, /*portDefault=*/Params().GetDefaultPort(), /*fAllowLookup=*/false); + service.has_value()) { + const auto ret = ValidateService(service.value()); + if (ret == NetInfoStatus::Success) { + m_addr = service.value(); + ASSERT_IF_DEBUG(m_addr != CService()); + } + return ret; + } + return NetInfoStatus::BadInput; +} + +CServiceList MnNetInfo::GetEntries() const +{ + CServiceList ret; + if (!IsEmpty()) { + ASSERT_IF_DEBUG(m_addr != CService()); + ret.push_back(m_addr); + } + // If MnNetInfo is empty, we probably don't expect any entries to show up, so + // we return a blank set instead. + return ret; +} + +std::string MnNetInfo::ToString() const +{ + // Extra padding to account for padding done by the calling function. + return strprintf("MnNetInfo()\n" + " CService(addr=%s, port=%u)\n", + m_addr.ToStringAddr(), m_addr.GetPort()); +} diff --git a/src/evo/netinfo.h b/src/evo/netinfo.h new file mode 100644 index 000000000000..a264d76b8e0d --- /dev/null +++ b/src/evo/netinfo.h @@ -0,0 +1,65 @@ +// 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_EVO_NETINFO_H +#define BITCOIN_EVO_NETINFO_H + +#include +#include + +class CService; + +enum class NetInfoStatus : uint8_t { + BadInput, + BadPort, + Success +}; + +constexpr std::string_view NISToString(const NetInfoStatus code) +{ + switch (code) { + case NetInfoStatus::BadInput: + return "invalid address"; + case NetInfoStatus::BadPort: + return "invalid port"; + case NetInfoStatus::Success: + return "success"; + } // no default case, so the compiler can warn about missing cases + assert(false); +} + +using CServiceList = std::vector>; + +class MnNetInfo +{ +private: + CService m_addr{}; + +private: + static NetInfoStatus ValidateService(const CService& service); + +public: + MnNetInfo() = default; + ~MnNetInfo() = default; + + bool operator==(const MnNetInfo& rhs) const { return m_addr == rhs.m_addr; } + bool operator!=(const MnNetInfo& rhs) const { return !(*this == rhs); } + + SERIALIZE_METHODS(MnNetInfo, obj) + { + READWRITE(obj.m_addr); + } + + NetInfoStatus AddEntry(const std::string& service); + CServiceList GetEntries() const; + + const CService& GetPrimary() const { return m_addr; } + bool IsEmpty() const { return *this == MnNetInfo(); } + NetInfoStatus Validate() const { return ValidateService(m_addr); } + std::string ToString() const; + + void Clear() { m_addr = CService(); } +}; + +#endif // BITCOIN_EVO_NETINFO_H diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 4763bf04dd7a..cb869fc2b88c 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -87,13 +87,14 @@ std::string CProRegTx::ToString() const payee = EncodeDestination(dest); } - return strprintf("CProRegTx(nVersion=%d, nType=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, " + return strprintf("CProRegTx(nVersion=%d, nType=%d, collateralOutpoint=%s, nOperatorReward=%f, " "ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, scriptPayout=%s, platformNodeID=%s, " - "platformP2PPort=%d, platformHTTPPort=%d)", - nVersion, ToUnderlying(nType), collateralOutpoint.ToStringShort(), addr.ToStringAddrPort(), - (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), + "platformP2PPort=%d, platformHTTPPort=%d)\n" + " %s", + nVersion, ToUnderlying(nType), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, + EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee, platformNodeID.ToString(), platformP2PPort, - platformHTTPPort); + platformHTTPPort, netInfo.ToString()); } bool CProUpServTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const @@ -116,10 +117,11 @@ std::string CProUpServTx::ToString() const payee = EncodeDestination(dest); } - return strprintf("CProUpServTx(nVersion=%d, nType=%d, proTxHash=%s, addr=%s, operatorPayoutAddress=%s, " - "platformNodeID=%s, platformP2PPort=%d, platformHTTPPort=%d)", - nVersion, ToUnderlying(nType), proTxHash.ToString(), addr.ToStringAddrPort(), payee, - platformNodeID.ToString(), platformP2PPort, platformHTTPPort); + return strprintf("CProUpServTx(nVersion=%d, nType=%d, proTxHash=%s, operatorPayoutAddress=%s, " + "platformNodeID=%s, platformP2PPort=%d, platformHTTPPort=%d)\n" + " %s", + nVersion, ToUnderlying(nType), proTxHash.ToString(), payee, platformNodeID.ToString(), + platformP2PPort, platformHTTPPort, netInfo.ToString()); } bool CProUpRegTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 50f777293221..48017579c14c 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -6,6 +6,7 @@ #define BITCOIN_EVO_PROVIDERTX_H #include +#include #include #include @@ -40,7 +41,7 @@ class CProRegTx MnType nType{MnType::Regular}; uint16_t nMode{0}; // only 0 supported for now COutPoint collateralOutpoint{uint256(), (uint32_t)-1}; // if hash is null, we refer to a ProRegTx output - CService addr; + MnNetInfo netInfo; uint160 platformNodeID{}; uint16_t platformP2PPort{0}; uint16_t platformHTTPPort{0}; @@ -66,7 +67,7 @@ class CProRegTx obj.nType, obj.nMode, obj.collateralOutpoint, - obj.addr, + obj.netInfo, obj.keyIDOwner, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == ProTxVersion::LegacyBLS)), obj.keyIDVoting, @@ -109,7 +110,7 @@ class CProUpServTx uint16_t nVersion{ProTxVersion::LegacyBLS}; // message version MnType nType{MnType::Regular}; uint256 proTxHash; - CService addr; + MnNetInfo netInfo; uint160 platformNodeID{}; uint16_t platformP2PPort{0}; uint16_t platformHTTPPort{0}; @@ -132,7 +133,7 @@ class CProUpServTx } READWRITE( obj.proTxHash, - obj.addr, + obj.netInfo, obj.scriptOperatorPayout, obj.inputsHash ); diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index f99dbe08166a..979c8e175bc7 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -30,7 +30,7 @@ CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : proRegTxHash(dmn.proTxHash), confirmedHash(dmn.pdmnState->confirmedHash), - service(dmn.pdmnState->addr), + netInfo(dmn.pdmnState->netInfo), pubKeyOperator(dmn.pdmnState->pubKeyOperator), keyIDVoting(dmn.pdmnState->keyIDVoting), isValid(!dmn.pdmnState->IsBanned()), @@ -62,12 +62,13 @@ std::string CSimplifiedMNListEntry::ToString() const operatorPayoutAddress = EncodeDestination(dest); } - return strprintf("CSimplifiedMNListEntry(nVersion=%d, nType=%d, proRegTxHash=%s, confirmedHash=%s, service=%s, " + return strprintf("CSimplifiedMNListEntry(nVersion=%d, nType=%d, proRegTxHash=%s, confirmedHash=%s, " "pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutAddress=%s, operatorPayoutAddress=%s, " - "platformHTTPPort=%d, platformNodeID=%s)", + "platformHTTPPort=%d, platformNodeID=%s)\n" + " %s", nVersion, ToUnderlying(nType), proRegTxHash.ToString(), confirmedHash.ToString(), - service.ToStringAddrPort(), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), - isValid, payoutAddress, operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString()); + pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutAddress, + operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString(), netInfo.ToString()); } UniValue CSimplifiedMNListEntry::ToJson(bool extended) const @@ -77,7 +78,7 @@ UniValue CSimplifiedMNListEntry::ToJson(bool extended) const obj.pushKV("nType", ToUnderlying(nType)); obj.pushKV("proRegTxHash", proRegTxHash.ToString()); obj.pushKV("confirmedHash", confirmedHash.ToString()); - obj.pushKV("service", service.ToStringAddrPort()); + obj.pushKV("service", netInfo.GetPrimary().ToStringAddrPort()); obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); obj.pushKV("isValid", isValid); diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 0a1fa4c4633e..5e5bf7efeb81 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -34,7 +35,7 @@ class CSimplifiedMNListEntry public: uint256 proRegTxHash; uint256 confirmedHash; - CService service; + MnNetInfo netInfo; CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; bool isValid{false}; @@ -52,7 +53,7 @@ class CSimplifiedMNListEntry { return proRegTxHash == rhs.proRegTxHash && confirmedHash == rhs.confirmedHash && - service == rhs.service && + netInfo == rhs.netInfo && pubKeyOperator == rhs.pubKeyOperator && keyIDVoting == rhs.keyIDVoting && isValid == rhs.isValid && @@ -75,7 +76,7 @@ class CSimplifiedMNListEntry READWRITE( obj.proRegTxHash, obj.confirmedHash, - obj.service, + obj.netInfo, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == ProTxVersion::LegacyBLS)), obj.keyIDVoting, obj.isValid diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index 2379e6eeef02..8966a3e6c05f 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -823,7 +823,8 @@ bool EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, CConnman& if (!dmn) { debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString()); } else { - debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToStringAddrPort()); + debugMsg += strprintf(" %s (%s)\n", c.ToString(), + dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); } } LogPrint(BCLog::NET_NETCONN, debugMsg.c_str()); /* Continued */ @@ -871,7 +872,8 @@ void AddQuorumProbeConnections(const Consensus::LLMQParams& llmqParams, CConnman if (!dmn) { debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString()); } else { - debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToStringAddrPort()); + debugMsg += strprintf(" %s (%s)\n", c.ToString(), + dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); } } LogPrint(BCLog::NET_NETCONN, debugMsg.c_str()); /* Continued */ diff --git a/src/masternode/node.cpp b/src/masternode/node.cpp index 3a51c1597278..7fe30266da5d 100644 --- a/src/masternode/node.cpp +++ b/src/masternode/node.cpp @@ -146,7 +146,7 @@ void CActiveMasternodeManager::InitInternal(const CBlockIndex* pindex) LogPrintf("CActiveMasternodeManager::Init -- proTxHash=%s, proTx=%s\n", dmn->proTxHash.ToString(), dmn->ToString()); - if (m_info.service != dmn->pdmnState->addr) { + if (m_info.service != dmn->pdmnState->netInfo.GetPrimary()) { m_state = MasternodeState::SOME_ERROR; m_error = "Local address does not match the address from ProTx"; LogPrintf("CActiveMasternodeManager::Init -- ERROR: %s\n", m_error); @@ -201,7 +201,7 @@ void CActiveMasternodeManager::UpdatedBlockTip(const CBlockIndex* pindexNew, con // MN operator key changed or revoked return reset(MasternodeState::OPERATOR_KEY_CHANGED); } - if (newDmn->pdmnState->addr != oldDmn->pdmnState->addr) { + if (newDmn->pdmnState->netInfo.GetPrimary() != oldDmn->pdmnState->netInfo.GetPrimary()) { // MN IP changed return reset(MasternodeState::PROTX_IP_CHANGED); } diff --git a/src/masternode/utils.cpp b/src/masternode/utils.cpp index 5dc707f4bf2a..0757ac8815c1 100644 --- a/src/masternode/utils.cpp +++ b/src/masternode/utils.cpp @@ -77,7 +77,9 @@ void CMasternodeUtils::DoMaintenance(CConnman& connman, CDeterministicMNManager& } #ifdef ENABLE_WALLET - bool fFound = ranges::any_of(vecDmns, [&pnode](const auto& dmn){ return pnode->addr == dmn->pdmnState->addr; }); + bool fFound = ranges::any_of(vecDmns, [&pnode](const auto& dmn) { + return pnode->addr == dmn->pdmnState->netInfo.GetPrimary(); + }); if (fFound) return; // do NOT disconnect mixing masternodes #endif // ENABLE_WALLET if (fLogIPs) { diff --git a/src/net.cpp b/src/net.cpp index 4b759ecd3469..4f6683e145cb 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3674,7 +3674,7 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, if (!dmn) { continue; } - const auto& addr2 = dmn->pdmnState->addr; + const auto addr2 = dmn->pdmnState->netInfo.GetPrimary(); if (connectedNodes.count(addr2) && !connectedProRegTxHashes.count(proRegTxHash)) { // we probably connected to it before it became a masternode // or maybe we are still waiting for mnauth @@ -3740,8 +3740,8 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, if (!vPendingMasternodes.empty()) { auto dmn = mnList.GetValidMN(vPendingMasternodes.front()); vPendingMasternodes.erase(vPendingMasternodes.begin()); - if (dmn && !connectedNodes.count(dmn->pdmnState->addr) && !IsMasternodeOrDisconnectRequested(dmn->pdmnState->addr)) { - LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- opening pending masternode connection to %s, service=%s\n", _func_, dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringAddrPort()); + if (dmn && !connectedNodes.count(dmn->pdmnState->netInfo.GetPrimary()) && !IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo.GetPrimary())) { + LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- opening pending masternode connection to %s, service=%s\n", _func_, dmn->proTxHash.ToString(), dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); return dmn; } } @@ -3750,7 +3750,7 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, // not-null auto dmn = pending[GetRand(pending.size())]; LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- opening quorum connection to %s, service=%s\n", - _func_, dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringAddrPort()); + _func_, dmn->proTxHash.ToString(), dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); return dmn; } @@ -3760,7 +3760,7 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, masternodePendingProbes.erase(dmn->proTxHash); isProbe = MasternodeProbeConn::IsConnection; - LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- probing masternode %s, service=%s\n", _func_, dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToStringAddrPort()); + LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- probing masternode %s, service=%s\n", _func_, dmn->proTxHash.ToString(), dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); return dmn; } return nullptr; @@ -3776,16 +3776,16 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, mn_metaman.GetMetaInfo(connectToDmn->proTxHash)->SetLastOutboundAttempt(nANow); - OpenMasternodeConnection(CAddress(connectToDmn->pdmnState->addr, NODE_NETWORK), /*use_v2transport=*/GetLocalServices() & NODE_P2P_V2, isProbe); + OpenMasternodeConnection(CAddress(connectToDmn->pdmnState->netInfo.GetPrimary(), NODE_NETWORK), /*use_v2transport=*/GetLocalServices() & NODE_P2P_V2, isProbe); // should be in the list now if connection was opened - bool connected = ForNode(connectToDmn->pdmnState->addr, CConnman::AllNodes, [&](CNode* pnode) { + bool connected = ForNode(connectToDmn->pdmnState->netInfo.GetPrimary(), CConnman::AllNodes, [&](CNode* pnode) { if (pnode->fDisconnect) { return false; } return true; }); if (!connected) { - LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- connection failed for masternode %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->addr.ToStringAddrPort()); + LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- connection failed for masternode %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); // Will take a few consequent failed attempts to PoSe-punish a MN. if (mn_metaman.GetMetaInfo(connectToDmn->proTxHash)->OutboundFailedTooManyTimes()) { LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- failed to connect to masternode %s too many times\n", __func__, connectToDmn->proTxHash.ToString()); diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 44a799559a67..546125d92eeb 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -221,10 +221,10 @@ void MasternodeList::updateDIP3List() } // populate list // Address, Protocol, Status, Active Seconds, Last Seen, Pub Key - auto addr_key = dmn.pdmnState->addr.GetKey(); + auto addr_key = dmn.pdmnState->netInfo.GetPrimary().GetKey(); QByteArray addr_ba(reinterpret_cast(addr_key.data()), addr_key.size()); QTableWidgetItem* addressItem = new CMasternodeListWidgetItem( - QString::fromStdString(dmn.pdmnState->addr.ToStringAddrPort()), addr_ba); + QString::fromStdString(dmn.pdmnState->netInfo.GetPrimary().ToStringAddrPort()), addr_ba); QTableWidgetItem* typeItem = new QTableWidgetItem(QString::fromStdString(std::string(GetMnType(dmn.nType).description))); QTableWidgetItem* statusItem = new QTableWidgetItem(dmn.pdmnState->IsBanned() ? tr("POSE_BANNED") : tr("ENABLED")); QTableWidgetItem* PoSeScoreItem = new CMasternodeListWidgetItem(QString::number(dmn.pdmnState->nPoSePenalty), dmn.pdmnState->nPoSePenalty); diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 78bd077c4678..4d76a6f6c5b2 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -683,10 +683,8 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, } if (!request.params[paramIdx].get_str().empty()) { - if (auto addr = Lookup(request.params[paramIdx].get_str(), Params().GetDefaultPort(), false); addr.has_value()) { - ptx.addr = addr.value(); - } else { - throw std::runtime_error(strprintf("invalid network address %s", request.params[paramIdx].get_str())); + 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())); } } @@ -978,10 +976,8 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques ptx.nVersion = dmn->pdmnState->nVersion; - if (auto addr = Lookup(request.params[1].get_str().c_str(), Params().GetDefaultPort(), false); addr.has_value()) { - ptx.addr = addr.value(); - } else { - throw std::runtime_error(strprintf("invalid network address %s", request.params[1].get_str())); + 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())); } CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey"); diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 02699c42c082..82bc5b0b7af6 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -565,8 +565,16 @@ static RPCHelpMan masternodelist_helper(bool is_composite) payeeStr = EncodeDestination(payeeDest); } + std::string strAddress{}; + if (strMode == "addr" || strMode == "full" || strMode == "info" || strMode == "json" || strMode == "recent" || + strMode == "evo") { + for (const CService& entry : dmn.pdmnState->netInfo.GetEntries()) { + strAddress += entry.ToStringAddrPort() + " "; + } + if (!strAddress.empty()) strAddress.pop_back(); // Remove trailing space + } + if (strMode == "addr") { - std::string strAddress = dmn.pdmnState->addr.ToStringAddrPort(); if (!strFilter.empty() && strAddress.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strAddress); @@ -577,7 +585,7 @@ static RPCHelpMan masternodelist_helper(bool is_composite) payeeStr, PadString(ToString(dmnToLastPaidTime(dmn)), 10), PadString(ToString(dmn.pdmnState->nLastPaidHeight), 6), - dmn.pdmnState->addr.ToStringAddrPort()); + strAddress); if (!strFilter.empty() && strFull.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strFull); @@ -586,14 +594,14 @@ static RPCHelpMan masternodelist_helper(bool is_composite) PadString(dmnToStatus(dmn), 18), dmn.pdmnState->nPoSePenalty, payeeStr, - dmn.pdmnState->addr.ToStringAddrPort()); + strAddress); if (!strFilter.empty() && strInfo.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strInfo); } else if (strMode == "json" || strMode == "recent" || strMode == "evo") { std::string strInfo = strprintf("%s %s %s %s %d %d %d %s %s %s %s", dmn.proTxHash.ToString(), - dmn.pdmnState->addr.ToStringAddrPort(), + strAddress, payeeStr, dmnToStatus(dmn), dmn.pdmnState->nPoSePenalty, @@ -607,7 +615,7 @@ 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->addr.ToStringAddrPort()); + objMN.pushKV("address", dmn.pdmnState->netInfo.GetPrimary().ToStringAddrPort()); 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 c7463d0e7557..e59b3a1df398 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -203,7 +203,7 @@ 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->addr.ToStringAddrPort()); + mo.pushKV("service", dmn->pdmnState->netInfo.GetPrimary().ToStringAddrPort()); mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.ToString()); mo.pushKV("valid", quorum->qc->validMembers[i]); if (quorum->qc->validMembers[i]) { diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index adb1f09ba415..50a1ecd477f7 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -117,7 +117,7 @@ static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxM CProRegTx proTx; proTx.nVersion = CProRegTx::GetMaxVersion(!bls::bls_legacy_scheme); proTx.collateralOutpoint.n = 0; - proTx.addr = LookupNumeric("1.1.1.1", port); + BOOST_CHECK_EQUAL(proTx.netInfo.AddEntry(strprintf("1.1.1.1:%d", port)), NetInfoStatus::Success); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load()); proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID(); diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 26eae84c15a7..30c2cb8b656c 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -106,7 +106,7 @@ static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxM CProRegTx proTx; proTx.nVersion = CProRegTx::GetMaxVersion(!bls::bls_legacy_scheme); proTx.collateralOutpoint.n = 0; - proTx.addr = LookupNumeric("1.1.1.1", port); + BOOST_CHECK_EQUAL(proTx.netInfo.AddEntry(strprintf("1.1.1.1:%d", port)), NetInfoStatus::Success); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load()); proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID(); @@ -128,7 +128,7 @@ static CMutableTransaction CreateProUpServTx(const CChain& active_chain, const C CProUpServTx proTx; proTx.nVersion = CProUpServTx::GetMaxVersion(!bls::bls_legacy_scheme); proTx.proTxHash = proTxHash; - proTx.addr = LookupNumeric("1.1.1.1", port); + BOOST_CHECK_EQUAL(proTx.netInfo.AddEntry(strprintf("1.1.1.1:%d", port)), NetInfoStatus::Success); proTx.scriptOperatorPayout = scriptOperatorPayout; CMutableTransaction tx; @@ -516,7 +516,7 @@ void FuncDIP3Protx(TestChainSetup& setup) nHeight++; auto dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]); - BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->addr.GetPort() == 1000); + BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo.GetPrimary().GetPort() == 1000); // test ProUpRevTx tx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], operatorKeys[dmnHashes[0]], setup.coinbaseKey); @@ -574,7 +574,7 @@ void FuncDIP3Protx(TestChainSetup& setup) nHeight++; dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]); - BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->addr.GetPort() == 100); + BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo.GetPrimary().GetPort() == 100); BOOST_REQUIRE(dmn != nullptr && !dmn->pdmnState->IsBanned()); // test that the revived MN gets payments again @@ -634,7 +634,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup) CProRegTx payload; payload.nVersion = CProRegTx::GetMaxVersion(!bls::bls_legacy_scheme); - payload.addr = LookupNumeric("1.1.1.1", 1); + BOOST_CHECK_EQUAL(payload.netInfo.AddEntry("1.1.1.1:1"), NetInfoStatus::Success); payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); @@ -708,7 +708,7 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup) auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey())); CProRegTx payload; - payload.addr = LookupNumeric("1.1.1.1", 2); + BOOST_CHECK_EQUAL(payload.netInfo.AddEntry("1.1.1.1:2"), NetInfoStatus::Success); payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); @@ -776,7 +776,7 @@ void FuncVerifyDB(TestChainSetup& setup) CProRegTx payload; payload.nVersion = CProRegTx::GetMaxVersion(!bls::bls_legacy_scheme); - payload.addr = LookupNumeric("1.1.1.1", 1); + BOOST_CHECK_EQUAL(payload.netInfo.AddEntry("1.1.1.1:1"), NetInfoStatus::Success); payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); diff --git a/src/test/evo_netinfo_tests.cpp b/src/test/evo_netinfo_tests.cpp new file mode 100644 index 000000000000..a7f376ff24c0 --- /dev/null +++ b/src/test/evo_netinfo_tests.cpp @@ -0,0 +1,56 @@ +// 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 + +const std::vector> vals{ + // Address and port specified + {"1.1.1.1:8888", NetInfoStatus::Success}, + // Address specified, port should default to default P2P core + {"1.1.1.1", NetInfoStatus::Success}, + // Mainnet P2P port on non-mainnet + {"1.1.1.1:9999", NetInfoStatus::BadPort}, + // Valid IPv4 formatting but invalid IPv4 address + {"0.0.0.0:8888", NetInfoStatus::BadInput}, + // Port greater than uint16_t max + {"1.1.1.1:99999", NetInfoStatus::BadInput}, + // Only IPv4 allowed + {"[2606:4700:4700::1111]:8888", NetInfoStatus::BadInput}, + // Domains are not allowed + {"example.com:8888", NetInfoStatus::BadInput}, + // Incorrect IPv4 address + {"1.1.1.256:8888", NetInfoStatus::BadInput}, + // Missing address + {":8888", NetInfoStatus::BadInput}, +}; + +BOOST_FIXTURE_TEST_SUITE(evo_netinfo_tests, RegTestingSetup) + +void ValidateGetEntries(const CServiceList& entries, const size_t expected_size) +{ + BOOST_CHECK_EQUAL(entries.size(), expected_size); +} + +BOOST_AUTO_TEST_CASE(mnnetinfo_rules) +{ + // Validate AddEntry() rules enforcement + for (const auto& [input, expected_ret] : vals) { + MnNetInfo netInfo; + BOOST_CHECK_EQUAL(netInfo.AddEntry(input), expected_ret); + if (expected_ret != NetInfoStatus::Success) { + BOOST_CHECK(netInfo.GetEntries().empty()); + } else { + ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/evo_simplifiedmns_tests.cpp b/src/test/evo_simplifiedmns_tests.cpp index fa833dbb1bf4..6c6856966d68 100644 --- a/src/test/evo_simplifiedmns_tests.cpp +++ b/src/test/evo_simplifiedmns_tests.cpp @@ -10,24 +10,19 @@ #include -BOOST_FIXTURE_TEST_SUITE(evo_simplifiedmns_tests, BasicTestingSetup) +BOOST_FIXTURE_TEST_SUITE(evo_simplifiedmns_tests, RegTestingSetup) BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) { //TODO: Provide raw data for basic scheme as well bls::bls_legacy_scheme.store(true); std::vector entries; - for (size_t i = 0; i < 15; i++) { + for (size_t i = 1; i < 16; i++) { CSimplifiedMNListEntry smle; smle.proRegTxHash.SetHex(strprintf("%064x", i)); smle.confirmedHash.SetHex(strprintf("%064x", i)); - std::string ip = strprintf("%d.%d.%d.%d", 0, 0, 0, i); - if (auto service = Lookup(ip, i, false); service.has_value()) { - smle.service = service.value(); - } else { - BOOST_REQUIRE(false); - } + BOOST_CHECK_EQUAL(smle.netInfo.AddEntry(strprintf("%d.%d.%d.%d:%d", 0, 0, 0, i, i)), NetInfoStatus::Success); std::vector vecBytes{static_cast(i)}; vecBytes.resize(CBLSSecretKey::SerSize); @@ -40,7 +35,6 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) } std::vector expectedHashes = { - "373b549f6380d8f7b04d7b04d7c58a749c5cbe3bf41536785ba819879c4870f1", "3a1010e28226558560e5296bcee6bf0b9b963b73a1514f5aa2885e270f6b90c1", "85d3d93b28689128daf3a41d706ae5002f447b9b6372776f0ca9d53b31146884", "8930eee6bd2e7971a7090edfb79f74c00a12280e59adfc2cc99d406a01e368f9", @@ -55,6 +49,7 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) "cbc25ca965d0fa69a1fdc1d796b8ee2726a0e2137414e92fb9541630e3189901", "ac9934c4049ae952d41fb38e7e9659a558a5ce748bdb7fb613741598d1b16a27", "a61177eb14450bb8c56e5f0547035e0f3a70fe46f36901351cc568b2e48e29d0", + "67798d40a2196f446114c68d86445fe088750515a96eb65392c6bcfac8f3be9b" }; std::vector calculatedHashes; @@ -67,7 +62,7 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) CSimplifiedMNList sml(entries); - std::string expectedMerkleRoot = "b2303aca677ae2091c882e44b58f57869fa88a6db1f4e1a5d71975e5387fa195"; + std::string expectedMerkleRoot = "0bae2176078cf42fa3e1fda761d4255d1c1c54777c6a793d0ab2b07c85ed4022"; std::string calculatedMerkleRoot = sml.CalcMerkleRoot(nullptr).ToString(); //printf("merkleRoot=\"%s\",\n", calculatedMerkleRoot.c_str()); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index e2b92ba5575e..53d90a6be9fa 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -691,7 +691,9 @@ void CTxMemPool::addUncheckedProTx(indexed_transaction_set::iterator& newit, con if (!proTx.collateralOutpoint.hash.IsNull()) { mapProTxRefs.emplace(tx_hash, proTx.collateralOutpoint.hash); } - mapProTxAddresses.emplace(proTx.addr, tx_hash); + for (const CService& entry : proTx.netInfo.GetEntries()) { + mapProTxAddresses.emplace(entry, tx_hash); + } mapProTxPubKeyIDs.emplace(proTx.keyIDOwner, tx_hash); mapProTxBlsPubKeyHashes.emplace(proTx.pubKeyOperator.GetHash(), tx_hash); if (!proTx.collateralOutpoint.hash.IsNull()) { @@ -702,7 +704,9 @@ void CTxMemPool::addUncheckedProTx(indexed_transaction_set::iterator& newit, con } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { auto proTx = *Assert(GetTxPayload(tx)); mapProTxRefs.emplace(proTx.proTxHash, tx_hash); - mapProTxAddresses.emplace(proTx.addr, tx_hash); + for (const CService& entry : proTx.netInfo.GetEntries()) { + mapProTxAddresses.emplace(entry, tx_hash); + } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { auto proTx = *Assert(GetTxPayload(tx)); mapProTxRefs.emplace(proTx.proTxHash, tx_hash); @@ -791,7 +795,9 @@ void CTxMemPool::removeUncheckedProTx(const CTransaction& tx) if (!proTx.collateralOutpoint.IsNull()) { eraseProTxRef(tx_hash, proTx.collateralOutpoint.hash); } - mapProTxAddresses.erase(proTx.addr); + for (const CService& entry : proTx.netInfo.GetEntries()) { + mapProTxAddresses.erase(entry); + } mapProTxPubKeyIDs.erase(proTx.keyIDOwner); mapProTxBlsPubKeyHashes.erase(proTx.pubKeyOperator.GetHash()); mapProTxCollaterals.erase(proTx.collateralOutpoint); @@ -799,7 +805,9 @@ void CTxMemPool::removeUncheckedProTx(const CTransaction& tx) } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) { auto proTx = *Assert(GetTxPayload(tx)); eraseProTxRef(proTx.proTxHash, tx_hash); - mapProTxAddresses.erase(proTx.addr); + for (const CService& entry : proTx.netInfo.GetEntries()) { + mapProTxAddresses.erase(entry); + } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { auto proTx = *Assert(GetTxPayload(tx)); eraseProTxRef(proTx.proTxHash, tx_hash); @@ -1026,10 +1034,12 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx) } auto& proTx = *opt_proTx; - if (mapProTxAddresses.count(proTx.addr)) { - uint256 conflictHash = mapProTxAddresses[proTx.addr]; - if (conflictHash != tx_hash && mapTx.count(conflictHash)) { - removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + for (const CService& entry : proTx.netInfo.GetEntries()) { + if (mapProTxAddresses.count(entry)) { + uint256 conflictHash = mapProTxAddresses[entry]; + if (conflictHash != tx_hash && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } } } removeProTxPubKeyConflicts(tx, proTx.keyIDOwner); @@ -1046,10 +1056,12 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx) return; } - if (mapProTxAddresses.count(opt_proTx->addr)) { - uint256 conflictHash = mapProTxAddresses[opt_proTx->addr]; - if (conflictHash != tx_hash && mapTx.count(conflictHash)) { - removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + for (const CService& entry : opt_proTx->netInfo.GetEntries()) { + if (mapProTxAddresses.count(entry)) { + uint256 conflictHash = mapProTxAddresses[entry]; + if (conflictHash != tx_hash && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } } } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { @@ -1382,8 +1394,14 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const { return true; // i.e. can't decode payload == conflict } auto& proTx = *opt_proTx; - if (mapProTxAddresses.count(proTx.addr) || mapProTxPubKeyIDs.count(proTx.keyIDOwner) || mapProTxBlsPubKeyHashes.count(proTx.pubKeyOperator.GetHash())) + for (const CService& entry : proTx.netInfo.GetEntries()) { + if (mapProTxAddresses.count(entry)) { + return true; + } + } + if (mapProTxPubKeyIDs.count(proTx.keyIDOwner) || mapProTxBlsPubKeyHashes.count(proTx.pubKeyOperator.GetHash())) { return true; + } if (!proTx.collateralOutpoint.hash.IsNull()) { if (mapProTxCollaterals.count(proTx.collateralOutpoint)) { // there is another ProRegTx that refers to the same collateral @@ -1401,8 +1419,12 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const { LogPrint(BCLog::MEMPOOL, "%s: ERROR: Invalid transaction payload, tx: %s\n", __func__, tx_hash.ToString()); return true; // i.e. can't decode payload == conflict } - auto it = mapProTxAddresses.find(opt_proTx->addr); - return it != mapProTxAddresses.end() && it->second != opt_proTx->proTxHash; + for (const CService& entry : opt_proTx->netInfo.GetEntries()) { + auto it = mapProTxAddresses.find(entry); + if (it != mapProTxAddresses.end() && it->second != opt_proTx->proTxHash) { + return true; + } + } } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) { const auto opt_proTx = GetTxPayload(tx); if (!opt_proTx) {