LCOV - code coverage report
Current view: top level - src/llmq - quorums_chainlocks.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 91 220 41.4 %
Date: 2026-08-09 10:51:41 Functions: 15 24 62.5 %

          Line data    Source code
       1             : // Copyright (c) 2019 The Dash Core developers
       2             : // Copyright (c) 2023 The PIVX Core developers
       3             : // Distributed under the MIT/X11 software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "quorums.h"
       7             : #include "quorums_chainlocks.h"
       8             : #include "quorums_signing.h"
       9             : #include "quorums_utils.h"
      10             : 
      11             : #include "chain.h"
      12             : #include "net_processing.h"
      13             : #include "scheduler.h"
      14             : #include "spork.h"
      15             : #include "sporkid.h"
      16             : #include "tiertwo/tiertwo_sync_state.h"
      17             : #include "validation.h"
      18             : 
      19             : namespace llmq
      20             : {
      21             : 
      22             : static const std::string CLSIG_REQUESTID_PREFIX = "clsig";
      23             : 
      24             : std::unique_ptr<CChainLocksHandler> chainLocksHandler{nullptr};
      25             : 
      26           0 : bool CChainLockSig::IsNull() const
      27             : {
      28           0 :     return nHeight == -1 && blockHash == uint256();
      29             : }
      30             : 
      31           0 : std::string CChainLockSig::ToString() const
      32             : {
      33           0 :     return strprintf("CChainLockSig(nHeight=%d, blockHash=%s)", nHeight, blockHash.ToString());
      34             : }
      35             : 
      36         413 : CChainLocksHandler::CChainLocksHandler(CScheduler* _scheduler) :
      37        1652 :     scheduler(_scheduler)
      38             : {
      39         413 : }
      40             : 
      41         826 : CChainLocksHandler::~CChainLocksHandler()
      42             : {
      43         826 : }
      44             : 
      45         285 : void CChainLocksHandler::Start()
      46             : {
      47         285 :     quorumSigningManager->RegisterRecoveredSigsListener(this);
      48         285 :     scheduler->scheduleEvery([&]() {
      49        1693 :         EnforceBestChainLock();
      50             :         // regularely retry signing the current chaintip
      51        1693 :         TrySignChainTip();
      52             :     },
      53             :         5000);
      54         285 : }
      55             : 
      56         295 : void CChainLocksHandler::Stop()
      57             : {
      58         295 :     quorumSigningManager->UnregisterRecoveredSigsListener(this);
      59         295 : }
      60             : 
      61           0 : bool CChainLocksHandler::AlreadyHave(const CInv& inv)
      62             : {
      63           0 :     LOCK(cs);
      64           0 :     return seenChainLocks.count(inv.hash) != 0;
      65             : }
      66             : 
      67           0 : bool CChainLocksHandler::GetChainLockByHash(const uint256& hash, llmq::CChainLockSig& ret)
      68             : {
      69           0 :     LOCK(cs);
      70             : 
      71           0 :     if (hash != bestChainLockHash) {
      72             :         // we only propagate the best one and ditch all the old ones
      73             :         return false;
      74             :     }
      75             : 
      76           0 :     ret = bestChainLock;
      77           0 :     return true;
      78             : }
      79             : 
      80           0 : CChainLockSig CChainLocksHandler::GetBestChainLock()
      81             : {
      82           0 :     LOCK(cs);
      83           0 :     return bestChainLock;
      84             : }
      85             : 
      86           0 : void CChainLocksHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
      87             : {
      88           0 :     if (!sporkManager.IsSporkActive(SPORK_23_CHAINLOCKS_ENFORCEMENT)) {
      89             :         return;
      90             :     }
      91             : 
      92           0 :     if (strCommand == NetMsgType::CLSIG) {
      93           0 :         CChainLockSig clsig;
      94           0 :         vRecv >> clsig;
      95             : 
      96           0 :         auto hash = ::SerializeHash(clsig);
      97             : 
      98           0 :         ProcessNewChainLock(pfrom->GetId(), clsig, hash);
      99             :     }
     100             : }
     101             : 
     102           0 : void CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLockSig& clsig, const uint256& hash)
     103             : {
     104           0 :     {
     105           0 :         LOCK(cs_main);
     106           0 :         g_connman->RemoveAskFor(hash, MSG_CLSIG);
     107             :     }
     108             : 
     109           0 :     {
     110           0 :         LOCK(cs);
     111           0 :         if (!seenChainLocks.emplace(hash, GetTimeMillis()).second) {
     112           0 :             return;
     113             :         }
     114             : 
     115           0 :         if (!bestChainLock.IsNull() && clsig.nHeight <= bestChainLock.nHeight) {
     116             :             // no need to process/relay older CLSIGs
     117             :             return;
     118             :         }
     119             :     }
     120             : 
     121           0 :     uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, clsig.nHeight));
     122           0 :     uint256 msgHash = clsig.blockHash;
     123           0 :     if (!quorumSigningManager->VerifyRecoveredSig(Params().GetConsensus().llmqTypeChainLocks, clsig.nHeight, requestId, msgHash, clsig.sig)) {
     124           0 :         LogPrintf("CChainLocksHandler::%s -- invalid CLSIG (%s), peer=%d\n", __func__, clsig.ToString(), from);
     125           0 :         if (from != -1) {
     126           0 :             LOCK(cs_main);
     127           0 :             Misbehaving(from, 10);
     128             :         }
     129           0 :         return;
     130             :     }
     131             : 
     132           0 :     {
     133           0 :         LOCK2(cs_main, cs);
     134             : 
     135           0 :         if (InternalHasConflictingChainLock(clsig.nHeight, clsig.blockHash)) {
     136             :             // This should not happen. If it happens, it means that a malicious entity controls a large part of the MN
     137             :             // network. In this case, we don't allow him to reorg older chainlocks.
     138           0 :             LogPrintf("CChainLocksHandler::%s -- new CLSIG (%s) tries to reorg previous CLSIG (%s), peer=%d\n",
     139           0 :                       __func__, clsig.ToString(), bestChainLock.ToString(), from);
     140           0 :             return;
     141             :         }
     142             : 
     143           0 :         bestChainLockHash = hash;
     144           0 :         bestChainLock = clsig;
     145             : 
     146           0 :         CInv inv(MSG_CLSIG, hash);
     147           0 :         g_connman->RelayInv(inv, LLMQS_PROTO_VERSION);
     148             : 
     149           0 :         auto blockIt = mapBlockIndex.find(clsig.blockHash);
     150           0 :         if (blockIt == mapBlockIndex.end()) {
     151             :             // we don't know the block/header for this CLSIG yet, so bail out for now
     152             :             // when the block or the header later comes in, we will enforce the correct chain
     153             :             return;
     154             :         }
     155             : 
     156           0 :         if (blockIt->second->nHeight != clsig.nHeight) {
     157             :             // Should not happen, same as the conflict check from above.
     158           0 :             LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n",
     159           0 :                     __func__, clsig.ToString(), blockIt->second->nHeight);
     160           0 :             return;
     161             :         }
     162             : 
     163           0 :         const CBlockIndex* pindex = blockIt->second;
     164           0 :         bestChainLockWithKnownBlock = bestChainLock;
     165           0 :         bestChainLockBlockIndex = pindex;
     166             :     }
     167             : 
     168           0 :     scheduler->scheduleFromNow([&]() {
     169           0 :         EnforceBestChainLock();
     170             :     },
     171             :         0);
     172             : 
     173           0 :     LogPrint(BCLog::LLMQ, "CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n",
     174             :         __func__, clsig.ToString(), from);
     175             : }
     176             : 
     177       24511 : void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
     178             : {
     179       73533 :     LOCK2(cs_main, cs);
     180             : 
     181       24511 :     if (pindexNew->GetBlockHash() == bestChainLock.blockHash) {
     182           0 :         LogPrintf("CChainLocksHandler::%s -- block header %s came in late, updating and enforcing\n", __func__, pindexNew->GetBlockHash().ToString());
     183             : 
     184           0 :         if (bestChainLock.nHeight != pindexNew->nHeight) {
     185             :             // Should not happen, same as the conflict check from ProcessNewChainLock.
     186           0 :             LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n",
     187           0 :                 __func__, bestChainLock.ToString(), pindexNew->nHeight);
     188           0 :             return;
     189             :         }
     190             : 
     191             :         // when EnforceBestChainLock is called later, it might end up invalidating other chains but not activating the
     192             :         // CLSIG locked chain. This happens when only the header is known but the block is still missing yet. The usual
     193             :         // block processing logic will handle this when the block arrives
     194           0 :         bestChainLockWithKnownBlock = bestChainLock;
     195           0 :         bestChainLockBlockIndex = pindexNew;
     196             :     }
     197             : }
     198             : 
     199       24761 : void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew)
     200             : {
     201             :     // don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is
     202             :     // never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to
     203             :     // EnforceBestChainLock switching chains.
     204       47679 :     LOCK(cs);
     205       24761 :     if (tryLockChainTipScheduled) {
     206        3686 :         return;
     207             :     }
     208       22918 :     tryLockChainTipScheduled = true;
     209       45836 :     scheduler->scheduleFromNow([&]() {
     210       22912 :         EnforceBestChainLock();
     211       22912 :         TrySignChainTip();
     212       22912 :         LOCK(cs);
     213       22912 :         tryLockChainTipScheduled = false;
     214       22912 :     },
     215             :         0);
     216             : }
     217             : 
     218       24605 : void CChainLocksHandler::TrySignChainTip()
     219             : {
     220       24605 :     Cleanup();
     221             : 
     222       24605 :     if (!fMasterNode) {
     223       24599 :         return;
     224             :     }
     225             : 
     226         333 :     if (!g_tiertwo_sync_state.IsBlockchainSynced()) {
     227             :         return;
     228             :     }
     229             : 
     230         333 :     const CBlockIndex* pindex;
     231         333 :     {
     232         333 :         LOCK(cs_main);
     233         666 :         pindex = chainActive.Tip();
     234             :     }
     235             : 
     236         333 :     if (!pindex->pprev) {
     237             :         return;
     238             :     }
     239         333 :     if (!sporkManager.IsSporkActive(SPORK_23_CHAINLOCKS_ENFORCEMENT)) {
     240             :         return;
     241             :     }
     242             : 
     243             :     // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
     244             :     // To simplify the initial implementation, we skip this process and directly try to create a CLSIG
     245             :     // This will fail when multiple blocks compete, but we accept this for the initial implementation.
     246             :     // Later, we'll add the multiple attempts process.
     247             : 
     248           6 :     {
     249           6 :         LOCK(cs);
     250             : 
     251           6 :         if (pindex->nHeight == lastSignedHeight) {
     252             :             // already signed this one
     253           0 :             return;
     254             :         }
     255             : 
     256           6 :         if (bestChainLock.nHeight >= pindex->nHeight) {
     257             :             // already got the same CLSIG or a better one
     258             :             return;
     259             :         }
     260             : 
     261           6 :         if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     262             :             // don't sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce
     263             :             // the correct chain.
     264             :             return;
     265             :         }
     266             :     }
     267             : 
     268          12 :     LogPrint(BCLog::LLMQ, "CChainLocksHandler::%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(), pindex->nHeight);
     269             : 
     270           6 :     uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight));
     271           6 :     uint256 msgHash = pindex->GetBlockHash();
     272             : 
     273           6 :     {
     274           6 :         LOCK(cs);
     275           6 :         if (bestChainLock.nHeight >= pindex->nHeight) {
     276             :             // might have happened while we didn't hold cs
     277           0 :             return;
     278             :         }
     279           6 :         lastSignedHeight = pindex->nHeight;
     280           6 :         lastSignedRequestId = requestId;
     281           6 :         lastSignedMsgHash = msgHash;
     282             :     }
     283             : 
     284           6 :     quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqTypeChainLocks, requestId, msgHash);
     285             : }
     286             : 
     287             : // WARNING: cs_main and cs should not be held!
     288             : // This should also not be called from validation signals, as this might result in recursive calls
     289       24605 : void CChainLocksHandler::EnforceBestChainLock()
     290             : {
     291       24605 :     AssertLockNotHeld(cs);
     292       24605 :     AssertLockNotHeld(cs_main);
     293             : 
     294       24605 :     CChainLockSig clsig;
     295       24605 :     const CBlockIndex* pindex;
     296       24605 :     const CBlockIndex* currentBestChainLockBlockIndex;
     297       24605 :     {
     298       24605 :         LOCK(cs);
     299       24605 :         clsig = bestChainLockWithKnownBlock;
     300       24605 :         pindex = currentBestChainLockBlockIndex = this->bestChainLockBlockIndex;
     301             : 
     302       24605 :         if (!currentBestChainLockBlockIndex) {
     303             :             // we don't have the header/block, so we can't do anything right now
     304       49210 :             return;
     305             :         }
     306             :     }
     307             : 
     308           0 :     bool activateNeeded;
     309           0 :     {
     310           0 :         LOCK(cs_main);
     311             : 
     312             :         // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain.
     313             :         // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig
     314             :         // and invalidate each of them.
     315           0 :         while (pindex && !chainActive.Contains(pindex)) {
     316             :             // Invalidate all blocks that have the same prevBlockHash but are not equal to blockHash
     317           0 :             auto itp = mapPrevBlockIndex.equal_range(pindex->pprev->GetBlockHash());
     318           0 :             for (auto jt = itp.first; jt != itp.second; ++jt) {
     319           0 :                 if (jt->second == pindex) {
     320           0 :                     continue;
     321             :                 }
     322           0 :                 LogPrintf("CChainLocksHandler::%s -- CLSIG (%s) invalidates block %s\n",
     323           0 :                     __func__, clsig.ToString(), jt->second->GetBlockHash().ToString());
     324           0 :                 DoInvalidateBlock(jt->second, false);
     325             :             }
     326             : 
     327           0 :             pindex = pindex->pprev;
     328             :         }
     329             :         // In case blocks from the correct chain are invalid at the moment, reconsider them. The only case where this
     330             :         // can happen right now is when missing superblock triggers caused the main chain to be dismissed first. When
     331             :         // the trigger later appears, this should bring us to the correct chain eventually. Please note that this does
     332             :         // NOT enforce invalid blocks in any way, it just causes re-validation.
     333           0 :         if (!currentBestChainLockBlockIndex->IsValid()) {
     334           0 :             CValidationState state;
     335           0 :             ReconsiderBlock(state, mapBlockIndex.at(currentBestChainLockBlockIndex->GetBlockHash()));
     336             :         }
     337             : 
     338           0 :         activateNeeded = chainActive.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex;
     339             :     }
     340             : 
     341           0 :     CValidationState state;
     342           0 :     if (activateNeeded && !ActivateBestChain(state)) {
     343           0 :         LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, state.GetRejectReason());
     344             :     }
     345             : }
     346             : 
     347           0 : void CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
     348             : {
     349           0 :     if (!sporkManager.IsSporkActive(SPORK_23_CHAINLOCKS_ENFORCEMENT)) {
     350           0 :         return;
     351             :     }
     352             : 
     353           0 :     CChainLockSig clsig;
     354           0 :     {
     355           0 :         LOCK(cs);
     356             : 
     357           0 :         if (recoveredSig.id != lastSignedRequestId || recoveredSig.msgHash != lastSignedMsgHash) {
     358             :             // this is not what we signed, so lets not create a CLSIG for it
     359           0 :             return;
     360             :         }
     361           0 :         if (bestChainLock.nHeight >= lastSignedHeight) {
     362             :             // already got the same or a better CLSIG through the CLSIG message
     363             :             return;
     364             :         }
     365             : 
     366           0 :         clsig.nHeight = lastSignedHeight;
     367           0 :         clsig.blockHash = lastSignedMsgHash;
     368           0 :         clsig.sig = recoveredSig.sig.Get();
     369             :     }
     370           0 :     ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));
     371             : }
     372             : 
     373             : // WARNING, do not hold cs while calling this method as we'll otherwise run into a deadlock
     374           0 : void CChainLocksHandler::DoInvalidateBlock(const CBlockIndex* pindex, bool activateBestChain)
     375             : {
     376           0 :     auto& params = Params();
     377             : 
     378           0 :     {
     379           0 :         LOCK(cs_main);
     380             : 
     381             :         // get the non-const pointer
     382           0 :         CBlockIndex* pindex2 = mapBlockIndex[pindex->GetBlockHash()];
     383             : 
     384           0 :         CValidationState state;
     385           0 :         if (!InvalidateBlock(state, params, pindex2)) {
     386           0 :             LogPrintf("CChainLocksHandler::%s -- InvalidateBlock failed: %s\n", __func__, state.GetRejectReason());
     387             :             // This should not have happened and we are in a state were it's not safe to continue anymore
     388           0 :             assert(false);
     389             :         }
     390             :     }
     391             : 
     392           0 :     CValidationState state;
     393           0 :     if (activateBestChain && !ActivateBestChain(state)) {
     394           0 :         LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, state.GetRejectReason());
     395             :         // This should not have happened and we are in a state were it's not safe to continue anymore
     396           0 :         assert(false);
     397             :     }
     398           0 : }
     399             : 
     400         834 : bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash)
     401             : {
     402         834 :     if (!sporkManager.IsSporkActive(SPORK_23_CHAINLOCKS_ENFORCEMENT)) {
     403             :         return false;
     404             :     }
     405             : 
     406         846 :     LOCK(cs);
     407          12 :     return InternalHasChainLock(nHeight, blockHash);
     408             : }
     409             : 
     410          12 : bool CChainLocksHandler::InternalHasChainLock(int nHeight, const uint256& blockHash)
     411             : {
     412          12 :     AssertLockHeld(cs);
     413             : 
     414          12 :     if (!bestChainLockBlockIndex) {
     415             :         return false;
     416             :     }
     417             : 
     418           0 :     if (nHeight > bestChainLockBlockIndex->nHeight) {
     419             :         return false;
     420             :     }
     421             : 
     422           0 :     if (nHeight == bestChainLockBlockIndex->nHeight) {
     423           0 :         return blockHash == bestChainLockBlockIndex->GetBlockHash();
     424             :     }
     425             : 
     426           0 :     auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);
     427           0 :     return pAncestor && pAncestor->GetBlockHash() == blockHash;
     428             : }
     429             : 
     430       59200 : bool CChainLocksHandler::HasConflictingChainLock(int nHeight, const uint256& blockHash)
     431             : {
     432       59200 :     if (!sporkManager.IsSporkActive(SPORK_23_CHAINLOCKS_ENFORCEMENT)) {
     433             :         return false;
     434             :     }
     435             : 
     436       61631 :     LOCK(cs);
     437        2431 :     return InternalHasConflictingChainLock(nHeight, blockHash);
     438             : }
     439             : 
     440        2437 : bool CChainLocksHandler::InternalHasConflictingChainLock(int nHeight, const uint256& blockHash)
     441             : {
     442        2437 :     AssertLockHeld(cs);
     443             : 
     444        2437 :     if (!bestChainLockBlockIndex) {
     445             :         return false;
     446             :     }
     447             : 
     448           0 :     if (nHeight > bestChainLockBlockIndex->nHeight) {
     449             :         return false;
     450             :     }
     451             : 
     452           0 :     if (nHeight == bestChainLockBlockIndex->nHeight) {
     453           0 :         return blockHash != bestChainLockBlockIndex->GetBlockHash();
     454             :     }
     455             : 
     456           0 :     auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);
     457           0 :     assert(pAncestor);
     458           0 :     return pAncestor->GetBlockHash() != blockHash;
     459             : }
     460             : 
     461       24605 : void CChainLocksHandler::Cleanup()
     462             : {
     463       24605 :     if (!g_tiertwo_sync_state.IsBlockchainSynced()) {
     464       24217 :         return;
     465             :     }
     466             : 
     467       14159 :     {
     468       14159 :         LOCK(cs);
     469       14159 :         if (GetTimeMillis() - lastCleanupTime < CLEANUP_INTERVAL) {
     470       27542 :             return;
     471             :         }
     472             :     }
     473             : 
     474        1164 :     LOCK2(cs_main, cs);
     475             : 
     476         388 :     for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {
     477           0 :         if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {
     478           0 :             it = seenChainLocks.erase(it);
     479             :         } else {
     480         388 :             ++it;
     481             :         }
     482             :     }
     483             : 
     484         388 :     lastCleanupTime = GetTimeMillis();
     485             : }
     486             : 
     487             : }

Generated by: LCOV version 1.14