LCOV - code coverage report
Current view: top level - src/chainlock - signing.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 171 180 95.0 %
Date: 2026-06-25 07:23:43 Functions: 18 19 94.7 %

          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 <chainlock/signing.h>
       6             : 
       7             : #include <chainlock/clsig.h>
       8             : #include <chainlock/handler.h>
       9             : #include <instantsend/instantsend.h>
      10             : #include <llmq/signing_shares.h>
      11             : #include <masternode/sync.h>
      12             : #include <msg_result.h>
      13             : #include <node/blockstorage.h>
      14             : #include <scheduler.h>
      15             : #include <util/thread.h>
      16             : #include <validation.h>
      17             : 
      18             : #include <thread>
      19             : 
      20             : using node::ReadBlockFromDisk;
      21             : 
      22             : namespace chainlock {
      23        3300 : ChainLockSigner::ChainLockSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks,
      24             :                                  ChainlockHandler& clhandler, const llmq::CInstantSendManager& isman,
      25             :                                  const llmq::CQuorumManager& qman, llmq::CSigningManager& sigman,
      26             :                                  llmq::CSigSharesManager& shareman, const CMasternodeSync& mn_sync) :
      27         660 :     m_chainstate{chainstate},
      28         660 :     m_chainlocks{chainlocks},
      29         660 :     m_clhandler{clhandler},
      30         660 :     m_isman{isman},
      31         660 :     m_qman{qman},
      32         660 :     m_sigman{sigman},
      33         660 :     m_shareman{shareman},
      34         660 :     m_mn_sync{mn_sync},
      35         660 :     m_scheduler{std::make_unique<CScheduler>()},
      36        1320 :     m_scheduler_thread{
      37        1320 :         std::make_unique<std::thread>(std::thread(util::TraceThread, "cls-schdlr", [&] { m_scheduler->serviceQueue(); }))}
      38        1980 : {
      39         660 : }
      40             : 
      41        1320 : ChainLockSigner::~ChainLockSigner() { Stop(); }
      42             : 
      43         660 : void ChainLockSigner::Start()
      44             : {
      45        1320 :     m_scheduler->scheduleEvery(
      46        8452 :         [&]() {
      47        7792 :             if (!m_chainlocks.IsSigningEnabled()) return;
      48             :             // regularly retry signing the current chaintip as it might have failed before due to missing islocks
      49        5905 :             TrySignChainTip();
      50        5905 :             Cleanup();
      51        7792 :         },
      52         660 :         std::chrono::seconds{5});
      53         660 : }
      54             : 
      55        1320 : void ChainLockSigner::Stop()
      56             : {
      57        1320 :     m_scheduler->stop();
      58        1320 :     if (m_scheduler_thread->joinable()) m_scheduler_thread->join();
      59        1320 : }
      60             : 
      61         660 : void ChainLockSigner::RegisterRecoveryInterface()
      62             : {
      63         660 :     m_sigman.RegisterRecoveredSigsListener(this);
      64         660 : }
      65             : 
      66         660 : void ChainLockSigner::UnregisterRecoveryInterface()
      67             : {
      68         660 :     m_sigman.UnregisterRecoveredSigsListener(this);
      69         660 : }
      70             : 
      71       86421 : void ChainLockSigner::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
      72             : {
      73       86421 :     TrySignChainTip();
      74       86421 : }
      75             : 
      76       92326 : void ChainLockSigner::TrySignChainTip()
      77             : {
      78       92326 :     TRY_LOCK(cs_try_sign, locked);
      79       92326 :     if (!locked) {
      80         116 :         return;
      81             :     }
      82             : 
      83       92210 :     if (!m_mn_sync.IsBlockchainSynced()) {
      84        6779 :         return;
      85             :     }
      86             : 
      87       85431 :     if (!m_chainlocks.IsEnabled() || !m_chainlocks.IsSigningEnabled()) {
      88       25366 :         return;
      89             :     }
      90             : 
      91      120130 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip());
      92             : 
      93       60065 :     if (!pindex || !pindex->pprev) {
      94           0 :         return;
      95             :     }
      96             : 
      97             :     // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
      98             :     // To simplify the initial implementation, we skip this process and directly try to create a CLSIG
      99             :     // This will fail when multiple blocks compete, but we accept this for the initial implementation.
     100             :     // Later, we'll add the multiple attempts process.
     101             : 
     102             :     {
     103       60065 :         LOCK(cs_signer);
     104             : 
     105       60065 :         if (pindex->nHeight == lastSignedHeight) {
     106             :             // already signed this one
     107       18197 :             return;
     108             :         }
     109       60065 :     }
     110             : 
     111       41868 :     if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
     112             :         // already got the same CLSIG or a better one
     113         143 :         return;
     114             :     }
     115             : 
     116       41725 :     if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     117             :         // don't sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce
     118             :         // the correct chain.
     119           0 :         return;
     120             :     }
     121             : 
     122       41725 :     LogPrint(BCLog::CHAINLOCKS, "%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(),
     123             :              pindex->nHeight);
     124             : 
     125             :     // When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is
     126             :     // considered safe when it is islocked or at least known since 10 minutes (from mempool or block). These checks are
     127             :     // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the
     128             :     // way down, we consider all TXs to be safe.
     129       41725 :     if (m_isman.IsInstantSendEnabled()) {
     130       40375 :         const auto* pindexWalk = pindex;
     131      193560 :         while (pindexWalk != nullptr) {
     132      193560 :             if (pindex->nHeight - pindexWalk->nHeight > TX_CONFIRM_THRESHOLD) {
     133             :                 // no need to check further down, safe to assume that TXs below this height won't be
     134             :                 // islocked anymore if they aren't already
     135       18537 :                 LogPrint(BCLog::CHAINLOCKS, "%s -- tip and previous %d blocks all safe\n", __func__, TX_CONFIRM_THRESHOLD);
     136       18537 :                 break;
     137             :             }
     138      175023 :             if (m_chainlocks.HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {
     139             :                 // we don't care about islocks for TXs that are ChainLocked already
     140       19517 :                 LogPrint(BCLog::CHAINLOCKS, "%s -- chainlock at height %d\n", __func__, pindexWalk->nHeight);
     141       19517 :                 break;
     142             :             }
     143             : 
     144      155506 :             auto txids = GetBlockTxs(pindexWalk->GetBlockHash());
     145      155506 :             if (!txids) {
     146           0 :                 pindexWalk = pindexWalk->pprev;
     147           0 :                 continue;
     148             :             }
     149             : 
     150      156650 :             for (const auto& txid : *txids) {
     151        3465 :                 if (!m_clhandler.IsTxSafeForMining(txid) && !m_isman.IsLocked(txid)) {
     152        2321 :                     LogPrint(BCLog::CHAINLOCKS, /* Continued */
     153             :                              "%s -- not signing block %s due to TX %s not being islocked and not old enough.\n",
     154             :                              __func__, pindexWalk->GetBlockHash().ToString(), txid.ToString());
     155        2321 :                     return;
     156             :                 }
     157             :             }
     158             : 
     159      153185 :             pindexWalk = pindexWalk->pprev;
     160      155506 :         }
     161       38054 :     }
     162             : 
     163       39404 :     uint256 requestId = GenSigRequestId(pindex->nHeight);
     164       39404 :     uint256 msgHash = pindex->GetBlockHash();
     165             : 
     166       39404 :     if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
     167             :         // might have happened while we didn't hold cs
     168           1 :         return;
     169             :     }
     170             :     {
     171       39403 :         LOCK(cs_signer);
     172       39403 :         lastSignedHeight = pindex->nHeight;
     173       39403 :         lastSignedRequestId = requestId;
     174       39403 :         lastSignedMsgHash = msgHash;
     175       39403 :     }
     176             : 
     177       39403 :     m_shareman.AsyncSignIfMember(Params().GetConsensus().llmqTypeChainLocks, m_sigman, requestId, msgHash);
     178       92326 : }
     179             : 
     180        2945 : void ChainLockSigner::BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     181             : {
     182        2945 :     AssertLockNotHeld(cs_signer);
     183        2945 :     LOCK(cs_signer);
     184        2945 :     blockTxs.erase(pindex->GetBlockHash());
     185        2945 : }
     186             : 
     187             : 
     188       86746 : void ChainLockSigner::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     189             : {
     190       86746 :     if (!m_mn_sync.IsBlockchainSynced()) {
     191        6649 :         return;
     192             :     }
     193             : 
     194             :     // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are safe.
     195       80097 :     const uint256& hash = pindex->GetBlockHash();
     196             : 
     197       80097 :     AssertLockNotHeld(cs_signer);
     198       80097 :     LOCK(cs_signer);
     199       80097 :     auto it = blockTxs.find(hash);
     200       80097 :     if (it == blockTxs.end()) {
     201             :         // We must create this entry even if there are no lockable transactions in the block, so that TrySignChainTip
     202             :         // later knows about this block
     203       61908 :         it = blockTxs.emplace(hash, std::make_shared<Uint256HashSet>()).first;
     204       61908 :     }
     205       80097 :     auto& txids = *it->second;
     206      259203 :     for (const auto& tx : block->vtx) {
     207      179106 :         if (!tx->IsCoinBase() && !tx->vin.empty()) {
     208        6757 :             txids.emplace(tx->GetHash());
     209        6757 :         }
     210             :     }
     211       86746 : }
     212      155506 : ChainLockSigner::BlockTxs::mapped_type ChainLockSigner::GetBlockTxs(const uint256& blockHash)
     213             : {
     214      155506 :     AssertLockNotHeld(cs_signer);
     215      155506 :     AssertLockNotHeld(::cs_main);
     216             : 
     217      155506 :     ChainLockSigner::BlockTxs::mapped_type ret;
     218             : 
     219             :     {
     220      155506 :         LOCK(cs_signer);
     221      155506 :         auto it = blockTxs.find(blockHash);
     222      155506 :         if (it != blockTxs.end()) {
     223      136203 :             ret = it->second;
     224      136203 :         }
     225      155506 :     }
     226      155506 :     if (!ret) {
     227             :         // This should only happen when freshly started.
     228             :         // If running for some time, SyncTransaction should have been called before which fills blockTxs.
     229       19303 :         LogPrint(BCLog::CHAINLOCKS, "%s -- blockTxs for %s not found. Trying ReadBlockFromDisk\n", __func__,
     230             :                  blockHash.ToString());
     231             : 
     232             :         uint32_t blockTime;
     233             :         {
     234       19303 :             LOCK(::cs_main);
     235       19303 :             const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(blockHash);
     236       19303 :             if (!pindex) {
     237           0 :                 return nullptr;
     238             :             }
     239       19303 :             CBlock block;
     240       19303 :             if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     241           0 :                 return nullptr;
     242             :             }
     243             : 
     244       19303 :             ret = std::make_shared<Uint256HashSet>();
     245       61454 :             for (const auto& tx : block.vtx) {
     246       42151 :                 if (tx->IsCoinBase() || tx->vin.empty()) {
     247       41999 :                     continue;
     248             :                 }
     249         152 :                 ret->emplace(tx->GetHash());
     250             :             }
     251             : 
     252       19303 :             blockTime = block.nTime;
     253       19303 :         }
     254             :         {
     255       19303 :             LOCK(cs_signer);
     256       19303 :             blockTxs.emplace(blockHash, ret);
     257       19303 :         }
     258       19303 :         m_clhandler.UpdateTxFirstSeenMap(*ret, blockTime);
     259       19303 :     }
     260      155506 :     return ret;
     261      155506 : }
     262             : 
     263       26962 : llmq::RecoveredSigResult ChainLockSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
     264             : {
     265       26962 :     if (!m_chainlocks.IsEnabled()) {
     266         341 :         return std::monostate{};
     267             :     }
     268             : 
     269       26621 :     ChainLockSig clsig;
     270             :     {
     271       26621 :         LOCK(cs_signer);
     272             : 
     273       26621 :         if (recoveredSig.getId() != lastSignedRequestId || recoveredSig.getMsgHash() != lastSignedMsgHash) {
     274             :             // this is not what we signed, so lets not create a CLSIG for it
     275       16624 :             return std::monostate{};
     276             :         }
     277        9997 :         if (m_chainlocks.GetBestChainLockHeight() >= lastSignedHeight) {
     278             :             // already got the same or a better CLSIG through the CLSIG message
     279        4938 :             return std::monostate{};
     280             :         }
     281             : 
     282        5059 :         clsig = ChainLockSig(lastSignedHeight, lastSignedMsgHash, recoveredSig.sig.Get());
     283       26621 :     }
     284             :     // TODO: split ProcessNewChainLock into network and non-network variants; when no peer
     285             :     // is specified (node == -1), only m_inventory is ever populated
     286        5059 :     auto clresult = m_clhandler.ProcessNewChainLock(-1, clsig, m_qman, ::SerializeHash(clsig));
     287        5059 :     if (!clresult.m_inventory.empty()) {
     288        4888 :         return clresult.m_inventory.front();
     289             :     }
     290         171 :     return std::monostate{};
     291       26962 : }
     292             : 
     293        5905 : void ChainLockSigner::Cleanup()
     294             : {
     295        5905 :     constexpr auto CLEANUP_INTERVAL{30s};
     296        5905 :     if (!m_mn_sync.IsBlockchainSynced()) {
     297         130 :         return;
     298             :     }
     299             : 
     300        5775 :     if (!m_cleanup_throttler.TryCleanup(CLEANUP_INTERVAL)) {
     301        3676 :         return;
     302             :     }
     303             : 
     304        2099 :     AssertLockNotHeld(cs_signer);
     305        2099 :     std::vector<std::shared_ptr<Uint256HashSet>> removed;
     306        2099 :     LOCK2(::cs_main, cs_signer);
     307       65697 :     for (auto it = blockTxs.begin(); it != blockTxs.end();) {
     308       63598 :         const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(it->first);
     309       63598 :         if (!pindex) {
     310           0 :             it = blockTxs.erase(it);
     311       63598 :         } else if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     312       44120 :             removed.push_back(it->second);
     313       44120 :             it = blockTxs.erase(it);
     314       63598 :         } else if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     315           0 :             it = blockTxs.erase(it);
     316           0 :         } else {
     317       19478 :             ++it;
     318             :         }
     319             :     }
     320        2099 :     m_clhandler.CleanupFromSigner(removed);
     321        5905 : }
     322             : } // namespace chainlock

Generated by: LCOV version 1.16