LCOV - code coverage report
Current view: top level - src/chainlock - signing.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 0 180 0.0 %
Date: 2026-06-25 07:23:51 Functions: 0 19 0.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 <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           0 : 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           0 :     m_chainstate{chainstate},
      28           0 :     m_chainlocks{chainlocks},
      29           0 :     m_clhandler{clhandler},
      30           0 :     m_isman{isman},
      31           0 :     m_qman{qman},
      32           0 :     m_sigman{sigman},
      33           0 :     m_shareman{shareman},
      34           0 :     m_mn_sync{mn_sync},
      35           0 :     m_scheduler{std::make_unique<CScheduler>()},
      36           0 :     m_scheduler_thread{
      37           0 :         std::make_unique<std::thread>(std::thread(util::TraceThread, "cls-schdlr", [&] { m_scheduler->serviceQueue(); }))}
      38           0 : {
      39           0 : }
      40             : 
      41           0 : ChainLockSigner::~ChainLockSigner() { Stop(); }
      42             : 
      43           0 : void ChainLockSigner::Start()
      44             : {
      45           0 :     m_scheduler->scheduleEvery(
      46           0 :         [&]() {
      47           0 :             if (!m_chainlocks.IsSigningEnabled()) return;
      48             :             // regularly retry signing the current chaintip as it might have failed before due to missing islocks
      49           0 :             TrySignChainTip();
      50           0 :             Cleanup();
      51           0 :         },
      52           0 :         std::chrono::seconds{5});
      53           0 : }
      54             : 
      55           0 : void ChainLockSigner::Stop()
      56             : {
      57           0 :     m_scheduler->stop();
      58           0 :     if (m_scheduler_thread->joinable()) m_scheduler_thread->join();
      59           0 : }
      60             : 
      61           0 : void ChainLockSigner::RegisterRecoveryInterface()
      62             : {
      63           0 :     m_sigman.RegisterRecoveredSigsListener(this);
      64           0 : }
      65             : 
      66           0 : void ChainLockSigner::UnregisterRecoveryInterface()
      67             : {
      68           0 :     m_sigman.UnregisterRecoveredSigsListener(this);
      69           0 : }
      70             : 
      71           0 : void ChainLockSigner::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
      72             : {
      73           0 :     TrySignChainTip();
      74           0 : }
      75             : 
      76           0 : void ChainLockSigner::TrySignChainTip()
      77             : {
      78           0 :     TRY_LOCK(cs_try_sign, locked);
      79           0 :     if (!locked) {
      80           0 :         return;
      81             :     }
      82             : 
      83           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
      84           0 :         return;
      85             :     }
      86             : 
      87           0 :     if (!m_chainlocks.IsEnabled() || !m_chainlocks.IsSigningEnabled()) {
      88           0 :         return;
      89             :     }
      90             : 
      91           0 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip());
      92             : 
      93           0 :     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           0 :         LOCK(cs_signer);
     104             : 
     105           0 :         if (pindex->nHeight == lastSignedHeight) {
     106             :             // already signed this one
     107           0 :             return;
     108             :         }
     109           0 :     }
     110             : 
     111           0 :     if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
     112             :         // already got the same CLSIG or a better one
     113           0 :         return;
     114             :     }
     115             : 
     116           0 :     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           0 :     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           0 :     if (m_isman.IsInstantSendEnabled()) {
     130           0 :         const auto* pindexWalk = pindex;
     131           0 :         while (pindexWalk != nullptr) {
     132           0 :             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           0 :                 LogPrint(BCLog::CHAINLOCKS, "%s -- tip and previous %d blocks all safe\n", __func__, TX_CONFIRM_THRESHOLD);
     136           0 :                 break;
     137             :             }
     138           0 :             if (m_chainlocks.HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {
     139             :                 // we don't care about islocks for TXs that are ChainLocked already
     140           0 :                 LogPrint(BCLog::CHAINLOCKS, "%s -- chainlock at height %d\n", __func__, pindexWalk->nHeight);
     141           0 :                 break;
     142             :             }
     143             : 
     144           0 :             auto txids = GetBlockTxs(pindexWalk->GetBlockHash());
     145           0 :             if (!txids) {
     146           0 :                 pindexWalk = pindexWalk->pprev;
     147           0 :                 continue;
     148             :             }
     149             : 
     150           0 :             for (const auto& txid : *txids) {
     151           0 :                 if (!m_clhandler.IsTxSafeForMining(txid) && !m_isman.IsLocked(txid)) {
     152           0 :                     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           0 :                     return;
     156             :                 }
     157             :             }
     158             : 
     159           0 :             pindexWalk = pindexWalk->pprev;
     160           0 :         }
     161           0 :     }
     162             : 
     163           0 :     uint256 requestId = GenSigRequestId(pindex->nHeight);
     164           0 :     uint256 msgHash = pindex->GetBlockHash();
     165             : 
     166           0 :     if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
     167             :         // might have happened while we didn't hold cs
     168           0 :         return;
     169             :     }
     170             :     {
     171           0 :         LOCK(cs_signer);
     172           0 :         lastSignedHeight = pindex->nHeight;
     173           0 :         lastSignedRequestId = requestId;
     174           0 :         lastSignedMsgHash = msgHash;
     175           0 :     }
     176             : 
     177           0 :     m_shareman.AsyncSignIfMember(Params().GetConsensus().llmqTypeChainLocks, m_sigman, requestId, msgHash);
     178           0 : }
     179             : 
     180           0 : void ChainLockSigner::BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     181             : {
     182           0 :     AssertLockNotHeld(cs_signer);
     183           0 :     LOCK(cs_signer);
     184           0 :     blockTxs.erase(pindex->GetBlockHash());
     185           0 : }
     186             : 
     187             : 
     188           0 : void ChainLockSigner::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     189             : {
     190           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     191           0 :         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           0 :     const uint256& hash = pindex->GetBlockHash();
     196             : 
     197           0 :     AssertLockNotHeld(cs_signer);
     198           0 :     LOCK(cs_signer);
     199           0 :     auto it = blockTxs.find(hash);
     200           0 :     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           0 :         it = blockTxs.emplace(hash, std::make_shared<Uint256HashSet>()).first;
     204           0 :     }
     205           0 :     auto& txids = *it->second;
     206           0 :     for (const auto& tx : block->vtx) {
     207           0 :         if (!tx->IsCoinBase() && !tx->vin.empty()) {
     208           0 :             txids.emplace(tx->GetHash());
     209           0 :         }
     210             :     }
     211           0 : }
     212           0 : ChainLockSigner::BlockTxs::mapped_type ChainLockSigner::GetBlockTxs(const uint256& blockHash)
     213             : {
     214           0 :     AssertLockNotHeld(cs_signer);
     215           0 :     AssertLockNotHeld(::cs_main);
     216             : 
     217           0 :     ChainLockSigner::BlockTxs::mapped_type ret;
     218             : 
     219             :     {
     220           0 :         LOCK(cs_signer);
     221           0 :         auto it = blockTxs.find(blockHash);
     222           0 :         if (it != blockTxs.end()) {
     223           0 :             ret = it->second;
     224           0 :         }
     225           0 :     }
     226           0 :     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           0 :         LogPrint(BCLog::CHAINLOCKS, "%s -- blockTxs for %s not found. Trying ReadBlockFromDisk\n", __func__,
     230             :                  blockHash.ToString());
     231             : 
     232             :         uint32_t blockTime;
     233             :         {
     234           0 :             LOCK(::cs_main);
     235           0 :             const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(blockHash);
     236           0 :             if (!pindex) {
     237           0 :                 return nullptr;
     238             :             }
     239           0 :             CBlock block;
     240           0 :             if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     241           0 :                 return nullptr;
     242             :             }
     243             : 
     244           0 :             ret = std::make_shared<Uint256HashSet>();
     245           0 :             for (const auto& tx : block.vtx) {
     246           0 :                 if (tx->IsCoinBase() || tx->vin.empty()) {
     247           0 :                     continue;
     248             :                 }
     249           0 :                 ret->emplace(tx->GetHash());
     250             :             }
     251             : 
     252           0 :             blockTime = block.nTime;
     253           0 :         }
     254             :         {
     255           0 :             LOCK(cs_signer);
     256           0 :             blockTxs.emplace(blockHash, ret);
     257           0 :         }
     258           0 :         m_clhandler.UpdateTxFirstSeenMap(*ret, blockTime);
     259           0 :     }
     260           0 :     return ret;
     261           0 : }
     262             : 
     263           0 : llmq::RecoveredSigResult ChainLockSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
     264             : {
     265           0 :     if (!m_chainlocks.IsEnabled()) {
     266           0 :         return std::monostate{};
     267             :     }
     268             : 
     269           0 :     ChainLockSig clsig;
     270             :     {
     271           0 :         LOCK(cs_signer);
     272             : 
     273           0 :         if (recoveredSig.getId() != lastSignedRequestId || recoveredSig.getMsgHash() != lastSignedMsgHash) {
     274             :             // this is not what we signed, so lets not create a CLSIG for it
     275           0 :             return std::monostate{};
     276             :         }
     277           0 :         if (m_chainlocks.GetBestChainLockHeight() >= lastSignedHeight) {
     278             :             // already got the same or a better CLSIG through the CLSIG message
     279           0 :             return std::monostate{};
     280             :         }
     281             : 
     282           0 :         clsig = ChainLockSig(lastSignedHeight, lastSignedMsgHash, recoveredSig.sig.Get());
     283           0 :     }
     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           0 :     auto clresult = m_clhandler.ProcessNewChainLock(-1, clsig, m_qman, ::SerializeHash(clsig));
     287           0 :     if (!clresult.m_inventory.empty()) {
     288           0 :         return clresult.m_inventory.front();
     289             :     }
     290           0 :     return std::monostate{};
     291           0 : }
     292             : 
     293           0 : void ChainLockSigner::Cleanup()
     294             : {
     295           0 :     constexpr auto CLEANUP_INTERVAL{30s};
     296           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     297           0 :         return;
     298             :     }
     299             : 
     300           0 :     if (!m_cleanup_throttler.TryCleanup(CLEANUP_INTERVAL)) {
     301           0 :         return;
     302             :     }
     303             : 
     304           0 :     AssertLockNotHeld(cs_signer);
     305           0 :     std::vector<std::shared_ptr<Uint256HashSet>> removed;
     306           0 :     LOCK2(::cs_main, cs_signer);
     307           0 :     for (auto it = blockTxs.begin(); it != blockTxs.end();) {
     308           0 :         const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(it->first);
     309           0 :         if (!pindex) {
     310           0 :             it = blockTxs.erase(it);
     311           0 :         } else if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     312           0 :             removed.push_back(it->second);
     313           0 :             it = blockTxs.erase(it);
     314           0 :         } else if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     315           0 :             it = blockTxs.erase(it);
     316           0 :         } else {
     317           0 :             ++it;
     318             :         }
     319             :     }
     320           0 :     m_clhandler.CleanupFromSigner(removed);
     321           0 : }
     322             : } // namespace chainlock

Generated by: LCOV version 1.16