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 8270 : 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 8270 : uint256 h1;
29 16540 : uint256 h2;
30 8270 : if (proTxHash1 < proTxHash2) {
31 4280 : h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
32 4280 : h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
33 : } else {
34 3990 : h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
35 3990 : h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
36 : }
37 8270 : if (h1 < h2) {
38 3990 : return proTxHash1;
39 : }
40 4280 : return proTxHash2;
41 : }
42 :
43 2005218 : std::set<uint256> GetQuorumRelayMembers(const std::vector<CDeterministicMNCPtr>& mnList,
44 : unsigned int forMemberIndex)
45 : {
46 2005218 : assert(forMemberIndex < mnList.size());
47 :
48 : // Special case
49 2005218 : 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 4010436 : std::set<uint256> r;
57 2005218 : int gap = 1;
58 2005218 : int gap_max = (int)mnList.size() - 1;
59 2005218 : int k = 0;
60 21323554 : while ((gap_max >>= 1) || k <= 1) {
61 19318336 : size_t idx = (forMemberIndex + gap) % mnList.size();
62 19318336 : r.emplace(mnList[idx]->proTxHash);
63 19318336 : gap <<= 1;
64 19318336 : k++;
65 : }
66 4010426 : return r;
67 : }
68 :
69 4135 : static std::set<uint256> GetQuorumConnections(const std::vector<CDeterministicMNCPtr>& mns, const uint256& forMember, bool onlyOutbound)
70 : {
71 4135 : std::set<uint256> result;
72 16540 : for (auto& dmn : mns) {
73 12405 : if (dmn->proTxHash == forMember) {
74 4135 : 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 8270 : uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
80 8270 : if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
81 12550 : result.emplace(dmn->proTxHash);
82 : }
83 : }
84 4135 : 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 4896 : void EnsureLatestQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexNew, const uint256& myProTxHash, std::vector<CQuorumCPtr>& lastQuorums)
111 : {
112 4896 : const auto& params = Params().GetConsensus().llmqs.at(llmqType);
113 4896 : auto connman = g_connman->GetTierTwoConnMan();
114 :
115 4896 : auto connmanQuorumsToDelete = connman->getQuorumNodes(llmqType);
116 :
117 : // don't remove connections for the currently in-progress DKG round
118 4896 : int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % params.dkgInterval);
119 4896 : auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash();
120 4896 : connmanQuorumsToDelete.erase(curDkgBlock);
121 :
122 12847 : for (auto& quorum : lastQuorums) {
123 7951 : if (!quorum->IsMember(myProTxHash)) {
124 3899 : continue;
125 : }
126 :
127 4052 : EnsureQuorumConnections(llmqType, quorum->pindexQuorum, myProTxHash);
128 :
129 4052 : connmanQuorumsToDelete.erase(quorum->pindexQuorum->GetBlockHash());
130 : }
131 :
132 4935 : for (auto& qh : connmanQuorumsToDelete) {
133 39 : LogPrintf("CQuorumManager::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, qh.ToString());
134 39 : connman->removeQuorumNodes(llmqType, qh);
135 : }
136 4896 : }
137 :
138 : // ensure connection to a given quorum
139 4216 : void EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum, const uint256& myProTxHash)
140 : {
141 4216 : const auto& members = deterministicMNManager->GetAllQuorumMembers(llmqType, pindexQuorum);
142 12668 : auto itMember = std::find_if(members.begin(), members.end(), [&](const CDeterministicMNCPtr& dmn) { return dmn->proTxHash == myProTxHash; });
143 4216 : bool isMember = itMember != members.end();
144 :
145 4216 : if (!isMember) { // && !CLLMQUtils::IsWatchQuorumsEnabled()) {
146 81 : return;
147 : }
148 :
149 8270 : std::set<uint256> connections;
150 4135 : std::set<uint256> relayMembers;
151 4135 : if (isMember) {
152 8270 : connections = GetQuorumConnections(members, myProTxHash, true);
153 4135 : unsigned int memberIndex = itMember - members.begin();
154 8270 : 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 4135 : if (!connections.empty()) {
163 2861 : auto connman = g_connman->GetTierTwoConnMan();
164 2924 : if (!connman->hasQuorumNodes(llmqType, pindexQuorum->GetBlockHash()) && LogAcceptCategory(BCLog::LLMQ)) {
165 63 : auto mnList = deterministicMNManager->GetListAtChainTip();
166 126 : std::string debugMsg = strprintf("CLLMQUtils::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, pindexQuorum->GetBlockHash().ToString());
167 150 : for (auto& c : connections) {
168 174 : auto dmn = mnList.GetValidMN(c);
169 87 : if (!dmn) {
170 0 : debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString());
171 : } else {
172 348 : debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToString());
173 : }
174 : }
175 63 : LogPrint(BCLog::LLMQ, debugMsg.c_str()); /* Continued */
176 : }
177 2861 : connman->setQuorumNodes(llmqType, pindexQuorum->GetBlockHash(), connections);
178 : }
179 4135 : if (!relayMembers.empty()) {
180 4135 : auto connman = g_connman->GetTierTwoConnMan();
181 4135 : connman->setMasternodeQuorumRelayMembers(llmqType, pindexQuorum->GetBlockHash(), relayMembers);
182 : }
183 : }
184 :
185 83 : void AddQuorumProbeConnections(Consensus::LLMQType llmqType, const CBlockIndex *pindexQuorum, const uint256 &myProTxHash)
186 : {
187 83 : auto members = deterministicMNManager->GetAllQuorumMembers(llmqType, pindexQuorum);
188 83 : auto curTime = GetAdjustedTime();
189 :
190 166 : std::set<uint256> probeConnections;
191 332 : for (auto& dmn : members) {
192 249 : if (dmn->proTxHash == myProTxHash) {
193 83 : continue;
194 : }
195 166 : 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 166 : if (curTime - lastOutbound > 50 * 60) {
199 341 : probeConnections.emplace(dmn->proTxHash);
200 : }
201 : }
202 :
203 83 : if (!probeConnections.empty()) {
204 62 : if (LogAcceptCategory(BCLog::LLMQ)) {
205 62 : auto mnList = deterministicMNManager->GetListAtChainTip();
206 124 : std::string debugMsg = strprintf("CLLMQUtils::%s -- adding masternodes probes for quorum %s:\n", __func__, pindexQuorum->GetBlockHash().ToString());
207 154 : for (auto& c : probeConnections) {
208 184 : auto dmn = mnList.GetValidMN(c);
209 92 : if (!dmn) {
210 0 : debugMsg += strprintf(" %s (not in valid MN set anymore)\n", c.ToString());
211 : } else {
212 368 : debugMsg += strprintf(" %s (%s)\n", c.ToString(), dmn->pdmnState->addr.ToString());
213 : }
214 : }
215 62 : LogPrint(BCLog::LLMQ, debugMsg.c_str()); /* Continued */
216 : }
217 62 : g_connman->GetTierTwoConnMan()->addPendingProbeConnections(probeConnections);
218 : }
219 83 : }
220 :
221 : } // namespace llmq
|