Line data Source code
1 : // Copyright (c) 2018-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #ifndef BITCOIN_LLMQ_SIGNING_SHARES_H
6 : #define BITCOIN_LLMQ_SIGNING_SHARES_H
7 :
8 : #include <bls/bls.h>
9 : #include <evo/types.h>
10 : #include <llmq/signhash.h>
11 : #include <llmq/signing.h>
12 : #include <util/std23.h>
13 :
14 : #include <random.h>
15 : #include <saltedhasher.h>
16 : #include <serialize.h>
17 : #include <sync.h>
18 : #include <uint256.h>
19 : #include <util/time.h>
20 :
21 : #include <atomic>
22 : #include <functional>
23 : #include <limits>
24 : #include <memory>
25 : #include <optional>
26 : #include <string>
27 : #include <unordered_map>
28 : #include <utility>
29 : #include <vector>
30 :
31 : class CActiveMasternodeManager;
32 : class ChainstateManager;
33 : class CNode;
34 : class CConnman;
35 : class CSporkManager;
36 :
37 : namespace llmq
38 : {
39 : class CSigningManager;
40 :
41 : // <signHash, quorumMember>
42 : using SigShareKey = std::pair<uint256, uint16_t>;
43 :
44 : constexpr uint32_t UNINITIALIZED_SESSION_ID{std::numeric_limits<uint32_t>::max()};
45 :
46 : class CSigShare : virtual public CSigBase
47 : {
48 : protected:
49 0 : uint16_t quorumMember{std::numeric_limits<uint16_t>::max()};
50 : public:
51 : CBLSLazySignature sigShare;
52 :
53 : SigShareKey key;
54 :
55 0 : [[nodiscard]] auto getQuorumMember() const {
56 0 : return quorumMember;
57 : }
58 :
59 0 : CSigShare(Consensus::LLMQType _llmqType, const uint256& _quorumHash, const uint256& _id, const uint256& _msgHash,
60 : uint16_t _quorumMember, const CBLSLazySignature& _sigShare) :
61 0 : CSigBase(_llmqType, _quorumHash, _id, _msgHash),
62 0 : quorumMember(_quorumMember),
63 0 : sigShare(_sigShare) {};
64 :
65 : // This should only be used for serialization
66 0 : CSigShare() = default;
67 :
68 :
69 : public:
70 : void UpdateKey();
71 0 : const SigShareKey& GetKey() const
72 : {
73 0 : return key;
74 : }
75 0 : const uint256& GetSignHash() const
76 : {
77 0 : assert(!key.first.IsNull());
78 0 : return key.first;
79 : }
80 :
81 0 : SERIALIZE_METHODS(CSigShare, obj)
82 : {
83 0 : READWRITE(obj.llmqType, obj.quorumHash, obj.quorumMember, obj.id, obj.msgHash, obj.sigShare);
84 0 : SER_READ(obj, obj.UpdateKey());
85 0 : }
86 : };
87 :
88 : // Nodes will first announce a signing session with a sessionId to be used in all future P2P messages related to that
89 : // session. We locally keep track of the mapping for each node. We also assign new sessionIds for outgoing sessions
90 : // and send QSIGSESANN messages appropriately. All values except the max value for uint32_t are valid as sessionId
91 : class CSigSesAnn : virtual public CSigBase
92 : {
93 : private:
94 0 : uint32_t sessionId{UNINITIALIZED_SESSION_ID};
95 :
96 : public:
97 0 : CSigSesAnn(uint32_t _sessionId, Consensus::LLMQType _llmqType, const uint256& _quorumHash, const uint256& _id,
98 0 : const uint256& _msgHash) : CSigBase(_llmqType, _quorumHash, _id, _msgHash), sessionId(_sessionId) {};
99 : // ONLY FOR SERIALIZATION
100 0 : CSigSesAnn() = default;
101 :
102 :
103 :
104 0 : [[nodiscard]] auto getSessionId() const {
105 0 : return sessionId;
106 : }
107 :
108 0 : SERIALIZE_METHODS(CSigSesAnn, obj)
109 : {
110 0 : READWRITE(VARINT(obj.sessionId), obj.llmqType, obj.quorumHash, obj.id, obj.msgHash);
111 0 : }
112 :
113 : [[nodiscard]] std::string ToString() const;
114 : };
115 :
116 0 : class CSigSharesInv
117 : {
118 : public:
119 0 : uint32_t sessionId{UNINITIALIZED_SESSION_ID};
120 : std::vector<bool> inv;
121 :
122 : public:
123 0 : SERIALIZE_METHODS(CSigSharesInv, obj)
124 : {
125 0 : uint64_t invSize = obj.inv.size();
126 0 : READWRITE(VARINT(obj.sessionId), COMPACTSIZE(invSize));
127 0 : autobitset_t bitset = std::make_pair(obj.inv, (size_t)invSize);
128 0 : READWRITE(AUTOBITSET(bitset));
129 0 : SER_READ(obj, obj.inv = bitset.first);
130 0 : }
131 :
132 : void Init(size_t size);
133 : void Set(uint16_t quorumMember, bool v);
134 : void SetAll(bool v);
135 : void Merge(const CSigSharesInv& inv2);
136 :
137 : [[nodiscard]] size_t CountSet() const;
138 : [[nodiscard]] std::string ToString() const;
139 : };
140 :
141 : // sent through the message QBSIGSHARES as a vector of multiple batches
142 0 : class CBatchedSigShares
143 : {
144 : public:
145 0 : uint32_t sessionId{UNINITIALIZED_SESSION_ID};
146 : std::vector<std::pair<uint16_t, CBLSLazySignature>> sigShares;
147 :
148 : public:
149 0 : SERIALIZE_METHODS(CBatchedSigShares, obj)
150 : {
151 0 : READWRITE(VARINT(obj.sessionId), obj.sigShares);
152 0 : }
153 :
154 : [[nodiscard]] std::string ToInvString() const;
155 : };
156 :
157 : template<typename T>
158 : class SigShareMap
159 : {
160 : private:
161 : Uint256HashMap<std::unordered_map<uint16_t, T>> internalMap;
162 :
163 : public:
164 0 : bool Add(const SigShareKey& k, const T& v)
165 : {
166 0 : auto& m = internalMap[k.first];
167 0 : return m.emplace(k.second, v).second;
168 : }
169 :
170 0 : void Erase(const SigShareKey& k)
171 : {
172 0 : auto it = internalMap.find(k.first);
173 0 : if (it == internalMap.end()) {
174 0 : return;
175 : }
176 0 : it->second.erase(k.second);
177 0 : if (it->second.empty()) {
178 0 : internalMap.erase(it);
179 0 : }
180 0 : }
181 :
182 0 : void Clear()
183 : {
184 0 : internalMap.clear();
185 0 : }
186 :
187 0 : [[nodiscard]] bool Has(const SigShareKey& k) const
188 : {
189 0 : auto it = internalMap.find(k.first);
190 0 : if (it == internalMap.end()) {
191 0 : return false;
192 : }
193 0 : return it->second.count(k.second) != 0;
194 0 : }
195 :
196 0 : T* Get(const SigShareKey& k)
197 : {
198 0 : auto it = internalMap.find(k.first);
199 0 : if (it == internalMap.end()) {
200 0 : return nullptr;
201 : }
202 :
203 0 : auto jt = it->second.find(k.second);
204 0 : if (jt == it->second.end()) {
205 0 : return nullptr;
206 : }
207 :
208 0 : return &jt->second;
209 0 : }
210 :
211 0 : T& GetOrAdd(const SigShareKey& k)
212 : {
213 0 : T* v = Get(k);
214 0 : if (!v) {
215 0 : Add(k, T());
216 0 : v = Get(k);
217 0 : }
218 0 : return *v;
219 : }
220 :
221 0 : const T* GetFirst() const
222 : {
223 0 : if (internalMap.empty()) {
224 0 : return nullptr;
225 : }
226 0 : return &internalMap.begin()->second.begin()->second;
227 0 : }
228 :
229 0 : [[nodiscard]] size_t Size() const
230 : {
231 0 : return std23::ranges::fold_left(internalMap, size_t{0},
232 0 : [](size_t s, const auto& p) { return s + p.second.size(); });
233 : }
234 :
235 0 : [[nodiscard]] size_t CountForSignHash(const uint256& signHash) const
236 : {
237 0 : auto it = internalMap.find(signHash);
238 0 : if (it == internalMap.end()) {
239 0 : return 0;
240 : }
241 0 : return it->second.size();
242 0 : }
243 :
244 0 : [[nodiscard]] bool Empty() const
245 : {
246 0 : return internalMap.empty();
247 : }
248 :
249 0 : const std::unordered_map<uint16_t, T>* GetAllForSignHash(const uint256& signHash) const
250 : {
251 0 : auto it = internalMap.find(signHash);
252 0 : if (it == internalMap.end()) {
253 0 : return nullptr;
254 : }
255 0 : return &it->second;
256 0 : }
257 :
258 0 : void EraseAllForSignHash(const uint256& signHash)
259 : {
260 0 : internalMap.erase(signHash);
261 0 : }
262 :
263 : template<typename F>
264 0 : void EraseIf(F&& f)
265 : {
266 0 : for (auto it = internalMap.begin(); it != internalMap.end(); ) {
267 0 : SigShareKey k;
268 0 : k.first = it->first;
269 0 : for (auto jt = it->second.begin(); jt != it->second.end(); ) {
270 0 : k.second = jt->first;
271 0 : if (f(k, jt->second)) {
272 0 : jt = it->second.erase(jt);
273 0 : } else {
274 0 : ++jt;
275 : }
276 : }
277 0 : if (it->second.empty()) {
278 0 : it = internalMap.erase(it);
279 0 : } else {
280 0 : ++it;
281 : }
282 : }
283 0 : }
284 :
285 : template<typename F>
286 0 : void ForEach(F&& f)
287 : {
288 0 : for (auto& p : internalMap) {
289 0 : SigShareKey k;
290 0 : k.first = p.first;
291 0 : for (auto& p2 : p.second) {
292 0 : k.second = p2.first;
293 0 : f(k, p2.second);
294 : }
295 : }
296 0 : }
297 : };
298 :
299 0 : class CSigSharesNodeState
300 : {
301 : public:
302 : // Used to avoid holding locks too long
303 0 : struct SessionInfo
304 : {
305 0 : Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE};
306 : uint256 quorumHash;
307 : uint256 id;
308 : uint256 msgHash;
309 : llmq::SignHash signHash;
310 :
311 : CQuorumCPtr quorum;
312 : };
313 :
314 0 : struct Session {
315 0 : uint32_t recvSessionId{UNINITIALIZED_SESSION_ID};
316 0 : uint32_t sendSessionId{UNINITIALIZED_SESSION_ID};
317 :
318 : Consensus::LLMQType llmqType;
319 : uint256 quorumHash;
320 : uint256 id;
321 : uint256 msgHash;
322 : llmq::SignHash signHash;
323 :
324 : CQuorumCPtr quorum;
325 :
326 : CSigSharesInv announced;
327 : CSigSharesInv requested;
328 : CSigSharesInv knows;
329 : };
330 : // TODO limit number of sessions per node
331 : Uint256HashMap<Session> sessions;
332 :
333 : std::unordered_map<uint32_t, Session*> sessionByRecvId;
334 :
335 : SigShareMap<CSigShare> pendingIncomingSigShares;
336 : SigShareMap<int64_t> requestedSigShares;
337 :
338 0 : bool banned{false};
339 :
340 : Session& GetOrCreateSessionFromShare(const CSigShare& sigShare);
341 : Session& GetOrCreateSessionFromAnn(const CSigSesAnn& ann);
342 : Session* GetSessionBySignHash(const uint256& signHash);
343 : Session* GetSessionByRecvId(uint32_t sessionId);
344 : bool GetSessionInfoByRecvId(uint32_t sessionId, SessionInfo& retInfo);
345 :
346 : void RemoveSession(const uint256& signHash);
347 : };
348 :
349 0 : class CSignedSession
350 : {
351 : public:
352 : CSigShare sigShare;
353 : CQuorumCPtr quorum;
354 :
355 0 : int64_t nextAttemptTime{0};
356 0 : int attempt{0};
357 : };
358 :
359 : struct PendingSignatureData {
360 : const CQuorumCPtr quorum;
361 : const uint256 id;
362 : const uint256 msgHash;
363 :
364 0 : PendingSignatureData(CQuorumCPtr quorum, const uint256& id, const uint256& msgHash) :
365 0 : quorum(std::move(quorum)),
366 0 : id(id),
367 0 : msgHash(msgHash)
368 0 : {
369 0 : }
370 : };
371 :
372 : class CSigSharesManager : public llmq::CRecoveredSigsListener
373 : {
374 : private:
375 : static constexpr int64_t SESSION_NEW_SHARES_TIMEOUT{60};
376 : static constexpr int64_t SIG_SHARE_REQUEST_TIMEOUT{5};
377 :
378 : public:
379 : // we try to keep total message size below 10k
380 : static constexpr size_t MAX_MSGS_CNT_QSIGSESANN{100};
381 : static constexpr size_t MAX_MSGS_CNT_QSIGSHARES{200};
382 : // 400 is the maximum quorum size, so this is also the maximum number of sigs we need to support
383 : static constexpr size_t MAX_MSGS_TOTAL_BATCHED_SIGS{400};
384 : static constexpr size_t MAX_MSGS_SIG_SHARES{32};
385 :
386 : private:
387 : static constexpr int64_t EXP_SEND_FOR_RECOVERY_TIMEOUT{2000};
388 : static constexpr int64_t MAX_SEND_FOR_RECOVERY_TIMEOUT{10000};
389 :
390 : mutable Mutex cs;
391 :
392 : SigShareMap<CSigShare> sigShares GUARDED_BY(cs);
393 : Uint256HashMap<CSignedSession> signedSessions GUARDED_BY(cs);
394 :
395 : // stores time of last receivedSigShare. Used to detect timeouts
396 : Uint256HashMap<int64_t> timeSeenForSessions GUARDED_BY(cs);
397 :
398 : std::unordered_map<NodeId, CSigSharesNodeState> nodeStates GUARDED_BY(cs);
399 : SigShareMap<std::pair<NodeId, int64_t>> sigSharesRequested GUARDED_BY(cs);
400 : SigShareMap<bool> sigSharesQueuedToAnnounce GUARDED_BY(cs);
401 :
402 : Mutex cs_pendingSigns;
403 : std::vector<PendingSignatureData> pendingSigns GUARDED_BY(cs_pendingSigns);
404 :
405 : FastRandomContext rnd GUARDED_BY(cs);
406 :
407 : CConnman& m_connman;
408 : const ChainstateManager& m_chainman;
409 : CSigningManager& sigman;
410 : const CActiveMasternodeManager& m_mn_activeman;
411 : const CQuorumManager& qman;
412 : const CSporkManager& m_sporkman;
413 :
414 : CleanupThrottler<NodeClock> cleanupThrottler;
415 : std::atomic<uint32_t> recoveredSigsCounter{0};
416 :
417 : public:
418 : CSigSharesManager() = delete;
419 : CSigSharesManager(const CSigSharesManager&) = delete;
420 : CSigSharesManager& operator=(const CSigSharesManager&) = delete;
421 : explicit CSigSharesManager(CConnman& connman, const ChainstateManager& chainman, CSigningManager& _sigman,
422 : const CActiveMasternodeManager& mn_activeman, const CQuorumManager& _qman,
423 : const CSporkManager& sporkman);
424 : ~CSigSharesManager() override;
425 :
426 : void RegisterRecoveryInterface() EXCLUSIVE_LOCKS_REQUIRED(!cs);
427 : void UnregisterRecoveryInterface() EXCLUSIVE_LOCKS_REQUIRED(!cs);
428 :
429 : void AsyncSign(CQuorumCPtr quorum, const uint256& id, const uint256& msgHash)
430 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns, !cs);
431 : std::optional<CSigShare> CreateSigShare(const CQuorum& quorum, const uint256& id, const uint256& msgHash) const
432 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
433 : void ForceReAnnouncement(const CQuorum& quorum, Consensus::LLMQType llmqType, const uint256& id,
434 : const uint256& msgHash) EXCLUSIVE_LOCKS_REQUIRED(!cs);
435 :
436 : [[nodiscard]] RecoveredSigResult HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override
437 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
438 :
439 : static CDeterministicMNCPtr SelectMemberForRecovery(const CQuorum& quorum, const uint256& id, int attempt);
440 :
441 : bool AsyncSignIfMember(Consensus::LLMQType llmqType, CSigningManager& sigman, const uint256& id,
442 : const uint256& msgHash, const uint256& quorumHash = uint256(), bool allowReSign = false,
443 : bool allowDiffMsgHashSigning = false) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns, !cs);
444 : private:
445 : std::optional<CSigShare> CreateSigShareForSingleMember(const CQuorum& quorum, const uint256& id, const uint256& msgHash) const;
446 :
447 : public:
448 : // all of these return false when the currently processed message should be aborted (as each message actually contains multiple messages) and ban node
449 : bool ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSesAnn& ann) EXCLUSIVE_LOCKS_REQUIRED(!cs);
450 : bool ProcessMessageSigShares(const CNode& pfrom, const CSigSharesInv& inv, const std::string& msg_type)
451 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
452 : bool ProcessMessageBatchedSigShares(const CNode& pfrom, const CBatchedSigShares& batchedSigShares)
453 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
454 :
455 : // if ProcessMessageSigShare returns false the node should be banned
456 : bool ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare) EXCLUSIVE_LOCKS_REQUIRED(!cs);
457 :
458 : // CollectPendingSigSharesToVerify returns true if there's more work to do
459 : bool CollectPendingSigSharesToVerify(
460 : size_t maxUniqueSessions, std::unordered_map<NodeId, std::vector<CSigShare>>& retSigShares,
461 : std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher>& retQuorums)
462 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
463 :
464 : std::vector<std::shared_ptr<CRecoveredSig>> ProcessPendingSigShares(
465 : const std::vector<CSigShare>& sigSharesToProcess,
466 : const std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher>& quorums)
467 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
468 :
469 : private:
470 : [[nodiscard]] std::shared_ptr<CRecoveredSig> ProcessSigShare(const CSigShare& sigShare, const CQuorumCPtr& quorum)
471 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
472 : [[nodiscard]] std::shared_ptr<CRecoveredSig> TryRecoverSig(const CQuorum& quorum, const uint256& id,
473 : const uint256& msgHash) EXCLUSIVE_LOCKS_REQUIRED(!cs);
474 :
475 : bool GetSessionInfoByRecvId(NodeId nodeId, uint32_t sessionId, CSigSharesNodeState::SessionInfo& retInfo)
476 : EXCLUSIVE_LOCKS_REQUIRED(!cs);
477 : static CSigShare RebuildSigShare(const CSigSharesNodeState::SessionInfo& session, const std::pair<uint16_t, CBLSLazySignature>& in);
478 :
479 : void RemoveSigSharesForSession(const uint256& signHash) EXCLUSIVE_LOCKS_REQUIRED(cs);
480 :
481 : public:
482 : void RemoveNodesIf(std::function<bool(NodeId)> predicate) EXCLUSIVE_LOCKS_REQUIRED(!cs);
483 : void MarkAsBanned(NodeId nodeId) EXCLUSIVE_LOCKS_REQUIRED(!cs);
484 :
485 : private:
486 : void CollectSigSharesToRequest(std::unordered_map<NodeId, Uint256HashMap<CSigSharesInv>>& sigSharesToRequest)
487 : EXCLUSIVE_LOCKS_REQUIRED(cs);
488 : void CollectSigSharesToSend(std::unordered_map<NodeId, Uint256HashMap<CBatchedSigShares>>& sigSharesToSend)
489 : EXCLUSIVE_LOCKS_REQUIRED(cs);
490 : void CollectSigSharesToSendConcentrated(std::unordered_map<NodeId, std::vector<CSigShare>>& sigSharesToSend, const std::vector<CNode*>& vNodes) EXCLUSIVE_LOCKS_REQUIRED(cs);
491 : void CollectSigSharesToAnnounce(std::unordered_map<NodeId, Uint256HashMap<CSigSharesInv>>& sigSharesToAnnounce)
492 : EXCLUSIVE_LOCKS_REQUIRED(cs);
493 :
494 : public:
495 : void Cleanup() EXCLUSIVE_LOCKS_REQUIRED(!cs);
496 : bool SendMessages() EXCLUSIVE_LOCKS_REQUIRED(!cs);
497 :
498 : // Dispatcher functions
499 : std::vector<PendingSignatureData> DispatchPendingSigns() EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns);
500 : // Worker pool task functions
501 : bool IsAnyPendingProcessing() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
502 : [[nodiscard]] std::shared_ptr<CRecoveredSig> SignAndProcessSingleShare(PendingSignatureData work)
503 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns, !cs);
504 : };
505 : } // namespace llmq
506 :
507 : #endif // BITCOIN_LLMQ_SIGNING_SHARES_H
|