Line data Source code
1 : // Copyright (c) 2019-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 <instantsend/db.h>
6 :
7 : #include <chain.h>
8 : #include <dbwrapper.h>
9 : #include <primitives/block.h>
10 : #include <util/system.h>
11 :
12 : static constexpr std::string_view DB_ARCHIVED_BY_HASH{"is_a2"};
13 : static constexpr std::string_view DB_ARCHIVED_BY_HEIGHT_AND_HASH{"is_a1"};
14 : static constexpr std::string_view DB_HASH_BY_OUTPOINT{"is_in"};
15 : static constexpr std::string_view DB_HASH_BY_TXID{"is_tx"};
16 : static constexpr std::string_view DB_ISLOCK_BY_HASH{"is_i"};
17 : static constexpr std::string_view DB_MINED_BY_HEIGHT_AND_HASH{"is_m"};
18 : static constexpr std::string_view DB_VERSION{"is_v"};
19 :
20 : namespace instantsend {
21 : namespace {
22 0 : static std::tuple<std::string, uint32_t, uint256> BuildInversedISLockKey(std::string_view k, int nHeight,
23 : const uint256& islockHash)
24 : {
25 0 : return std::make_tuple(std::string{k}, htobe32_internal(std::numeric_limits<uint32_t>::max() - nHeight), islockHash);
26 0 : }
27 : } // anonymous namespace
28 :
29 360 : CInstantSendDb::CInstantSendDb(const util::DbWrapperParams& db_params) :
30 : db{util::MakeDbWrapper({db_params.path / "llmq" / "isdb", db_params.memory, db_params.wipe, /*cache_size=*/32 << 20})}
31 180 : {
32 : Upgrade({db_params.path / "llmq" / "isdb", db_params.memory, /*wipe=*/true, /*cache_size=*/32 << 20});
33 180 : }
34 :
35 360 : CInstantSendDb::~CInstantSendDb() = default;
36 :
37 180 : void CInstantSendDb::Upgrade(const util::DbWrapperParams& db_params)
38 : {
39 180 : LOCK(cs_db);
40 180 : int v{0};
41 180 : if (!db->Read(DB_VERSION, v) || v < CInstantSendDb::CURRENT_VERSION) {
42 : // Wipe db
43 180 : db.reset();
44 180 : db = util::MakeDbWrapper(db_params);
45 180 : CDBBatch batch(*db);
46 180 : batch.Write(DB_VERSION, CInstantSendDb::CURRENT_VERSION);
47 : // Sync DB changes to disk
48 180 : db->WriteBatch(batch, /*fSync=*/true);
49 180 : batch.Clear();
50 180 : }
51 180 : }
52 :
53 0 : void CInstantSendDb::WriteNewInstantSendLock(const uint256& hash, const InstantSendLockPtr& islock)
54 : {
55 0 : LOCK(cs_db);
56 0 : CDBBatch batch(*db);
57 0 : batch.Write(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *islock);
58 0 : batch.Write(std::make_tuple(DB_HASH_BY_TXID, islock->txid), hash);
59 0 : for (const auto& in : islock->inputs) {
60 0 : batch.Write(std::make_tuple(DB_HASH_BY_OUTPOINT, in), hash);
61 : }
62 0 : db->WriteBatch(batch);
63 :
64 0 : islockCache.insert(hash, islock);
65 0 : txidCache.insert(islock->txid, hash);
66 0 : for (const auto& in : islock->inputs) {
67 0 : outpointCache.insert(in, hash);
68 : }
69 0 : }
70 :
71 0 : void CInstantSendDb::RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, const InstantSendLock& islock,
72 : bool keep_cache)
73 : {
74 0 : AssertLockHeld(cs_db);
75 :
76 0 : batch.Erase(std::make_tuple(DB_ISLOCK_BY_HASH, hash));
77 0 : batch.Erase(std::make_tuple(DB_HASH_BY_TXID, islock.txid));
78 0 : for (auto& in : islock.inputs) {
79 0 : batch.Erase(std::make_tuple(DB_HASH_BY_OUTPOINT, in));
80 : }
81 :
82 0 : if (!keep_cache) {
83 0 : islockCache.erase(hash);
84 0 : txidCache.erase(islock.txid);
85 0 : for (const auto& in : islock.inputs) {
86 0 : outpointCache.erase(in);
87 : }
88 0 : }
89 0 : }
90 :
91 0 : void CInstantSendDb::WriteInstantSendLockMined(const uint256& hash, int nHeight)
92 : {
93 0 : LOCK(cs_db);
94 0 : CDBBatch batch(*db);
95 0 : WriteInstantSendLockMined(batch, hash, nHeight);
96 0 : db->WriteBatch(batch);
97 0 : }
98 :
99 0 : void CInstantSendDb::WriteInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight)
100 : {
101 0 : AssertLockHeld(cs_db);
102 0 : batch.Write(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash), true);
103 0 : }
104 :
105 0 : void CInstantSendDb::RemoveInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight)
106 : {
107 0 : AssertLockHeld(cs_db);
108 0 : batch.Erase(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash));
109 0 : }
110 :
111 0 : void CInstantSendDb::WriteInstantSendLockArchived(CDBBatch& batch, const uint256& hash, int nHeight)
112 : {
113 0 : AssertLockHeld(cs_db);
114 0 : batch.Write(BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nHeight, hash), true);
115 0 : batch.Write(std::make_tuple(DB_ARCHIVED_BY_HASH, hash), true);
116 0 : }
117 :
118 0 : Uint256HashMap<InstantSendLockPtr> CInstantSendDb::RemoveConfirmedInstantSendLocks(int nUntilHeight)
119 : {
120 0 : LOCK(cs_db);
121 0 : if (nUntilHeight <= best_confirmed_height) {
122 0 : LogPrint(BCLog::ALL, "CInstantSendDb::%s -- Attempting to confirm height %d, however we've already confirmed height %d. This should never happen.\n", __func__,
123 : nUntilHeight, best_confirmed_height);
124 0 : return {};
125 : }
126 0 : best_confirmed_height = nUntilHeight;
127 :
128 0 : auto it = std::unique_ptr<CDBIterator>(db->NewIterator());
129 :
130 0 : auto firstKey = BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256());
131 :
132 0 : it->Seek(firstKey);
133 :
134 0 : CDBBatch batch(*db);
135 0 : Uint256HashMap<InstantSendLockPtr> ret;
136 0 : while (it->Valid()) {
137 0 : decltype(firstKey) curKey;
138 0 : if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_MINED_BY_HEIGHT_AND_HASH) {
139 0 : break;
140 : }
141 0 : uint32_t nHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<1>(curKey));
142 0 : if (nHeight > uint32_t(nUntilHeight)) {
143 0 : break;
144 : }
145 :
146 0 : auto& islockHash = std::get<2>(curKey);
147 :
148 0 : if (auto islock = GetInstantSendLockByHashInternal(islockHash, false)) {
149 0 : RemoveInstantSendLock(batch, islockHash, *islock);
150 0 : ret.try_emplace(islockHash, std::move(islock));
151 0 : }
152 :
153 : // archive the islock hash, so that we're still able to check if we've seen the islock in the past
154 0 : WriteInstantSendLockArchived(batch, islockHash, nHeight);
155 :
156 0 : batch.Erase(curKey);
157 :
158 0 : it->Next();
159 0 : }
160 :
161 0 : db->WriteBatch(batch);
162 :
163 0 : return ret;
164 0 : }
165 :
166 0 : void CInstantSendDb::RemoveArchivedInstantSendLocks(int nUntilHeight)
167 : {
168 0 : LOCK(cs_db);
169 0 : if (nUntilHeight <= 0) {
170 0 : return;
171 : }
172 :
173 0 : auto it = std::unique_ptr<CDBIterator>(db->NewIterator());
174 :
175 0 : auto firstKey = BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256());
176 :
177 0 : it->Seek(firstKey);
178 :
179 0 : CDBBatch batch(*db);
180 0 : while (it->Valid()) {
181 0 : decltype(firstKey) curKey;
182 0 : if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ARCHIVED_BY_HEIGHT_AND_HASH) {
183 0 : break;
184 : }
185 0 : uint32_t nHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<1>(curKey));
186 0 : if (nHeight > uint32_t(nUntilHeight)) {
187 0 : break;
188 : }
189 :
190 0 : auto& islockHash = std::get<2>(curKey);
191 0 : batch.Erase(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash));
192 0 : batch.Erase(curKey);
193 :
194 0 : it->Next();
195 0 : }
196 :
197 0 : db->WriteBatch(batch);
198 0 : }
199 :
200 0 : void CInstantSendDb::WriteBlockInstantSendLocks(const gsl::not_null<std::shared_ptr<const CBlock>>& pblock,
201 : gsl::not_null<const CBlockIndex*> pindexConnected)
202 : {
203 0 : LOCK(cs_db);
204 0 : CDBBatch batch(*db);
205 0 : for (const auto& tx : pblock->vtx) {
206 0 : if (tx->IsCoinBase() || tx->vin.empty()) {
207 : // coinbase and TXs with no inputs can't be locked
208 0 : continue;
209 : }
210 0 : uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash());
211 : // update DB about when an IS lock was mined
212 0 : if (!islockHash.IsNull()) {
213 0 : WriteInstantSendLockMined(batch, islockHash, pindexConnected->nHeight);
214 0 : }
215 : }
216 0 : db->WriteBatch(batch);
217 0 : }
218 :
219 0 : void CInstantSendDb::RemoveBlockInstantSendLocks(const gsl::not_null<std::shared_ptr<const CBlock>>& pblock,
220 : gsl::not_null<const CBlockIndex*> pindexDisconnected)
221 : {
222 0 : LOCK(cs_db);
223 0 : CDBBatch batch(*db);
224 0 : for (const auto& tx : pblock->vtx) {
225 0 : if (tx->IsCoinBase() || tx->vin.empty()) {
226 : // coinbase and TXs with no inputs can't be locked
227 0 : continue;
228 : }
229 0 : uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash());
230 0 : if (!islockHash.IsNull()) {
231 0 : RemoveInstantSendLockMined(batch, islockHash, pindexDisconnected->nHeight);
232 0 : }
233 : }
234 0 : db->WriteBatch(batch);
235 0 : }
236 :
237 598034 : bool CInstantSendDb::KnownInstantSendLock(const uint256& islockHash) const
238 : {
239 598034 : LOCK(cs_db);
240 1196068 : return GetInstantSendLockByHashInternal(islockHash) != nullptr ||
241 598034 : db->Exists(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash));
242 598034 : }
243 :
244 0 : size_t CInstantSendDb::GetInstantSendLockCount() const
245 : {
246 0 : LOCK(cs_db);
247 0 : auto it = std::unique_ptr<CDBIterator>(db->NewIterator());
248 0 : auto firstKey = std::make_tuple(std::string{DB_ISLOCK_BY_HASH}, uint256());
249 :
250 0 : it->Seek(firstKey);
251 :
252 0 : size_t cnt = 0;
253 0 : while (it->Valid()) {
254 0 : decltype(firstKey) curKey;
255 0 : if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ISLOCK_BY_HASH) {
256 0 : break;
257 : }
258 :
259 0 : cnt++;
260 :
261 0 : it->Next();
262 0 : }
263 :
264 0 : return cnt;
265 0 : }
266 :
267 598034 : InstantSendLockPtr CInstantSendDb::GetInstantSendLockByHashInternal(const uint256& hash, bool use_cache) const
268 : {
269 598034 : AssertLockHeld(cs_db);
270 598034 : if (hash.IsNull()) {
271 598034 : return nullptr;
272 : }
273 :
274 0 : InstantSendLockPtr ret;
275 0 : if (use_cache && islockCache.get(hash, ret)) {
276 0 : return ret;
277 : }
278 :
279 0 : ret = std::make_shared<InstantSendLock>();
280 0 : bool exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret);
281 0 : if (!exists || (::SerializeHash(*ret) != hash)) {
282 0 : ret = nullptr;
283 0 : }
284 0 : islockCache.insert(hash, ret);
285 0 : return ret;
286 598034 : }
287 :
288 598034 : uint256 CInstantSendDb::GetInstantSendLockHashByTxidInternal(const uint256& txid) const
289 : {
290 598034 : AssertLockHeld(cs_db);
291 598034 : uint256 islockHash;
292 598034 : if (!txidCache.get(txid, islockHash)) {
293 598034 : if (!db->Read(std::make_tuple(DB_HASH_BY_TXID, txid), islockHash)) {
294 598034 : return {};
295 : }
296 0 : txidCache.insert(txid, islockHash);
297 0 : }
298 0 : return islockHash;
299 598034 : }
300 :
301 0 : InstantSendLockPtr CInstantSendDb::GetInstantSendLockByTxid(const uint256& txid) const
302 : {
303 0 : LOCK(cs_db);
304 0 : return GetInstantSendLockByHashInternal(GetInstantSendLockHashByTxidInternal(txid));
305 0 : }
306 :
307 0 : InstantSendLockPtr CInstantSendDb::GetInstantSendLockByInput(const COutPoint& outpoint) const
308 : {
309 0 : LOCK(cs_db);
310 0 : uint256 islockHash;
311 0 : if (!outpointCache.get(outpoint, islockHash)) {
312 0 : if (!db->Read(std::make_tuple(DB_HASH_BY_OUTPOINT, outpoint), islockHash)) {
313 0 : return nullptr;
314 : }
315 0 : outpointCache.insert(outpoint, islockHash);
316 0 : }
317 0 : return GetInstantSendLockByHashInternal(islockHash);
318 0 : }
319 :
320 0 : std::vector<uint256> CInstantSendDb::GetInstantSendLocksByParent(const uint256& parent) const
321 : {
322 0 : AssertLockHeld(cs_db);
323 0 : auto it = std::unique_ptr<CDBIterator>(db->NewIterator());
324 0 : auto firstKey = std::make_tuple(std::string{DB_HASH_BY_OUTPOINT}, COutPoint(parent, 0));
325 0 : it->Seek(firstKey);
326 :
327 0 : std::vector<uint256> result;
328 :
329 0 : while (it->Valid()) {
330 0 : decltype(firstKey) curKey;
331 0 : if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_HASH_BY_OUTPOINT) {
332 0 : break;
333 : }
334 0 : const auto& outpoint = std::get<1>(curKey);
335 0 : if (outpoint.hash != parent) {
336 0 : break;
337 : }
338 :
339 0 : uint256 islockHash;
340 0 : if (!it->GetValue(islockHash)) {
341 0 : break;
342 : }
343 0 : result.emplace_back(islockHash);
344 0 : it->Next();
345 0 : }
346 :
347 0 : return result;
348 0 : }
349 :
350 0 : std::vector<uint256> CInstantSendDb::RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid,
351 : int nHeight)
352 : {
353 0 : LOCK(cs_db);
354 0 : std::vector<uint256> result;
355 :
356 0 : std::vector<uint256> stack;
357 0 : Uint256HashSet added;
358 0 : stack.emplace_back(txid);
359 :
360 0 : CDBBatch batch(*db);
361 0 : while (!stack.empty()) {
362 0 : auto children = GetInstantSendLocksByParent(stack.back());
363 0 : stack.pop_back();
364 :
365 0 : for (auto& childIslockHash : children) {
366 0 : auto childIsLock = GetInstantSendLockByHashInternal(childIslockHash, false);
367 0 : if (!childIsLock) {
368 0 : continue;
369 : }
370 :
371 0 : RemoveInstantSendLock(batch, childIslockHash, *childIsLock, false);
372 0 : WriteInstantSendLockArchived(batch, childIslockHash, nHeight);
373 0 : result.emplace_back(childIslockHash);
374 :
375 0 : if (added.emplace(childIsLock->txid).second) {
376 0 : stack.emplace_back(childIsLock->txid);
377 0 : }
378 0 : }
379 0 : }
380 :
381 0 : if (auto islock = GetInstantSendLockByHashInternal(islockHash, /*use_cache=*/false)) {
382 0 : RemoveInstantSendLock(batch, islockHash, *islock, false);
383 0 : }
384 0 : WriteInstantSendLockArchived(batch, islockHash, nHeight);
385 0 : result.emplace_back(islockHash);
386 :
387 0 : db->WriteBatch(batch);
388 :
389 0 : return result;
390 0 : }
391 : } // namespace instantsend
|