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