LCOV - code coverage report
Current view: top level - src/llmq - snapshot.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 131 189 69.3 %
Date: 2026-06-25 07:23:43 Functions: 22 22 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2021-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 <llmq/snapshot.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <evo/evodb.h>
       9             : #include <evo/simplifiedmns.h>
      10             : #include <evo/smldiff.h>
      11             : #include <llmq/blockprocessor.h>
      12             : #include <llmq/commitment.h>
      13             : #include <validation.h>
      14             : 
      15             : #include <univalue.h>
      16             : 
      17             : #include <algorithm>
      18             : 
      19             : namespace {
      20             : constexpr std::string_view DB_QUORUM_SNAPSHOT{"llmq_S"};
      21             : 
      22             : //! Constructs a llmq::CycleData and populate it with metadata
      23          16 : std::optional<llmq::CycleData> ConstructCycle(llmq::CQuorumSnapshotManager& qsnapman,
      24             :                                               const Consensus::LLMQType& llmq_type, bool skip_snap, int32_t height,
      25             :                                               gsl::not_null<const CBlockIndex*> index_tip, std::string& error)
      26             : {
      27          16 :     llmq::CycleData ret;
      28          16 :     ret.m_cycle_index = index_tip->GetAncestor(height);
      29          16 :     if (!ret.m_cycle_index) {
      30           0 :         error = "Cannot find block";
      31           0 :         return std::nullopt;
      32             :     }
      33          16 :     ret.m_work_index = ret.m_cycle_index->GetAncestor(ret.m_cycle_index->nHeight - llmq::WORK_DIFF_DEPTH);
      34          16 :     if (!ret.m_work_index) {
      35           0 :         error = "Cannot find work block";
      36           0 :         return std::nullopt;
      37             :     }
      38          16 :     if (!skip_snap) {
      39          24 :         if (auto opt_snap = qsnapman.GetSnapshotForBlock(llmq_type, ret.m_cycle_index); opt_snap.has_value()) {
      40          12 :             ret.m_snap = opt_snap.value();
      41          12 :         } else {
      42           0 :             error = "Cannot find quorum snapshot";
      43           0 :             return std::nullopt;
      44             :         }
      45          12 :     }
      46          16 :     return ret;
      47          16 : }
      48             : } // anonymous namespace
      49             : 
      50             : namespace llmq {
      51           4 : bool BuildQuorumRotationInfo(CDeterministicMNManager& dmnman, CQuorumSnapshotManager& qsnapman,
      52             :                              const ChainstateManager& chainman, const CQuorumManager& qman,
      53             :                              const CQuorumBlockProcessor& qblockman, const CGetQuorumRotationInfo& request,
      54             :                              bool use_legacy_construction, CQuorumRotationInfo& response, std::string& errorRet)
      55             : {
      56           4 :     AssertLockHeld(::cs_main);
      57             : 
      58           4 :     std::vector<const CBlockIndex*> baseBlockIndexes;
      59           4 :     if (request.baseBlockHashes.size() == 0) {
      60           0 :         const CBlockIndex* blockIndex = chainman.ActiveChain().Genesis();
      61           0 :         if (!blockIndex) {
      62           0 :             errorRet = strprintf("genesis block not found");
      63           0 :             return false;
      64             :         }
      65           0 :         baseBlockIndexes.push_back(blockIndex);
      66           0 :     } else {
      67          10 :         for (const auto& blockHash : request.baseBlockHashes) {
      68           6 :             const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(blockHash);
      69           6 :             if (!blockIndex) {
      70           0 :                 errorRet = strprintf("block %s not found", blockHash.ToString());
      71           0 :                 return false;
      72             :             }
      73           6 :             if (!chainman.ActiveChain().Contains(blockIndex)) {
      74           0 :                 errorRet = strprintf("block %s is not in the active chain", blockHash.ToString());
      75           0 :                 return false;
      76             :             }
      77           6 :             baseBlockIndexes.push_back(blockIndex);
      78             :         }
      79             :         // Sort in all cases: the legacy path (served to peers < EFFICIENT_QRINFO_VERSION)
      80             :         // relies on the order for baseBlockIndexes.back() and GetLastBaseBlockHash().
      81           4 :         std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
      82           2 :                   [](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
      83           4 :         if (!use_legacy_construction) {
      84             :             // Only deduplicate on the non-legacy path; leave the legacy path untouched so the
      85             :             // wire response to older peers stays bit-for-bit identical to the pre-fix behavior.
      86           4 :             baseBlockIndexes.erase(std::unique(baseBlockIndexes.begin(), baseBlockIndexes.end()),
      87           4 :                                    baseBlockIndexes.end());
      88           4 :         }
      89             :     }
      90             : 
      91           4 :     const CBlockIndex* tipBlockIndex = chainman.ActiveChain().Tip();
      92           4 :     if (!tipBlockIndex) {
      93           0 :         errorRet = strprintf("tip block not found");
      94           0 :         return false;
      95             :     }
      96           4 :     if (use_legacy_construction) {
      97             :         // Build MN list Diff always with highest baseblock
      98           0 :         if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman, baseBlockIndexes.back()->GetBlockHash(),
      99           0 :                                        tipBlockIndex->GetBlockHash(), response.mnListDiffTip, errorRet)) {
     100           0 :             return false;
     101             :         }
     102           0 :     }
     103             : 
     104           4 :     const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(request.blockRequestHash);
     105           4 :     if (!blockIndex) {
     106           0 :         errorRet = strprintf("block not found");
     107           0 :         return false;
     108             :     }
     109             : 
     110             :     // Quorum rotation is enabled only for InstantSend atm.
     111           4 :     Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     112             : 
     113             :     // Since the returned quorums are in reversed order, the most recent one is at index 0
     114           4 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     115           4 :     assert(llmq_params_opt.has_value());
     116             : 
     117           4 :     const int cycleLength = llmq_params_opt->dkgInterval;
     118             : 
     119           4 :     auto cycle_base_opt = ConstructCycle(qsnapman, llmqType, /*skip_snap=*/true,
     120           4 :                                          /*height=*/blockIndex->nHeight - (blockIndex->nHeight % cycleLength),
     121           4 :                                          blockIndex, errorRet);
     122           4 :     if (!cycle_base_opt.has_value()) {
     123           0 :         return false;
     124             :     }
     125           4 :     if (use_legacy_construction) {
     126             :         // Build MN list Diff always with highest baseblock
     127           0 :         if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     128           0 :                                        GetLastBaseBlockHash(baseBlockIndexes, cycle_base_opt->m_work_index,
     129           0 :                                                             use_legacy_construction),
     130           0 :                                        cycle_base_opt->m_work_index->GetBlockHash(), response.mnListDiffH, errorRet)) {
     131           0 :             return false;
     132             :         }
     133           0 :     }
     134             : 
     135           4 :     response.extraShare = request.extraShare;
     136             : 
     137           4 :     auto target_cycles{response.GetCycles()};
     138          16 :     for (size_t idx{0}; idx < target_cycles.size(); idx++) {
     139          12 :         auto cycle_opt = ConstructCycle(qsnapman, llmqType, /*skip_snap=*/false,
     140          12 :                                         /*height=*/cycle_base_opt->m_cycle_index->nHeight - (cycleLength * (idx + 1)),
     141          12 :                                         tipBlockIndex, errorRet);
     142          12 :         if (!cycle_opt.has_value()) {
     143           0 :             return false;
     144             :         }
     145          12 :         if (use_legacy_construction) {
     146           0 :             if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     147           0 :                                            GetLastBaseBlockHash(baseBlockIndexes, cycle_opt->m_work_index,
     148           0 :                                                                 use_legacy_construction),
     149           0 :                                            cycle_opt->m_work_index->GetBlockHash(), cycle_opt->m_diff, errorRet)) {
     150           0 :                 return false;
     151             :             }
     152           0 :         }
     153          12 :         *target_cycles[idx] = cycle_opt.value();
     154          12 :     }
     155             : 
     156           4 :     std::set<int> snapshotHeightsNeeded;
     157          12 :     for (const auto& obj : qblockman.GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, blockIndex, /*cycle=*/0)) {
     158          16 :         auto [qc, minedBlockHash] = qblockman.GetMinedCommitment(llmqType, obj->GetBlockHash());
     159           8 :         if (minedBlockHash == uint256::ZERO) {
     160           0 :             return false;
     161             :         }
     162          16 :         response.lastCommitmentPerIndex.emplace_back(std::move(qc));
     163             : 
     164           8 :         int quorumCycleStartHeight = obj->nHeight - (obj->nHeight % llmq_params_opt->dkgInterval);
     165           8 :         snapshotHeightsNeeded.insert(quorumCycleStartHeight - cycleLength);
     166           8 :         snapshotHeightsNeeded.insert(quorumCycleStartHeight - 2 * cycleLength);
     167           8 :         snapshotHeightsNeeded.insert(quorumCycleStartHeight - 3 * cycleLength);
     168           8 :     }
     169             : 
     170          16 :     for (auto* cycle : target_cycles) {
     171          12 :         snapshotHeightsNeeded.erase(cycle->m_cycle_index->nHeight);
     172             :     }
     173             : 
     174           4 :     for (const auto& h : snapshotHeightsNeeded) {
     175           0 :         auto cycle_opt = ConstructCycle(qsnapman, llmqType, /*skip_snap=*/false, /*height=*/h, tipBlockIndex, errorRet);
     176           0 :         if (!cycle_opt.has_value()) {
     177           0 :             return false;
     178             :         }
     179           0 :         response.quorumSnapshotList.push_back(cycle_opt->m_snap);
     180           0 :         CSimplifiedMNListDiff mnhneeded;
     181           0 :         if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     182           0 :                                        GetLastBaseBlockHash(baseBlockIndexes, cycle_opt->m_work_index,
     183           0 :                                                             use_legacy_construction),
     184           0 :                                        cycle_opt->m_work_index->GetBlockHash(), mnhneeded, errorRet)) {
     185           0 :             return false;
     186             :         }
     187           0 :         if (!use_legacy_construction) {
     188           0 :             baseBlockIndexes.push_back(cycle_opt->m_work_index);
     189           0 :         }
     190           0 :         response.mnListDiffList.push_back(mnhneeded);
     191           0 :     }
     192             : 
     193           4 :     if (!use_legacy_construction) {
     194          16 :         for (size_t idx = target_cycles.size(); idx-- > 0;) {
     195          12 :             auto* cycle{target_cycles[idx]};
     196          24 :             if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     197          12 :                                            GetLastBaseBlockHash(baseBlockIndexes, cycle->m_work_index,
     198          12 :                                                                 use_legacy_construction),
     199          12 :                                            cycle->m_work_index->GetBlockHash(), cycle->m_diff, errorRet)) {
     200           0 :                 return false;
     201             :             }
     202          12 :             baseBlockIndexes.push_back(cycle->m_work_index);
     203             :         }
     204             : 
     205           8 :         if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     206           4 :                                        GetLastBaseBlockHash(baseBlockIndexes, cycle_base_opt->m_work_index,
     207           4 :                                                             use_legacy_construction),
     208           4 :                                        cycle_base_opt->m_work_index->GetBlockHash(), response.mnListDiffH, errorRet)) {
     209           0 :             return false;
     210             :         }
     211           4 :         baseBlockIndexes.push_back(cycle_base_opt->m_work_index);
     212             : 
     213           8 :         if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
     214           4 :                                        GetLastBaseBlockHash(baseBlockIndexes, tipBlockIndex, use_legacy_construction),
     215           4 :                                        tipBlockIndex->GetBlockHash(), response.mnListDiffTip, errorRet)) {
     216           0 :             return false;
     217             :         }
     218           4 :     }
     219           4 :     return true;
     220           4 : }
     221             : 
     222          28 : uint256 GetLastBaseBlockHash(Span<const CBlockIndex*> baseBlockIndexes, const CBlockIndex* blockIndex,
     223             :                              bool use_legacy_construction)
     224             : {
     225          28 :     if (!use_legacy_construction) {
     226          22 :         std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
     227          52 :                   [](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
     228          22 :     }
     229             :     // default to genesis block
     230          28 :     uint256 hash{Params().GenesisBlock().GetHash()};
     231         106 :     for (const auto baseBlock : baseBlockIndexes) {
     232          90 :         if (baseBlock->nHeight > blockIndex->nHeight) break;
     233          78 :         hash = baseBlock->GetBlockHash();
     234             :     }
     235          28 :     return hash;
     236             : }
     237             : 
     238       86756 : CQuorumSnapshot::CQuorumSnapshot() = default;
     239             : 
     240          44 : CQuorumSnapshot::CQuorumSnapshot(std::vector<bool> active_quorum_members, SnapshotSkipMode skip_mode,
     241             :                                  std::vector<int> skip_list) :
     242          22 :     activeQuorumMembers(std::move(active_quorum_members)),
     243          22 :     mnSkipListMode(skip_mode),
     244          22 :     mnSkipList(std::move(skip_list))
     245          22 : {
     246          44 : }
     247             : 
     248      131832 : CQuorumSnapshot::~CQuorumSnapshot() = default;
     249             : 
     250          20 : CQuorumRotationInfo::CQuorumRotationInfo() = default;
     251             : 
     252          20 : CQuorumRotationInfo::~CQuorumRotationInfo() = default;
     253             : 
     254           4 : std::vector<CycleData*> CQuorumRotationInfo::GetCycles()
     255             : {
     256           4 :     std::vector<CycleData*> ret{&cycleHMinusC, &cycleHMinus2C, &cycleHMinus3C};
     257           4 :     if (extraShare) {
     258           0 :         if (!cycleHMinus4C.has_value()) { cycleHMinus4C = CycleData{}; }
     259             :         ret.emplace_back(&(cycleHMinus4C.value()));
     260             :     }
     261             :     return ret;
     262             : }
     263             : 
     264        6126 : CQuorumSnapshotManager::CQuorumSnapshotManager(CEvoDB& evoDb) :
     265        3063 :     m_evoDb{evoDb},
     266        3063 :     quorumSnapshotCache{32}
     267        3063 : {
     268        6126 : }
     269             : 
     270        6126 : CQuorumSnapshotManager::~CQuorumSnapshotManager() = default;
     271             : 
     272       15707 : std::optional<CQuorumSnapshot> CQuorumSnapshotManager::GetSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex)
     273             : {
     274       15707 :     CQuorumSnapshot snapshot = {};
     275             : 
     276       15707 :     auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash()));
     277             : 
     278       15707 :     LOCK(snapshotCacheCs);
     279             :     // try using cache before reading from disk
     280       15707 :     if (quorumSnapshotCache.get(snapshotHash, snapshot)) {
     281       10317 :         return snapshot;
     282             :     }
     283        5390 :     if (m_evoDb.Read(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot)) {
     284        1019 :         quorumSnapshotCache.insert(snapshotHash, snapshot);
     285        1019 :         return snapshot;
     286             :     }
     287             : 
     288        4371 :     return std::nullopt;
     289       15707 : }
     290             : 
     291        4859 : void CQuorumSnapshotManager::StoreSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex, const CQuorumSnapshot& snapshot)
     292             : {
     293        4859 :     auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash()));
     294             : 
     295             :     // LOCK(::cs_main);
     296        4859 :     AssertLockNotHeld(m_evoDb.cs);
     297        4859 :     LOCK2(snapshotCacheCs, m_evoDb.cs);
     298        4859 :     m_evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot);
     299        4859 :     quorumSnapshotCache.insert(snapshotHash, snapshot);
     300        4859 : }
     301             : } // namespace llmq

Generated by: LCOV version 1.16