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