LCOV - code coverage report
Current view: top level - src/instantsend - db.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 239 250 95.6 %
Date: 2026-06-25 07:23:43 Functions: 24 24 100.0 %

          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

Generated by: LCOV version 1.16