PIVX Core  5.6.99
P2P Digital Currency
quorums.cpp
Go to the documentation of this file.
1 // Copyright (c) 2018 The Dash Core developers
2 // Copyright (c) 2023 The PIVX 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.h"
7 
8 #include "activemasternode.h"
9 #include "chainparams.h"
10 #include "cxxtimer.h"
11 #include "evo/deterministicmns.h"
13 #include "logging.h"
14 #include "quorums_blockprocessor.h"
15 #include "quorums_commitment.h"
16 #include "quorums_dkgsessionmgr.h"
17 #include "shutdown.h"
19 #include "univalue.h"
20 #include "validation.h"
21 
22 #include <cstddef>
23 #include <iostream>
24 
25 namespace llmq
26 {
27 
28 static const std::string DB_QUORUM_SK_SHARE = "q_Qsk";
29 static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec";
30 
31 std::unique_ptr<CQuorumManager> quorumManager{nullptr};
32 
33 static uint256 MakeQuorumKey(const CQuorum& q)
34 {
35  CHashWriter hw(SER_NETWORK, 0);
36  hw << (uint8_t)q.params.type;
37  hw << q.qc.quorumHash;
38  for (const auto& dmn : q.members) {
39  hw << dmn->proTxHash;
40  }
41  return hw.GetHash();
42 }
43 
45 {
46  // most likely the thread is already done
48  // watch out to not join the thread when we're called from inside the thread, which might happen on shutdown. This
49  // is because on shutdown the thread is the last owner of the shared CQuorum instance and thus the destroyer of it.
50  if (cachePopulatorThread.joinable() && cachePopulatorThread.get_id() != std::this_thread::get_id()) {
51  cachePopulatorThread.join();
52  }
53 }
54 
55 void CQuorum::Init(const CFinalCommitment& _qc, const CBlockIndex* _pindexQuorum, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members)
56 {
57  qc = _qc;
58  pindexQuorum = _pindexQuorum;
59  members = _members;
60  minedBlockHash = _minedBlockHash;
61 }
62 
64 {
65  for (auto& dmn : members) {
66  if (dmn->proTxHash == proTxHash) {
67  return true;
68  }
69  }
70  return false;
71 }
72 
74 {
75  for (size_t i = 0; i < members.size(); i++) {
76  if (members[i]->proTxHash == proTxHash) {
77  return qc.validMembers[i];
78  }
79  }
80  return false;
81 }
82 
83 CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const
84 {
85  if (quorumVvec == nullptr || memberIdx >= members.size() || !qc.validMembers[memberIdx]) {
86  return CBLSPublicKey();
87  }
88  auto& m = members[memberIdx];
89  return blsCache.BuildPubKeyShare(m->proTxHash, quorumVvec, CBLSId(m->proTxHash));
90 }
91 
93 {
94  return skShare;
95 }
96 
98 {
99  for (size_t i = 0; i < members.size(); i++) {
100  if (members[i]->proTxHash == proTxHash) {
101  return (int)i;
102  }
103  }
104  return -1;
105 }
106 
108 {
109  uint256 dbKey = MakeQuorumKey(*this);
110 
111  if (quorumVvec != nullptr) {
112  evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), *quorumVvec);
113  }
114  if (skShare.IsValid()) {
115  evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
116  }
117 }
118 
120 {
121  uint256 dbKey = MakeQuorumKey(*this);
122 
124  if (evoDb.Read(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), qv)) {
125  quorumVvec = std::make_shared<BLSVerificationVector>(std::move(qv));
126  } else {
127  return false;
128  }
129 
130  // We ignore the return value here as it is ok if this fails. If it fails, it usually means that we are not a
131  // member of the quorum but observed the whole DKG process to have the quorum verification vector.
132  evoDb.Read(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
133 
134  return true;
135 }
136 
137 void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
138 {
139  if (_this->quorumVvec == nullptr) {
140  return;
141  }
142 
143  cxxtimer::Timer t(true);
144  LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- start\n");
145 
146  // this thread will exit after some time
147  // when then later some other thread tries to get keys, it will be much faster
148  _this->cachePopulatorThread = std::thread(&TraceThread<std::function<void()> >, "quorum-cachepop", [_this, t] {
149  for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) {
150  if (_this->qc.validMembers[i]) {
151  _this->GetPubKeyShare(i);
152  }
153  }
154  LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count());
155  });
156 }
157 
158 CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : evoDb(_evoDb),
159  blsWorker(_blsWorker),
160  dkgManager(_dkgManager)
161 {
164 }
165 
166 void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
167 {
169  return;
170  }
171 
172  for (auto& p : Params().GetConsensus().llmqs) {
173  const auto& params = Params().GetConsensus().llmqs.at(p.first);
174 
175  auto lastQuorums = ScanQuorums(p.first, pindexNew, (size_t)params.keepOldConnections);
176 
177  llmq::EnsureLatestQuorumConnections(p.first, pindexNew, activeMasternodeManager->GetProTx(), lastQuorums);
178  }
179 }
180 
181 
182 bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const
183 {
184  assert(pindexQuorum);
185  assert(qc.quorumHash == pindexQuorum->GetBlockHash());
186 
187  auto members = deterministicMNManager->GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, pindexQuorum);
188  quorum->Init(qc, pindexQuorum, minedBlockHash, members);
189 
190  bool hasValidVvec = false;
191  if (quorum->ReadContributions(evoDb)) {
192  hasValidVvec = true;
193  } else {
194  if (BuildQuorumContributions(qc, quorum)) {
195  quorum->WriteContributions(evoDb);
196  hasValidVvec = true;
197  } else {
198  LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed\n", __func__, qc.quorumHash.ToString());
199  }
200  }
201 
202  if (hasValidVvec) {
203  // pre-populate caches in the background
204  // recovering public key shares is quite expensive and would result in serious lags for the first few signing
205  // sessions if the shares would be calculated on-demand
207  }
208 
209  return true;
210 }
211 
212 bool CQuorumManager::BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr<CQuorum>& quorum) const
213 {
214  std::vector<uint16_t> memberIndexes;
215  std::vector<BLSVerificationVectorPtr> vvecs;
216  BLSSecretKeyVector skContributions;
217  if (!dkgManager.GetVerifiedContributions((Consensus::LLMQType)fqc.llmqType, quorum->pindexQuorum, fqc.validMembers, memberIndexes, vvecs, skContributions)) {
218  return false;
219  }
220 
221  BLSVerificationVectorPtr quorumVvec;
222  CBLSSecretKey skShare;
223 
224  cxxtimer::Timer t2(true);
225  quorumVvec = blsWorker.BuildQuorumVerificationVector(vvecs);
226  if (quorumVvec == nullptr) {
227  LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build quorumVvec\n", __func__);
228  // without the quorum vvec, there can't be a skShare, so we fail here. Failure is not fatal here, as it still
229  // allows to use the quorum as a non-member (verification through the quorum pub key)
230  return false;
231  }
232  skShare = blsWorker.AggregateSecretKeys(skContributions);
233  if (!skShare.IsValid()) {
234  LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build skShare\n", __func__);
235  // We don't bail out here as this is not a fatal error and still allows us to recover public key shares (as we
236  // have a valid quorum vvec at this point)
237  }
238  t2.stop();
239 
240  LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count());
241 
242  quorum->quorumVvec = quorumVvec;
243  quorum->skShare = skShare;
244 
245  return true;
246 }
247 
248 bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
249 {
250  return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash);
251 }
252 
253 std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount)
254 {
255  const CBlockIndex* pindex = WITH_LOCK(cs_main, return chainActive.Tip());
256  return ScanQuorums(llmqType, pindex, maxCount);
257 }
258 
259 std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
260 {
261  if (pindexStart == nullptr || maxCount == 0) {
262  return {};
263  }
264 
265  bool fCacheExists{false};
266  void* pIndexScanCommitments{(void*)pindexStart};
267  size_t nScanCommitments{maxCount};
268  std::vector<CQuorumCPtr> vecResultQuorums;
269 
270  {
272  auto& cache = scanQuorumsCache.at(llmqType);
273  fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
274  if (fCacheExists) {
275  // We have exactly what requested so just return it
276  if (vecResultQuorums.size() == maxCount) {
277  return vecResultQuorums;
278  }
279  // If we have more cached than requested return only a subvector
280  if (vecResultQuorums.size() > maxCount) {
281  return {vecResultQuorums.begin(), vecResultQuorums.begin() + maxCount};
282  }
283  // If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start
284  // scanning for the rests
285  if (vecResultQuorums.size() > 0) {
286  nScanCommitments -= vecResultQuorums.size();
287  pIndexScanCommitments = (void*)vecResultQuorums.back()->pindexQuorum->pprev;
288  }
289  } else {
290  // If there is nothing in cache request at least cache.max_size() because this gets cached then later
291  nScanCommitments = std::max(maxCount, cache.max_size());
292  }
293  }
294  // Get the block indexes of the mined commitments to build the required quorums from
295  auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, (const CBlockIndex*)pIndexScanCommitments, nScanCommitments);
296  vecResultQuorums.reserve(vecResultQuorums.size() + quorumIndexes.size());
297 
298  for (auto& quorumIndex : quorumIndexes) {
299  assert(quorumIndex);
300  auto quorum = GetQuorum(llmqType, quorumIndex);
301  assert(quorum != nullptr);
302  vecResultQuorums.emplace_back(quorum);
303  }
304 
305  size_t nCountResult{vecResultQuorums.size()};
306  if (nCountResult > 0 && !fCacheExists) {
308  // Don't cache more than cache.max_size() elements
309  auto& cache = scanQuorumsCache.at(llmqType);
310  size_t nCacheEndIndex = std::min(nCountResult, cache.max_size());
311  cache.emplace(pindexStart->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
312  }
313  // Don't return more than nCountRequested elements
314  size_t nResultEndIndex = std::min(nCountResult, maxCount);
315  return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex};
316 }
317 
319 {
320  CBlockIndex* pindexQuorum;
321  {
322  LOCK(cs_main);
323  pindexQuorum = LookupBlockIndex(quorumHash);
324 
325  if (!pindexQuorum) {
326  LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found.\n", __func__, quorumHash.ToString());
327  return nullptr;
328  }
329  }
330  return GetQuorum(llmqType, pindexQuorum);
331 }
332 
334 {
335  assert(pindexQuorum);
336 
337  auto quorumHash = pindexQuorum->GetBlockHash();
338  // we must check this before we look into the cache. Reorgs might have happened which would mean we might have
339  // cached quorums which are not in the active chain anymore
340  if (!HasQuorum(llmqType, quorumHash)) {
341  return nullptr;
342  }
343 
345  CQuorumCPtr pQuorum;
346  if (mapQuorumsCache.at(llmqType).get(quorumHash, pQuorum)) {
347  return pQuorum;
348  }
349 
350  CFinalCommitment qc;
351  uint256 retMinedBlockHash;
352  if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc, retMinedBlockHash)) {
353  return nullptr;
354  }
355 
356  auto& params = Params().GetConsensus().llmqs.at(llmqType);
357 
358  auto quorum = std::make_shared<CQuorum>(params, blsWorker);
359  if (!BuildQuorumFromCommitment(qc, pindexQuorum, retMinedBlockHash, quorum)) {
360  return nullptr;
361  }
362 
363  mapQuorumsCache.at(llmqType).emplace(quorumHash, quorum);
364 
365  return quorum;
366 }
367 
368 } // namespace llmq
CActiveDeterministicMasternodeManager * activeMasternodeManager
std::vector< CBLSPublicKey > BLSVerificationVector
Definition: bls_wrapper.h:409
std::shared_ptr< BLSVerificationVector > BLSVerificationVectorPtr
Definition: bls_wrapper.h:415
std::vector< CBLSSecretKey > BLSSecretKeyVector
Definition: bls_wrapper.h:411
const CChainParams & Params()
Return the currently selected parameters.
CBLSPublicKey BuildPubKeyShare(const uint256 &cacheKey, const BLSVerificationVectorPtr &vvec, const CBLSId &id)
Definition: bls_worker.h:174
CBLSSecretKey AggregateSecretKeys(const BLSSecretKeyVector &secKeys, size_t start=0, size_t count=0, bool parallel=true)
Definition: bls_worker.cpp:675
BLSVerificationVectorPtr BuildQuorumVerificationVector(const std::vector< BLSVerificationVectorPtr > &vvecs, size_t start=0, size_t count=0, bool parallel=true)
Definition: bls_worker.cpp:633
bool IsValid() const
Definition: bls_wrapper.h:87
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:139
uint256 GetBlockHash() const
Definition: chain.h:215
CBlockIndex * Tip(bool fProofOfStake=false) const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:405
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:72
Definition: evodb.h:32
A writer stream (for serialization) that computes a 256-bit hash.
Definition: hash.h:216
bool IsBlockchainSynced() const
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
bool GetVerifiedContributions(Consensus::LLMQType llmqType, const CBlockIndex *pindexQuorum, const std::vector< bool > &validMembers, std::vector< uint16_t > &memberIndexesRet, std::vector< BLSVerificationVectorPtr > &vvecsRet, BLSSecretKeyVector &skContributionsRet)
std::vector< bool > validMembers
std::thread cachePopulatorThread
Definition: quorums.h:52
void WriteContributions(CEvoDB &evoDb)
Definition: quorums.cpp:107
CBLSPublicKey GetPubKeyShare(size_t memberIdx) const
Definition: quorums.cpp:83
const CBlockIndex * pindexQuorum
Definition: quorums.h:39
bool ReadContributions(CEvoDB &evoDb)
Definition: quorums.cpp:119
CBLSWorkerCache blsCache
Definition: quorums.h:50
uint256 minedBlockHash
Definition: quorums.h:40
BLSVerificationVectorPtr quorumVvec
Definition: quorums.h:44
std::vector< CDeterministicMNCPtr > members
Definition: quorums.h:41
int GetMemberIndex(const uint256 &proTxHash) const
Definition: quorums.cpp:97
CFinalCommitment qc
Definition: quorums.h:38
CBLSSecretKey skShare
Definition: quorums.h:45
bool IsValidMember(const uint256 &proTxHash) const
Definition: quorums.cpp:73
std::atomic< bool > stopCachePopulatorThread
Definition: quorums.h:51
void Init(const CFinalCommitment &_qc, const CBlockIndex *_pindexQuorum, const uint256 &_minedBlockHash, const std::vector< CDeterministicMNCPtr > &_members)
Definition: quorums.cpp:55
CBLSSecretKey GetSkShare() const
Definition: quorums.cpp:92
bool IsMember(const uint256 &proTxHash) const
Definition: quorums.cpp:63
static void StartCachePopulatorThread(std::shared_ptr< CQuorum > _this)
Definition: quorums.cpp:137
std::vector< CQuorumCPtr > ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount)
Definition: quorums.cpp:253
std::map< Consensus::LLMQType, unordered_lru_cache< uint256, CQuorumCPtr, StaticSaltedHasher > > mapQuorumsCache
Definition: quorums.h:88
CDKGSessionManager & dkgManager
Definition: quorums.h:85
CQuorumManager(CEvoDB &_evoDb, CBLSWorker &_blsWorker, CDKGSessionManager &_dkgManager)
Definition: quorums.cpp:158
RecursiveMutex quorumsCacheCs
Definition: quorums.h:87
bool BuildQuorumContributions(const CFinalCommitment &fqc, std::shared_ptr< CQuorum > &quorum) const
Definition: quorums.cpp:212
std::map< Consensus::LLMQType, unordered_lru_cache< uint256, std::vector< CQuorumCPtr >, StaticSaltedHasher > > scanQuorumsCache
Definition: quorums.h:89
CEvoDB & evoDb
Definition: quorums.h:83
CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256 &quorumHash)
Definition: quorums.cpp:318
bool BuildQuorumFromCommitment(const CFinalCommitment &qc, const CBlockIndex *pindexQuorum, const uint256 &minedBlockHash, std::shared_ptr< CQuorum > &quorum) const
Definition: quorums.cpp:182
bool HasQuorum(Consensus::LLMQType llmqType, const uint256 &quorumHash)
Definition: quorums.cpp:248
CBLSWorker & blsWorker
Definition: quorums.h:84
void UpdatedBlockTip(const CBlockIndex *pindexNew, bool fInitialDownload)
Definition: quorums.cpp:166
256-bit opaque blob.
Definition: uint256.h:138
std::unique_ptr< CDeterministicMNManager > deterministicMNManager
std::unique_ptr< CEvoDB > evoDb
Definition: evodb.cpp:10
@ LOCK
Definition: lockunlock.h:16
#define LogPrint(category,...)
Definition: logging.h:163
@ LLMQ
Definition: logging.h:66
LLMQType
Definition: params.h:90
void InitQuorumsCache(CacheType &cache)
Definition: quorums.cpp:26
std::shared_ptr< const CQuorum > CQuorumCPtr
Definition: quorums.h:72
std::unique_ptr< CQuorumBlockProcessor > quorumBlockProcessor
void EnsureLatestQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex *pindexNew, const uint256 &myProTxHash, std::vector< CQuorumCPtr > &lastQuorums)
std::unique_ptr< CQuorumManager > quorumManager
Definition: quorums.cpp:31
CBLSWorker * blsWorker
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:80
@ proTxHash
Definition: rpcevo.cpp:50
@ SER_NETWORK
Definition: serialize.h:174
bool ShutdownRequested()
Definition: shutdown.cpp:22
std::map< LLMQType, LLMQParams > llmqs
Definition: params.h:279
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:247
void TraceThread(const std::string name, Callable func)
Definition: system.h:271
TierTwoSyncState g_tiertwo_sync_state
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84
CBlockIndex * LookupBlockIndex(const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Definition: validation.h:345