LCOV - code coverage report
Current view: top level - src/chainlock - handler.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 178 183 97.3 %
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/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        9123 : ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool,
      42             :                                    const CMasternodeSync& mn_sync) :
      43        3041 :     m_chainlocks{chainlocks},
      44        3041 :     m_chainman{chainman},
      45        3041 :     mempool{_mempool},
      46        3041 :     m_mn_sync{mn_sync},
      47        3041 :     scheduler{std::make_unique<CScheduler>()},
      48        6082 :     scheduler_thread{
      49        6082 :         std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}
      50        6082 : {
      51        3041 : }
      52             : 
      53        6082 : ChainlockHandler::~ChainlockHandler()
      54        3041 : {
      55        3041 :     scheduler->stop();
      56        3041 :     scheduler_thread->join();
      57        6082 : }
      58             : 
      59        2831 : void ChainlockHandler::Start()
      60             : {
      61        5662 :     scheduler->scheduleEvery(
      62       15932 :         [&]() {
      63       13101 :             CheckActiveState();
      64       13101 :             EnforceBestChainLock();
      65       13101 :             Cleanup();
      66       13101 :         },
      67        2831 :         std::chrono::seconds{5});
      68        2831 : }
      69             : 
      70        3037 : void ChainlockHandler::Stop() { scheduler->stop(); }
      71             : 
      72       58807 : bool ChainlockHandler::AlreadyHave(const CInv& inv) const
      73             : {
      74       58807 :     LOCK(cs);
      75       58807 :     return seenChainLocks.count(inv.hash) != 0;
      76       58807 : }
      77             : 
      78       21321 : void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time)
      79             : {
      80       21321 :     AssertLockNotHeld(cs);
      81       21321 :     LOCK(cs);
      82       23499 :     for (const auto& txid : tx) {
      83        2178 :         txFirstSeenTime.emplace(txid, time);
      84             :     }
      85       21321 : }
      86             : 
      87       14028 : MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig,
      88             :                                                               const llmq::CQuorumManager& qman, const uint256& hash)
      89             : {
      90       14028 :     CheckActiveState();
      91             : 
      92             :     {
      93       14028 :         LOCK(cs);
      94       14028 :         if (!seenChainLocks.emplace(hash, GetTime<std::chrono::seconds>()).second) {
      95         459 :             return {};
      96             :         }
      97             : 
      98             :         // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation)
      99       13569 :         if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) {
     100             :             // no need to process older/same CLSIGs
     101          18 :             return {};
     102             :         }
     103       14028 :     }
     104             : 
     105       27102 :     if (const auto ret = chainlock::VerifyChainLock(Params().GetConsensus(), m_chainman.ActiveChain(), qman, clsig);
     106       13551 :         ret != llmq::VerifyRecSigStatus::Valid) {
     107           1 :         LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__,
     108             :                  clsig.ToString(), std23::to_underlying(ret), from);
     109           1 :         if (from != -1) {
     110           1 :             return MisbehavingError{10};
     111             :         }
     112           0 :         return {};
     113             :     }
     114             : 
     115       27100 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()));
     116             : 
     117       13550 :     if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {};
     118             : 
     119       13549 :     if (pindex) {
     120       27072 :         scheduler->scheduleFromNow(
     121       27072 :             [&]() {
     122       13536 :                 CheckActiveState();
     123       13536 :                 EnforceBestChainLock();
     124       13536 :             },
     125       13536 :             std::chrono::seconds{0});
     126             : 
     127       13536 :         LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__,
     128             :                  clsig.ToString(), from);
     129       13536 :     }
     130       13549 :     const CInv clsig_inv(MSG_CLSIG, hash);
     131       13549 :     return clsig_inv;
     132       14028 : }
     133             : 
     134      220688 : void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
     135             : {
     136      220688 :     if (pindexNew == pindexFork) // blocks were disconnected without any new ones
     137           0 :         return;
     138      220688 :     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      204231 :     if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) {
     147      398542 :         scheduler->scheduleFromNow(
     148      398539 :             [&]() {
     149      199268 :                 CheckActiveState();
     150      199268 :                 EnforceBestChainLock();
     151      199268 :                 Cleanup();
     152      199268 :                 tryLockChainTipScheduled = false;
     153      199268 :             },
     154      199271 :             std::chrono::seconds{0});
     155      199271 :     }
     156      220688 : }
     157             : 
     158      239933 : void ChainlockHandler::CheckActiveState()
     159             : {
     160      239933 :     bool oldIsEnabled = isEnabled;
     161      239933 :     isEnabled = m_chainlocks.IsEnabled();
     162             : 
     163      239933 :     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         688 :         LOCK(cs);
     168         688 :         m_chainlocks.ResetChainlock();
     169         688 :         lastNotifyChainLockBlockIndex = nullptr;
     170         688 :     }
     171      239933 : }
     172             : 
     173       36800 : void ChainlockHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t)
     174             : {
     175       36800 :     if (tx->IsCoinBase() || tx->vin.empty()) {
     176         572 :         return;
     177             :     }
     178             : 
     179       36228 :     LOCK(cs);
     180       36228 :     txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime);
     181       36800 : }
     182             : 
     183      244470 : void ChainlockHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
     184             : {
     185      244470 :     m_chainlocks.AcceptedBlockHeader(pindexNew);
     186      244470 : }
     187             : 
     188      228002 : void ChainlockHandler::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
     189             : {
     190      228002 :     if (!m_mn_sync.IsBlockchainSynced()) {
     191      124351 :         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      103651 :     int64_t curTime = GetTime<std::chrono::seconds>().count();
     196             :     {
     197      103651 :         LOCK(cs);
     198      337310 :         for (const auto& tx : pblock->vtx) {
     199      233659 :             if (!tx->IsCoinBase() && !tx->vin.empty()) {
     200       11589 :                 txFirstSeenTime.emplace(tx->GetHash(), curTime);
     201       11589 :             }
     202             :         }
     203      103651 :     }
     204      228002 : }
     205             : 
     206        6109 : bool ChainlockHandler::IsTxSafeForMining(const uint256& txid) const
     207             : {
     208        6109 :     auto tx_age{0s};
     209             :     {
     210        6109 :         LOCK(cs);
     211        6109 :         auto it = txFirstSeenTime.find(txid);
     212        6109 :         if (it != txFirstSeenTime.end()) {
     213        5990 :             tx_age = GetTime<std::chrono::seconds>() - it->second;
     214        5990 :         }
     215        6109 :     }
     216             : 
     217        6109 :     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      225905 : void ChainlockHandler::EnforceBestChainLock()
     223             : {
     224      225905 :     if (!isEnabled) {
     225      141085 :         return;
     226             :     }
     227             : 
     228       84820 :     AssertLockNotHeld(cs);
     229       84820 :     AssertLockNotHeld(cs_main);
     230             : 
     231             : 
     232      448020 :     auto [clsig, currentBestChainLockBlockIndex] = m_chainlocks.GetBestChainlockWithPindex();
     233       84820 :     if (currentBestChainLockBlockIndex == nullptr) {
     234             :         // we don't have the header/block, so we can't do anything right now
     235       25735 :         return;
     236             :     }
     237             : 
     238       59085 :     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       59085 :     LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__,
     244             :              currentBestChainLockBlockIndex->GetBlockHash().ToString(), clsig.ToString());
     245       59085 :     m_chainman.ActiveChainstate().EnforceBlock(dummy_state, currentBestChainLockBlockIndex);
     246             : 
     247             : 
     248      118170 :     if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainman.ActiveTip()->GetAncestor(
     249       59085 :                                                      currentBestChainLockBlockIndex->nHeight)) !=
     250       59085 :         currentBestChainLockBlockIndex) {
     251          53 :         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          53 :         LOCK(::cs_main);
     256          53 :         if (m_chainman.ActiveTip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex) {
     257          23 :             return;
     258             :         }
     259          53 :     }
     260             : 
     261             :     {
     262       59062 :         LOCK(cs);
     263      118124 :         if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return;
     264       13549 :         lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;
     265       59062 :     }
     266             : 
     267       13549 :     GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, std::make_shared<chainlock::ChainLockSig>(clsig),
     268       13549 :                                      clsig.ToString());
     269       13549 :     uiInterface.NotifyChainLock(clsig.getBlockHash().ToString(), clsig.getHeight());
     270       13549 :     ::g_stats_client->gauge("chainlocks.blockHeight", clsig.getHeight(), 1.0f);
     271      225905 : }
     272             : 
     273        2099 : void ChainlockHandler::CleanupFromSigner(const std::vector<std::shared_ptr<Uint256HashSet>>& cleanup_txes)
     274             : {
     275        2099 :     LOCK(cs);
     276       46219 :     for (const auto& tx : cleanup_txes) {
     277       45589 :         for (const uint256& txid : *tx) {
     278        1469 :             txFirstSeenTime.erase(txid);
     279             :         }
     280             :     }
     281        2099 : }
     282             : 
     283      212369 : void ChainlockHandler::Cleanup()
     284             : {
     285      212369 :     constexpr auto CLEANUP_INTERVAL{30s};
     286      212369 :     if (!m_mn_sync.IsBlockchainSynced()) {
     287      106101 :         return;
     288             :     }
     289             : 
     290      106268 :     if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
     291      100539 :         return;
     292             :     }
     293             : 
     294             :     {
     295        5729 :         LOCK(cs);
     296       38579 :         for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) {
     297       32850 :             if (GetTime<std::chrono::seconds>() - it->second >= CLEANUP_SEEN_TIMEOUT) {
     298         426 :                 it = seenChainLocks.erase(it);
     299         426 :             } else {
     300       32424 :                 ++it;
     301             :             }
     302             :         }
     303        5729 :     }
     304             : 
     305        5729 :     LOCK(::cs_main);
     306        5729 :     LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls
     307       22566 :     for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) {
     308       16837 :         uint256 hashBlock;
     309       33674 :         if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) {
     310             :             // tx has vanished, probably due to conflicts
     311          64 :             it = txFirstSeenTime.erase(it);
     312       16837 :         } else if (!hashBlock.IsNull()) {
     313       16007 :             const auto* pindex = m_chainman.m_blockman.LookupBlockIndex(hashBlock);
     314       16007 :             assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index
     315       31326 :             if (m_chainman.ActiveTip()->GetAncestor(pindex->nHeight) == pindex &&
     316       15319 :                 m_chainman.ActiveChain().Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) {
     317             :                 // tx is sufficiently deep, we can stop tracking it
     318        6400 :                 it = txFirstSeenTime.erase(it);
     319        6400 :             } else {
     320        9607 :                 ++it;
     321             :             }
     322       16007 :         } else {
     323         766 :             ++it;
     324             :         }
     325             :     }
     326      212369 : }
     327             : 
     328             : } // namespace chainlock

Generated by: LCOV version 1.16