LCOV - code coverage report
Current view: top level - src/chainlock - handler.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 31 183 16.9 %
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/X11 software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <chainlock/handler.h>
       6             : 
       7             : #include <chain.h>
       8             : #include <chainlock/chainlock.h>
       9             : #include <chainlock/clsig.h>
      10             : #include <chainparams.h>
      11             : #include <consensus/validation.h>
      12             : #include <instantsend/instantsend.h>
      13             : #include <llmq/quorumsman.h>
      14             : #include <masternode/sync.h>
      15             : #include <msg_result.h>
      16             : #include <node/interface_ui.h>
      17             : #include <scheduler.h>
      18             : #include <stats/client.h>
      19             : #include <txmempool.h>
      20             : #include <util/std23.h>
      21             : #include <util/thread.h>
      22             : #include <util/time.h>
      23             : #include <validation.h>
      24             : #include <validationinterface.h>
      25             : 
      26             : // Forward declaration to break dependency over node/transaction.h
      27             : namespace node {
      28             : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
      29             :                                const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
      30             : } // namespace node
      31             : 
      32             : using node::GetTransaction;
      33             : 
      34             : namespace {
      35             : static constexpr auto CLEANUP_SEEN_TIMEOUT{24h};
      36             : //! How long to wait for islocks until we consider a block with non-islocked TXs to be safe to sign
      37             : static constexpr auto WAIT_FOR_ISLOCK_TIMEOUT{10min};
      38             : } // anonymous namespace
      39             : 
      40             : namespace chainlock {
      41         552 : ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool,
      42             :                                    const CMasternodeSync& mn_sync) :
      43         184 :     m_chainlocks{chainlocks},
      44         184 :     m_chainman{chainman},
      45         184 :     mempool{_mempool},
      46         184 :     m_mn_sync{mn_sync},
      47         184 :     scheduler{std::make_unique<CScheduler>()},
      48         368 :     scheduler_thread{
      49         368 :         std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}
      50         368 : {
      51         184 : }
      52             : 
      53         368 : ChainlockHandler::~ChainlockHandler()
      54         184 : {
      55         184 :     scheduler->stop();
      56         184 :     scheduler_thread->join();
      57         368 : }
      58             : 
      59           0 : void ChainlockHandler::Start()
      60             : {
      61           0 :     scheduler->scheduleEvery(
      62           0 :         [&]() {
      63           0 :             CheckActiveState();
      64           0 :             EnforceBestChainLock();
      65           0 :             Cleanup();
      66           0 :         },
      67           0 :         std::chrono::seconds{5});
      68           0 : }
      69             : 
      70         180 : void ChainlockHandler::Stop() { scheduler->stop(); }
      71             : 
      72           0 : bool ChainlockHandler::AlreadyHave(const CInv& inv) const
      73             : {
      74           0 :     LOCK(cs);
      75           0 :     return seenChainLocks.count(inv.hash) != 0;
      76           0 : }
      77             : 
      78        2018 : void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time)
      79             : {
      80        2018 :     AssertLockNotHeld(cs);
      81        2018 :     LOCK(cs);
      82        4044 :     for (const auto& txid : tx) {
      83        2026 :         txFirstSeenTime.emplace(txid, time);
      84             :     }
      85        2018 : }
      86             : 
      87           0 : MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig,
      88             :                                                               const llmq::CQuorumManager& qman, const uint256& hash)
      89             : {
      90           0 :     CheckActiveState();
      91             : 
      92             :     {
      93           0 :         LOCK(cs);
      94           0 :         if (!seenChainLocks.emplace(hash, GetTime<std::chrono::seconds>()).second) {
      95           0 :             return {};
      96             :         }
      97             : 
      98             :         // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation)
      99           0 :         if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) {
     100             :             // no need to process older/same CLSIGs
     101           0 :             return {};
     102             :         }
     103           0 :     }
     104             : 
     105           0 :     if (const auto ret = chainlock::VerifyChainLock(Params().GetConsensus(), m_chainman.ActiveChain(), qman, clsig);
     106           0 :         ret != llmq::VerifyRecSigStatus::Valid) {
     107           0 :         LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__,
     108             :                  clsig.ToString(), std23::to_underlying(ret), from);
     109           0 :         if (from != -1) {
     110           0 :             return MisbehavingError{10};
     111             :         }
     112           0 :         return {};
     113             :     }
     114             : 
     115           0 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()));
     116             : 
     117           0 :     if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {};
     118             : 
     119           0 :     if (pindex) {
     120           0 :         scheduler->scheduleFromNow(
     121           0 :             [&]() {
     122           0 :                 CheckActiveState();
     123           0 :                 EnforceBestChainLock();
     124           0 :             },
     125           0 :             std::chrono::seconds{0});
     126             : 
     127           0 :         LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__,
     128             :                  clsig.ToString(), from);
     129           0 :     }
     130           0 :     const CInv clsig_inv(MSG_CLSIG, hash);
     131           0 :     return clsig_inv;
     132           0 : }
     133             : 
     134           0 : void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
     135             : {
     136           0 :     if (pindexNew == pindexFork) // blocks were disconnected without any new ones
     137           0 :         return;
     138           0 :     if (fInitialDownload) return;
     139             : 
     140             :     // TODO: reconsider removing scheduler from here, because UpdatedBlockTip is already async call from notification
     141             :     // scheduler don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that
     142             :     // cs_main is never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to
     143             :     // EnforceBestChainLock switching chains.
     144             :     // atomic[If tryLockChainTipScheduled is false, do (set it to true] and schedule signing).
     145             : 
     146           0 :     if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) {
     147           0 :         scheduler->scheduleFromNow(
     148           0 :             [&]() {
     149           0 :                 CheckActiveState();
     150           0 :                 EnforceBestChainLock();
     151           0 :                 Cleanup();
     152           0 :                 tryLockChainTipScheduled = false;
     153           0 :             },
     154           0 :             std::chrono::seconds{0});
     155           0 :     }
     156           0 : }
     157             : 
     158           0 : void ChainlockHandler::CheckActiveState()
     159             : {
     160           0 :     bool oldIsEnabled = isEnabled;
     161           0 :     isEnabled = m_chainlocks.IsEnabled();
     162             : 
     163           0 :     if (!oldIsEnabled && isEnabled) {
     164             :         // ChainLocks got activated just recently, but it's possible that it was already running before, leaving
     165             :         // us with some stale values which we should not try to enforce anymore (there probably was a good reason
     166             :         // to disable spork19)
     167           0 :         LOCK(cs);
     168           0 :         m_chainlocks.ResetChainlock();
     169           0 :         lastNotifyChainLockBlockIndex = nullptr;
     170           0 :     }
     171           0 : }
     172             : 
     173           0 : void ChainlockHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t)
     174             : {
     175           0 :     if (tx->IsCoinBase() || tx->vin.empty()) {
     176           0 :         return;
     177             :     }
     178             : 
     179           0 :     LOCK(cs);
     180           0 :     txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime);
     181           0 : }
     182             : 
     183           0 : void ChainlockHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
     184             : {
     185           0 :     m_chainlocks.AcceptedBlockHeader(pindexNew);
     186           0 : }
     187             : 
     188           0 : void ChainlockHandler::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
     189             : {
     190           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     191           0 :         return;
     192             :     }
     193             : 
     194             :     // We listen for BlockConnected so that we can collect all TX ids of all included TXs of newly received blocks
     195           0 :     int64_t curTime = GetTime<std::chrono::seconds>().count();
     196             :     {
     197           0 :         LOCK(cs);
     198           0 :         for (const auto& tx : pblock->vtx) {
     199           0 :             if (!tx->IsCoinBase() && !tx->vin.empty()) {
     200           0 :                 txFirstSeenTime.emplace(tx->GetHash(), curTime);
     201           0 :             }
     202             :         }
     203           0 :     }
     204           0 : }
     205             : 
     206        2143 : bool ChainlockHandler::IsTxSafeForMining(const uint256& txid) const
     207             : {
     208        2143 :     auto tx_age{0s};
     209             :     {
     210        2143 :         LOCK(cs);
     211        2143 :         auto it = txFirstSeenTime.find(txid);
     212        2143 :         if (it != txFirstSeenTime.end()) {
     213        2035 :             tx_age = GetTime<std::chrono::seconds>() - it->second;
     214        2035 :         }
     215        2143 :     }
     216             : 
     217        2143 :     return tx_age >= WAIT_FOR_ISLOCK_TIMEOUT;
     218           0 : }
     219             : 
     220             : // WARNING: cs_main and cs should not be held!
     221             : // This should also not be called from validation signals, as this might result in recursive calls
     222           0 : void ChainlockHandler::EnforceBestChainLock()
     223             : {
     224           0 :     if (!isEnabled) {
     225           0 :         return;
     226             :     }
     227             : 
     228           0 :     AssertLockNotHeld(cs);
     229           0 :     AssertLockNotHeld(cs_main);
     230             : 
     231             : 
     232           0 :     auto [clsig, currentBestChainLockBlockIndex] = m_chainlocks.GetBestChainlockWithPindex();
     233           0 :     if (currentBestChainLockBlockIndex == nullptr) {
     234             :         // we don't have the header/block, so we can't do anything right now
     235           0 :         return;
     236             :     }
     237             : 
     238           0 :     BlockValidationState dummy_state;
     239             : 
     240             :     // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain.
     241             :     // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig
     242             :     // and mark all of them as conflicting.
     243           0 :     LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__,
     244             :              currentBestChainLockBlockIndex->GetBlockHash().ToString(), clsig.ToString());
     245           0 :     m_chainman.ActiveChainstate().EnforceBlock(dummy_state, currentBestChainLockBlockIndex);
     246             : 
     247             : 
     248           0 :     if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainman.ActiveTip()->GetAncestor(
     249           0 :                                                      currentBestChainLockBlockIndex->nHeight)) !=
     250           0 :         currentBestChainLockBlockIndex) {
     251           0 :         if (!m_chainman.ActiveChainstate().ActivateBestChain(dummy_state)) {
     252           0 :             LogPrintf("ChainlockHandler::%s -- ActivateBestChain failed: %s\n", __func__, dummy_state.ToString());
     253           0 :             return;
     254             :         }
     255           0 :         LOCK(::cs_main);
     256           0 :         if (m_chainman.ActiveTip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex) {
     257           0 :             return;
     258             :         }
     259           0 :     }
     260             : 
     261             :     {
     262           0 :         LOCK(cs);
     263           0 :         if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return;
     264           0 :         lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;
     265           0 :     }
     266             : 
     267           0 :     GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, std::make_shared<chainlock::ChainLockSig>(clsig),
     268           0 :                                      clsig.ToString());
     269           0 :     uiInterface.NotifyChainLock(clsig.getBlockHash().ToString(), clsig.getHeight());
     270           0 :     ::g_stats_client->gauge("chainlocks.blockHeight", clsig.getHeight(), 1.0f);
     271           0 : }
     272             : 
     273           0 : void ChainlockHandler::CleanupFromSigner(const std::vector<std::shared_ptr<Uint256HashSet>>& cleanup_txes)
     274             : {
     275           0 :     LOCK(cs);
     276           0 :     for (const auto& tx : cleanup_txes) {
     277           0 :         for (const uint256& txid : *tx) {
     278           0 :             txFirstSeenTime.erase(txid);
     279             :         }
     280             :     }
     281           0 : }
     282             : 
     283           0 : void ChainlockHandler::Cleanup()
     284             : {
     285           0 :     constexpr auto CLEANUP_INTERVAL{30s};
     286           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     287           0 :         return;
     288             :     }
     289             : 
     290           0 :     if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
     291           0 :         return;
     292             :     }
     293             : 
     294             :     {
     295           0 :         LOCK(cs);
     296           0 :         for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) {
     297           0 :             if (GetTime<std::chrono::seconds>() - it->second >= CLEANUP_SEEN_TIMEOUT) {
     298           0 :                 it = seenChainLocks.erase(it);
     299           0 :             } else {
     300           0 :                 ++it;
     301             :             }
     302             :         }
     303           0 :     }
     304             : 
     305           0 :     LOCK(::cs_main);
     306           0 :     LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls
     307           0 :     for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) {
     308           0 :         uint256 hashBlock;
     309           0 :         if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) {
     310             :             // tx has vanished, probably due to conflicts
     311           0 :             it = txFirstSeenTime.erase(it);
     312           0 :         } else if (!hashBlock.IsNull()) {
     313           0 :             const auto* pindex = m_chainman.m_blockman.LookupBlockIndex(hashBlock);
     314           0 :             assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index
     315           0 :             if (m_chainman.ActiveTip()->GetAncestor(pindex->nHeight) == pindex &&
     316           0 :                 m_chainman.ActiveChain().Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) {
     317             :                 // tx is sufficiently deep, we can stop tracking it
     318           0 :                 it = txFirstSeenTime.erase(it);
     319           0 :             } else {
     320           0 :                 ++it;
     321             :             }
     322           0 :         } else {
     323           0 :             ++it;
     324             :         }
     325             :     }
     326           0 : }
     327             : 
     328             : } // namespace chainlock

Generated by: LCOV version 1.16