LCOV - code coverage report
Current view: top level - src/instantsend - db.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 33 250 13.2 %
Date: 2026-06-25 07:23:51 Functions: 8 24 33.3 %

          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

Generated by: LCOV version 1.16