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 0 : 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 0 : llmq::CycleData ret;
28 0 : ret.m_cycle_index = index_tip->GetAncestor(height);
29 0 : if (!ret.m_cycle_index) {
30 0 : error = "Cannot find block";
31 0 : return std::nullopt;
32 : }
33 0 : ret.m_work_index = ret.m_cycle_index->GetAncestor(ret.m_cycle_index->nHeight - llmq::WORK_DIFF_DEPTH);
34 0 : if (!ret.m_work_index) {
35 0 : error = "Cannot find work block";
36 0 : return std::nullopt;
37 : }
38 0 : if (!skip_snap) {
39 0 : if (auto opt_snap = qsnapman.GetSnapshotForBlock(llmq_type, ret.m_cycle_index); opt_snap.has_value()) {
40 0 : ret.m_snap = opt_snap.value();
41 0 : } else {
42 0 : error = "Cannot find quorum snapshot";
43 0 : return std::nullopt;
44 : }
45 0 : }
46 0 : return ret;
47 0 : }
48 : } // anonymous namespace
49 :
50 : namespace llmq {
51 0 : 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 0 : AssertLockHeld(::cs_main);
57 :
58 0 : std::vector<const CBlockIndex*> baseBlockIndexes;
59 0 : 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 0 : for (const auto& blockHash : request.baseBlockHashes) {
68 0 : const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(blockHash);
69 0 : if (!blockIndex) {
70 0 : errorRet = strprintf("block %s not found", blockHash.ToString());
71 0 : return false;
72 : }
73 0 : 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 0 : 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 0 : std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
82 0 : [](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
83 0 : 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 0 : baseBlockIndexes.erase(std::unique(baseBlockIndexes.begin(), baseBlockIndexes.end()),
87 0 : baseBlockIndexes.end());
88 0 : }
89 : }
90 :
91 0 : const CBlockIndex* tipBlockIndex = chainman.ActiveChain().Tip();
92 0 : if (!tipBlockIndex) {
93 0 : errorRet = strprintf("tip block not found");
94 0 : return false;
95 : }
96 0 : 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 0 : const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(request.blockRequestHash);
105 0 : 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 0 : 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 0 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
115 0 : assert(llmq_params_opt.has_value());
116 :
117 0 : const int cycleLength = llmq_params_opt->dkgInterval;
118 :
119 0 : auto cycle_base_opt = ConstructCycle(qsnapman, llmqType, /*skip_snap=*/true,
120 0 : /*height=*/blockIndex->nHeight - (blockIndex->nHeight % cycleLength),
121 0 : blockIndex, errorRet);
122 0 : if (!cycle_base_opt.has_value()) {
123 0 : return false;
124 : }
125 0 : 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 0 : response.extraShare = request.extraShare;
136 :
137 0 : auto target_cycles{response.GetCycles()};
138 0 : for (size_t idx{0}; idx < target_cycles.size(); idx++) {
139 0 : auto cycle_opt = ConstructCycle(qsnapman, llmqType, /*skip_snap=*/false,
140 0 : /*height=*/cycle_base_opt->m_cycle_index->nHeight - (cycleLength * (idx + 1)),
141 0 : tipBlockIndex, errorRet);
142 0 : if (!cycle_opt.has_value()) {
143 0 : return false;
144 : }
145 0 : 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 0 : *target_cycles[idx] = cycle_opt.value();
154 0 : }
155 :
156 0 : std::set<int> snapshotHeightsNeeded;
157 0 : for (const auto& obj : qblockman.GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, blockIndex, /*cycle=*/0)) {
158 0 : auto [qc, minedBlockHash] = qblockman.GetMinedCommitment(llmqType, obj->GetBlockHash());
159 0 : if (minedBlockHash == uint256::ZERO) {
160 0 : return false;
161 : }
162 0 : response.lastCommitmentPerIndex.emplace_back(std::move(qc));
163 :
164 0 : int quorumCycleStartHeight = obj->nHeight - (obj->nHeight % llmq_params_opt->dkgInterval);
165 0 : snapshotHeightsNeeded.insert(quorumCycleStartHeight - cycleLength);
166 0 : snapshotHeightsNeeded.insert(quorumCycleStartHeight - 2 * cycleLength);
167 0 : snapshotHeightsNeeded.insert(quorumCycleStartHeight - 3 * cycleLength);
168 0 : }
169 :
170 0 : for (auto* cycle : target_cycles) {
171 0 : snapshotHeightsNeeded.erase(cycle->m_cycle_index->nHeight);
172 : }
173 :
174 0 : 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 0 : if (!use_legacy_construction) {
194 0 : for (size_t idx = target_cycles.size(); idx-- > 0;) {
195 0 : auto* cycle{target_cycles[idx]};
196 0 : if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
197 0 : GetLastBaseBlockHash(baseBlockIndexes, cycle->m_work_index,
198 0 : use_legacy_construction),
199 0 : cycle->m_work_index->GetBlockHash(), cycle->m_diff, errorRet)) {
200 0 : return false;
201 : }
202 0 : baseBlockIndexes.push_back(cycle->m_work_index);
203 : }
204 :
205 0 : if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
206 0 : GetLastBaseBlockHash(baseBlockIndexes, cycle_base_opt->m_work_index,
207 0 : use_legacy_construction),
208 0 : cycle_base_opt->m_work_index->GetBlockHash(), response.mnListDiffH, errorRet)) {
209 0 : return false;
210 : }
211 0 : baseBlockIndexes.push_back(cycle_base_opt->m_work_index);
212 :
213 0 : if (!BuildSimplifiedMNListDiff(dmnman, chainman, qblockman, qman,
214 0 : GetLastBaseBlockHash(baseBlockIndexes, tipBlockIndex, use_legacy_construction),
215 0 : tipBlockIndex->GetBlockHash(), response.mnListDiffTip, errorRet)) {
216 0 : return false;
217 : }
218 0 : }
219 0 : return true;
220 0 : }
221 :
222 8 : uint256 GetLastBaseBlockHash(Span<const CBlockIndex*> baseBlockIndexes, const CBlockIndex* blockIndex,
223 : bool use_legacy_construction)
224 : {
225 8 : if (!use_legacy_construction) {
226 2 : std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(),
227 8 : [](const CBlockIndex* a, const CBlockIndex* b) { return a->nHeight < b->nHeight; });
228 2 : }
229 : // default to genesis block
230 8 : uint256 hash{Params().GenesisBlock().GetHash()};
231 34 : for (const auto baseBlock : baseBlockIndexes) {
232 30 : if (baseBlock->nHeight > blockIndex->nHeight) break;
233 26 : hash = baseBlock->GetBlockHash();
234 : }
235 8 : return hash;
236 : }
237 :
238 7330 : 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 8994 : CQuorumSnapshot::~CQuorumSnapshot() = default;
249 :
250 12 : CQuorumRotationInfo::CQuorumRotationInfo() = default;
251 :
252 12 : CQuorumRotationInfo::~CQuorumRotationInfo() = default;
253 :
254 0 : std::vector<CycleData*> CQuorumRotationInfo::GetCycles()
255 : {
256 0 : std::vector<CycleData*> ret{&cycleHMinusC, &cycleHMinus2C, &cycleHMinus3C};
257 0 : if (extraShare) {
258 0 : if (!cycleHMinus4C.has_value()) { cycleHMinus4C = CycleData{}; }
259 : ret.emplace_back(&(cycleHMinus4C.value()));
260 : }
261 : return ret;
262 : }
263 :
264 360 : CQuorumSnapshotManager::CQuorumSnapshotManager(CEvoDB& evoDb) :
265 180 : m_evoDb{evoDb},
266 180 : quorumSnapshotCache{32}
267 180 : {
268 360 : }
269 :
270 360 : CQuorumSnapshotManager::~CQuorumSnapshotManager() = default;
271 :
272 1093 : std::optional<CQuorumSnapshot> CQuorumSnapshotManager::GetSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex)
273 : {
274 1093 : CQuorumSnapshot snapshot = {};
275 :
276 1093 : auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash()));
277 :
278 1093 : LOCK(snapshotCacheCs);
279 : // try using cache before reading from disk
280 1093 : if (quorumSnapshotCache.get(snapshotHash, snapshot)) {
281 458 : return snapshot;
282 : }
283 635 : if (m_evoDb.Read(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot)) {
284 0 : quorumSnapshotCache.insert(snapshotHash, snapshot);
285 0 : return snapshot;
286 : }
287 :
288 635 : return std::nullopt;
289 1093 : }
290 :
291 164 : void CQuorumSnapshotManager::StoreSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex, const CQuorumSnapshot& snapshot)
292 : {
293 164 : auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash()));
294 :
295 : // LOCK(::cs_main);
296 164 : AssertLockNotHeld(m_evoDb.cs);
297 164 : LOCK2(snapshotCacheCs, m_evoDb.cs);
298 164 : m_evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot);
299 164 : quorumSnapshotCache.insert(snapshotHash, snapshot);
300 164 : }
301 : } // namespace llmq
|