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
|