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_H
6 : #define BITCOIN_LLMQ_SIGNING_H
7 :
8 : #include <bls/bls.h>
9 : #include <llmq/params.h>
10 : #include <llmq/types.h>
11 : #include <net_types.h>
12 : #include <random.h>
13 : #include <saltedhasher.h>
14 : #include <sync.h>
15 : #include <unordered_lru_cache.h>
16 :
17 : #include <memory>
18 : #include <string_view>
19 : #include <unordered_map>
20 : #include <variant>
21 :
22 : class CChainState;
23 : class CDataStream;
24 : class CDBBatch;
25 : class CDBWrapper;
26 : class CInv;
27 : class CTransaction;
28 : struct RPCResult;
29 : namespace util {
30 : struct DbWrapperParams;
31 : } // namespace util
32 :
33 : class UniValue;
34 : using CTransactionRef = std::shared_ptr<const CTransaction>;
35 :
36 : namespace llmq {
37 :
38 : using RecoveredSigResult = std::variant<std::monostate, CInv, CTransactionRef>;
39 :
40 : class CQuorumManager;
41 : class CSigSharesManager;
42 : class SignHash;
43 :
44 : // Keep recovered signatures for a week. This is a "-maxrecsigsage" option default.
45 : static constexpr int64_t DEFAULT_MAX_RECOVERED_SIGS_AGE{60 * 60 * 24 * 7};
46 :
47 : class CSigBase
48 : {
49 : protected:
50 0 : Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE};
51 : uint256 quorumHash;
52 : uint256 id;
53 : uint256 msgHash;
54 :
55 0 : CSigBase(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
56 0 : : llmqType(llmqType), quorumHash(quorumHash), id(id), msgHash(msgHash) {};
57 0 : CSigBase() = default;
58 :
59 : public:
60 0 : [[nodiscard]] constexpr Consensus::LLMQType getLlmqType() const { return llmqType; }
61 :
62 0 : [[nodiscard]] constexpr auto getQuorumHash() const -> const uint256& {
63 0 : return quorumHash;
64 : }
65 :
66 0 : [[nodiscard]] constexpr auto getId() const -> const uint256& {
67 0 : return id;
68 : }
69 :
70 0 : [[nodiscard]] constexpr auto getMsgHash() const -> const uint256& {
71 0 : return msgHash;
72 : }
73 :
74 : [[nodiscard]] SignHash buildSignHash() const;
75 : };
76 :
77 : class CRecoveredSig : virtual public CSigBase
78 : {
79 : public:
80 : const CBLSLazySignature sig;
81 :
82 0 : CRecoveredSig() = default;
83 :
84 0 : CRecoveredSig(Consensus::LLMQType _llmqType, const uint256& _quorumHash, const uint256& _id, const uint256& _msgHash, const CBLSLazySignature& _sig) :
85 0 : CSigBase(_llmqType, _quorumHash, _id, _msgHash), sig(_sig) {UpdateHash();};
86 0 : CRecoveredSig(Consensus::LLMQType _llmqType, const uint256& _quorumHash, const uint256& _id, const uint256& _msgHash, const CBLSSignature& _sig) :
87 0 : CSigBase(_llmqType, _quorumHash, _id, _msgHash) {const_cast<CBLSLazySignature&>(sig).Set(_sig, bls::bls_legacy_scheme.load()); UpdateHash();};
88 :
89 : private:
90 : // only in-memory
91 : uint256 hash;
92 :
93 0 : void UpdateHash()
94 : {
95 0 : hash = ::SerializeHash(*this);
96 0 : }
97 :
98 : public:
99 0 : SERIALIZE_METHODS(CRecoveredSig, obj)
100 : {
101 0 : READWRITE(const_cast<Consensus::LLMQType&>(obj.llmqType), const_cast<uint256&>(obj.quorumHash), const_cast<uint256&>(obj.id),
102 : const_cast<uint256&>(obj.msgHash), const_cast<CBLSLazySignature&>(obj.sig));
103 0 : SER_READ(obj, obj.UpdateHash());
104 0 : }
105 :
106 0 : const uint256& GetHash() const
107 : {
108 0 : assert(!hash.IsNull());
109 0 : return hash;
110 : }
111 :
112 : [[nodiscard]] static RPCResult GetJsonHelp(const std::string& key, bool optional);
113 : [[nodiscard]] UniValue ToJson() const;
114 : };
115 :
116 : class CRecoveredSigsDb
117 : {
118 : private:
119 : std::unique_ptr<CDBWrapper> db{nullptr};
120 :
121 : mutable Mutex cs_cache;
122 : mutable unordered_lru_cache<std::pair<Consensus::LLMQType, uint256>, bool, StaticSaltedHasher, 30000> hasSigForIdCache GUARDED_BY(cs_cache);
123 : mutable Uint256LruHashMap<bool, 30000> hasSigForSessionCache GUARDED_BY(cs_cache);
124 : mutable Uint256LruHashMap<bool, 30000> hasSigForHashCache GUARDED_BY(cs_cache);
125 :
126 : public:
127 : explicit CRecoveredSigsDb(const util::DbWrapperParams& db_params);
128 : ~CRecoveredSigsDb();
129 :
130 : bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash) const;
131 : bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id) const EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
132 : bool HasRecoveredSigForSession(const uint256& signHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
133 : bool HasRecoveredSigForHash(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
134 : bool GetRecoveredSigByHash(const uint256& hash, CRecoveredSig& ret) const;
135 : bool GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret) const;
136 : void WriteRecoveredSig(const CRecoveredSig& recSig) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
137 : void TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
138 :
139 : void CleanupOldRecoveredSigs(int64_t maxAge) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
140 :
141 : // votes are removed when the recovered sig is written to the db
142 : bool HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id) const;
143 : bool GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet) const;
144 : void WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
145 :
146 : void CleanupOldVotes(int64_t maxAge);
147 :
148 : private:
149 : bool ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret) const;
150 : void RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteHashKey,
151 : bool deleteTimeKey) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache);
152 : };
153 :
154 : class CRecoveredSigsListener
155 : {
156 : public:
157 0 : virtual ~CRecoveredSigsListener() = default;
158 :
159 : [[nodiscard]] virtual RecoveredSigResult HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) = 0;
160 : };
161 :
162 : class CSigningManager
163 : {
164 : private:
165 : CRecoveredSigsDb db;
166 : const CQuorumManager& qman;
167 : const int64_t m_max_recsigs_age;
168 :
169 : mutable Mutex cs_pending;
170 : // Incoming and not verified yet
171 : std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>> pendingRecoveredSigs GUARDED_BY(cs_pending);
172 : Uint256HashMap<std::shared_ptr<const CRecoveredSig>> pendingReconstructedRecoveredSigs GUARDED_BY(cs_pending);
173 :
174 : FastRandomContext rnd GUARDED_BY(cs_pending);
175 :
176 : mutable Mutex cs_listeners;
177 : std::vector<CRecoveredSigsListener*> recoveredSigsListeners GUARDED_BY(cs_listeners);
178 :
179 : public:
180 : CSigningManager() = delete;
181 : CSigningManager(const CSigningManager&) = delete;
182 : CSigningManager& operator=(const CSigningManager&) = delete;
183 : explicit CSigningManager(const CQuorumManager& _qman, const util::DbWrapperParams& db_params, int64_t max_recsigs_age);
184 : ~CSigningManager();
185 :
186 : bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
187 : bool GetRecoveredSigForGetData(const uint256& hash, CRecoveredSig& ret) const;
188 :
189 : void VerifyAndProcessRecoveredSig(NodeId from, std::shared_ptr<CRecoveredSig> recovered_sig)
190 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
191 :
192 : // This is called when a recovered signature was was reconstructed from another P2P message and is known to be valid
193 : // This is the case for example when a signature appears as part of InstantSend or ChainLocks
194 : void PushReconstructedRecoveredSig(const std::shared_ptr<const CRecoveredSig>& recoveredSig)
195 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
196 :
197 : // This is called when a recovered signature can be safely removed from the DB. This is only safe when some other
198 : // mechanism prevents possible conflicts. As an example, ChainLocks prevent conflicts in confirmed TXs InstantSend votes
199 : // This won't completely remove all traces of the recovered sig but instead leave the hash and signHash entries in the
200 : // DB. This allows AlreadyHave/late-share filtering to keep returning true. Cleanup will later remove the remains
201 : void TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id);
202 :
203 : // Used by NetSigning:
204 : [[nodiscard]] Uint256HashMap<std::shared_ptr<const CRecoveredSig>> FetchPendingReconstructed()
205 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
206 : [[nodiscard]] bool CollectPendingRecoveredSigsToVerify(
207 : size_t maxUniqueSessions, std::unordered_map<NodeId, std::list<std::shared_ptr<const CRecoveredSig>>>& retSigShares,
208 : std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CBLSPublicKey, StaticSaltedHasher>& ret_pubkeys)
209 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
210 : [[nodiscard]] std::vector<CRecoveredSigsListener*> GetListeners() const EXCLUSIVE_LOCKS_REQUIRED(!cs_listeners);
211 : // Returns true if recovered sigs should be send to listeners
212 : [[nodiscard]] bool ProcessRecoveredSig(const std::shared_ptr<const CRecoveredSig>& recoveredSig)
213 : EXCLUSIVE_LOCKS_REQUIRED(!cs_pending);
214 :
215 : private:
216 : // Used by CSigSharesManager
217 0 : CRecoveredSigsDb& GetDb() { return db; }
218 :
219 : // Needed for access to GetDb() and ProcessRecoveredSig()
220 : friend class CSigSharesManager;
221 :
222 : public:
223 : // public interface
224 : void RegisterRecoveredSigsListener(CRecoveredSigsListener* l) EXCLUSIVE_LOCKS_REQUIRED(!cs_listeners);
225 : void UnregisterRecoveredSigsListener(CRecoveredSigsListener* l) EXCLUSIVE_LOCKS_REQUIRED(!cs_listeners);
226 :
227 : bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash) const;
228 : bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id) const;
229 : bool HasRecoveredSigForSession(const uint256& signHash) const;
230 : bool GetRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& retRecSig) const;
231 : bool IsConflicting(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash) const;
232 :
233 : bool GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet) const;
234 :
235 : public:
236 : void Cleanup();
237 : };
238 :
239 : template<typename NodesContainer, typename Continue, typename Callback>
240 0 : void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&& callback, FastRandomContext& rnd)
241 : {
242 0 : std::vector<typename NodesContainer::iterator> rndNodes;
243 0 : rndNodes.reserve(nodeStates.size());
244 0 : for (auto it = nodeStates.begin(); it != nodeStates.end(); ++it) {
245 0 : rndNodes.emplace_back(it);
246 0 : }
247 0 : if (rndNodes.empty()) {
248 0 : return;
249 : }
250 0 : Shuffle(rndNodes.begin(), rndNodes.end(), rnd);
251 :
252 0 : size_t idx = 0;
253 0 : while (!rndNodes.empty() && cont()) {
254 0 : auto nodeId = rndNodes[idx]->first;
255 0 : auto& ns = rndNodes[idx]->second;
256 :
257 0 : if (callback(nodeId, ns)) {
258 0 : idx = (idx + 1) % rndNodes.size();
259 0 : } else {
260 0 : rndNodes.erase(rndNodes.begin() + idx);
261 0 : if (rndNodes.empty()) {
262 0 : break;
263 : }
264 0 : idx %= rndNodes.size();
265 : }
266 : }
267 0 : }
268 :
269 : bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, const uint256& quorumHash);
270 :
271 : } // namespace llmq
272 :
273 : #endif // BITCOIN_LLMQ_SIGNING_H
|