PIVX Core  5.6.99
P2P Digital Currency
quorums_signing.cpp
Go to the documentation of this file.
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"
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 
28 {
29  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  if (db.Exists(std::string("rs_upgraded"))) {
32  return;
33  }
34 
37 
38  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
44 {
45  LogPrintf("CRecoveredSigsDb::%s -- converting invalid rs_t keys\n", __func__);
46 
47  std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
48 
49  auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
50  pcursor->Seek(start);
51 
52  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
53  size_t cnt = 0;
54  while (pcursor->Valid()) {
55  decltype(start) k;
56 
57  if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_t") {
58  break;
59  }
60 
61  batch.Erase(k);
62  std::get<1>(k) = htobe32(std::get<1>(k));
63  batch.Write(k, (uint8_t)1);
64 
65  cnt++;
66 
67  pcursor->Next();
68  }
69  pcursor.reset();
70 
71  db.WriteBatch(batch);
72 
73  LogPrintf("CRecoveredSigsDb::%s -- converted %d invalid rs_t keys\n", __func__, cnt);
74 }
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.
79 {
80  LogPrintf("CRecoveredSigsDb::%s -- adding rs_vt keys with current time\n", __func__);
81 
82  auto curTime = GetAdjustedTime();
83 
84  std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
85 
86  auto start = std::make_tuple(std::string("rs_v"), (uint8_t)0, uint256());
87  pcursor->Seek(start);
88 
89  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
90  size_t cnt = 0;
91  while (pcursor->Valid()) {
92  decltype(start) k;
93 
94  if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_v") {
95  break;
96  }
97 
98  uint8_t llmqType = std::get<1>(k);
99  const uint256& id = std::get<2>(k);
100 
101  auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(curTime), llmqType, id);
102  batch.Write(k2, (uint8_t)1);
103 
104  cnt++;
105 
106  pcursor->Next();
107  }
108  pcursor.reset();
109 
110  db.WriteBatch(batch);
111 
112  LogPrintf("CRecoveredSigsDb::%s -- added %d rs_vt entries\n", __func__, cnt);
113 }
114 
115 bool CRecoveredSigsDb::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
116 {
117  auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id, msgHash);
118  return db.Exists(k);
119 }
120 
122 {
123  auto cacheKey = std::make_pair(llmqType, id);
124  bool ret;
125  {
126  LOCK(cs);
127  if (hasSigForIdCache.get(cacheKey, ret)) {
128  return ret;
129  }
130  }
131 
132 
133  auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
134  ret = db.Exists(k);
135 
136  LOCK(cs);
137  hasSigForIdCache.insert(cacheKey, ret);
138  return ret;
139 }
140 
142 {
143  bool ret;
144  {
145  LOCK(cs);
146  if (hasSigForSessionCache.get(signHash, ret)) {
147  return ret;
148  }
149  }
150 
151  auto k = std::make_tuple(std::string("rs_s"), signHash);
152  ret = db.Exists(k);
153 
154  LOCK(cs);
155  hasSigForSessionCache.insert(signHash, ret);
156  return ret;
157 }
158 
160 {
161  bool ret;
162  {
163  LOCK(cs);
164  if (hasSigForHashCache.get(hash, ret)) {
165  return ret;
166  }
167  }
168 
169  auto k = std::make_tuple(std::string("rs_h"), hash);
170  ret = db.Exists(k);
171 
172  LOCK(cs);
173  hasSigForHashCache.insert(hash, ret);
174  return ret;
175 }
176 
178 {
179  auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
180 
181  CDataStream ds(SER_DISK, CLIENT_VERSION);
182  if (!db.ReadDataStream(k, ds)) {
183  return false;
184  }
185 
186  try {
187  ret.Unserialize(ds);
188  return true;
189  } catch (std::exception&) {
190  return false;
191  }
192 }
193 
195 {
196  auto k1 = std::make_tuple(std::string("rs_h"), hash);
197  std::pair<uint8_t, uint256> k2;
198  if (!db.Read(k1, k2)) {
199  return false;
200  }
201 
202  return ReadRecoveredSig((Consensus::LLMQType)k2.first, k2.second, ret);
203 }
204 
206 {
207  return ReadRecoveredSig(llmqType, id, ret);
208 }
209 
211 {
212  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
213 
214  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  auto k1 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id);
219  auto k2 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id, recSig.msgHash);
220  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  batch.Write(k2, curTime);
223 
224  // store by object hash
225  auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash());
226  batch.Write(k3, std::make_pair(recSig.llmqType, recSig.id));
227 
228  // store by signHash
229  auto signHash = llmq::utils::BuildSignHash(recSig);
230  auto k4 = std::make_tuple(std::string("rs_s"), signHash);
231  batch.Write(k4, (uint8_t)1);
232 
233  // store by current time. Allows fast cleanup of old recSigs
234  auto k5 = std::make_tuple(std::string("rs_t"), (uint32_t)htobe32(curTime), recSig.llmqType, recSig.id);
235  batch.Write(k5, (uint8_t)1);
236 
237  db.WriteBatch(batch);
238 
239  {
240  int64_t t = GetTimeMillis();
241 
242  LOCK(cs);
243  hasSigForIdCache.insert(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.id), true);
244  hasSigForSessionCache.insert(signHash, true);
245  hasSigForHashCache.insert(recSig.GetHash(), true);
246  }
247 }
248 
249 void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteHashKey, bool deleteTimeKey)
250 {
252 
253  CRecoveredSig recSig;
254  if (!ReadRecoveredSig(llmqType, id, recSig)) {
255  return;
256  }
257 
258  auto signHash = llmq::utils::BuildSignHash(recSig);
259 
260  auto k1 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id);
261  auto k2 = std::make_tuple(std::string("rs_r"), recSig.llmqType, recSig.id, recSig.msgHash);
262  auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash());
263  auto k4 = std::make_tuple(std::string("rs_s"), signHash);
264  batch.Erase(k1);
265  batch.Erase(k2);
266  if (deleteHashKey) {
267  batch.Erase(k3);
268  }
269  batch.Erase(k4);
270 
271  if (deleteTimeKey) {
272  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  if (db.ReadDataStream(k2, writeTimeDs) && writeTimeDs.size() == sizeof(uint32_t)) {
275  uint32_t writeTime;
276  writeTimeDs >> writeTime;
277  auto k5 = std::make_tuple(std::string("rs_t"), (uint32_t)htobe32(writeTime), recSig.llmqType, recSig.id);
278  batch.Erase(k5);
279  }
280  }
281 
282  hasSigForIdCache.erase(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.id));
283  hasSigForSessionCache.erase(signHash);
284  if (deleteHashKey) {
285  hasSigForHashCache.erase(recSig.GetHash());
286  }
287 }
288 
289 // Completely remove any traces of the recovered sig
291 {
292  LOCK(cs);
293  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
294  RemoveRecoveredSig(batch, llmqType, id, true, true);
295  db.WriteBatch(batch);
296 }
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
301 {
302  LOCK(cs);
303  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
304  RemoveRecoveredSig(batch, llmqType, id, false, false);
305  db.WriteBatch(batch);
306 }
307 
309 {
310  std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
311 
312  auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
313  uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
314  pcursor->Seek(start);
315 
316  std::vector<std::pair<Consensus::LLMQType, uint256>> toDelete;
317  std::vector<decltype(start)> toDelete2;
318 
319  while (pcursor->Valid()) {
320  decltype(start) k;
321 
322  if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_t") {
323  break;
324  }
325  if (be32toh(std::get<1>(k)) >= endTime) {
326  break;
327  }
328 
329  toDelete.emplace_back((Consensus::LLMQType)std::get<2>(k), std::get<3>(k));
330  toDelete2.emplace_back(k);
331 
332  pcursor->Next();
333  }
334  pcursor.reset();
335 
336  if (toDelete.empty()) {
337  return;
338  }
339 
340  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
341  {
342  LOCK(cs);
343  for (auto& e : toDelete) {
344  RemoveRecoveredSig(batch, e.first, e.second, true, false);
345 
346  if (batch.SizeEstimate() >= (1 << 24)) {
347  db.WriteBatch(batch);
348  batch.Clear();
349  }
350  }
351  }
352 
353  for (auto& e : toDelete2) {
354  batch.Erase(e);
355  }
356 
357  db.WriteBatch(batch);
358 
359  LogPrint(BCLog::LLMQ, "CRecoveredSigsDb::%d -- deleted %d entries\n", __func__, toDelete.size());
360 }
361 
363 {
364  auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
365  return db.Exists(k);
366 }
367 
369 {
370  auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
371  return db.Read(k, msgHashRet);
372 }
373 
374 void CRecoveredSigsDb::WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
375 {
376  auto k1 = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
377  auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(GetAdjustedTime()), (uint8_t)llmqType, id);
378 
379  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
380  batch.Write(k1, msgHash);
381  batch.Write(k2, (uint8_t)1);
382 
383  db.WriteBatch(batch);
384 }
385 
387 {
388  std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
389 
390  auto start = std::make_tuple(std::string("rs_vt"), (uint32_t)0, (uint8_t)0, uint256());
391  uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
392  pcursor->Seek(start);
393 
394  CDBBatch batch(CLIENT_VERSION | ADDRV2_FORMAT);
395  size_t cnt = 0;
396  while (pcursor->Valid()) {
397  decltype(start) k;
398 
399  if (!pcursor->GetKey(k) || std::get<0>(k) != "rs_vt") {
400  break;
401  }
402  if (be32toh(std::get<1>(k)) >= endTime) {
403  break;
404  }
405 
406  uint8_t llmqType = std::get<2>(k);
407  const uint256& id = std::get<3>(k);
408 
409  batch.Erase(k);
410  batch.Erase(std::make_tuple(std::string("rs_v"), llmqType, id));
411 
412  cnt++;
413 
414  pcursor->Next();
415  }
416  pcursor.reset();
417 
418  if (cnt == 0) {
419  return;
420  }
421 
422  db.WriteBatch(batch);
423 
424  LogPrint(BCLog::LLMQ, "CRecoveredSigsDb::%d -- deleted %d entries\n", __func__, cnt);
425 }
426 
428 
430 {
431 }
432 
434 {
435  if (inv.type != MSG_QUORUM_RECOVERED_SIG) {
436  return false;
437  }
438  return db.HasRecoveredSigForHash(inv.hash);
439 }
440 
442 {
443  if (!db.GetRecoveredSigByHash(hash, ret)) {
444  return false;
445  }
447  // we don't want to propagate sigs from inactive quorums
448  return false;
449  }
450  return true;
451 }
452 
453 void CSigningManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
454 {
455  if (strCommand == NetMsgType::QSIGREC) {
456  CRecoveredSig recoveredSig;
457  vRecv >> recoveredSig;
458  ProcessMessageRecoveredSig(pfrom, recoveredSig, connman);
459  }
460 }
461 
462 void CSigningManager::ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman)
463 {
464  bool ban = false;
465  if (!PreVerifyRecoveredSig(pfrom->GetId(), recoveredSig, ban)) {
466  if (ban) {
467  LOCK(cs_main);
468  Misbehaving(pfrom->GetId(), 100);
469  }
470  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  if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
476  return;
477  }
478 
479  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  LOCK(cs);
483  pendingRecoveredSigs[pfrom->GetId()].emplace_back(recoveredSig);
484 }
485 
486 bool CSigningManager::PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan)
487 {
488  retBan = false;
489 
490  auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
491  if (!Params().GetConsensus().llmqs.count(llmqType)) {
492  retBan = true;
493  return false;
494  }
495 
496  CQuorumCPtr quorum = quorumManager->GetQuorum(llmqType, recoveredSig.quorumHash);
497 
498  if (!quorum) {
499  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
500  recoveredSig.quorumHash.ToString(), nodeId);
501  return false;
502  }
503  if (!llmq::utils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
504  return false;
505  }
506 
507  return true;
508 }
509 
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  {
516  LOCK(cs);
517  if (pendingRecoveredSigs.empty()) {
518  return;
519  }
520 
521  std::unordered_set<std::pair<NodeId, uint256>, StaticSaltedHasher> uniqueSignHashes;
522  llmq::utils::IterateNodesRandom(pendingRecoveredSigs, [&]() { return uniqueSignHashes.size() < maxUniqueSessions; }, [&](NodeId nodeId, std::list<CRecoveredSig>& ns) {
523  if (ns.empty()) {
524  return false;
525  }
526  auto& recSig = *ns.begin();
527 
528  bool alreadyHave = db.HasRecoveredSigForHash(recSig.GetHash());
529  if (!alreadyHave) {
530  uniqueSignHashes.emplace(nodeId, llmq::utils::BuildSignHash(recSig));
531  retSigShares[nodeId].emplace_back(recSig);
532  }
533  ns.erase(ns.begin());
534  return !ns.empty(); }, rnd);
535 
536  if (retSigShares.empty()) {
537  return;
538  }
539  }
540 
541  for (auto& p : retSigShares) {
542  NodeId nodeId = p.first;
543  auto& v = p.second;
544 
545  for (auto it = v.begin(); it != v.end();) {
546  auto& recSig = *it;
547 
548  Consensus::LLMQType llmqType = (Consensus::LLMQType)recSig.llmqType;
549  auto quorumKey = std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash);
550  if (!retQuorums.count(quorumKey)) {
551  CQuorumCPtr quorum = quorumManager->GetQuorum(llmqType, recSig.quorumHash);
552  if (!quorum) {
553  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not found, node=%d\n", __func__,
554  recSig.quorumHash.ToString(), nodeId);
555  it = v.erase(it);
556  continue;
557  }
558  if (!llmq::utils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
559  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- quorum %s not active anymore, node=%d\n", __func__,
560  recSig.quorumHash.ToString(), nodeId);
561  it = v.erase(it);
562  continue;
563  }
564 
565  retQuorums.emplace(quorumKey, quorum);
566  }
567 
568  ++it;
569  }
570  }
571 }
572 
574 {
575  std::unordered_map<NodeId, std::list<CRecoveredSig>> recSigsByNode;
576  std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher> quorums;
577 
578  CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums);
579  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  CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, false);
586 
587  size_t verifyCount = 0;
588  for (auto& p : recSigsByNode) {
589  NodeId nodeId = p.first;
590  auto& v = p.second;
591 
592  for (auto& recSig : v) {
593  // we didn't verify the lazy signature until now
594  if (!recSig.sig.Get().IsValid()) {
595  batchVerifier.badSources.emplace(nodeId);
596  break;
597  }
598 
599  const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
600  batchVerifier.PushMessage(nodeId, recSig.GetHash(), llmq::utils::BuildSignHash(recSig), recSig.sig.Get(), quorum->qc.quorumPublicKey);
601  verifyCount++;
602  }
603  }
604 
605  cxxtimer::Timer verifyTimer(true);
606  batchVerifier.Verify();
607  verifyTimer.stop();
608 
609  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- verified recovered sig(s). count=%d, vt=%d, nodes=%d\n", __func__, verifyCount, verifyTimer.count(), recSigsByNode.size());
610 
611  std::unordered_set<uint256, StaticSaltedHasher> processed;
612  for (auto& p : recSigsByNode) {
613  NodeId nodeId = p.first;
614  auto& v = p.second;
615 
616  if (batchVerifier.badSources.count(nodeId)) {
617  LOCK(cs_main);
618  LogPrintf("CSigningManager::%s -- invalid recSig from other node, banning peer=%d\n", __func__, nodeId);
619  Misbehaving(nodeId, 100);
620  continue;
621  }
622 
623  for (auto& recSig : v) {
624  if (!processed.emplace(recSig.GetHash()).second) {
625  continue;
626  }
627 
628  const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
629  ProcessRecoveredSig(nodeId, recSig, quorum, connman);
630  }
631  }
632 
633  return true;
634 }
635 
636 // signature must be verified already
637 void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman)
638 {
639  auto llmqType = (Consensus::LLMQType)recoveredSig.llmqType;
640 
641  {
642  LOCK(cs_main);
643  connman.RemoveAskFor(recoveredSig.GetHash(), MSG_QUORUM_RECOVERED_SIG);
644  }
645 
646  if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
647  return;
648  }
649 
650  std::vector<CRecoveredSigsListener*> listeners;
651  {
652  LOCK(cs);
653  listeners = recoveredSigsListeners;
654 
655  auto signHash = llmq::utils::BuildSignHash(recoveredSig);
656 
657  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  if (db.HasRecoveredSigForId(llmqType, recoveredSig.id)) {
661  CRecoveredSig otherRecoveredSig;
662  if (db.GetRecoveredSigById(llmqType, recoveredSig.id, otherRecoveredSig)) {
663  auto otherSignHash = llmq::utils::BuildSignHash(recoveredSig);
664  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  LogPrintf("CSigningManager::%s -- conflicting recoveredSig for signHash=%s, id=%s, msgHash=%s, otherSignHash=%s\n", __func__,
668  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  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  db.WriteRecoveredSig(recoveredSig);
683  }
684 
685  CInv inv(MSG_QUORUM_RECOVERED_SIG, recoveredSig.GetHash());
686  g_connman->ForEachNode([&](CNode* pnode) {
687  if (pnode->nVersion >= LLMQS_PROTO_VERSION && pnode->m_wants_recsigs && pnode->CanRelay()) {
688  pnode->PushInventory(inv);
689  }
690  });
691  for (auto& l : listeners) {
692  l->HandleNewRecoveredSig(recoveredSig);
693  }
694 }
695 
696 void CSigningManager::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
697 {
698  db.TruncateRecoveredSig(llmqType, id);
699 }
700 
701 void CSigningManager::Cleanup()
702 {
703  int64_t now = GetTimeMillis();
704  if (now - lastCleanupTime < 5000) {
705  return;
706  }
707 
708  int64_t maxAge = DEFAULT_MAX_RECOVERED_SIGS_AGE;
709 
710  db.CleanupOldRecoveredSigs(maxAge);
711  db.CleanupOldVotes(maxAge);
712 
713  lastCleanupTime = GetTimeMillis();
714 }
715 
716 void CSigningManager::RegisterRecoveredSigsListener(CRecoveredSigsListener* l)
717 {
718  LOCK(cs);
719  recoveredSigsListeners.emplace_back(l);
720 }
721 
722 void CSigningManager::UnregisterRecoveredSigsListener(CRecoveredSigsListener* l)
723 {
724  LOCK(cs);
725  auto itRem = std::remove(recoveredSigsListeners.begin(), recoveredSigsListeners.end(), l);
726  recoveredSigsListeners.erase(itRem, recoveredSigsListeners.end());
727 }
728 
729 bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign)
730 {
731  auto& params = Params().GetConsensus().llmqs.at(llmqType);
732 
734  return false;
735  }
736 
737  {
738  LOCK(cs);
739 
740  bool hasVoted = db.HasVotedOnId(llmqType, id);
741  if (hasVoted) {
742  uint256 prevMsgHash;
743  db.GetVoteForId(llmqType, id, prevMsgHash);
744  if (msgHash != prevMsgHash) {
745  LogPrintf("CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting on conflicting msgHash=%s\n", __func__,
746  id.ToString(), prevMsgHash.ToString(), msgHash.ToString());
747  return false;
748  } else if (allowReSign) {
749  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Resigning!\n", __func__,
750  id.ToString(), prevMsgHash.ToString());
751  } else {
752  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting again.\n", __func__,
753  id.ToString(), prevMsgHash.ToString());
754  return false;
755  }
756  }
757 
758  if (db.HasRecoveredSigForId(llmqType, id)) {
759  // no need to sign it if we already have a recovered sig
760  return true;
761  }
762  if (!hasVoted) {
763  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  CQuorumCPtr quorum = SelectQuorumForSigning(llmqType, id);
773  if (!quorum) {
774  LogPrint(BCLog::LLMQ, "CSigningManager::%s -- failed to select quorum. id=%s, msgHash=%s\n", __func__, id.ToString(), msgHash.ToString());
775  return false;
776  }
777 
778  if (!quorum->IsValidMember(activeMasternodeManager->GetProTx())) {
779  return false;
780  }
781 
782  if (allowReSign) {
783  // make us re-announce all known shares (other nodes might have run into a timeout)
784  quorumSigSharesManager->ForceReAnnouncement(quorum, llmqType, id, msgHash);
785  }
786  quorumSigSharesManager->AsyncSign(quorum, id, msgHash);
787 
788  return true;
789 }
790 
791 bool CSigningManager::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
792 {
793  return db.HasRecoveredSig(llmqType, id, msgHash);
794 }
795 
796 bool CSigningManager::HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id)
797 {
798  return db.HasRecoveredSigForId(llmqType, id);
799 }
800 
801 bool CSigningManager::HasRecoveredSigForSession(const uint256& signHash)
802 {
803  return db.HasRecoveredSigForSession(signHash);
804 }
805 
806 bool CSigningManager::IsConflicting(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
807 {
808  if (!db.HasRecoveredSigForId(llmqType, id)) {
809  // no recovered sig present, so no conflict
810  return false;
811  }
812 
813  if (!db.HasRecoveredSig(llmqType, id, msgHash)) {
814  // recovered sig is present, but not for the given msgHash. That's a conflict!
815  return true;
816  }
817 
818  // all good
819  return false;
820 }
821 
822 bool CSigningManager::HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id)
823 {
824  return db.HasVotedOnId(llmqType, id);
825 }
826 
827 bool CSigningManager::GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet)
828 {
829  return db.GetVoteForId(llmqType, id, msgHashRet);
830 }
831 
832 CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType, const uint256& selectionHash, int signHeight, int signOffset)
833 {
834  auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);
835  size_t poolSize = (size_t)llmqParams.signingActiveQuorumCount;
836 
837  CBlockIndex* pindexStart;
838  {
839  LOCK(cs_main);
840  if (signHeight == -1) {
841  signHeight = chainActive.Height();
842  }
843  int startBlockHeight = signHeight - signOffset;
844  if (startBlockHeight > chainActive.Height()) {
845  return {};
846  }
847  pindexStart = chainActive[startBlockHeight];
848  }
849 
850  auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize);
851  if (quorums.empty()) {
852  return nullptr;
853  }
854 
855  std::vector<std::pair<uint256, size_t>> scores;
856  scores.reserve(quorums.size());
857  for (size_t i = 0; i < quorums.size(); i++) {
858  CHashWriter h(SER_NETWORK, 0);
859  h << (uint8_t)llmqType;
860  h << quorums[i]->qc.quorumHash;
861  h << selectionHash;
862  scores.emplace_back(h.GetHash(), i);
863  }
864  std::sort(scores.begin(), scores.end());
865  return quorums[scores.front().second];
866 }
867 
868 bool CSigningManager::VerifyRecoveredSig(Consensus::LLMQType llmqType, int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig)
869 {
870  auto quorum = SelectQuorumForSigning(llmqType, id, signedAtHeight);
871  if (!quorum) {
872  return false;
873  }
874 
875  uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, msgHash);
876  return sig.VerifyInsecure(quorum->qc.quorumPublicKey, signHash);
877 }
878 
879 } // namespace llmq
CActiveDeterministicMasternodeManager * activeMasternodeManager
const CChainParams & Params()
Return the currently selected parameters.
std::set< SourceId > badSources
void PushMessage(const SourceId &sourceId, const MessageId &msgId, const uint256 &msgHash, const CBLSSignature &sig, const CBLSPublicKey &pubKey)
bool VerifyInsecure(const CBLSPublicKey &pubKey, const uint256 &hash) const
static const std::string TESTNET
size_type size() const
Definition: streams.h:165
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:139
int Height() const
Return the maximal height in the chain.
Definition: chain.h:450
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:72
Definition: net.h:145
void RemoveAskFor(const uint256 &invHash, int invType)
Definition: net.cpp:2579
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:46
void Erase(const K &key)
Definition: dbwrapper.h:100
size_t SizeEstimate() const
Definition: dbwrapper.h:122
void Write(const K &key, const V &value)
Definition: dbwrapper.h:69
void Clear()
Definition: dbwrapper.h:62
CDBIterator * NewIterator()
Definition: dbwrapper.h:338
bool WriteBatch(CDBBatch &batch, bool fSync=false)
Definition: dbwrapper.cpp:94
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:260
bool ReadDataStream(const K &key, CDataStream &ssValue) const
Definition: dbwrapper.h:234
bool Write(const K &key, const V &value, bool fSync=false)
Definition: dbwrapper.h:284
bool Exists(const K &key) const
Definition: dbwrapper.h:292
A writer stream (for serialization) that computes a 256-bit hash.
Definition: hash.h:216
inv message data
Definition: protocol.h:466
int type
Definition: protocol.h:479
uint256 hash
Definition: protocol.h:480
Information about a peer.
Definition: net.h:669
std::atomic< int > nVersion
Definition: net.h:699
NodeId GetId() const
Definition: net.h:825
std::atomic< bool > m_wants_recsigs
Definition: net.h:738
bool CanRelay() const
Definition: net.h:946
std::string ToString() const
Definition: uint256.cpp:65
This class works as a stopwatch.
Definition: cxxtimer.h:37
void stop()
Stop/pause the timer.
Definition: cxxtimer.h:151
duration_t::rep count() const
Return the elapsed time.
Definition: cxxtimer.h:169
const uint256 & GetHash() const
void CleanupOldVotes(int64_t maxAge)
bool HasRecoveredSigForHash(const uint256 &hash)
bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256 &id, const uint256 &msgHash)
CRecoveredSigsDb(CDBWrapper &_db)
unordered_lru_cache< uint256, bool, StaticSaltedHasher, 30000 > hasSigForHashCache
bool GetVoteForId(Consensus::LLMQType llmqType, const uint256 &id, uint256 &msgHashRet)
unordered_lru_cache< std::pair< Consensus::LLMQType, uint256 >, bool, StaticSaltedHasher, 30000 > hasSigForIdCache
void TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256 &id)
bool ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256 &id, CRecoveredSig &ret)
void RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256 &id)
void WriteVoteForId(Consensus::LLMQType llmqType, const uint256 &id, const uint256 &msgHash)
unordered_lru_cache< uint256, bool, StaticSaltedHasher, 30000 > hasSigForSessionCache
bool HasRecoveredSigForSession(const uint256 &signHash)
void CleanupOldRecoveredSigs(int64_t maxAge)
bool GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256 &id, CRecoveredSig &ret)
void WriteRecoveredSig(const CRecoveredSig &recSig)
bool GetRecoveredSigByHash(const uint256 &hash, CRecoveredSig &ret)
bool HasVotedOnId(Consensus::LLMQType llmqType, const uint256 &id)
bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256 &id)
void ProcessMessage(CNode *pnode, const std::string &strCommand, CDataStream &vRecv, CConnman &connman)
FastRandomContext rnd
void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig &recoveredSig, const CQuorumCPtr &quorum, CConnman &connman)
bool AlreadyHave(const CInv &inv)
bool PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig &recoveredSig, bool &retBan)
bool GetRecoveredSigForGetData(const uint256 &hash, CRecoveredSig &ret)
bool ProcessPendingRecoveredSigs(CConnman &connman)
void ProcessMessageRecoveredSig(CNode *pfrom, const CRecoveredSig &recoveredSig, CConnman &connman)
std::unordered_map< NodeId, std::list< CRecoveredSig > > pendingRecoveredSigs
CSigningManager(CDBWrapper &llmqDb, bool fMemory)
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions, std::unordered_map< NodeId, std::list< CRecoveredSig >> &retSigShares, std::unordered_map< std::pair< Consensus::LLMQType, uint256 >, CQuorumCPtr, StaticSaltedHasher > &retQuorums)
CRecoveredSigsDb db
256-bit opaque blob.
Definition: uint256.h:138
bool get(const Key &key, Value &value)
void erase(const Key &key)
void insert(const Key &key, const Value &v)
uint32_t htobe32(uint32_t host_32bits)
Definition: endian.h:184
uint32_t be32toh(uint32_t big_endian_32bits)
Definition: endian.h:198
if(!read_stdin(buffer))
Definition: fuzz.cpp:72
std::unique_ptr< CConnman > g_connman
Definition: init.cpp:90
@ LOCK
Definition: lockunlock.h:16
#define LogPrint(category,...)
Definition: logging.h:163
@ LLMQ
Definition: logging.h:66
LLMQType
Definition: params.h:90
const char * QSIGREC
Definition: protocol.cpp:67
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256 &quorumHash, const uint256 &id, const uint256 &msgHash)
bool IsQuorumActive(Consensus::LLMQType llmqType, const uint256 &quorumHash)
Definition: quorums.cpp:26
std::shared_ptr< const CQuorum > CQuorumCPtr
Definition: quorums.h:72
std::unique_ptr< CQuorumManager > quorumManager
Definition: quorums.cpp:31
std::unique_ptr< CSigningManager > quorumSigningManager
std::unique_ptr< CSigSharesManager > quorumSigSharesManager
CDBWrapper * llmqDb
int NodeId
Definition: net.h:109
void Misbehaving(NodeId pnode, int howmuch, const std::string &message) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Increase a node's misbehavior score.
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:80
@ MSG_QUORUM_RECOVERED_SIG
Definition: protocol.h:459
@ SER_DISK
Definition: serialize.h:175
@ SER_NETWORK
Definition: serialize.h:174
std::map< LLMQType, LLMQParams > llmqs
Definition: params.h:279
#define AssertLockHeld(cs)
Definition: sync.h:75
int64_t GetAdjustedTime()
Definition: timedata.cpp:36
int64_t GetTimeMillis()
Returns the system time (not mockable)
Definition: utiltime.cpp:61
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84