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 : #include <llmq/dkgsessionmgr.h>
6 :
7 : #include <bls/bls_ies.h>
8 : #include <evo/deterministicmns.h>
9 : #include <llmq/dkgsessionhandler.h>
10 : #include <llmq/options.h>
11 : #include <llmq/utils.h>
12 : #include <msg_result.h>
13 : #include <spork.h>
14 : #include <unordered_lru_cache.h>
15 : #include <util/helpers.h>
16 : #include <util/std23.h>
17 :
18 : #include <chainparams.h>
19 : #include <dbwrapper.h>
20 : #include <deploymentstatus.h>
21 : #include <validation.h>
22 :
23 : namespace llmq
24 : {
25 : static const std::string DB_VVEC = "qdkg_V";
26 : static const std::string DB_SKCONTRIB = "qdkg_S";
27 : static const std::string DB_ENC_CONTRIB = "qdkg_E";
28 :
29 0 : CDKGSessionManager::CDKGSessionManager(CDeterministicMNManager& dmnman, CQuorumSnapshotManager& qsnapman,
30 : const ChainstateManager& chainman, const CSporkManager& sporkman,
31 : const util::DbWrapperParams& db_params) :
32 0 : m_dmnman{dmnman},
33 0 : m_qsnapman{qsnapman},
34 0 : m_chainman{chainman},
35 0 : m_sporkman{sporkman},
36 0 : db{util::MakeDbWrapper({db_params.path / "llmq" / "dkgdb", db_params.memory, db_params.wipe, /*cache_size=*/1 << 20})}
37 0 : {
38 0 : }
39 :
40 0 : CDKGSessionManager::~CDKGSessionManager() = default;
41 :
42 0 : void CDKGSessionManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
43 : {
44 0 : CleanupCache();
45 :
46 0 : if (fInitialDownload)
47 0 : return;
48 0 : if (!DeploymentDIP0003Enforced(pindexNew->nHeight, Params().GetConsensus()))
49 0 : return;
50 0 : if (!IsQuorumDKGEnabled(m_sporkman))
51 0 : return;
52 :
53 0 : for (auto& [_, dkgType] : dkgSessionHandlers) {
54 0 : Assert(dkgType)->UpdatedBlockTip(pindexNew);
55 : }
56 0 : }
57 :
58 0 : bool CDKGSessionManager::GetContribution(const uint256& hash, CDKGContribution& ret) const
59 : {
60 0 : if (!IsQuorumDKGEnabled(m_sporkman))
61 0 : return false;
62 :
63 0 : for (const auto& [_, dkgType] : dkgSessionHandlers) {
64 0 : const auto dkgPhase = Assert(dkgType)->GetPhase();
65 0 : if (dkgPhase < QuorumPhase::Initialized || dkgPhase > QuorumPhase::Contribute) {
66 0 : continue;
67 : }
68 0 : if (dkgType->GetContribution(hash, ret)) {
69 0 : return true;
70 : }
71 : }
72 0 : return false;
73 0 : }
74 :
75 0 : bool CDKGSessionManager::GetComplaint(const uint256& hash, CDKGComplaint& ret) const
76 : {
77 0 : if (!IsQuorumDKGEnabled(m_sporkman))
78 0 : return false;
79 :
80 0 : for (const auto& [_, dkgType] : dkgSessionHandlers) {
81 0 : const auto dkgPhase = Assert(dkgType)->GetPhase();
82 0 : if (dkgPhase < QuorumPhase::Contribute || dkgPhase > QuorumPhase::Complain) {
83 0 : continue;
84 : }
85 0 : if (dkgType->GetComplaint(hash, ret)) {
86 0 : return true;
87 : }
88 : }
89 0 : return false;
90 0 : }
91 :
92 0 : bool CDKGSessionManager::GetJustification(const uint256& hash, CDKGJustification& ret) const
93 : {
94 0 : if (!IsQuorumDKGEnabled(m_sporkman))
95 0 : return false;
96 :
97 0 : for (const auto& [_, dkgType] : dkgSessionHandlers) {
98 0 : const auto dkgPhase = Assert(dkgType)->GetPhase();
99 0 : if (dkgPhase < QuorumPhase::Complain || dkgPhase > QuorumPhase::Justify) {
100 0 : continue;
101 : }
102 0 : if (dkgType->GetJustification(hash, ret)) {
103 0 : return true;
104 : }
105 : }
106 0 : return false;
107 0 : }
108 :
109 0 : bool CDKGSessionManager::GetPrematureCommitment(const uint256& hash, CDKGPrematureCommitment& ret) const
110 : {
111 0 : if (!IsQuorumDKGEnabled(m_sporkman))
112 0 : return false;
113 :
114 0 : for (const auto& [_, dkgType] : dkgSessionHandlers) {
115 0 : const auto dkgPhase = Assert(dkgType)->GetPhase();
116 0 : if (dkgPhase < QuorumPhase::Justify || dkgPhase > QuorumPhase::Commit) {
117 0 : continue;
118 : }
119 0 : if (dkgType->GetPrematureCommitment(hash, ret)) {
120 0 : return true;
121 : }
122 : }
123 0 : return false;
124 0 : }
125 :
126 0 : void CDKGSessionManager::WriteVerifiedVvecContribution(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& proTxHash, const BLSVerificationVectorPtr& vvec)
127 : {
128 0 : CDataStream s(SER_DISK, CLIENT_VERSION);
129 0 : WriteCompactSize(s, vvec->size());
130 0 : for (auto& pubkey : *vvec) {
131 0 : s << CBLSPublicKeyVersionWrapper(pubkey, false);
132 : }
133 0 : db->Write(std::make_tuple(DB_VVEC, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), s);
134 0 : }
135 :
136 0 : void CDKGSessionManager::WriteVerifiedSkContribution(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& proTxHash, const CBLSSecretKey& skContribution)
137 : {
138 0 : db->Write(std::make_tuple(DB_SKCONTRIB, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), skContribution);
139 0 : }
140 :
141 0 : void CDKGSessionManager::WriteEncryptedContributions(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& proTxHash, const CBLSIESMultiRecipientObjects<CBLSSecretKey>& contributions)
142 : {
143 0 : db->Write(std::make_tuple(DB_ENC_CONTRIB, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), contributions);
144 0 : }
145 :
146 0 : bool CDKGSessionManager::GetVerifiedContributions(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const std::vector<bool>& validMembers, std::vector<uint16_t>& memberIndexesRet, std::vector<BLSVerificationVectorPtr>& vvecsRet, std::vector<CBLSSecretKey>& skContributionsRet) const
147 : {
148 0 : auto members = utils::GetAllQuorumMembers(llmqType, {m_dmnman, m_qsnapman, m_chainman, pQuorumBaseBlockIndex});
149 :
150 0 : memberIndexesRet.clear();
151 0 : vvecsRet.clear();
152 0 : skContributionsRet.clear();
153 0 : memberIndexesRet.reserve(members.size());
154 0 : vvecsRet.reserve(members.size());
155 0 : skContributionsRet.reserve(members.size());
156 :
157 : // NOTE: the `cs_main` should not be locked under scope of `contributionsCacheCs`
158 0 : LOCK(contributionsCacheCs);
159 0 : for (const auto i : util::irange(members.size())) {
160 0 : if (validMembers[i]) {
161 0 : const uint256& proTxHash = members[i]->proTxHash;
162 0 : ContributionsCacheKey cacheKey = {llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash};
163 0 : auto it = contributionsCache.find(cacheKey);
164 0 : if (it == contributionsCache.end()) {
165 0 : CDataStream s(SER_DISK, CLIENT_VERSION);
166 0 : if (!db->ReadDataStream(std::make_tuple(DB_VVEC, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), s)) {
167 0 : LogPrint(BCLog::LLMQ, "%s -- this node does not have vvec for llmq=%d block=%s protx=%s\n",
168 : __func__, std23::to_underlying(llmqType), pQuorumBaseBlockIndex->GetBlockHash().ToString(),
169 : proTxHash.ToString());
170 0 : return false;
171 : }
172 0 : size_t vvec_size = ReadCompactSize(s);
173 0 : CBLSPublicKey pubkey;
174 0 : std::vector<CBLSPublicKey> qv;
175 0 : for ([[maybe_unused]] size_t _ : util::irange(vvec_size)) {
176 0 : s >> CBLSPublicKeyVersionWrapper(pubkey, false);
177 0 : qv.emplace_back(pubkey);
178 : }
179 0 : auto vvecPtr = std::make_shared<std::vector<CBLSPublicKey>>(std::move(qv));
180 :
181 0 : CBLSSecretKey skContribution;
182 0 : db->Read(std::make_tuple(DB_SKCONTRIB, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), skContribution);
183 :
184 0 : it = contributionsCache.emplace(cacheKey, ContributionsCacheEntry{SteadyClock::now(), vvecPtr, skContribution}).first;
185 0 : }
186 :
187 0 : memberIndexesRet.emplace_back(i);
188 0 : vvecsRet.emplace_back(it->second.vvec);
189 0 : skContributionsRet.emplace_back(it->second.skContribution);
190 0 : }
191 : }
192 0 : return true;
193 0 : }
194 :
195 0 : bool CDKGSessionManager::GetEncryptedContributions(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const std::vector<bool>& validMembers, const uint256& nProTxHash, std::vector<CBLSIESEncryptedObject<CBLSSecretKey>>& vecRet) const
196 : {
197 0 : auto members = utils::GetAllQuorumMembers(llmqType, {m_dmnman, m_qsnapman, m_chainman, pQuorumBaseBlockIndex});
198 :
199 0 : vecRet.clear();
200 0 : vecRet.reserve(members.size());
201 :
202 0 : size_t nRequestedMemberIdx{std::numeric_limits<size_t>::max()};
203 0 : for (const auto i : util::irange(members.size())) {
204 : // cppcheck-suppress useStlAlgorithm
205 0 : if (members[i]->proTxHash == nProTxHash) {
206 0 : nRequestedMemberIdx = i;
207 0 : break;
208 : }
209 : }
210 0 : if (nRequestedMemberIdx == std::numeric_limits<size_t>::max()) {
211 0 : LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- not a member, nProTxHash=%s\n", __func__, nProTxHash.ToString());
212 0 : return false;
213 : }
214 :
215 0 : for (const auto i : util::irange(members.size())) {
216 0 : if (validMembers[i]) {
217 0 : CBLSIESMultiRecipientObjects<CBLSSecretKey> encryptedContributions;
218 0 : if (!db->Read(std::make_tuple(DB_ENC_CONTRIB, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), members[i]->proTxHash), encryptedContributions)) {
219 0 : LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- can't read from db, nProTxHash=%s\n", __func__, nProTxHash.ToString());
220 0 : return false;
221 : }
222 0 : vecRet.emplace_back(encryptedContributions.Get(nRequestedMemberIdx));
223 0 : }
224 : }
225 0 : return true;
226 0 : }
227 :
228 0 : void CDKGSessionManager::CleanupCache() const
229 : {
230 0 : LOCK(contributionsCacheCs);
231 0 : const auto curTime = SteadyClock::now();
232 0 : for (auto it = contributionsCache.begin(); it != contributionsCache.end(); ) {
233 0 : if (curTime - it->second.entryTime > MAX_CONTRIBUTION_CACHE_TIME) {
234 0 : it = contributionsCache.erase(it);
235 0 : } else {
236 0 : ++it;
237 : }
238 : }
239 0 : }
240 :
241 0 : void CDKGSessionManager::CleanupOldContributions() const
242 : {
243 0 : if (db->IsEmpty()) {
244 0 : return;
245 : }
246 :
247 0 : const auto prefixes = {DB_VVEC, DB_SKCONTRIB, DB_ENC_CONTRIB};
248 :
249 0 : for (const auto& params : Params().GetConsensus().llmqs) {
250 0 : LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- looking for old entries for llmq type %d\n", __func__, std23::to_underlying(params.type));
251 :
252 0 : CDBBatch batch(*db);
253 0 : size_t cnt_old{0}, cnt_all{0};
254 0 : for (const auto& prefix : prefixes) {
255 0 : std::unique_ptr<CDBIterator> pcursor(db->NewIterator());
256 0 : auto start = std::make_tuple(prefix, params.type, uint256(), uint256());
257 0 : decltype(start) k;
258 :
259 0 : pcursor->Seek(start);
260 0 : LOCK(::cs_main);
261 0 : while (pcursor->Valid()) {
262 0 : if (!pcursor->GetKey(k) || std::get<0>(k) != prefix || std::get<1>(k) != params.type) {
263 0 : break;
264 : }
265 0 : cnt_all++;
266 0 : const CBlockIndex* pindexQuorum = m_chainman.m_blockman.LookupBlockIndex(std::get<2>(k));
267 0 : if (pindexQuorum == nullptr ||
268 0 : m_chainman.ActiveHeight() - pindexQuorum->nHeight > params.max_store_depth()) {
269 : // not found or too old
270 0 : batch.Erase(k);
271 0 : cnt_old++;
272 0 : }
273 0 : pcursor->Next();
274 : }
275 0 : pcursor.reset();
276 0 : }
277 0 : LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- found %lld entries for llmq type %d\n", __func__, cnt_all, uint8_t(params.type));
278 0 : if (cnt_old > 0) {
279 0 : db->WriteBatch(batch);
280 0 : LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- removed %lld old entries for llmq type %d\n", __func__, cnt_old, uint8_t(params.type));
281 0 : }
282 0 : }
283 0 : }
284 : } // namespace llmq
|