Line data Source code
1 : // Copyright (c) 2018-2022 The Dash Core developers
2 : // Copyright (c) 2023 The PIVX Core developers
3 : // Distributed under the MIT/X11 software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "quorums_signing.h"
7 : #include "clientversion.h"
8 : #include "netaddress.h"
9 : #include "quorums_signing_shares.h"
10 : #include "quorums_utils.h"
11 :
12 : #include "activemasternode.h"
13 : #include "bls/bls_batchverifier.h"
14 : #include "cxxtimer.h"
15 : #include "net_processing.h"
16 : #include "validation.h"
17 :
18 : #include <algorithm>
19 : #include <limits>
20 : #include <unordered_set>
21 :
22 : namespace llmq
23 : {
24 :
25 : std::unique_ptr<CSigningManager> quorumSigningManager{nullptr};
26 :
27 475 : CRecoveredSigsDb::CRecoveredSigsDb(CDBWrapper& _db) : db(_db)
28 : {
29 950 : if (Params().NetworkIDString() == CBaseChainParams::TESTNET) {
30 : // TODO this can be completely removed after some time (when we're pretty sure the conversion has been run on most testnet MNs)
31 1 : if (db.Exists(std::string("rs_upgraded"))) {
32 : return;
33 : }
34 :
35 1 : ConvertInvalidTimeKeys();
36 1 : AddVoteTimeKeys();
37 :
38 2 : db.Write(std::string("rs_upgraded"), (uint8_t)1);
39 : }
40 : }
41 :
42 : // This converts time values in "rs_t" from host endianness to big endianness, which is required to have proper ordering of the keys
43 1 : void CRecoveredSigsDb::ConvertInvalidTimeKeys()
44 : {
45 1 : LogPrintf("CRecoveredSigsDb::%s -- converting invalid rs_t keys\n", __func__);
46 :
47 1 : std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
48 :
49 4 : auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
50 1 : pcursor->Seek(start);
51 :
52 2 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
53 1 : size_t cnt = 0;
54 1 : while (pcursor->Valid()) {
55 0 : decltype(start) k;
56 :
57 0 : if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_t") {
58 : break;
59 : }
60 :
61 0 : batch.Erase(k);
62 0 : std::get<1>(k) = htobe32(std::get<1>(k));
63 0 : batch.Write(k, (uint8_t)1);
64 :
65 0 : cnt++;
66 :
67 0 : pcursor->Next();
68 : }
69 1 : pcursor.reset();
70 :
71 1 : db.WriteBatch(batch);
72 :
73 1 : LogPrintf("CRecoveredSigsDb::%s -- converted %d invalid rs_t keys\n", __func__, cnt);
74 1 : }
75 :
76 : // This adds rs_vt keys for every rs_v entry to the DB. The time in the key is set to the current time.
77 : // This causes cleanup of all these votes a week later.
78 1 : void CRecoveredSigsDb::AddVoteTimeKeys()
79 : {
80 1 : LogPrintf("CRecoveredSigsDb::%s -- adding rs_vt keys with current time\n", __func__);
81 :
82 1 : auto curTime = GetAdjustedTime();
83 :
84 1 : std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
85 :
86 4 : auto start = std::make_tuple(std::string("rs_v"), (uint8_t)0, uint256());
87 1 : pcursor->Seek(start);
88 :
89 2 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
90 1 : size_t cnt = 0;
91 1 : while (pcursor->Valid()) {
92 0 : decltype(start) k;
93 :
94 0 : if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_v") {
95 : break;
96 : }
97 :
98 0 : uint8_t llmqType = std::get<1>(k);
99 0 : const uint256& id = std::get<2>(k);
100 :
101 0 : auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(curTime), llmqType, id);
102 0 : batch.Write(k2, (uint8_t)1);
103 :
104 0 : cnt++;
105 :
106 0 : pcursor->Next();
107 : }
108 1 : pcursor.reset();
109 :
110 1 : db.WriteBatch(batch);
111 :
112 1 : LogPrintf("CRecoveredSigsDb::%s -- added %d rs_vt entries\n", __func__, cnt);
113 1 : }
114 :
115 12 : bool CRecoveredSigsDb::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
116 : {
117 24 : auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id, msgHash);
118 12 : return db.Exists(k);
119 : }
120 :
121 8738 : bool CRecoveredSigsDb::HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id)
122 : {
123 8738 : auto cacheKey = std::make_pair(llmqType, id);
124 8738 : bool ret;
125 8738 : {
126 8738 : LOCK(cs);
127 8738 : if (hasSigForIdCache.get(cacheKey, ret)) {
128 10596 : return ret;
129 : }
130 : }
131 :
132 :
133 15618 : auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
134 3440 : ret = db.Exists(k);
135 :
136 6880 : LOCK(cs);
137 3440 : hasSigForIdCache.insert(cacheKey, ret);
138 3440 : return ret;
139 : }
140 :
141 733 : bool CRecoveredSigsDb::HasRecoveredSigForSession(const uint256& signHash)
142 : {
143 733 : bool ret;
144 733 : {
145 733 : LOCK(cs);
146 733 : if (hasSigForSessionCache.get(signHash, ret)) {
147 1166 : return ret;
148 : }
149 : }
150 :
151 1033 : auto k = std::make_tuple(std::string("rs_s"), signHash);
152 150 : ret = db.Exists(k);
153 :
154 300 : LOCK(cs);
155 150 : hasSigForSessionCache.insert(signHash, ret);
156 150 : return ret;
157 : }
158 :
159 13701 : bool CRecoveredSigsDb::HasRecoveredSigForHash(const uint256& hash)
160 : {
161 13701 : bool ret;
162 13701 : {
163 13701 : LOCK(cs);
164 13701 : if (hasSigForHashCache.get(hash, ret)) {
165 23304 : return ret;
166 : }
167 : }
168 :
169 17799 : auto k = std::make_tuple(std::string("rs_h"), hash);
170 2049 : ret = db.Exists(k);
171 :
172 2049 : LOCK(cs);
173 2049 : hasSigForHashCache.insert(hash, ret);
174 2049 : return ret;
175 : }
176 :
177 1664 : bool CRecoveredSigsDb::ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
178 : {
179 4992 : auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
180 :
181 3328 : CDataStream ds(SER_DISK, CLIENT_VERSION);
182 1664 : if (!db.ReadDataStream(k, ds)) {
183 : return false;
184 : }
185 :
186 1664 : try {
187 1664 : ret.Unserialize(ds);
188 : return true;
189 0 : } catch (std::exception&) {
190 0 : return false;
191 : }
192 : }
193 :
194 1664 : bool CRecoveredSigsDb::GetRecoveredSigByHash(const uint256& hash, CRecoveredSig& ret)
195 : {
196 4992 : auto k1 = std::make_tuple(std::string("rs_h"), hash);
197 1664 : std::pair<uint8_t, uint256> k2;
198 1664 : if (!db.Read(k1, k2)) {
199 : return false;
200 : }
201 :
202 1664 : return ReadRecoveredSig((Consensus::LLMQType)k2.first, k2.second, ret);
203 : }
204 :
205 0 : bool CRecoveredSigsDb::GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
206 : {
207 0 : return ReadRecoveredSig(llmqType, id, ret);
208 : }
209 :
210 2049 : void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig)
211 : {
212 2049 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
213 :
214 2049 : uint32_t curTime = GetAdjustedTime();
215 :
216 : // we put these close to each other to leverage leveldb's key compaction
217 : // this way, the second key can be used for fast HasRecoveredSig checks while the first key stores the recSig
218 6147 : auto k1 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id);
219 6147 : auto k2 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id, recSig.msgHash);
220 2049 : batch.Write(k1, recSig);
221 : // this key is also used to store the current time, so that we can easily get to the "rs_t" key when we have the id
222 2049 : batch.Write(k2, curTime);
223 :
224 : // store by object hash
225 6147 : auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash());
226 2049 : batch.Write(k3, std::make_pair(recSig.llmqType, recSig.id));
227 :
228 : // store by signHash
229 2049 : auto signHash = llmq::utils::BuildSignHash(recSig);
230 6147 : auto k4 = std::make_tuple(std::string("rs_s"), signHash);
231 2049 : batch.Write(k4, (uint8_t)1);
232 :
233 : // store by current time. Allows fast cleanup of old recSigs
234 6147 : auto k5 = std::make_tuple(std::string("rs_t"), (uint32_t)htobe32(curTime), recSig.llmqType, recSig.id);
235 2049 : batch.Write(k5, (uint8_t)1);
236 :
237 2049 : db.WriteBatch(batch);
238 :
239 2049 : {
240 2049 : int64_t t = GetTimeMillis();
241 :
242 4098 : LOCK(cs);
243 2049 : hasSigForIdCache.insert(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.id), true);
244 2049 : hasSigForSessionCache.insert(signHash, true);
245 4098 : hasSigForHashCache.insert(recSig.GetHash(), true);
246 : }
247 2049 : }
248 :
249 0 : void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteHashKey, bool deleteTimeKey)
250 : {
251 0 : AssertLockHeld(cs);
252 :
253 0 : CRecoveredSig recSig;
254 0 : if (!ReadRecoveredSig(llmqType, id, recSig)) {
255 0 : return;
256 : }
257 :
258 0 : auto signHash = llmq::utils::BuildSignHash(recSig);
259 :
260 0 : auto k1 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id);
261 0 : auto k2 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id, recSig.msgHash);
262 0 : auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash());
263 0 : auto k4 = std::make_tuple(std::string("rs_s"), signHash);
264 0 : batch.Erase(k1);
265 0 : batch.Erase(k2);
266 0 : if (deleteHashKey) {
267 0 : batch.Erase(k3);
268 : }
269 0 : batch.Erase(k4);
270 :
271 0 : if (deleteTimeKey) {
272 0 : CDataStream writeTimeDs(SER_DISK, CLIENT_VERSION);
273 : // TODO remove the size() == sizeof(uint32_t) in a future version (when we stop supporting upgrades from < 0.14.1)
274 0 : if (db.ReadDataStream(k2, writeTimeDs) && writeTimeDs.size() == sizeof(uint32_t)) {
275 0 : uint32_t writeTime;
276 0 : writeTimeDs >> writeTime;
277 0 : auto k5 = std::make_tuple(std::string("rs_t"), (uint32_t)htobe32(writeTime), recSig.llmqType, recSig.id);
278 0 : batch.Erase(k5);
279 : }
280 : }
281 :
282 0 : hasSigForIdCache.erase(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.id));
283 0 : hasSigForSessionCache.erase(signHash);
284 0 : if (deleteHashKey) {
285 0 : hasSigForHashCache.erase(recSig.GetHash());
286 : }
287 : }
288 :
289 : // Completely remove any traces of the recovered sig
290 0 : void CRecoveredSigsDb::RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
291 : {
292 0 : LOCK(cs);
293 0 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
294 0 : RemoveRecoveredSig(batch, llmqType, id, true, true);
295 0 : db.WriteBatch(batch);
296 0 : }
297 :
298 : // Remove the recovered sig itself and all keys required to get from id -> recSig
299 : // This will leave the byHash key in-place so that HasRecoveredSigForHash still returns true
300 0 : void CRecoveredSigsDb::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
301 : {
302 0 : LOCK(cs);
303 0 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
304 0 : RemoveRecoveredSig(batch, llmqType, id, false, false);
305 0 : db.WriteBatch(batch);
306 0 : }
307 :
308 3400 : void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
309 : {
310 3400 : std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
311 :
312 10200 : auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
313 3400 : uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
314 3400 : pcursor->Seek(start);
315 :
316 3400 : std::vector<std::pair<Consensus::LLMQType, uint256>> toDelete;
317 3400 : std::vector<decltype(start)> toDelete2;
318 :
319 3400 : while (pcursor->Valid()) {
320 415 : decltype(start) k;
321 :
322 415 : if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_t") {
323 : break;
324 : }
325 221 : if (be32toh(std::get<1>(k)) >= endTime) {
326 : break;
327 : }
328 :
329 0 : toDelete.emplace_back((Consensus::LLMQType)std::get<2>(k), std::get<3>(k));
330 0 : toDelete2.emplace_back(k);
331 :
332 0 : pcursor->Next();
333 : }
334 3400 : pcursor.reset();
335 :
336 3400 : if (toDelete.empty()) {
337 3400 : return;
338 : }
339 :
340 0 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
341 0 : {
342 0 : LOCK(cs);
343 0 : for (auto& e : toDelete) {
344 0 : RemoveRecoveredSig(batch, e.first, e.second, true, false);
345 :
346 0 : if (batch.SizeEstimate() >= (1 << 24)) {
347 0 : db.WriteBatch(batch);
348 0 : batch.Clear();
349 : }
350 : }
351 : }
352 :
353 0 : for (auto& e : toDelete2) {
354 0 : batch.Erase(e);
355 : }
356 :
357 0 : db.WriteBatch(batch);
358 :
359 0 : LogPrint(BCLog::LLMQ, "CRecoveredSigsDb::%d -- deleted %d entries\n", __func__, toDelete.size());
360 : }
361 :
362 3278 : bool CRecoveredSigsDb::HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id)
363 : {
364 6556 : auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
365 3278 : return db.Exists(k);
366 : }
367 :
368 0 : bool CRecoveredSigsDb::GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet)
369 : {
370 0 : auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
371 0 : return db.Read(k, msgHashRet);
372 : }
373 :
374 3278 : void CRecoveredSigsDb::WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
375 : {
376 6556 : auto k1 = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
377 9834 : auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(GetAdjustedTime()), (uint8_t)llmqType, id);
378 :
379 6556 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
380 3278 : batch.Write(k1, msgHash);
381 3278 : batch.Write(k2, (uint8_t)1);
382 :
383 3278 : db.WriteBatch(batch);
384 3278 : }
385 :
386 3400 : void CRecoveredSigsDb::CleanupOldVotes(int64_t maxAge)
387 : {
388 3400 : std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
389 :
390 10200 : auto start = std::make_tuple(std::string("rs_vt"), (uint32_t)0, (uint8_t)0, uint256());
391 3400 : uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
392 3400 : pcursor->Seek(start);
393 :
394 3400 : CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
395 3400 : size_t cnt = 0;
396 3400 : while (pcursor->Valid()) {
397 415 : decltype(start) k;
398 :
399 415 : if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_vt") {
400 : break;
401 : }
402 370 : if (be32toh(std::get<1>(k)) >= endTime) {
403 : break;
404 : }
405 :
406 0 : uint8_t llmqType = std::get<2>(k);
407 0 : const uint256& id = std::get<3>(k);
408 :
409 0 : batch.Erase(k);
410 0 : batch.Erase(std::make_tuple(std::string("rs_v"), llmqType, id));
411 :
412 0 : cnt++;
413 :
414 0 : pcursor->Next();
415 : }
416 3400 : pcursor.reset();
417 :
418 3400 : if (cnt == 0) {
419 3400 : return;
420 : }
421 :
422 0 : db.WriteBatch(batch);
423 :
424 0 : LogPrint(BCLog::LLMQ, "CRecoveredSigsDb::%d -- deleted %d entries\n", __func__, cnt);
425 : }
426 :
427 : //////////////////
428 :
429 475 : CSigningManager::CSigningManager(CDBWrapper& llmqDb, bool fMemory) : db(llmqDb)
430 : {
431 475 : }
432 :
433 8324 : bool CSigningManager::AlreadyHave(const CInv& inv)
434 : {
435 8324 : if (inv.type != MSG_QUORUM_RECOVERED_SIG) {
436 : return false;
437 : }
438 8324 : return db.HasRecoveredSigForHash(inv.hash);
439 : }
440 :
441 1664 : bool CSigningManager::GetRecoveredSigForGetData(const uint256& hash, CRecoveredSig& ret)
442 : {
443 1664 : if (!db.GetRecoveredSigByHash(hash, ret)) {
444 : return false;
445 : }
446 1664 : if (!llmq::utils::IsQuorumActive((Consensus::LLMQType)(ret.llmqType), ret.quorumHash)) {
447 : // we don't want to propagate sigs from inactive quorums
448 0 : return false;
449 : }
450 : return true;
451 : }
452 :
453 1664 : void CSigningManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
454 : {
455 1664 : if (strCommand == NetMsgType::QSIGREC) {
456 3328 : CRecoveredSig recoveredSig;
457 1664 : vRecv >> recoveredSig;
458 1664 : ProcessMessageRecoveredSig(pfrom, recoveredSig, connman);
459 : }
460 1664 : }
461 :
462 1664 : void CSigningManager::ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman)
463 : {
464 1664 : bool ban = false;
465 1664 : if (!PreVerifyRecoveredSig(pfrom->GetId(), recoveredSig, ban)) {
466 0 : if (ban) {
467 0 : LOCK(cs_main);
468 0 : Misbehaving(pfrom->GetId(), 100);
469 : }
470 0 : return;
471 : }
472 :
473 : // It's important to only skip seen *valid* sig shares here. See comment for CBatchedSigShare
474 : // We don't receive recovered sigs in batches, but we do batched verification per node on these
475 1664 : if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
476 : return;
477 : }
478 :
479 6656 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- signHash=%s, id=%s, msgHash=%s, node=%d\n", __func__,
480 : llmq::utils::BuildSignHash(recoveredSig).ToString(), recoveredSig.id.ToString(), recoveredSig.msgHash.ToString(), pfrom->GetId());
481 :
482 3328 : LOCK(cs);
483 3328 : pendingRecoveredSigs[pfrom->GetId()].emplace_back(recoveredSig);
484 : }
485 :
486 1664 : bool CSigningManager::PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan)
487 : {
488 1664 : retBan = false;
489 :
490 1664 : auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
491 1664 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
492 0 : retBan = true;
493 0 : return false;
494 : }
495 :
496 3328 : CQuorumCPtr quorum = quorumManager->GetQuorum(llmqType, recoveredSig.quorumHash);
497 :
498 1664 : if (!quorum) {
499 0 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
500 : recoveredSig.quorumHash.ToString(), nodeId);
501 0 : return false;
502 : }
503 1664 : if (!llmq::utils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
504 0 : return false;
505 : }
506 :
507 : return true;
508 : }
509 :
510 155386 : void CSigningManager::CollectPendingRecoveredSigsToVerify(
511 : size_t maxUniqueSessions,
512 : std::unordered_map<NodeId, std::list<CRecoveredSig>>& retSigShares,
513 : std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher>& retQuorums)
514 : {
515 155386 : {
516 155386 : LOCK(cs);
517 155386 : if (pendingRecoveredSigs.empty()) {
518 154567 : return;
519 : }
520 :
521 13210 : std::unordered_set<std::pair<NodeId, uint256>, StaticSaltedHasher> uniqueSignHashes;
522 34924 : llmq::utils::IterateNodesRandom(pendingRecoveredSigs, [&]() { return uniqueSignHashes.size() < maxUniqueSessions; }, [&](NodeId nodeId, std::list<CRecoveredSig>& ns) {
523 34924 : if (ns.empty()) {
524 : return false;
525 : }
526 1664 : auto& recSig = *ns.begin();
527 :
528 1664 : bool alreadyHave = db.HasRecoveredSigForHash(recSig.GetHash());
529 1664 : if (!alreadyHave) {
530 1664 : uniqueSignHashes.emplace(nodeId, llmq::utils::BuildSignHash(recSig));
531 1664 : retSigShares[nodeId].emplace_back(recSig);
532 : }
533 1664 : ns.erase(ns.begin());
534 14055 : return !ns.empty(); }, rnd);
535 :
536 12391 : if (retSigShares.empty()) {
537 166139 : return;
538 : }
539 : }
540 :
541 1859 : for (auto& p : retSigShares) {
542 1040 : NodeId nodeId = p.first;
543 1040 : auto& v = p.second;
544 :
545 2704 : for (auto it = v.begin(); it != v.end();) {
546 1664 : auto& recSig = *it;
547 :
548 1664 : Consensus::LLMQType llmqType = (Consensus::LLMQType)recSig.llmqType;
549 1664 : auto quorumKey = std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash);
550 1664 : if (!retQuorums.count(quorumKey)) {
551 2050 : CQuorumCPtr quorum = quorumManager->GetQuorum(llmqType, recSig.quorumHash);
552 1025 : if (!quorum) {
553 0 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
554 : recSig.quorumHash.ToString(), nodeId);
555 0 : it = v.erase(it);
556 0 : continue;
557 : }
558 1025 : if (!llmq::utils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
559 0 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not active anymore, node=%d\n", __func__,
560 : recSig.quorumHash.ToString(), nodeId);
561 0 : it = v.erase(it);
562 0 : continue;
563 : }
564 :
565 1025 : retQuorums.emplace(quorumKey, quorum);
566 : }
567 :
568 1664 : ++it;
569 : }
570 : }
571 : }
572 :
573 155386 : bool CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman)
574 : {
575 310772 : std::unordered_map<NodeId, std::list<CRecoveredSig>> recSigsByNode;
576 155386 : std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher> quorums;
577 :
578 155386 : CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums);
579 155386 : if (recSigsByNode.empty()) {
580 : return false;
581 : }
582 :
583 : // It's ok to perform insecure batched verification here as we verify against the quorum public keys, which are not
584 : // craftable by individual entities, making the rogue public key attack impossible
585 819 : CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, false);
586 :
587 819 : size_t verifyCount = 0;
588 1859 : for (auto& p : recSigsByNode) {
589 1040 : NodeId nodeId = p.first;
590 1040 : auto& v = p.second;
591 :
592 2704 : for (auto& recSig : v) {
593 : // we didn't verify the lazy signature until now
594 1664 : if (!recSig.sig.Get().IsValid()) {
595 1040 : batchVerifier.badSources.emplace(nodeId);
596 : break;
597 : }
598 :
599 1664 : const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
600 1664 : batchVerifier.PushMessage(nodeId, recSig.GetHash(), llmq::utils::BuildSignHash(recSig), recSig.sig.Get(), quorum->qc.quorumPublicKey);
601 1664 : verifyCount++;
602 : }
603 : }
604 :
605 1638 : cxxtimer::Timer verifyTimer(true);
606 819 : batchVerifier.Verify();
607 819 : verifyTimer.stop();
608 :
609 819 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- verified recovered sig(s). count=%d, vt=%d, nodes=%d\n", __func__, verifyCount, verifyTimer.count(), recSigsByNode.size());
610 :
611 1638 : std::unordered_set<uint256, StaticSaltedHasher> processed;
612 1859 : for (auto& p : recSigsByNode) {
613 1040 : NodeId nodeId = p.first;
614 1040 : auto& v = p.second;
615 :
616 1040 : if (batchVerifier.badSources.count(nodeId)) {
617 0 : LOCK(cs_main);
618 0 : LogPrintf("CSigningManager::%s -- invalid recSig from other node, banning peer=%d\n", __func__, nodeId);
619 0 : Misbehaving(nodeId, 100);
620 0 : continue;
621 : }
622 :
623 2704 : for (auto& recSig : v) {
624 1664 : if (!processed.emplace(recSig.GetHash()).second) {
625 0 : continue;
626 : }
627 :
628 1664 : const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
629 1664 : ProcessRecoveredSig(nodeId, recSig, quorum, connman);
630 : }
631 : }
632 :
633 819 : return true;
634 : }
635 :
636 : // signature must be verified already
637 2049 : void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman)
638 : {
639 2049 : auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
640 :
641 2049 : {
642 2049 : LOCK(cs_main);
643 2049 : connman.RemoveAskFor(recoveredSig.GetHash(), MSG_QUORUM_RECOVERED_SIG);
644 : }
645 :
646 2049 : if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
647 0 : return;
648 : }
649 :
650 4098 : std::vector<CRecoveredSigsListener*> listeners;
651 2049 : {
652 2049 : LOCK(cs);
653 2049 : listeners = recoveredSigsListeners;
654 :
655 2049 : auto signHash = llmq::utils::BuildSignHash(recoveredSig);
656 :
657 8196 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- valid recSig. signHash=%s, id=%s, msgHash=%s, node=%d\n", __func__,
658 : signHash.ToString(), recoveredSig.id.ToString(), recoveredSig.msgHash.ToString(), nodeId);
659 :
660 2049 : if (db.HasRecoveredSigForId(llmqType, recoveredSig.id)) {
661 0 : CRecoveredSig otherRecoveredSig;
662 0 : if (db.GetRecoveredSigById(llmqType, recoveredSig.id, otherRecoveredSig)) {
663 0 : auto otherSignHash = llmq::utils::BuildSignHash(recoveredSig);
664 0 : if (signHash != otherSignHash) {
665 : // this should really not happen, as each masternode is participating in only one vote,
666 : // even if it's a member of multiple quorums. so a majority is only possible on one quorum and one msgHash per id
667 0 : LogPrintf("CSigningManager::%s -- conflicting recoveredSig for signHash=%s, id=%s, msgHash=%s, otherSignHash=%s\n", __func__,
668 0 : signHash.ToString(), recoveredSig.id.ToString(), recoveredSig.msgHash.ToString(), otherSignHash.ToString());
669 : } else {
670 : // Looks like we're trying to process a recSig that is already known. This might happen if the same
671 : // recSig comes in through regular QRECSIG messages and at the same time through some other message
672 : // which allowed to reconstruct a recSig (e.g. IXLOCK). In this case, just bail out.
673 : }
674 0 : return;
675 : } else {
676 : // This case is very unlikely. It can only happen when cleanup caused this specific recSig to vanish
677 : // between the HasRecoveredSigForId and GetRecoveredSigById call. If that happens, treat it as if we
678 : // never had that recSig
679 : }
680 : }
681 :
682 2049 : db.WriteRecoveredSig(recoveredSig);
683 : }
684 :
685 2049 : CInv inv(MSG_QUORUM_RECOVERED_SIG, recoveredSig.GetHash());
686 2049 : g_connman->ForEachNode([&](CNode* pnode) {
687 21669 : if (pnode->nVersion >= LLMQS_PROTO_VERSION && pnode->m_wants_recsigs && pnode->CanRelay()) {
688 6658 : pnode->PushInventory(inv);
689 : }
690 21669 : });
691 6147 : for (auto& l : listeners) {
692 4098 : l->HandleNewRecoveredSig(recoveredSig);
693 : }
694 : }
695 :
696 0 : void CSigningManager::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
697 : {
698 0 : db.TruncateRecoveredSig(llmqType, id);
699 0 : }
700 :
701 155386 : void CSigningManager::Cleanup()
702 : {
703 155386 : int64_t now = GetTimeMillis();
704 155386 : if (now - lastCleanupTime < 5000) {
705 : return;
706 : }
707 :
708 3400 : int64_t maxAge = DEFAULT_MAX_RECOVERED_SIGS_AGE;
709 :
710 3400 : db.CleanupOldRecoveredSigs(maxAge);
711 3400 : db.CleanupOldVotes(maxAge);
712 :
713 3400 : lastCleanupTime = GetTimeMillis();
714 : }
715 :
716 694 : void CSigningManager::RegisterRecoveredSigsListener(CRecoveredSigsListener* l)
717 : {
718 694 : LOCK(cs);
719 694 : recoveredSigsListeners.emplace_back(l);
720 694 : }
721 :
722 714 : void CSigningManager::UnregisterRecoveredSigsListener(CRecoveredSigsListener* l)
723 : {
724 714 : LOCK(cs);
725 714 : auto itRem = std::remove(recoveredSigsListeners.begin(), recoveredSigsListeners.end(), l);
726 714 : recoveredSigsListeners.erase(itRem, recoveredSigsListeners.end());
727 714 : }
728 :
729 3282 : bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign)
730 : {
731 3282 : auto& params = Params().GetConsensus().llmqs.at(llmqType);
732 :
733 3282 : if (!activeMasternodeManager) {
734 : return false;
735 : }
736 :
737 3278 : {
738 3278 : LOCK(cs);
739 :
740 3278 : bool hasVoted = db.HasVotedOnId(llmqType, id);
741 3278 : if (hasVoted) {
742 0 : uint256 prevMsgHash;
743 0 : db.GetVoteForId(llmqType, id, prevMsgHash);
744 0 : if (msgHash != prevMsgHash) {
745 0 : LogPrintf("CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting on conflicting msgHash=%s\n", __func__,
746 0 : id.ToString(), prevMsgHash.ToString(), msgHash.ToString());
747 0 : return false;
748 0 : } else if (allowReSign) {
749 0 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Resigning!\n", __func__,
750 : id.ToString(), prevMsgHash.ToString());
751 : } else {
752 0 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting again.\n", __func__,
753 : id.ToString(), prevMsgHash.ToString());
754 0 : return false;
755 : }
756 : }
757 :
758 3278 : if (db.HasRecoveredSigForId(llmqType, id)) {
759 : // no need to sign it if we already have a recovered sig
760 : return true;
761 : }
762 3278 : if (!hasVoted) {
763 3278 : db.WriteVoteForId(llmqType, id, msgHash);
764 : }
765 : }
766 :
767 : // This might end up giving different results on different members
768 : // This might happen when we are on the brink of confirming a new quorum
769 : // This gives a slight risk of not getting enough shares to recover a signature
770 : // But at least it shouldn't be possible to get conflicting recovered signatures
771 : // TODO fix this by re-signing when the next block arrives, but only when that block results in a change of the quorum list and no recovered signature has been created in the mean time
772 6560 : CQuorumCPtr quorum = SelectQuorumForSigning(llmqType, id);
773 3278 : if (!quorum) {
774 2823 : LogPrint(BCLog::LLMQ, "CSigningManager::%s -- failed to select quorum. id=%s, msgHash=%s\n", __func__, id.ToString(), msgHash.ToString());
775 941 : return false;
776 : }
777 :
778 2337 : if (!quorum->IsValidMember(activeMasternodeManager->GetProTx())) {
779 : return false;
780 : }
781 :
782 968 : if (allowReSign) {
783 : // make us re-announce all known shares (other nodes might have run into a timeout)
784 0 : quorumSigSharesManager->ForceReAnnouncement(quorum, llmqType, id, msgHash);
785 : }
786 968 : quorumSigSharesManager->AsyncSign(quorum, id, msgHash);
787 :
788 : return true;
789 : }
790 :
791 12 : bool CSigningManager::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
792 : {
793 12 : return db.HasRecoveredSig(llmqType, id, msgHash);
794 : }
795 :
796 3411 : bool CSigningManager::HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id)
797 : {
798 3411 : return db.HasRecoveredSigForId(llmqType, id);
799 : }
800 :
801 733 : bool CSigningManager::HasRecoveredSigForSession(const uint256& signHash)
802 : {
803 733 : return db.HasRecoveredSigForSession(signHash);
804 : }
805 :
806 0 : bool CSigningManager::IsConflicting(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
807 : {
808 0 : if (!db.HasRecoveredSigForId(llmqType, id)) {
809 : // no recovered sig present, so no conflict
810 : return false;
811 : }
812 :
813 0 : if (!db.HasRecoveredSig(llmqType, id, msgHash)) {
814 : // recovered sig is present, but not for the given msgHash. That's a conflict!
815 0 : return true;
816 : }
817 :
818 : // all good
819 : return false;
820 : }
821 :
822 0 : bool CSigningManager::HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id)
823 : {
824 0 : return db.HasVotedOnId(llmqType, id);
825 : }
826 :
827 0 : bool CSigningManager::GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet)
828 : {
829 0 : return db.GetVoteForId(llmqType, id, msgHashRet);
830 : }
831 :
832 4013 : CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, const uint256& selectionHash, int signHeight, int signOffset)
833 : {
834 4013 : auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);
835 4013 : size_t poolSize = (size_t)llmqParams.signingActiveQuorumCount;
836 :
837 4013 : CBlockIndex* pindexStart;
838 4013 : {
839 4013 : LOCK(cs_main);
840 4013 : if (signHeight == -1) {
841 3278 : signHeight = chainActive.Height();
842 : }
843 4013 : int startBlockHeight = signHeight - signOffset;
844 4013 : if (startBlockHeight > chainActive.Height()) {
845 0 : return {};
846 : }
847 8026 : pindexStart = chainActive[startBlockHeight];
848 : }
849 :
850 4013 : auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
851 4013 : if (quorums.empty()) {
852 941 : return nullptr;
853 : }
854 :
855 7085 : std::vector<std::pair<uint256, size_t>> scores;
856 3072 : scores.reserve(quorums.size());
857 8394 : for (size_t i = 0; i < quorums.size(); i++) {
858 5322 : CHashWriter h(SER_NETWORK, 0);
859 5322 : h << (uint8_t)llmqType;
860 5322 : h << quorums[i]->qc.quorumHash;
861 5322 : h << selectionHash;
862 10644 : scores.emplace_back(h.GetHash(), i);
863 : }
864 3072 : std::sort(scores.begin(), scores.end());
865 3072 : return quorums[scores.front().second];
866 : }
867 :
868 735 : bool CSigningManager::VerifyRecoveredSig(Consensus::LLMQType llmqType, int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig)
869 : {
870 1470 : auto quorum = SelectQuorumForSigning(llmqType, id, signedAtHeight);
871 735 : if (!quorum) {
872 : return false;
873 : }
874 :
875 735 : uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, msgHash);
876 735 : return sig.VerifyInsecure(quorum->qc.quorumPublicKey, signHash);
877 : }
878 :
879 : } // namespace llmq
|