Line data Source code
1 : // Copyright (c) 2018-2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <evo/deterministicmns.h>
6 :
7 : #include <chainparams.h>
8 : #include <coins.h>
9 : #include <consensus/validation.h>
10 : #include <deploymentstatus.h>
11 : #include <evo/dmn_types.h>
12 : #include <evo/dmnstate.h>
13 : #include <evo/evodb.h>
14 : #include <evo/providertx.h>
15 : #include <evo/simplifiedmns.h>
16 : #include <evo/specialtx.h>
17 : #include <masternode/meta.h>
18 : #include <node/blockstorage.h>
19 : #include <script/standard.h>
20 : #include <stats/client.h>
21 : #include <uint256.h>
22 : #include <util/helpers.h>
23 :
24 : #include <univalue.h>
25 :
26 : #include <functional>
27 : #include <optional>
28 : #include <memory>
29 : #include <ranges>
30 :
31 : static const std::string DB_LIST_SNAPSHOT = "dmn_S3";
32 : static const std::string DB_LIST_DIFF = "dmn_D4"; // Bumped for nVersion-first format
33 : static const std::string DB_LIST_DIFF_LEGACY = "dmn_D3"; // Legacy format key
34 : static const std::string DB_LIST_REPAIRED = "dmn_R1";
35 :
36 1001002 : uint64_t CDeterministicMN::GetInternalId() const
37 : {
38 : // can't get it if it wasn't set yet
39 1001002 : assert(internalId != std::numeric_limits<uint64_t>::max());
40 1001002 : return internalId;
41 : }
42 :
43 642687 : CSimplifiedMNListEntry CDeterministicMN::to_sml_entry() const
44 : {
45 642687 : const CDeterministicMNState& state{*pdmnState};
46 1285374 : return CSimplifiedMNListEntry(proTxHash, state.confirmedHash, state.netInfo, state.pubKeyOperator,
47 642687 : state.keyIDVoting, !state.IsBanned(), state.platformHTTPPort, state.platformNodeID,
48 642687 : state.scriptPayout, state.scriptOperatorPayout, state.nVersion, nType);
49 : }
50 :
51 898 : std::string CDeterministicMN::ToString() const
52 : {
53 898 : return strprintf("CDeterministicMN(proTxHash=%s, collateralOutpoint=%s, nOperatorReward=%f, state=%s", proTxHash.ToString(), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, pdmnState->ToString());
54 0 : }
55 :
56 73124 : bool CDeterministicMNList::IsMNValid(const uint256& proTxHash) const
57 : {
58 73124 : auto p = mnMap.find(proTxHash);
59 73124 : if (p == nullptr) {
60 32 : return false;
61 : }
62 73092 : return !(*p)->pdmnState->IsBanned();
63 73124 : }
64 :
65 22322 : bool CDeterministicMNList::IsMNPoSeBanned(const uint256& proTxHash) const
66 : {
67 22322 : auto p = mnMap.find(proTxHash);
68 22322 : if (p == nullptr) {
69 0 : return false;
70 : }
71 22322 : return (*p)->pdmnState->IsBanned();
72 22322 : }
73 :
74 2651494 : CDeterministicMNCPtr CDeterministicMNList::GetMN(const uint256& proTxHash) const
75 : {
76 2651494 : auto p = mnMap.find(proTxHash);
77 2651494 : if (p == nullptr) {
78 42386 : return nullptr;
79 : }
80 2609108 : return *p;
81 2651494 : }
82 :
83 7738 : CDeterministicMNCPtr CDeterministicMNList::GetValidMN(const uint256& proTxHash) const
84 : {
85 7738 : auto dmn = GetMN(proTxHash);
86 7738 : if (dmn && dmn->pdmnState->IsBanned()) {
87 41 : return nullptr;
88 : }
89 7697 : return dmn;
90 7738 : }
91 :
92 3340 : CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CBLSPublicKey& pubKey) const
93 : {
94 26256 : const auto it = std::ranges::find_if(mnMap, [&pubKey](const auto& p) {
95 22916 : return p.second->pdmnState->pubKeyOperator.Get() == pubKey;
96 : });
97 3340 : if (it == mnMap.end()) {
98 1812 : return nullptr;
99 : }
100 1528 : return it->second;
101 3340 : }
102 :
103 851851 : CDeterministicMNCPtr CDeterministicMNList::GetMNByCollateral(const COutPoint& collateralOutpoint) const
104 : {
105 851851 : return GetUniquePropertyMN(collateralOutpoint);
106 : }
107 :
108 0 : CDeterministicMNCPtr CDeterministicMNList::GetValidMNByCollateral(const COutPoint& collateralOutpoint) const
109 : {
110 0 : auto dmn = GetMNByCollateral(collateralOutpoint);
111 0 : if (dmn && dmn->pdmnState->IsBanned()) {
112 0 : return nullptr;
113 : }
114 0 : return dmn;
115 0 : }
116 :
117 56 : CDeterministicMNCPtr CDeterministicMNList::GetMNByService(const CService& service) const
118 : {
119 56 : return GetUniquePropertyMN(service);
120 : }
121 :
122 495407 : CDeterministicMNCPtr CDeterministicMNList::GetMNByInternalId(uint64_t internalId) const
123 : {
124 495407 : auto proTxHash = mnInternalIdMap.find(internalId);
125 495407 : if (!proTxHash) {
126 0 : return nullptr;
127 : }
128 495407 : return GetMN(*proTxHash);
129 495407 : }
130 :
131 2446190 : static int CompareByLastPaid_GetHeight(const CDeterministicMN& dmn)
132 : {
133 2446190 : int height = dmn.pdmnState->nLastPaidHeight;
134 2446190 : if (dmn.pdmnState->nPoSeRevivedHeight != -1 && dmn.pdmnState->nPoSeRevivedHeight > height) {
135 1436 : height = dmn.pdmnState->nPoSeRevivedHeight;
136 2446190 : } else if (height == 0) {
137 69907 : height = dmn.pdmnState->nRegisteredHeight;
138 69907 : }
139 2446190 : return height;
140 : }
141 :
142 1223095 : static bool CompareByLastPaid(const CDeterministicMN& _a, const CDeterministicMN& _b)
143 : {
144 1223095 : int ah = CompareByLastPaid_GetHeight(_a);
145 1223095 : int bh = CompareByLastPaid_GetHeight(_b);
146 1223095 : if (ah == bh) {
147 9865 : return _a.proTxHash < _b.proTxHash;
148 : } else {
149 1213230 : return ah < bh;
150 : }
151 1223095 : }
152 1223095 : static bool CompareByLastPaid(const CDeterministicMN* _a, const CDeterministicMN* _b)
153 : {
154 1223095 : return CompareByLastPaid(*_a, *_b);
155 : }
156 :
157 348142 : CDeterministicMNCPtr CDeterministicMNList::GetMNPayee(gsl::not_null<const CBlockIndex*> pindexPrev) const
158 : {
159 348142 : if (mnMap.size() == 0) {
160 79299 : return nullptr;
161 : }
162 :
163 : // The flag is-v19-activate is used for optimization; we don't need to go over all masternodes every pre-v19 block
164 268843 : const bool isv19Active{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_V19)};
165 268843 : const bool isMNRewardReallocation{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_MN_RR)};
166 : // EvoNodes are rewarded 4 blocks in a row until MNRewardReallocation (Platform release)
167 : // For optimization purposes we also check if v19 active to avoid loop over all masternodes
168 268843 : CDeterministicMNCPtr best = nullptr;
169 268843 : if (isv19Active && !isMNRewardReallocation) {
170 585424 : ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
171 477311 : if (dmn->pdmnState->nLastPaidHeight == nHeight) {
172 : // We found the last MN Payee.
173 : // If the last payee is an EvoNode, we need to check its consecutive payments and pay him again if needed
174 106420 : if (dmn->nType == MnType::Evo && dmn->pdmnState->nConsecutivePayments < dmn_types::Evo.voting_weight) {
175 17637 : best = dmn;
176 17637 : }
177 106420 : }
178 477311 : });
179 :
180 108113 : if (best != nullptr) return best;
181 :
182 : // Note: If the last payee was a regular MN or if the payee is an EvoNode that was removed from the mnList then that's fine.
183 : // We can proceed with classic MN payee selection
184 90476 : }
185 :
186 1723000 : ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
187 1471794 : if (best == nullptr || CompareByLastPaid(dmn.get(), best.get())) {
188 550179 : best = dmn;
189 550179 : }
190 1471794 : });
191 :
192 251206 : return best;
193 616985 : }
194 :
195 159 : std::vector<CDeterministicMNCPtr> CDeterministicMNList::GetProjectedMNPayees(gsl::not_null<const CBlockIndex* const> pindexPrev, int nCount) const
196 : {
197 159 : if (nCount < 0 ) {
198 0 : return {};
199 : }
200 159 : const bool isMNRewardReallocation = DeploymentActiveAfter(pindexPrev, Params().GetConsensus(),
201 : Consensus::DEPLOYMENT_MN_RR);
202 159 : const auto counts = GetCounts();
203 159 : const auto weighted_count = isMNRewardReallocation ? counts.enabled() : counts.m_valid_weighted;
204 159 : nCount = std::min(nCount, int(weighted_count));
205 :
206 159 : std::vector<CDeterministicMNCPtr> result;
207 159 : result.reserve(weighted_count);
208 :
209 159 : int remaining_evo_payments{0};
210 159 : CDeterministicMNCPtr evo_to_be_skipped{nullptr};
211 159 : if (!isMNRewardReallocation) {
212 230 : ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
213 192 : if (dmn->pdmnState->nLastPaidHeight == nHeight) {
214 : // We found the last MN Payee.
215 : // If the last payee is an EvoNode, we need to check its consecutive payments and pay him again if needed
216 38 : if (dmn->nType == MnType::Evo && dmn->pdmnState->nConsecutivePayments < dmn_types::Evo.voting_weight) {
217 2 : remaining_evo_payments = dmn_types::Evo.voting_weight - dmn->pdmnState->nConsecutivePayments;
218 7 : for ([[maybe_unused]] auto _ : util::irange(remaining_evo_payments)) {
219 5 : result.emplace_back(dmn);
220 5 : evo_to_be_skipped = dmn;
221 : }
222 2 : }
223 38 : }
224 192 : });
225 38 : }
226 :
227 948 : ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
228 789 : if (dmn == evo_to_be_skipped) return;
229 1592 : for ([[maybe_unused]] auto _ : util::irange(isMNRewardReallocation ? 1 : GetMnType(dmn->nType).voting_weight)) {
230 805 : result.emplace_back(dmn);
231 : }
232 789 : });
233 :
234 159 : if (evo_to_be_skipped != nullptr) {
235 : // if EvoNode is in the middle of payments, add entries for already paid ones to the end of the list
236 5 : for ([[maybe_unused]] auto _ : util::irange(evo_to_be_skipped->pdmnState->nConsecutivePayments)) {
237 3 : result.emplace_back(evo_to_be_skipped);
238 : }
239 2 : }
240 :
241 1521 : std::sort(result.begin() + remaining_evo_payments, result.end(), [&](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) {
242 1362 : return CompareByLastPaid(a.get(), b.get());
243 : });
244 :
245 159 : result.resize(nCount);
246 :
247 159 : return result;
248 318 : }
249 :
250 344121 : gsl::not_null<std::shared_ptr<const CSimplifiedMNList>> CDeterministicMNList::to_sml() const
251 : {
252 344121 : LOCK(m_cached_sml_mutex);
253 344121 : if (!m_cached_sml) {
254 7675 : std::vector<std::unique_ptr<CSimplifiedMNListEntry>> sml_entries;
255 7675 : sml_entries.reserve(mnMap.size());
256 :
257 58536 : ForEachMN(/*onlyValid=*/false, [&sml_entries](const auto& dmn) {
258 50861 : sml_entries.emplace_back(std::make_unique<CSimplifiedMNListEntry>(dmn.to_sml_entry()));
259 50861 : });
260 7675 : m_cached_sml = std::make_shared<CSimplifiedMNList>(std::move(sml_entries));
261 7675 : }
262 :
263 344121 : return m_cached_sml;
264 344121 : }
265 :
266 708 : int CDeterministicMNList::CalcMaxPoSePenalty() const
267 : {
268 : // Maximum PoSe penalty is dynamic and equals the number of registered MNs
269 : // It's however at least 100.
270 : // This means that the max penalty is usually equal to a full payment cycle
271 708 : return std::max(100, (int)GetCounts().total());
272 : }
273 :
274 354 : int CDeterministicMNList::CalcPenalty(int percent) const
275 : {
276 354 : assert(percent > 0);
277 354 : return (CalcMaxPoSePenalty() * percent) / 100;
278 : }
279 :
280 354 : void CDeterministicMNList::PoSePunish(const uint256& proTxHash, int penalty, bool debugLogs)
281 : {
282 354 : assert(penalty > 0);
283 :
284 354 : auto dmn = GetMN(proTxHash);
285 354 : if (!dmn) {
286 0 : throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
287 : }
288 :
289 354 : int maxPenalty = CalcMaxPoSePenalty();
290 :
291 354 : auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
292 354 : newState->nPoSePenalty += penalty;
293 354 : newState->nPoSePenalty = std::min(maxPenalty, newState->nPoSePenalty);
294 :
295 354 : if (!dmn->pdmnState->IsBanned()) {
296 354 : if (newState->nPoSePenalty >= maxPenalty) {
297 153 : newState->BanIfNotBanned(nHeight);
298 153 : }
299 354 : if (debugLogs) {
300 354 : LogPrintf("CDeterministicMNList::%s -- %s MN %s at height %d, penalty %d->%d (max=%d)\n", __func__,
301 : newState->IsBanned() ? "banned" : "punished", proTxHash.ToString(), nHeight,
302 : dmn->pdmnState->nPoSePenalty, newState->nPoSePenalty, maxPenalty);
303 354 : }
304 354 : }
305 354 : UpdateMN(proTxHash, newState);
306 354 : }
307 :
308 214461 : void CDeterministicMNList::DecreaseScores()
309 : {
310 214461 : std::vector<CDeterministicMNCPtr> toDecrease;
311 214461 : toDecrease.reserve(GetCounts().total() / 10);
312 : // only iterate and decrease for valid ones (not PoSe banned yet)
313 : // if a MN ever reaches the maximum, it stays in PoSe banned state until revived
314 975504 : ForEachMNShared(/*onlyValid=*/true, [&toDecrease](const auto& dmn) {
315 : // There is no reason to check if this MN is banned here since onlyValid=true will only run on non-banned MNs
316 761043 : if (dmn->pdmnState->nPoSePenalty > 0) {
317 4508 : toDecrease.emplace_back(dmn);
318 4508 : }
319 761043 : });
320 :
321 218969 : for (const auto& proTxHash : toDecrease) {
322 4508 : PoSeDecrease(*proTxHash);
323 : }
324 214461 : }
325 :
326 4508 : void CDeterministicMNList::PoSeDecrease(const CDeterministicMN& dmn)
327 : {
328 4508 : assert(dmn.pdmnState->nPoSePenalty > 0 && !dmn.pdmnState->IsBanned());
329 :
330 4508 : auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
331 4508 : newState->nPoSePenalty--;
332 4508 : UpdateMN(dmn, newState);
333 4508 : }
334 :
335 229021 : CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNList& to) const
336 : {
337 229021 : CDeterministicMNListDiff diffRet;
338 :
339 1475676 : for (const auto& p : to.mnMap) {
340 1246655 : const auto& toPtr = p.second;
341 1246655 : auto fromPtr = GetMN(toPtr->proTxHash);
342 1246655 : if (fromPtr == nullptr) {
343 6168 : diffRet.addedMNs.emplace_back(toPtr);
344 1246655 : } else if (fromPtr != toPtr || fromPtr->pdmnState != toPtr->pdmnState) {
345 209707 : CDeterministicMNStateDiff stateDiff(*fromPtr->pdmnState, *toPtr->pdmnState);
346 209707 : if (stateDiff.fields) {
347 208371 : diffRet.updatedMNs.emplace(toPtr->GetInternalId(), std::move(stateDiff));
348 208371 : }
349 209707 : }
350 1246655 : }
351 229021 : if (mnMap.size() + diffRet.addedMNs.size() != to.mnMap.size()) {
352 8603 : for (auto& fromPtr : mnMap) {
353 8603 : const auto toPtr = to.GetMN(fromPtr.second->proTxHash);
354 8603 : if (toPtr == nullptr) {
355 2468 : diffRet.removedMns.emplace(fromPtr.second->GetInternalId());
356 2468 : if (mnMap.size() + diffRet.addedMNs.size() - diffRet.removedMns.size() == to.mnMap.size()) break;
357 0 : }
358 8603 : };
359 2468 : }
360 :
361 : // added MNs need to be sorted by internalId so that these are added in correct order when the diff is applied later
362 : // otherwise internalIds will not match with the original list
363 236138 : std::sort(diffRet.addedMNs.begin(), diffRet.addedMNs.end(), [](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) {
364 7117 : return a->GetInternalId() < b->GetInternalId();
365 : });
366 :
367 229021 : return diffRet;
368 229021 : }
369 :
370 533628 : void CDeterministicMNList::ApplyDiff(gsl::not_null<const CBlockIndex*> pindex, const CDeterministicMNListDiff& diff)
371 : {
372 533628 : blockHash = pindex->GetBlockHash();
373 533628 : nHeight = pindex->nHeight;
374 :
375 534350 : for (const auto& id : diff.removedMns) {
376 722 : auto dmn = GetMNByInternalId(id);
377 722 : if (!dmn) {
378 0 : throw std::runtime_error(strprintf("%s: can't find a removed masternode, id=%d", __func__, id));
379 : }
380 722 : RemoveMN(dmn->proTxHash);
381 722 : }
382 559644 : for (const auto& dmn : diff.addedMNs) {
383 26016 : AddMN(dmn);
384 : }
385 935129 : for (const auto& p : diff.updatedMNs) {
386 401501 : auto dmn = GetMNByInternalId(p.first);
387 401501 : if (!dmn) {
388 0 : throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, p.first));
389 : }
390 401501 : UpdateMN(*dmn, p.second);
391 401501 : }
392 533628 : }
393 :
394 82668 : void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount)
395 : {
396 82668 : assert(dmn != nullptr);
397 :
398 82668 : if (mnMap.find(dmn->proTxHash)) {
399 7108 : throw(std::runtime_error(strprintf("%s: Can't add a masternode with a duplicate proTxHash=%s", __func__, dmn->proTxHash.ToString())));
400 : }
401 75560 : if (mnInternalIdMap.find(dmn->GetInternalId())) {
402 0 : throw(std::runtime_error(strprintf("%s: Can't add a masternode with a duplicate internalId=%d", __func__, dmn->GetInternalId())));
403 : }
404 :
405 : // All mnUniquePropertyMap's updates must be atomic.
406 : // Using this temporary map as a checkpoint to roll back to in case of any issues.
407 75560 : decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
408 :
409 75560 : if (!AddUniqueProperty(*dmn, dmn->collateralOutpoint)) {
410 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
411 0 : throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate collateralOutpoint=%s", __func__,
412 0 : dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort())));
413 : }
414 151171 : for (const auto& entry : dmn->pdmnState->netInfo->GetEntries()) {
415 151222 : if (const auto service_opt{entry.GetAddrPort()}) {
416 75611 : if (!AddUniqueProperty(*dmn, *service_opt)) {
417 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
418 0 : throw std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s", __func__,
419 0 : dmn->proTxHash.ToString(), service_opt->ToStringAddrPort()));
420 : }
421 75611 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
422 0 : if (!AddUniqueProperty(*dmn, *domain_opt)) {
423 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
424 0 : throw std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s",
425 0 : __func__, dmn->proTxHash.ToString(), domain_opt->ToStringAddrPort()));
426 : }
427 0 : } else {
428 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
429 0 : throw std::runtime_error(
430 0 : strprintf("%s: Can't add a masternode %s with invalid address", __func__, dmn->proTxHash.ToString()));
431 : }
432 : }
433 75560 : if (!AddUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) {
434 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
435 0 : throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate keyIDOwner=%s", __func__,
436 0 : dmn->proTxHash.ToString(), EncodeDestination(PKHash(dmn->pdmnState->keyIDOwner)))));
437 : }
438 75560 : if (dmn->pdmnState->pubKeyOperator != CBLSLazyPublicKey() && !AddUniqueProperty(*dmn, dmn->pdmnState->pubKeyOperator)) {
439 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
440 0 : throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate pubKeyOperator=%s", __func__,
441 0 : dmn->proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.ToString())));
442 : }
443 :
444 75560 : if (dmn->nType == MnType::Evo) {
445 6885 : if (dmn->pdmnState->platformNodeID != uint160() && !AddUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
446 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
447 0 : throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate platformNodeID=%s", __func__,
448 0 : dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
449 : }
450 6885 : }
451 :
452 75560 : mnMap = mnMap.set(dmn->proTxHash, dmn);
453 75560 : mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash);
454 75560 : InvalidateSMLCache();
455 75560 : if (fBumpTotalCount) {
456 : // nTotalRegisteredCount acts more like a checkpoint, not as a limit,
457 72502 : nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount);
458 72502 : }
459 82668 : }
460 :
461 550296 : void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
462 : {
463 550296 : auto dmn = std::make_shared<CDeterministicMN>(oldDmn);
464 550296 : auto oldState = dmn->pdmnState;
465 :
466 : // All mnUniquePropertyMap's updates must be atomic.
467 : // Using this temporary map as a checkpoint to roll back to in case of any issues.
468 550296 : decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
469 :
470 1100592 : auto updateNetInfo = [this](const CDeterministicMN& dmn, const std::shared_ptr<NetInfoInterface>& oldInfo,
471 : const std::shared_ptr<NetInfoInterface>& newInfo) -> std::string {
472 550296 : if (util::shared_ptr_not_equal(oldInfo, newInfo)) {
473 : // We track each individual entry in netInfo as opposed to netInfo itself (preventing us from
474 : // using UpdateUniqueProperty()), so we need to successfully purge all old entries and insert
475 : // new entries to successfully update.
476 7134 : for (const auto& old_entry : oldInfo->GetEntries()) {
477 7250 : if (const auto service_opt{old_entry.GetAddrPort()}) {
478 3625 : if (!DeleteUniqueProperty(dmn, *service_opt)) {
479 0 : return "internal error"; // This shouldn't be possible
480 : }
481 3625 : } else if (const auto domain_opt{old_entry.GetDomainPort()}) {
482 0 : if (!DeleteUniqueProperty(dmn, *domain_opt)) {
483 0 : return "internal error"; // This shouldn't be possible
484 : }
485 0 : } else {
486 0 : return "invalid address";
487 : }
488 : }
489 6977 : for (const auto& new_entry : newInfo->GetEntries()) {
490 6936 : if (const auto service_opt{new_entry.GetAddrPort()}) {
491 3448 : if (!AddUniqueProperty(dmn, *service_opt)) {
492 0 : return strprintf("duplicate (%s)", service_opt->ToStringAddrPort());
493 : }
494 3488 : } else if (const auto domain_opt{new_entry.GetDomainPort()}) {
495 20 : if (!AddUniqueProperty(dmn, *domain_opt)) {
496 0 : return strprintf("duplicate (%s)", domain_opt->ToStringAddrPort());
497 : }
498 20 : } else {
499 0 : return "invalid address";
500 : }
501 : }
502 3509 : }
503 550296 : return "";
504 550296 : };
505 :
506 550296 : assert(oldState->netInfo && pdmnState->netInfo);
507 550296 : if (auto err = updateNetInfo(*dmn, oldState->netInfo, pdmnState->netInfo); !err.empty()) {
508 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
509 0 : throw(std::runtime_error(strprintf("%s: Can't update masternode %s with addresses, reason=%s", __func__,
510 0 : oldDmn.proTxHash.ToString(), err)));
511 : }
512 550296 : if (!UpdateUniqueProperty(*dmn, oldState->keyIDOwner, pdmnState->keyIDOwner)) {
513 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
514 0 : throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate keyIDOwner=%s", __func__,
515 0 : oldDmn.proTxHash.ToString(), EncodeDestination(PKHash(pdmnState->keyIDOwner)))));
516 : }
517 550296 : if (!UpdateUniqueProperty(*dmn, oldState->pubKeyOperator, pdmnState->pubKeyOperator)) {
518 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
519 0 : throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate pubKeyOperator=%s", __func__,
520 0 : oldDmn.proTxHash.ToString(), pdmnState->pubKeyOperator.ToString())));
521 : }
522 550296 : if (dmn->nType == MnType::Evo) {
523 78072 : if (!UpdateUniqueProperty(*dmn, oldState->platformNodeID, pdmnState->platformNodeID)) {
524 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
525 0 : throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate platformNodeID=%s", __func__,
526 0 : oldDmn.proTxHash.ToString(), pdmnState->platformNodeID.ToString())));
527 : }
528 78072 : }
529 :
530 550296 : dmn->pdmnState = pdmnState;
531 550296 : mnMap = mnMap.set(oldDmn.proTxHash, dmn);
532 550296 : LOCK(m_cached_sml_mutex);
533 845714 : if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) {
534 5440 : m_cached_sml = nullptr;
535 5440 : }
536 550296 : }
537 :
538 144283 : void CDeterministicMNList::UpdateMN(const uint256& proTxHash, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
539 : {
540 144283 : auto oldDmn = mnMap.find(proTxHash);
541 144283 : if (!oldDmn) {
542 0 : throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
543 : }
544 144283 : UpdateMN(**oldDmn, pdmnState);
545 144283 : }
546 :
547 401501 : void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const CDeterministicMNStateDiff& stateDiff)
548 : {
549 401501 : auto oldState = oldDmn.pdmnState;
550 401501 : auto newState = std::make_shared<CDeterministicMNState>(*oldState);
551 401501 : stateDiff.ApplyToState(*newState);
552 401501 : UpdateMN(oldDmn, newState);
553 401501 : }
554 :
555 1285 : void CDeterministicMNList::RemoveMN(const uint256& proTxHash)
556 : {
557 1285 : auto dmn = GetMN(proTxHash);
558 1285 : if (!dmn) {
559 0 : throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
560 : }
561 :
562 : // All mnUniquePropertyMap's updates must be atomic.
563 : // Using this temporary map as a checkpoint to roll back to in case of any issues.
564 1285 : decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
565 :
566 1285 : if (!DeleteUniqueProperty(*dmn, dmn->collateralOutpoint)) {
567 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
568 0 : throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a collateralOutpoint=%s", __func__,
569 0 : proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort())));
570 : }
571 2574 : for (const auto& entry : dmn->pdmnState->netInfo->GetEntries()) {
572 2578 : if (const auto service_opt{entry.GetAddrPort()}) {
573 1289 : if (!DeleteUniqueProperty(*dmn, *service_opt)) {
574 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
575 0 : throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with an address=%s", __func__,
576 0 : proTxHash.ToString(), service_opt->ToStringAddrPort()));
577 : }
578 1289 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
579 0 : if (!DeleteUniqueProperty(*dmn, *domain_opt)) {
580 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
581 0 : throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with an address=%s", __func__,
582 0 : proTxHash.ToString(), domain_opt->ToStringAddrPort()));
583 : }
584 0 : } else {
585 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
586 0 : throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with invalid address", __func__,
587 0 : dmn->proTxHash.ToString()));
588 : }
589 : }
590 1285 : if (!DeleteUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) {
591 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
592 0 : throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a keyIDOwner=%s", __func__,
593 0 : proTxHash.ToString(), EncodeDestination(PKHash(dmn->pdmnState->keyIDOwner)))));
594 : }
595 2567 : if (dmn->pdmnState->pubKeyOperator != CBLSLazyPublicKey() &&
596 1282 : !DeleteUniqueProperty(*dmn, dmn->pdmnState->pubKeyOperator)) {
597 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
598 0 : throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a pubKeyOperator=%s", __func__,
599 0 : proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.ToString())));
600 : }
601 :
602 1285 : if (dmn->nType == MnType::Evo) {
603 100 : if (dmn->pdmnState->platformNodeID != uint160() && !DeleteUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
604 0 : mnUniquePropertyMap = mnUniquePropertyMapSaved;
605 0 : throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a platformNodeID=%s", __func__,
606 0 : dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
607 : }
608 100 : }
609 :
610 1285 : mnMap = mnMap.erase(proTxHash);
611 1285 : mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId());
612 1285 : InvalidateSMLCache();
613 1285 : }
614 :
615 6126 : CDeterministicMNManager::CDeterministicMNManager(CEvoDB& evoDb, CMasternodeMetaMan& mn_metaman) :
616 : m_evoDb{evoDb},
617 : m_mn_metaman{mn_metaman}
618 3063 : {
619 3063 : }
620 :
621 6126 : CDeterministicMNManager::~CDeterministicMNManager() = default;
622 :
623 129650 : bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex,
624 : BlockValidationState& state, const CDeterministicMNList& newList,
625 : std::optional<MNListUpdates>& updatesRet)
626 : {
627 129650 : AssertLockHeld(::cs_main);
628 :
629 129650 : const auto& consensusParams = Params().GetConsensus();
630 129650 : if (!DeploymentActiveAt(*pindex, consensusParams, Consensus::DEPLOYMENT_DIP0003)) {
631 0 : return true;
632 : }
633 :
634 129650 : CDeterministicMNList oldList;
635 129650 : CDeterministicMNListDiff diff;
636 :
637 129650 : int nHeight = pindex->nHeight;
638 :
639 : try {
640 129650 : newList.to_sml(); // to populate the SML cache
641 :
642 129638 : LOCK(cs);
643 :
644 129650 : oldList = GetListForBlockInternal(pindex->pprev);
645 129638 : diff = oldList.BuildDiff(newList);
646 :
647 : // apply platform unban for platform revive too
648 317866 : for (int i = 1; i < (int)block.vtx.size(); i++) {
649 188228 : const CTransaction& tx = *block.vtx[i];
650 188228 : if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_PROVIDER_UPDATE_SERVICE) {
651 : // only interested in revive transactions
652 186065 : continue;
653 : }
654 2163 : const auto opt_proTx = GetTxPayload<CProUpServTx>(tx);
655 2163 : if (!opt_proTx) continue; // should not happen but does not matter
656 :
657 2163 : if (!m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {
658 2112 : LogPrint(BCLog::LLMQ, "%s -- MN %s is failed to Platform revived at height %d\n", __func__,
659 : opt_proTx->proTxHash.ToString(), nHeight);
660 2112 : }
661 2163 : }
662 :
663 129638 : m_evoDb.Write(std::make_pair(DB_LIST_DIFF, newList.GetBlockHash()), diff);
664 129638 : if ((nHeight % DISK_SNAPSHOT_PERIOD) == 0 || pindex->pprev == m_initial_snapshot_index) {
665 56 : m_evoDb.Write(std::make_pair(DB_LIST_SNAPSHOT, newList.GetBlockHash()), newList);
666 62 : mnListsCache.emplace(newList.GetBlockHash(), newList);
667 62 : LogPrintf("CDeterministicMNManager::%s -- Wrote snapshot. nHeight=%d, mapCurMNs.allMNsCount=%d\n",
668 : __func__, nHeight, newList.GetCounts().total());
669 62 : }
670 :
671 129644 : diff.nHeight = pindex->nHeight;
672 129638 : mnListDiffsCache.emplace(pindex->GetBlockHash(), diff);
673 129638 : mnListsCache.emplace(newList.GetBlockHash(), newList);
674 129638 : } catch (const std::exception& e) {
675 0 : LogPrintf("CDeterministicMNManager::%s -- internal error: %s\n", __func__, e.what());
676 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-dmn-block");
677 0 : }
678 :
679 129638 : if (diff.HasChanges()) {
680 97741 : updatesRet = {.old_list = oldList, .new_list = newList, .diff = diff};
681 97741 : }
682 :
683 129638 : if (::g_stats_client->active()) {
684 0 : const auto counts{newList.GetCounts()};
685 0 : ::g_stats_client->gauge("masternodes.count", counts.total());
686 0 : ::g_stats_client->gauge("masternodes.weighted_count", counts.m_total_weighted);
687 0 : ::g_stats_client->gauge("masternodes.enabled", counts.enabled());
688 0 : ::g_stats_client->gauge("masternodes.weighted_enabled", counts.m_valid_weighted);
689 0 : ::g_stats_client->gauge("masternodes.evo.count", counts.m_total_evo);
690 0 : ::g_stats_client->gauge("masternodes.evo.enabled", counts.m_valid_evo);
691 0 : ::g_stats_client->gauge("masternodes.mn.count", counts.m_total_mn);
692 0 : ::g_stats_client->gauge("masternodes.mn.enabled", counts.m_valid_mn);
693 0 : }
694 :
695 129638 : if (nHeight == consensusParams.DIP0003EnforcementHeight) {
696 219 : if (!consensusParams.DIP0003EnforcementHash.IsNull() && consensusParams.DIP0003EnforcementHash != pindex->GetBlockHash()) {
697 0 : LogPrintf("CDeterministicMNManager::%s -- DIP3 enforcement block has wrong hash: hash=%s, expected=%s, nHeight=%d\n", __func__,
698 : pindex->GetBlockHash().ToString(), consensusParams.DIP0003EnforcementHash.ToString(), nHeight);
699 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-dip3-enf-block");
700 : }
701 219 : LogPrintf("CDeterministicMNManager::%s -- DIP3 is enforced now. nHeight=%d\n", __func__, nHeight);
702 219 : }
703 129638 : int current = to_cleanup.load();
704 129638 : while (nHeight > current && !to_cleanup.compare_exchange_weak(current, nHeight)) {
705 : // Loop continues if compare_exchange_weak failed (another thread changed it) (current is updated to the new value in to_cleanup)
706 : }
707 129638 : return true;
708 129686 : }
709 :
710 26566 : bool CDeterministicMNManager::UndoBlock(gsl::not_null<const CBlockIndex*> pindex, std::optional<MNListUpdates>& updatesRet)
711 : {
712 26566 : int nHeight = pindex->nHeight;
713 26566 : uint256 blockHash = pindex->GetBlockHash();
714 :
715 26566 : CDeterministicMNList prevList;
716 26566 : CDeterministicMNListDiff diff;
717 : {
718 26566 : LOCK(cs);
719 26566 : m_evoDb.Read(std::make_pair(DB_LIST_DIFF, blockHash), diff);
720 :
721 26566 : if (diff.HasChanges()) {
722 : // need to call this before erasing
723 8080 : prevList = GetListForBlockInternal(pindex->pprev);
724 8080 : }
725 :
726 26566 : mnListsCache.erase(blockHash);
727 26566 : mnListDiffsCache.erase(blockHash);
728 26566 : }
729 26566 : if (diff.HasChanges()) {
730 8080 : CDeterministicMNList curList{prevList};
731 8080 : curList.ApplyDiff(pindex, diff);
732 :
733 8080 : auto inversedDiff{curList.BuildDiff(prevList)};
734 8080 : updatesRet = {.old_list = curList, .new_list = prevList, .diff = inversedDiff};
735 8080 : }
736 :
737 26566 : const auto& consensusParams = Params().GetConsensus();
738 26566 : if (nHeight == consensusParams.DIP0003EnforcementHeight) {
739 0 : LogPrintf("CDeterministicMNManager::%s -- DIP3 is not enforced anymore. nHeight=%d\n", __func__, nHeight);
740 0 : }
741 :
742 : return true;
743 26566 : }
744 :
745 224882 : void CDeterministicMNManager::UpdatedBlockTip(gsl::not_null<const CBlockIndex*> pindex)
746 : {
747 224882 : LOCK(cs);
748 :
749 224882 : tipIndex = pindex;
750 224882 : }
751 :
752 2499166 : CDeterministicMNList CDeterministicMNManager::GetListForBlockInternal(gsl::not_null<const CBlockIndex*> pindex)
753 : {
754 2499166 : CDeterministicMNList snapshot;
755 :
756 2499166 : if (!DeploymentActiveAt(*pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
757 478438 : return snapshot;
758 : }
759 :
760 2020728 : AssertLockHeld(cs);
761 :
762 2020728 : std::list<const CBlockIndex*> listDiffIndexes;
763 :
764 2541657 : while (true) {
765 : // try using cache before reading from disk
766 2541657 : auto itLists = mnListsCache.find(pindex->GetBlockHash());
767 2541657 : if (itLists != mnListsCache.end()) {
768 2019839 : snapshot = itLists->second;
769 2019839 : break;
770 : }
771 :
772 521818 : if (m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, pindex->GetBlockHash()), snapshot)) {
773 35 : mnListsCache.emplace(pindex->GetBlockHash(), snapshot);
774 35 : break;
775 : }
776 :
777 : // no snapshot found yet, check diffs
778 521783 : auto itDiffs = mnListDiffsCache.find(pindex->GetBlockHash());
779 521783 : if (itDiffs != mnListDiffsCache.end()) {
780 394583 : listDiffIndexes.emplace_front(pindex);
781 394583 : pindex = pindex->pprev;
782 394583 : continue;
783 : }
784 :
785 127200 : CDeterministicMNListDiff diff;
786 127200 : if (!m_evoDb.Read(std::make_pair(DB_LIST_DIFF, pindex->GetBlockHash()), diff)) {
787 : // no snapshot and no diff on disk means that it's the initial snapshot
788 854 : m_initial_snapshot_index = pindex;
789 854 : snapshot = CDeterministicMNList(pindex->GetBlockHash(), pindex->nHeight, 0);
790 854 : mnListsCache.emplace(pindex->GetBlockHash(), snapshot);
791 854 : LogPrintf("CDeterministicMNManager::%s -- initial snapshot. blockHash=%s nHeight=%d\n",
792 : __func__, snapshot.GetBlockHash().ToString(), snapshot.GetHeight());
793 854 : break;
794 : }
795 :
796 126346 : diff.nHeight = pindex->nHeight;
797 126346 : mnListDiffsCache.emplace(pindex->GetBlockHash(), std::move(diff));
798 126346 : listDiffIndexes.emplace_front(pindex);
799 126346 : pindex = pindex->pprev;
800 127200 : }
801 :
802 2541657 : for (const auto& diffIndex : listDiffIndexes) {
803 520929 : const auto& diff = mnListDiffsCache.at(diffIndex->GetBlockHash());
804 520929 : snapshot.ApplyDiff(diffIndex, diff);
805 :
806 : static constexpr int MINI_SNAPSHOT_INTERVAL = 32;
807 520929 : if (!node::fReindex && snapshot.GetHeight() % MINI_SNAPSHOT_INTERVAL == 0) {
808 : // Add this temporary mini-snapshot to the cache.
809 : // Persistent masternode list snapshots are stored in evo-db every 576 blocks.
810 : // To answer GetListForBlock() between these snapshots, the node must rebuild
811 : // state by applying up to 575 diffs from the nearest persistent snapshot.
812 : // If GetListForBlock() is called repeatedly in that range, the work multiplies
813 : // (up to 575 diffs * number of calls).
814 : // Mini-snapshots reduce this overhead by caching intermediate states
815 : // every MINI_SNAPSHOT_INTERVAL blocks. Unlike persistent snapshots, these live
816 : // only in memory and are cleaned up after a short time by the scheduled cleanup().
817 : // There is also separate in-memory caching for the current tip and active quorums,
818 : // but this mini-snapshot cache specifically speeds up repeated requests
819 : // for nearby historical blocks.
820 8660 : mnListsCache.emplace(snapshot.GetBlockHash(), snapshot);
821 8660 : }
822 : }
823 :
824 2020728 : if (tipIndex) {
825 : // always keep a snapshot for the tip
826 1961641 : if (const auto snapshot_hash = snapshot.GetBlockHash(); snapshot_hash == tipIndex->GetBlockHash()) {
827 1827612 : mnListsCache.emplace(snapshot_hash, snapshot);
828 1827612 : } else {
829 : // keep snapshots for yet alive quorums
830 786514 : if (std::ranges::any_of(Params().GetConsensus().llmqs, [&snapshot, this](
831 : const auto& params) EXCLUSIVE_LOCKS_REQUIRED(cs) {
832 652485 : AssertLockHeld(cs);
833 657290 : return (snapshot.GetHeight() % params.dkgInterval == 0) &&
834 9610 : (snapshot.GetHeight() + params.dkgInterval * (params.keepOldConnections + 1) >=
835 4805 : tipIndex->nHeight);
836 : })) {
837 4421 : mnListsCache.emplace(snapshot_hash, snapshot);
838 4421 : }
839 : }
840 1961641 : }
841 :
842 2020728 : assert(snapshot.GetHeight() != -1);
843 2020728 : return snapshot;
844 2499166 : }
845 :
846 1655062 : CDeterministicMNList CDeterministicMNManager::GetListAtChainTip()
847 : {
848 1655062 : LOCK(cs);
849 1655062 : if (!tipIndex) {
850 56618 : return {};
851 : }
852 1598444 : return GetListForBlockInternal(tipIndex);
853 1655062 : }
854 :
855 230483 : bool CDeterministicMNManager::IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n)
856 : {
857 230483 : if (!tx->IsSpecialTxVersion() || tx->nType != TRANSACTION_PROVIDER_REGISTER) {
858 229241 : return false;
859 : }
860 1242 : const auto opt_proTx = GetTxPayload<CProRegTx>(*tx);
861 1242 : if (!opt_proTx) {
862 0 : return false;
863 : }
864 1242 : auto& proTx = *opt_proTx;
865 :
866 1242 : if (!proTx.collateralOutpoint.hash.IsNull()) {
867 246 : return false;
868 : }
869 996 : if (proTx.collateralOutpoint.n >= tx->vout.size() || proTx.collateralOutpoint.n != n) {
870 325 : return false;
871 : }
872 :
873 :
874 671 : if (const CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount; tx->vout[n].nValue != expectedCollateral) {
875 0 : return false;
876 : }
877 671 : return true;
878 230483 : }
879 :
880 8710 : void CDeterministicMNManager::CleanupCache(int nHeight)
881 : {
882 8710 : AssertLockHeld(cs);
883 :
884 8710 : std::vector<uint256> toDeleteLists;
885 8710 : std::vector<uint256> toDeleteDiffs;
886 168011 : for (const auto& p : mnListsCache) {
887 159301 : if (p.second.GetHeight() + LIST_DIFFS_CACHE_SIZE < nHeight) {
888 : // too old, drop it
889 0 : toDeleteLists.emplace_back(p.first);
890 0 : continue;
891 : }
892 318602 : if (tipIndex != nullptr && p.first == tipIndex->GetBlockHash()) {
893 : // it's a snapshot for the tip, keep it
894 8710 : continue;
895 : }
896 786283 : bool fQuorumCache = std::ranges::any_of(Params().GetConsensus().llmqs, [&nHeight, &p](const auto& params) {
897 696719 : return (p.second.GetHeight() % params.dkgInterval == 0) &&
898 61027 : (p.second.GetHeight() + params.dkgInterval * (params.keepOldConnections + 1) >= nHeight);
899 : });
900 150591 : if (fQuorumCache) {
901 : // at least one quorum could be using it, keep it
902 33089 : continue;
903 : }
904 : // none of the above, drop it
905 117502 : toDeleteLists.emplace_back(p.first);
906 : }
907 126212 : for (const auto& h : toDeleteLists) {
908 117502 : mnListsCache.erase(h);
909 : }
910 2250645 : for (const auto& p : mnListDiffsCache) {
911 2241935 : if (p.second.nHeight + LIST_DIFFS_CACHE_SIZE < nHeight) {
912 0 : toDeleteDiffs.emplace_back(p.first);
913 0 : }
914 : }
915 8710 : for (const auto& h : toDeleteDiffs) {
916 0 : mnListDiffsCache.erase(h);
917 : }
918 :
919 8710 : }
920 :
921 : //end
922 :
923 15672 : void CDeterministicMNManager::DoMaintenance() {
924 15672 : LOCK(cs_cleanup);
925 15672 : int loc_to_cleanup = to_cleanup.load();
926 15672 : if (loc_to_cleanup <= did_cleanup) return;
927 8710 : LOCK(cs);
928 8710 : CleanupCache(loc_to_cleanup);
929 8710 : did_cleanup = loc_to_cleanup;
930 15672 : }
931 :
932 3049 : bool CDeterministicMNManager::IsMigrationRequired() const
933 : {
934 : // Check if there are any legacy format diffs in the database
935 : // by looking for DB_LIST_DIFF_LEGACY entries
936 :
937 3049 : AssertLockHeld(::cs_main);
938 :
939 3049 : std::unique_ptr<CDBIterator> pcursor{m_evoDb.GetRawDB().NewIterator()};
940 3049 : auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})};
941 3049 : pcursor->Seek(start);
942 :
943 : // If we find any entries with the legacy key, migration is needed
944 3049 : if (pcursor->Valid()) {
945 1724 : decltype(start) k;
946 1724 : if (pcursor->GetKey(k) && std::get<0>(k) == DB_LIST_DIFF_LEGACY) {
947 0 : LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is needed\n", __func__);
948 0 : pcursor.reset();
949 0 : return true;
950 : }
951 1724 : }
952 :
953 3049 : LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is not needed\n", __func__);
954 3049 : pcursor.reset();
955 3049 : return false; // No legacy format found
956 3049 : }
957 :
958 0 : bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_index)
959 : {
960 : // CRITICAL: This migration converts ALL stored CDeterministicMNListDiff entries
961 : // from legacy database key (DB_LIST_DIFF_LEGACY) to new key (DB_LIST_DIFF) format
962 :
963 0 : AssertLockHeld(::cs_main);
964 :
965 0 : LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__);
966 :
967 0 : std::vector<const CBlockIndex*> keys_to_erase;
968 :
969 0 : CDBBatch batch(m_evoDb.GetRawDB());
970 0 : std::unique_ptr<CDBIterator> pcursor{m_evoDb.GetRawDB().NewIterator()};
971 :
972 : // Keep track of the list to get correct nVersion at the current height
973 0 : CDeterministicMNList snapshot;
974 :
975 0 : const auto start_height{Params().GetConsensus().DeploymentHeight(Consensus::DEPLOYMENT_DIP0003)};
976 0 : for (auto current_height : std::views::iota(start_height, tip_index->nHeight + 1)) {
977 0 : auto current_index = tip_index->GetAncestor(current_height);
978 0 : auto target_key{std::make_tuple(DB_LIST_DIFF_LEGACY, current_index->GetBlockHash())};
979 0 : pcursor->Seek(target_key);
980 :
981 0 : decltype(target_key) key;
982 0 : if (!pcursor->Valid() || !pcursor->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) {
983 0 : break;
984 : }
985 :
986 0 : if (std::get<1>(key) != current_index->GetBlockHash()) {
987 0 : throw std::ios_base::failure("Invalid data, we must have legacy diffs for each height");
988 : }
989 :
990 : // Use legacy-aware deserialization for DB_LIST_DIFF_LEGACY entries
991 0 : CDataStream s(SER_DISK, CLIENT_VERSION);
992 0 : if (!m_evoDb.GetRawDB().ReadDataStream(key, s)) {
993 0 : break;
994 : }
995 :
996 0 : CDeterministicMNListDiff legacyDiff;
997 0 : legacyDiff.UnserializeLegacyFormat(s); // Use legacy format deserializer
998 0 : snapshot.ApplyDiff(current_index, legacyDiff);
999 :
1000 0 : CDeterministicMNListDiff convertedDiff;
1001 0 : convertedDiff.addedMNs = legacyDiff.addedMNs;
1002 0 : convertedDiff.removedMns = legacyDiff.removedMns;
1003 :
1004 : // The conversion is already done by UnserializeLegacyFormat()!
1005 : // CDeterministicMNStateDiffLegacy.ToNewFormat() was called during deserialization
1006 : // So legacyDiff.updatedMNs already contains properly converted CDeterministicMNStateDiff objects
1007 :
1008 : // Copy the already-converted state diffs but make sure pubKeyOperator, nVersion and fields are set properly
1009 0 : for (auto& [internalId, stateDiff] : legacyDiff.updatedMNs) {
1010 0 : auto dmn = snapshot.GetMNByInternalId(internalId);
1011 0 : if (!dmn) {
1012 : // shouldn't happen
1013 0 : throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, internalId));
1014 : }
1015 0 : if (!(stateDiff.fields & CDeterministicMNStateDiff::Field_nVersion)) {
1016 0 : if ((stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) ||
1017 0 : (stateDiff.fields & CDeterministicMNStateDiff::Field_netInfo)) {
1018 0 : stateDiff.fields |= CDeterministicMNStateDiff::Field_nVersion;
1019 0 : stateDiff.state.nVersion = dmn->pdmnState->nVersion;
1020 0 : }
1021 0 : }
1022 0 : if (stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) {
1023 0 : stateDiff.state.pubKeyOperator.SetLegacy(stateDiff.state.nVersion == ProTxVersion::LegacyBLS);
1024 0 : }
1025 0 : convertedDiff.updatedMNs.emplace(internalId, stateDiff);
1026 0 : }
1027 :
1028 : // Write the converted diff to new database key
1029 0 : batch.Write(std::make_pair(DB_LIST_DIFF, std::get<1>(key)), convertedDiff);
1030 0 : keys_to_erase.push_back(current_index);
1031 :
1032 0 : if (batch.SizeEstimate() >= (1 << 24)) {
1033 0 : LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, current_height);
1034 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1035 0 : batch.Clear();
1036 0 : }
1037 0 : }
1038 0 : pcursor.reset();
1039 :
1040 0 : LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, tip_index->nHeight);
1041 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1042 0 : batch.Clear();
1043 0 : LogPrintf("CDeterministicMNManager::%s -- Wrote %d new diffs\n", __func__, keys_to_erase.size());
1044 :
1045 : // Delete all found legacy format entries
1046 0 : for (const auto& index : keys_to_erase) {
1047 0 : batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, index->GetBlockHash()));
1048 :
1049 0 : if (batch.SizeEstimate() >= (1 << 24)) {
1050 0 : LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__,
1051 : index->nHeight);
1052 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1053 0 : batch.Clear();
1054 0 : }
1055 : }
1056 :
1057 0 : LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__, tip_index->nHeight);
1058 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1059 0 : batch.Clear();
1060 0 : LogPrintf("CDeterministicMNManager::%s -- Erased %d found legacy diffs\n", __func__, keys_to_erase.size());
1061 :
1062 : // Delete all dangling legacy format entries
1063 0 : std::unique_ptr<CDBIterator> pcursor_dangling{m_evoDb.GetRawDB().NewIterator()};
1064 0 : auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})};
1065 0 : pcursor_dangling->Seek(start);
1066 0 : int count{0};
1067 0 : while (pcursor_dangling->Valid()) {
1068 0 : decltype(start) key;
1069 0 : if (!pcursor_dangling->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) {
1070 0 : break;
1071 : }
1072 0 : LogPrintf("CDeterministicMNManager::%s -- Erasing dangling legacy diff, hash=%s\n", __func__,
1073 : std::get<1>(key).ToString());
1074 0 : batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, std::get<1>(key)));
1075 0 : pcursor_dangling->Next();
1076 0 : ++count;
1077 0 : }
1078 0 : pcursor_dangling.reset();
1079 :
1080 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1081 0 : batch.Clear();
1082 0 : LogPrintf("CDeterministicMNManager::%s -- Erased %d dangling legacy diffs\n", __func__, count);
1083 :
1084 0 : LogPrintf("CDeterministicMNManager::%s -- Compacting database...\n", __func__);
1085 0 : m_evoDb.GetRawDB().CompactFull();
1086 :
1087 : // flush it to disk
1088 0 : if (!m_evoDb.CommitRootTransaction()) {
1089 0 : LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__);
1090 0 : return false;
1091 : }
1092 :
1093 : // Clear caches to force reload with new format
1094 0 : LOCK(cs);
1095 0 : mnListsCache.clear();
1096 0 : mnListDiffsCache.clear();
1097 :
1098 0 : LogPrintf("CDeterministicMNManager::%s -- Successfully migrated %d diffs to nVersion-first format\n", __func__,
1099 : keys_to_erase.size());
1100 :
1101 0 : return true;
1102 0 : }
1103 :
1104 415 : CDeterministicMNManager::RecalcDiffsResult CDeterministicMNManager::RecalculateAndRepairDiffs(
1105 : const CBlockIndex* start_index, const CBlockIndex* stop_index, ChainstateManager& chainman,
1106 : BuildListFromBlockFunc build_list_func, bool repair)
1107 : {
1108 415 : RecalcDiffsResult result;
1109 415 : result.start_height = start_index->nHeight;
1110 415 : result.stop_height = stop_index->nHeight;
1111 :
1112 415 : const auto& consensus_params = Params().GetConsensus();
1113 :
1114 : // Clamp start height to DIP0003 activation (no snapshots/diffs exist before this)
1115 415 : if (start_index->nHeight < consensus_params.DIP0003Height) {
1116 0 : start_index = stop_index->GetAncestor(consensus_params.DIP0003Height);
1117 0 : if (!start_index) {
1118 0 : result.verification_errors.push_back(strprintf("Stop height %d is below DIP0003 activation height %d",
1119 0 : stop_index->nHeight, consensus_params.DIP0003Height));
1120 0 : return result;
1121 : }
1122 0 : LogPrintf("CDeterministicMNManager::%s -- Clamped start height from %d to DIP0003 activation height %d\n",
1123 : __func__, result.start_height, consensus_params.DIP0003Height);
1124 : // Update result to reflect the clamped start height
1125 0 : result.start_height = start_index->nHeight;
1126 0 : }
1127 :
1128 : // Collect all snapshot blocks in the range
1129 415 : std::vector<const CBlockIndex*> snapshot_blocks = CollectSnapshotBlocks(start_index, stop_index, consensus_params);
1130 :
1131 415 : if (snapshot_blocks.empty()) {
1132 0 : result.verification_errors.push_back("Could not find starting snapshot");
1133 0 : return result;
1134 : }
1135 :
1136 415 : if (snapshot_blocks.size() < 2) {
1137 408 : result.verification_errors.push_back(strprintf("Need at least 2 snapshots, found %d", snapshot_blocks.size()));
1138 408 : return result;
1139 : }
1140 :
1141 7 : LogPrintf("CDeterministicMNManager::%s -- Processing %d snapshot pairs between heights %d and %d\n", __func__,
1142 : snapshot_blocks.size() - 1, result.start_height, result.stop_height);
1143 :
1144 : // Storage for recalculated diffs if we plan to repair
1145 7 : std::vector<std::pair<uint256, CDeterministicMNListDiff>> recalculated_diffs;
1146 :
1147 : // Process each pair of consecutive snapshots
1148 18 : for (size_t i = 0; i < snapshot_blocks.size() - 1; ++i) {
1149 11 : const CBlockIndex* from_index = snapshot_blocks[i];
1150 11 : const CBlockIndex* to_index = snapshot_blocks[i + 1];
1151 :
1152 : // Load the snapshots from disk
1153 11 : CDeterministicMNList from_snapshot;
1154 11 : CDeterministicMNList to_snapshot;
1155 :
1156 11 : bool has_from_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, from_index->GetBlockHash()), from_snapshot);
1157 11 : bool has_to_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, to_index->GetBlockHash()), to_snapshot);
1158 :
1159 : // Handle missing snapshots
1160 11 : if (!has_from_snapshot) {
1161 : // The initial snapshot at DIP0003 activation might not exist in the database on nodes
1162 : // that synced before the fix to explicitly write it. This is the only acceptable case.
1163 7 : if (from_index->nHeight == consensus_params.DIP0003Height) {
1164 : // Create an empty initial snapshot (matching what GetListForBlockInternal does)
1165 7 : from_snapshot = CDeterministicMNList(from_index->GetBlockHash(), from_index->nHeight, 0);
1166 7 : LogPrintf("CDeterministicMNManager::%s -- Using empty initial snapshot at DIP0003 height %d\n",
1167 : __func__, from_index->nHeight);
1168 7 : } else {
1169 : // Any other missing snapshot is critical corruption beyond our repair capability
1170 0 : result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
1171 0 : "This cannot be repaired by this tool - full reindex required.", from_index->nHeight));
1172 0 : return result;
1173 : }
1174 7 : }
1175 :
1176 11 : if (!has_to_snapshot) {
1177 : // Missing target snapshot is always critical - we cannot repair snapshots, only diffs
1178 0 : result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
1179 0 : "This cannot be repaired by this tool - full reindex required.", to_index->nHeight));
1180 0 : return result;
1181 : }
1182 :
1183 : // Log progress periodically (every 100 snapshot pairs) to avoid spam
1184 11 : if (i % 100 == 0) {
1185 7 : LogPrintf("CDeterministicMNManager::%s -- Progress: verifying snapshot pair %d/%d (heights %d-%d)\n",
1186 : __func__, i + 1, snapshot_blocks.size() - 1, from_index->nHeight, to_index->nHeight);
1187 7 : }
1188 :
1189 : // Verify this snapshot pair
1190 11 : bool is_snapshot_pair_valid = VerifySnapshotPair(from_index, to_index, from_snapshot, to_snapshot, result);
1191 :
1192 : // If repair mode is enabled and verification failed, recalculate diffs from blockchain
1193 11 : if (repair && !is_snapshot_pair_valid) {
1194 0 : auto temp_diffs = RepairSnapshotPair(from_index, to_index, from_snapshot, to_snapshot, build_list_func, result);
1195 0 : if (temp_diffs.empty()) {
1196 : // RepairSnapshotPair failed - this is a critical error, cannot continue
1197 0 : return result;
1198 : }
1199 : // Only commit diffs if recalculation verification passed
1200 0 : recalculated_diffs.insert(recalculated_diffs.end(), temp_diffs.begin(), temp_diffs.end());
1201 0 : result.diffs_recalculated += temp_diffs.size();
1202 0 : }
1203 11 : }
1204 :
1205 : // Write repaired diffs to database
1206 7 : if (repair) {
1207 7 : WriteRepairedDiffs(recalculated_diffs, result);
1208 7 : }
1209 :
1210 7 : return result;
1211 415 : }
1212 :
1213 2758 : bool CDeterministicMNManager::IsRepaired() const { return m_evoDb.Exists(DB_LIST_REPAIRED); }
1214 :
1215 488 : void CDeterministicMNManager::CompleteRepair()
1216 : {
1217 488 : auto dbTx = m_evoDb.BeginTransaction();
1218 488 : m_evoDb.Write(DB_LIST_REPAIRED, 1);
1219 488 : dbTx->Commit();
1220 : // flush it to disk
1221 488 : if (!m_evoDb.CommitRootTransaction()) {
1222 0 : LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__);
1223 0 : assert(false);
1224 : }
1225 488 : }
1226 :
1227 415 : std::vector<const CBlockIndex*> CDeterministicMNManager::CollectSnapshotBlocks(
1228 : const CBlockIndex* start_index, const CBlockIndex* stop_index, const Consensus::Params& consensus_params)
1229 : {
1230 415 : std::vector<const CBlockIndex*> snapshot_blocks;
1231 :
1232 : // Add the starting snapshot (find the snapshot at or before start)
1233 : // Walk backwards to find a snapshot block (divisible by DISK_SNAPSHOT_PERIOD)
1234 : // or the initial snapshot at DIP0003 activation height
1235 415 : const CBlockIndex* snapshot_start_index = start_index;
1236 415 : while (snapshot_start_index && snapshot_start_index->nHeight > consensus_params.DIP0003Height &&
1237 0 : (snapshot_start_index->nHeight % DISK_SNAPSHOT_PERIOD) != 0) {
1238 0 : snapshot_start_index = snapshot_start_index->pprev;
1239 : }
1240 :
1241 415 : if (!snapshot_start_index) {
1242 0 : return snapshot_blocks; // Empty vector indicates error
1243 : }
1244 :
1245 : // Collect all snapshot blocks up to and including the stop block
1246 415 : snapshot_blocks.push_back(snapshot_start_index);
1247 :
1248 : // Find all subsequent snapshot heights
1249 415 : int current_snapshot_height = snapshot_start_index->nHeight;
1250 426 : while (true) {
1251 : // Calculate next snapshot height
1252 : int next_snapshot_height;
1253 426 : if (current_snapshot_height == consensus_params.DIP0003Height) {
1254 : // If we're at DIP0003 activation (initial snapshot), next is at first regular interval
1255 415 : next_snapshot_height = ((consensus_params.DIP0003Height / DISK_SNAPSHOT_PERIOD) + 1) * DISK_SNAPSHOT_PERIOD;
1256 415 : } else {
1257 : // Otherwise, add DISK_SNAPSHOT_PERIOD
1258 11 : next_snapshot_height = current_snapshot_height + DISK_SNAPSHOT_PERIOD;
1259 : }
1260 :
1261 426 : if (next_snapshot_height > stop_index->nHeight) {
1262 415 : break;
1263 : }
1264 :
1265 11 : const CBlockIndex* next_snapshot_index = stop_index->GetAncestor(next_snapshot_height);
1266 11 : if (!next_snapshot_index) {
1267 0 : break;
1268 : }
1269 :
1270 11 : snapshot_blocks.push_back(next_snapshot_index);
1271 11 : current_snapshot_height = next_snapshot_height;
1272 : }
1273 :
1274 415 : return snapshot_blocks;
1275 415 : }
1276 :
1277 11 : bool CDeterministicMNManager::VerifySnapshotPair(
1278 : const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot,
1279 : const CDeterministicMNList& to_snapshot, RecalcDiffsResult& result)
1280 : {
1281 : // Verify this snapshot pair by applying all stored diffs sequentially
1282 11 : CDeterministicMNList test_list = from_snapshot;
1283 :
1284 : try {
1285 4613 : for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) {
1286 4602 : const CBlockIndex* pIndex = to_index->GetAncestor(nHeight);
1287 4602 : if (!pIndex) {
1288 0 : result.verification_errors.push_back(strprintf("Failed to get ancestor at height %d", nHeight));
1289 0 : return false;
1290 : }
1291 :
1292 4602 : CDeterministicMNListDiff diff;
1293 4602 : if (!m_evoDb.Read(std::make_pair(DB_LIST_DIFF, pIndex->GetBlockHash()), diff)) {
1294 0 : result.verification_errors.push_back(strprintf("Failed to read diff at height %d", nHeight));
1295 0 : return false;
1296 : }
1297 :
1298 4602 : diff.nHeight = nHeight;
1299 4602 : test_list.ApplyDiff(pIndex, diff);
1300 4602 : }
1301 11 : } catch (const std::exception& e) {
1302 0 : result.verification_errors.push_back(strprintf("Exception during verification: %s", e.what()));
1303 0 : return false;
1304 0 : }
1305 :
1306 : // Verify that applying all diffs results in the target snapshot
1307 11 : bool is_snapshot_pair_valid = test_list.IsEqual(to_snapshot);
1308 :
1309 11 : if (is_snapshot_pair_valid) {
1310 11 : result.snapshots_verified++;
1311 11 : } else {
1312 0 : result.verification_errors.push_back(
1313 0 : strprintf("Verification failed between snapshots at heights %d and %d: "
1314 : "Applied diffs do not match target snapshot",
1315 0 : from_index->nHeight, to_index->nHeight));
1316 : }
1317 :
1318 11 : return is_snapshot_pair_valid;
1319 11 : }
1320 :
1321 0 : std::vector<std::pair<uint256, CDeterministicMNListDiff>> CDeterministicMNManager::RepairSnapshotPair(
1322 : const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot,
1323 : const CDeterministicMNList& to_snapshot, BuildListFromBlockFunc build_list_func, RecalcDiffsResult& result)
1324 : {
1325 0 : CDeterministicMNList current_list = from_snapshot;
1326 : // Temporary storage for recalculated diffs (one per block in this snapshot interval)
1327 0 : std::vector<std::pair<uint256, CDeterministicMNListDiff>> temp_diffs;
1328 0 : temp_diffs.reserve(to_index->nHeight - from_index->nHeight);
1329 :
1330 0 : LogPrintf("CDeterministicMNManager::%s -- Repairing: recalculating diffs between snapshots at heights %d and %d\n",
1331 : __func__, from_index->nHeight, to_index->nHeight);
1332 :
1333 : try {
1334 0 : for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) {
1335 0 : const CBlockIndex* pIndex = to_index->GetAncestor(nHeight);
1336 :
1337 : // Read the actual block from disk
1338 0 : CBlock block;
1339 0 : if (!node::ReadBlockFromDisk(block, pIndex, Params().GetConsensus())) {
1340 0 : result.repair_errors.push_back(strprintf("CRITICAL: Failed to read block at height %d. "
1341 : "Cannot repair - full reindex required.", nHeight));
1342 0 : return {}; // Critical error - cannot continue repair
1343 : }
1344 :
1345 : // Use a dummy coins view to avoid UTXO lookups. At chain tip, coins from
1346 : // historical blocks may already be spent. Since these blocks were fully
1347 : // validated when originally connected, we don't need to re-verify coin
1348 : // availability - we only need to extract special transactions.
1349 0 : CCoinsView view_dummy;
1350 0 : CCoinsViewCache view(&view_dummy);
1351 :
1352 : // Build the new list by processing this block's special transactions
1353 : // Starting from current_list (our trusted state), not from corrupted diffs
1354 0 : CDeterministicMNList next_list;
1355 0 : BlockValidationState state;
1356 0 : if (!build_list_func(block, pIndex->pprev, current_list, view, false, state, next_list)) {
1357 0 : result.repair_errors.push_back(
1358 0 : strprintf("CRITICAL: Failed to build list for block at height %d: %s. "
1359 0 : "Cannot repair - full reindex required.", nHeight, state.ToString()));
1360 0 : return {}; // Critical error - cannot continue repair
1361 : }
1362 :
1363 : // Set the correct block hash
1364 0 : next_list.SetBlockHash(pIndex->GetBlockHash());
1365 :
1366 : // Calculate the diff between current and next
1367 0 : CDeterministicMNListDiff recalc_diff = current_list.BuildDiff(next_list);
1368 0 : recalc_diff.nHeight = nHeight;
1369 : // Store in temporary vector for this snapshot pair
1370 0 : temp_diffs.emplace_back(pIndex->GetBlockHash(), recalc_diff);
1371 :
1372 : // Move forward
1373 0 : current_list = next_list; // TODO: make CDeterministicMNList moveable
1374 0 : }
1375 :
1376 : // Verify that applying all diffs results in the target snapshot
1377 0 : if (current_list.IsEqual(to_snapshot)) {
1378 0 : LogPrintf("CDeterministicMNManager::%s -- Successfully recalculated %d diffs between heights %d and %d\n",
1379 : __func__, temp_diffs.size(), from_index->nHeight, to_index->nHeight);
1380 0 : return temp_diffs; // Success - return recalculated diffs
1381 : } else {
1382 0 : result.repair_errors.push_back(
1383 0 : strprintf("CRITICAL: Recalculation failed between snapshots at heights %d and %d: "
1384 : "Applied diffs do not match target snapshot. Cannot repair - full reindex required.",
1385 0 : from_index->nHeight, to_index->nHeight));
1386 0 : return {}; // Failed verification - return empty vector
1387 : }
1388 0 : } catch (const std::exception& e) {
1389 0 : result.repair_errors.push_back(strprintf("CRITICAL: Exception during recalculation: %s. "
1390 0 : "Cannot repair - full reindex required.", e.what()));
1391 0 : return {}; // Exception - return empty vector
1392 0 : }
1393 0 : }
1394 :
1395 7 : void CDeterministicMNManager::WriteRepairedDiffs(
1396 : const std::vector<std::pair<uint256, CDeterministicMNListDiff>>& recalculated_diffs, RecalcDiffsResult& result)
1397 : {
1398 7 : AssertLockNotHeld(cs);
1399 :
1400 7 : if (recalculated_diffs.empty()) {
1401 7 : return;
1402 : }
1403 :
1404 0 : CDBBatch batch(m_evoDb.GetRawDB());
1405 0 : const size_t BATCH_SIZE_THRESHOLD = 1 << 24; // 16MB
1406 0 : size_t diffs_written = 0;
1407 :
1408 0 : LogPrintf("CDeterministicMNManager::%s -- Writing %d repaired diffs to database...\n",
1409 : __func__, recalculated_diffs.size());
1410 :
1411 0 : for (const auto& [block_hash, diff] : recalculated_diffs) {
1412 0 : batch.Write(std::make_pair(DB_LIST_DIFF, block_hash), diff);
1413 0 : diffs_written++;
1414 :
1415 : // Write batch when it gets too large
1416 0 : if (batch.SizeEstimate() >= BATCH_SIZE_THRESHOLD) {
1417 0 : LogPrintf("CDeterministicMNManager::%s -- Flushing batch (%d diffs written so far)...\n",
1418 : __func__, diffs_written);
1419 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1420 0 : batch.Clear();
1421 0 : }
1422 : }
1423 :
1424 : // Write any remaining diffs in the batch
1425 0 : if (batch.SizeEstimate() > 0) {
1426 0 : LogPrintf("CDeterministicMNManager::%s -- Writing final batch...\n", __func__);
1427 0 : m_evoDb.GetRawDB().WriteBatch(batch);
1428 0 : batch.Clear();
1429 0 : }
1430 :
1431 : // Clear caches for repaired diffs so next read gets fresh data from disk
1432 : // Must clear both diff cache and list cache since lists were built from old diffs
1433 0 : LOCK(cs);
1434 0 : for (const auto& [block_hash, diff] : recalculated_diffs) {
1435 0 : mnListDiffsCache.erase(block_hash);
1436 0 : mnListsCache.erase(block_hash);
1437 : }
1438 :
1439 0 : LogPrintf("CDeterministicMNManager::%s -- Successfully repaired %d diffs (caches cleared)\n", __func__,
1440 : recalculated_diffs.size());
1441 7 : }
|