diff --git a/configure.ac b/configure.ac index d9bcb165dd..d392381990 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 18) define(_CLIENT_VERSION_MINOR, 0) define(_CLIENT_VERSION_BUILD, 0) -define(_CLIENT_VERSION_RC, 8) +define(_CLIENT_VERSION_RC, 9) define(_CLIENT_VERSION_IS_RELEASE, false) define(_COPYRIGHT_YEAR, 2022) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index d9b94dddc6..535319dd32 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -732,6 +732,8 @@ std::optional> CQuorumBlockProcessor::GetMineableC break; } + if (HasMinedCommitment(llmqParams.type, quorumHash)) continue; + LOCK(minableCommitmentsCs); auto k = std::make_pair(llmqParams.type, quorumHash); diff --git a/src/llmq/dkgsession.cpp b/src/llmq/dkgsession.cpp index 668af32f60..1f53c5864d 100644 --- a/src/llmq/dkgsession.cpp +++ b/src/llmq/dkgsession.cpp @@ -472,10 +472,9 @@ void CDKGSession::VerifyConnectionAndMinProtoVersions() const logger.Batch("%s does not have min proto version %d (has %d)", m->dmn->proTxHash.ToString(), MIN_MASTERNODE_PROTO_VERSION, it->second); } - auto lastOutbound = mmetaman.GetMetaInfo(m->dmn->proTxHash)->GetLastOutboundSuccess(); - if (GetAdjustedTime() - lastOutbound > 60 * 60) { + if (mmetaman.GetMetaInfo(m->dmn->proTxHash)->OutboundFailedTooManyTimes()) { m->badConnection = true; - logger.Batch("%s no outbound connection since %d seconds", m->dmn->proTxHash.ToString(), GetAdjustedTime() - lastOutbound); + logger.Batch("%s failed to connect to it too many times", m->dmn->proTxHash.ToString()); } } } diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index ac91719ecc..d4b2232f21 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -243,7 +243,7 @@ void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitial } for (auto& params : Params().GetConsensus().llmqs) { - EnsureQuorumConnections(params, pindexNew); + CheckQuorumConnections(params, pindexNew); } { @@ -262,7 +262,7 @@ void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitial TriggerQuorumDataRecoveryThreads(pindexNew); } -void CQuorumManager::EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pindexNew) const +void CQuorumManager::CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pindexNew) const { auto lastQuorums = ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections); @@ -289,11 +289,36 @@ void CQuorumManager::EnsureQuorumConnections(const Consensus::LLMQParams& llmqPa LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, curDkgHeight, curDkgBlock.ToString()); } + const auto myProTxHash = WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash); + bool isISType = llmqParams.type == Params().GetConsensus().llmqTypeInstantSend || + llmqParams.type == Params().GetConsensus().llmqTypeDIP0024InstantSend; + + bool watchOtherISQuorums = isISType && !myProTxHash.IsNull() && + ranges::any_of(lastQuorums, [&myProTxHash](const auto& old_quorum){ + return old_quorum->IsMember(myProTxHash); + }); + for (const auto& quorum : lastQuorums) { - if (CLLMQUtils::EnsureQuorumConnections(llmqParams, quorum->m_quorum_base_block_index, WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash))) { + if (CLLMQUtils::EnsureQuorumConnections(llmqParams, quorum->m_quorum_base_block_index, myProTxHash)) { if (connmanQuorumsToDelete.erase(quorum->qc->quorumHash) > 0) { LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); } + } else if (watchOtherISQuorums && !quorum->IsMember(myProTxHash)) { + std::set connections; + const auto& cindexes = CLLMQUtils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1); + for (auto idx : cindexes) { + connections.emplace(quorum->members[idx]->proTxHash); + } + if (!connections.empty()) { + if (!g_connman->HasMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash())) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] adding mn inter-quorum connections for quorum: [%d:%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); + g_connman->SetMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); + g_connman->SetMasternodeQuorumRelayMembers(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); + } + if (connmanQuorumsToDelete.erase(quorum->qc->quorumHash) > 0) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn inter-quorum connections for quorum: [%d:%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); + } + } } } for (const auto& quorumHash : connmanQuorumsToDelete) { diff --git a/src/llmq/quorums.h b/src/llmq/quorums.h index 375b8eb8a9..47871ae0e7 100644 --- a/src/llmq/quorums.h +++ b/src/llmq/quorums.h @@ -224,7 +224,7 @@ public: private: // all private methods here are cs_main-free - void EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex *pindexNew) const; + void CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex *pindexNew) const; CQuorumPtr BuildQuorumFromCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex) const EXCLUSIVE_LOCKS_REQUIRED(quorumsCacheCs); bool BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const; diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index e5539114a6..4976b677de 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -701,13 +701,24 @@ std::set CLLMQUtils::CalcDeterministicWatchConnections(Consensus::LLMQTy bool CLLMQUtils::EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& myProTxHash) { + if (!fMasternodeMode && !CLLMQUtils::IsWatchQuorumsEnabled()) { + return false; + } + auto members = GetAllQuorumMembers(llmqParams.type, pQuorumBaseBlockIndex); + if (members.empty()) { + return false; + } + bool isMember = std::find_if(members.begin(), members.end(), [&](const auto& dmn) { return dmn->proTxHash == myProTxHash; }) != members.end(); if (!isMember && !CLLMQUtils::IsWatchQuorumsEnabled()) { return false; } + LogPrint(BCLog::NET_NETCONN, "CLLMQUtils::%s -- isMember=%d for quorum %s:\n", + __func__, isMember, pQuorumBaseBlockIndex->GetBlockHash().ToString()); + std::set connections; std::set relayMembers; if (isMember) { diff --git a/src/net.cpp b/src/net.cpp index b9d7686a84..819d75cb4e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2478,8 +2478,7 @@ void CConnman::ThreadOpenMasternodeConnections() LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- connection failed for masternode %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->addr.ToString(false)); // Will take a few consequent failed attempts to PoSe-punish a MN. if (mmetaman.GetMetaInfo(connectToDmn->proTxHash)->OutboundFailedTooManyTimes()) { - LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- failed to connect to masternode %s too many times, resetting outbound success time\n", __func__, connectToDmn->proTxHash.ToString()); - mmetaman.GetMetaInfo(connectToDmn->proTxHash)->SetLastOutboundSuccess(0); + LogPrint(BCLog::NET_NETCONN, "CConnman::%s -- failed to connect to masternode %s too many times\n", __func__, connectToDmn->proTxHash.ToString()); } } } @@ -3362,6 +3361,9 @@ size_t CConnman::GetNodeCount(NumConnections flags) if (pnode->fDisconnect) { continue; } + if ((flags & CONNECTIONS_VERIFIED) && pnode->GetVerifiedProRegTxHash().IsNull()) { + continue; + } if (flags & (pnode->fInbound ? CONNECTIONS_IN : CONNECTIONS_OUT)) { nNum++; } diff --git a/src/net.h b/src/net.h index f0cf7870f2..1ca626b351 100644 --- a/src/net.h +++ b/src/net.h @@ -142,6 +142,9 @@ public: CONNECTIONS_IN = (1U << 0), CONNECTIONS_OUT = (1U << 1), CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), + CONNECTIONS_VERIFIED = (1U << 2), + CONNECTIONS_VERIFIED_IN = (CONNECTIONS_VERIFIED | CONNECTIONS_IN), + CONNECTIONS_VERIFIED_OUT = (CONNECTIONS_VERIFIED | CONNECTIONS_OUT), }; enum SocketEventsMode { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 317888e8b2..47bbd5cbf7 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -494,7 +494,12 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) " ],\n" " \"localrelay\" : true|false, (boolean) true if transaction relay is requested from peers\n" " \"timeoffset\" : xxxxx, (numeric) the time offset\n" - " \"connections\" : xxxxx, (numeric) the number of connections\n" + " \"connections\" : xxxxx, (numeric) the number of inbound and outbound connections\n" + " \"inboundconnections\" : xxxxx, (numeric) the number of inbound connections\n" + " \"outboundconnections\" : xxxxx, (numeric) the number of outbound connections\n" + " \"mnconnections\" : xxxxx, (numeric) the number of verified mn connections\n" + " \"inboundmnconnections\" : xxxxx, (numeric) the number of inbound verified mn connections\n" + " \"outboundmnconnections\" : xxxxx, (numeric) the number of outbound verified mn connections\n" " \"networkactive\" : true|false, (boolean) whether p2p networking is enabled\n" " \"socketevents\" : \"xxx/\", (string) the socket events mode, either kqueue, epoll, poll or select\n" " \"networks\" : [ (json array) information per network\n" @@ -542,6 +547,11 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) if (g_connman) { obj.pushKV("networkactive", g_connman->GetNetworkActive()); obj.pushKV("connections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); + obj.pushKV("inboundconnections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_IN)); + obj.pushKV("outboundconnections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_OUT)); + obj.pushKV("mnconnections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_VERIFIED)); + obj.pushKV("inboundmnconnections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_VERIFIED_IN)); + obj.pushKV("outboundmnconnections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_VERIFIED_OUT)); std::string strSocketEvents; switch (g_connman->GetSocketEventsMode()) { case CConnman::SOCKETEVENTS_SELECT: diff --git a/test/functional/feature_llmq_connections.py b/test/functional/feature_llmq_connections.py index 51852b1e11..dfa2a285bd 100755 --- a/test/functional/feature_llmq_connections.py +++ b/test/functional/feature_llmq_connections.py @@ -69,6 +69,20 @@ class LLMQConnections(DashTestFramework): self.check_reconnects(4) + self.log.info("check that inter-quorum masternode conections are added") + added = False + for mn in self.mninfo: + if len(mn.node.quorum("memberof", mn.proTxHash)) > 0: + try: + with mn.node.assert_debug_log(['adding mn inter-quorum connections']): + self.mine_quorum() + added = True + except: + pass # it's ok to not add connections sometimes + if added: + break + assert added # no way we added none + def check_reconnects(self, expected_connection_count): self.log.info("disable and re-enable networking on all masternodes") for mn in self.mninfo: diff --git a/test/functional/feature_llmq_simplepose.py b/test/functional/feature_llmq_simplepose.py index 56c0d67f6f..339019bf97 100755 --- a/test/functional/feature_llmq_simplepose.py +++ b/test/functional/feature_llmq_simplepose.py @@ -66,7 +66,7 @@ class LLMQSimplePoSeTest(DashTestFramework): def isolate_mn(self, mn): mn.node.setnetworkactive(False) wait_until(lambda: mn.node.getconnectioncount() == 0) - return True + return True, True def close_mn_port(self, mn): self.stop_node(mn.node.index) @@ -77,14 +77,14 @@ class LLMQSimplePoSeTest(DashTestFramework): if mn2 is not mn: connect_nodes(mn.node, mn2.node.index) self.reset_probe_timeouts() - return False + return False, False def force_old_mn_proto(self, mn): self.stop_node(mn.node.index) self.start_masternode(mn, ["-pushversion=70216"]) connect_nodes(mn.node, 0) self.reset_probe_timeouts() - return False + return False, True def test_no_banning(self, expected_connections=None): for i in range(3): @@ -98,15 +98,20 @@ class LLMQSimplePoSeTest(DashTestFramework): expected_contributors = len(mninfos_online) for i in range(2): mn = mninfos_valid.pop() - went_offline = invalidate_proc(mn) + went_offline, instant_ban = invalidate_proc(mn) if went_offline: mninfos_online.remove(mn) expected_contributors -= 1 - t = time.time() - while (not self.check_banned(mn)) and (time.time() - t) < 120: - self.reset_probe_timeouts() - self.mine_quorum(expected_connections=expected_connections, expected_members=expected_contributors, expected_contributions=expected_contributors, expected_complaints=expected_contributors-1, expected_commitments=expected_contributors, mninfos_online=mninfos_online, mninfos_valid=mninfos_valid) + # NOTE: Min PoSe penalty is 100 (see CDeterministicMNList::CalcMaxPoSePenalty()), + # so nodes are PoSe-banned in the same DKG they misbehave without being PoSe-punished first. + if not instant_ban: + # it's ok to miss probes/quorum connections up to 5 times + for i in range(5): + self.reset_probe_timeouts() + self.mine_quorum(expected_connections=expected_connections, expected_members=expected_contributors, expected_contributions=expected_contributors, expected_complaints=0, expected_commitments=expected_contributors, mninfos_online=mninfos_online, mninfos_valid=mninfos_valid) + self.reset_probe_timeouts() + self.mine_quorum(expected_connections=expected_connections, expected_members=expected_contributors, expected_contributions=expected_contributors, expected_complaints=expected_contributors-1, expected_commitments=expected_contributors, mninfos_online=mninfos_online, mninfos_valid=mninfos_valid) assert self.check_banned(mn) @@ -144,7 +149,7 @@ class LLMQSimplePoSeTest(DashTestFramework): def reset_probe_timeouts(self): # Make sure all masternodes will reconnect/re-probe - self.bump_mocktime(50 * 60 + 1) + self.bump_mocktime(10 * 60 + 1) # Sleep a couple of seconds to let mn sync tick to happen time.sleep(2)