Line data Source code
1 : // Copyright (c) 2018-2019 The Dash Core developers
2 : // Copyright (c) 2022 The PIVX Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "llmq/quorums_connections.h"
7 :
8 : #include "evo/deterministicmns.h"
9 : #include "llmq/quorums.h"
10 : #include "net.h"
11 : #include "tiertwo/masternode_meta_manager.h" // for g_mmetaman
12 : #include "tiertwo/net_masternodes.h"
13 : #include "validation.h"
14 :
15 : #include <vector>
16 :
17 : namespace llmq
18 : {
19 :
20 :
21 6280 : uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2)
22 : {
23 : // We need to deterministically select who is going to initiate the connection. The naive way would be to simply
24 : // return the min(proTxHash1, proTxHash2), but this would create a bias towards MNs with a numerically low
25 : // hash. To fix this, we return the proTxHash that has the lowest value of:
26 : // hash(min(proTxHash1, proTxHash2), max(proTxHash1, proTxHash2), proTxHashX)
27 : // where proTxHashX is the proTxHash to compare
28 6280 : uint256 h1;
29 12560 : uint256 h2;
30 6280 : if (proTxHash1 < proTxHash2) {
31 3164 : h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
32 3164 : h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
33 : } else {
34 3116 : h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
35 3116 : h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
36 : }
37 6280 : if (h1 < h2) {
38 3163 : return proTxHash1;
39 : }
40 3117 : return proTxHash2;
41 : }
42 :
43 2004219 : std::set<uint256> GetQuorumRelayMembers(const std::vector<CDeterministicMNCPtr>& mnList,
44 : unsigned int forMemberIndex)
45 : {
46 2004219 : assert(forMemberIndex < mnList.size());
47 :
48 : // Special case
49 2004219 : if (mnList.size() == 2) {
50 2 : return {mnList[1 - forMemberIndex]->proTxHash};
51 : }
52 :
53 : // Relay to nodes at indexes (i+2^k)%n, where
54 : // k: 0..max(1, floor(log2(n-1))-1)
55 : // n: size of the quorum/ring
56 4008438 : std::set<uint256> r;
57 2004219 : int gap = 1;
58 2004219 : int gap_max = (int)mnList.size() - 1;
59 2004219 : int k = 0;
60 21320557 : while ((gap_max >>= 1) || k <= 1) {
61 19316338 : size_t idx = (forMemberIndex + gap) % mnList.size();
62 19316338 : r.emplace(mnList[idx]->proTxHash);
63 19316338 : gap <<= 1;
64 19316338 : k++;
65 : }
66 4008428 : return r;
67 : }
68 :
69 3140 : static std::set<uint256> GetQuorumConnections(const std::vector<CDeterministicMNCPtr>& mns, const uint256& forMember, bool onlyOutbound)
70 : {
71 3140 : std::set<uint256> result;
72 12560 : for (auto& dmn : mns) {
73 9420 : if (dmn->proTxHash == forMember) {
74 3140 : continue;
75 : }
76 : // Determine which of the two MNs (forMember vs dmn) should initiate the outbound connection and which
77 : // one should wait for the inbound connection. We do this in a deterministic way, so that even when we
78 : // end up with both connecting to each other, we know which one to disconnect
79 6280 : uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
80 6280 : if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
81 9397 : result.emplace(dmn->proTxHash);
82 : }
83 : }
84 3140 : return result;
85 : }
86 :
87 0 : std::set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum, size_t memberCount, size_t connectionCount)
88 : {
89 0 : static uint256 qwatchConnectionSeed;
90 0 : static std::atomic<bool> qwatchConnectionSeedGenerated{false};
91 0 : static RecursiveMutex qwatchConnectionSeedCs;
92 0 : if (!qwatchConnectionSeedGenerated) {
93 0 : LOCK(qwatchConnectionSeedCs);
94 0 : if (!qwatchConnectionSeedGenerated) {
95 0 : qwatchConnectionSeed = GetRandHash();
96 0 : qwatchConnectionSeedGenerated = true;
97 : }
98 : }
99 :
100 0 : std::set<size_t> result;
101 0 : uint256 rnd = qwatchConnectionSeed;
102 0 : for (size_t i = 0; i < connectionCount; i++) {
103 0 : rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(static_cast<uint8_t>(llmqType), pindexQuorum->GetBlockHash())));
104 0 : result.emplace(rnd.GetUint64(0) % memberCount);
105 : }
106 0 : return result;
107 : }
108 :
109 : // ensure connection to a given list of quorums
110 4296 : void EnsureLatestQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexNew, const uint256& myProTxHash, std::vector<CQuorumCPtr>& lastQuorums)
111 : {
112 4296 : const auto& params = Params().GetConsensus().llmqs.at(llmqType);
113 4296 : auto connman = g_connman->GetTierTwoConnMan();
114 :
115 4296 : auto connmanQuorumsToDelete = connman->getQuorumNodes(llmqType);
116 :
117 : // don't remove connections for the currently in-progress DKG round
118 4296 : int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % params.dkgInterval);
119 4296 : auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash();
120 4296 : connmanQuorumsToDelete.erase(curDkgBlock);
121 :
122 10447 : for (auto& quorum : lastQuorums) {
123 6151 : if (!quorum->IsMember(myProTxHash)) {
124 3090 : continue;
125 : }
126 :
127 3061 : EnsureQuorumConnections(llmqType, quorum->pindexQuorum, myProTxHash);
128 :
129 3061 : connmanQuorumsToDelete.erase(quorum->pindexQuorum->GetBlockHash());
130 : }
131 :
132 4334 : for (auto& qh : connmanQuorumsToDelete) {
133 38 : LogPrintf("CQuorumManager::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, qh.ToString());
134 38 : connman->removeQuorumNodes(llmqType, qh);
135 : }
136 4296 : }
137 :
138 : // ensure connection to a given quorum
139 3219 : void EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum, const uint256& myProTxHash)
140 : {
141 3219 : const auto& members = deterministicMNManager->GetAllQuorumMembers(llmqType, pindexQuorum);
142 9682 : auto itMember = std::find_if(members.begin(), members.end(), [&](const CDeterministicMNCPtr& dmn) { return dmn->proTxHash == myProTxHash; });
143 3219 : bool isMember = itMember != members.end();
144 :
145 3219 : if (!isMember) { // && !CLLMQUtils::IsWatchQuorumsEnabled()) {
146 79 : return;
147 : }
148 :
149 6280 : std::set<uint256> connections;
150 3140 : std::set<uint256> relayMembers;
151 3140 : if (isMember) {
152 6280 : connections = GetQuorumConnections(members, myProTxHash, true);
153 3140 : unsigned int memberIndex = itMember - members.begin();
154 6280 : relayMembers = GetQuorumRelayMembers(members, memberIndex);
155 : } else {
156 : auto cindexes = CalcDeterministicWatchConnections(llmqType, pindexQuorum, members.size(), 1);
157 : for (auto idx : cindexes) {
158 : connections.emplace(members[idx]->proTxHash);
159 : }
160 : relayMembers = connections;
161 : }
162 3140 : if (!connections.empty()) {
163 2683 : auto connman = g_connman->GetTierTwoConnMan();
164 2743 : if (!connman->hasQuorumNodes(llmqType, pindexQuorum->GetBlockHash()) && LogAcceptCategory(BCLog::LLMQ)) {
165 60 : auto mnList = deterministicMNManager->GetListAtChainTip();
166 120 : std::string debugMsg = strprintf("CLLMQUtils::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, pindexQuorum->GetBlockHash().ToString());
167 138 : for (auto& c : connections) {
168 156 : auto dmn = mnList.GetValidMN(c);
169 78 : if (!dmn) {
170 0 : debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString());
171 : } else {
172 312 : debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToString());
173 : }
174 : }
175 60 : LogPrint(BCLog::LLMQ, debugMsg.c_str()); /* Continued */
176 : }
177 2683 : connman->setQuorumNodes(llmqType, pindexQuorum->GetBlockHash(), connections);
178 : }
179 3140 : if (!relayMembers.empty()) {
180 3140 : auto connman = g_connman->GetTierTwoConnMan();
181 3140 : connman->setMasternodeQuorumRelayMembers(llmqType, pindexQuorum->GetBlockHash(), relayMembers);
182 : }
183 : }
184 :
185 79 : void AddQuorumProbeConnections(Consensus::LLMQType llmqType, const CBlockIndex *pindexQuorum, const uint256 &myProTxHash)
186 : {
187 79 : auto members = deterministicMNManager->GetAllQuorumMembers(llmqType, pindexQuorum);
188 79 : auto curTime = GetAdjustedTime();
189 :
190 158 : std::set<uint256> probeConnections;
191 316 : for (auto& dmn : members) {
192 237 : if (dmn->proTxHash == myProTxHash) {
193 79 : continue;
194 : }
195 158 : auto lastOutbound = g_mmetaman.GetMetaInfo(dmn->proTxHash)->GetLastOutboundSuccess();
196 : // re-probe after 50 minutes so that the "good connection" check in the DKG doesn't fail just because we're on
197 : // the brink of timeout
198 158 : if (curTime - lastOutbound > 50 * 60) {
199 333 : probeConnections.emplace(dmn->proTxHash);
200 : }
201 : }
202 :
203 79 : if (!probeConnections.empty()) {
204 60 : if (LogAcceptCategory(BCLog::LLMQ)) {
205 60 : auto mnList = deterministicMNManager->GetListAtChainTip();
206 120 : std::string debugMsg = strprintf("CLLMQUtils::%s -- adding masternodes probes for quorum %s:\n", __func__, pindexQuorum->GetBlockHash().ToString());
207 156 : for (auto& c : probeConnections) {
208 192 : auto dmn = mnList.GetValidMN(c);
209 96 : if (!dmn) {
210 0 : debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString());
211 : } else {
212 384 : debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToString());
213 : }
214 : }
215 60 : LogPrint(BCLog::LLMQ, debugMsg.c_str()); /* Continued */
216 : }
217 60 : g_connman->GetTierTwoConnMan()->addPendingProbeConnections(probeConnections);
218 : }
219 79 : }
220 :
221 : } // namespace llmq
|