LCOV - code coverage report
Current view: top level - src/instantsend - signing.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 205 230 89.1 %
Date: 2026-06-25 07:23:43 Functions: 22 23 95.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 <instantsend/signing.h>
       6             : 
       7             : #include <chain.h>
       8             : #include <chainlock/chainlock.h>
       9             : #include <chainparams.h>
      10             : #include <index/txindex.h>
      11             : #include <instantsend/instantsend.h>
      12             : #include <llmq/quorumsman.h>
      13             : #include <llmq/signing_shares.h>
      14             : #include <logging.h>
      15             : #include <masternode/sync.h>
      16             : #include <spork.h>
      17             : #include <util/helpers.h>
      18             : #include <validation.h>
      19             : 
      20             : #include <ranges>
      21             : 
      22             : // Forward declaration to break dependency over node/transaction.h
      23             : namespace node {
      24             : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
      25             :                                const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
      26             : } // namespace node
      27             : 
      28             : using node::fImporting;
      29             : using node::fReindex;
      30             : using node::GetTransaction;
      31             : 
      32             : namespace instantsend {
      33        1980 : InstantSendSigner::InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks,
      34             :                                      llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman,
      35             :                                      llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman,
      36             :                                      CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync) :
      37         660 :     m_chainstate{chainstate},
      38         660 :     m_chainlocks{chainlocks},
      39         660 :     m_isman{isman},
      40         660 :     m_sigman{sigman},
      41         660 :     m_shareman{shareman},
      42         660 :     m_qman{qman},
      43         660 :     m_sporkman{sporkman},
      44         660 :     m_mempool{mempool},
      45         660 :     m_mn_sync{mn_sync}
      46        1320 : {
      47        1320 : }
      48             : 
      49        1320 : InstantSendSigner::~InstantSendSigner() = default;
      50             : 
      51         660 : void InstantSendSigner::RegisterRecoveryInterface()
      52             : {
      53         660 :     m_sigman.RegisterRecoveredSigsListener(this);
      54         660 : }
      55             : 
      56         660 : void InstantSendSigner::UnregisterRecoveryInterface()
      57             : {
      58         660 :     m_sigman.UnregisterRecoveredSigsListener(this);
      59         660 : }
      60             : 
      61        1022 : void InstantSendSigner::ClearInputsFromQueue(const Uint256HashSet& ids)
      62             : {
      63        1022 :     LOCK(cs_input_requests);
      64        4084 :     for (const auto& id : ids) {
      65        3062 :         inputRequestIds.erase(id);
      66             :     }
      67        1022 : }
      68             : 
      69         601 : void InstantSendSigner::ClearLockFromQueue(const InstantSendLockPtr& islock)
      70             : {
      71         601 :     LOCK(cs_creating);
      72         601 :     creatingInstantSendLocks.erase(islock->GetRequestId());
      73         601 :     txToCreatingInstantSendLocks.erase(islock->txid);
      74         601 : }
      75             : 
      76       26962 : llmq::RecoveredSigResult InstantSendSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
      77             : {
      78       26962 :     if (!m_isman.IsInstantSendEnabled()) {
      79         182 :         return std::monostate{};
      80             :     }
      81             : 
      82       26780 :     if (Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
      83           0 :         return std::monostate{};
      84             :     }
      85             : 
      86       26780 :     uint256 txid;
      87       28506 :     if (LOCK(cs_input_requests); inputRequestIds.count(recoveredSig.getId())) {
      88        1726 :         txid = recoveredSig.getMsgHash();
      89        1726 :     }
      90       26780 :     if (!txid.IsNull()) {
      91        1726 :         HandleNewInputLockRecoveredSig(recoveredSig, txid);
      92       51834 :     } else if (/*isInstantSendLock=*/WITH_LOCK(cs_creating, return creatingInstantSendLocks.count(recoveredSig.getId()))) {
      93         538 :         HandleNewInstantSendLockRecoveredSig(recoveredSig);
      94         538 :     }
      95       26780 :     return std::monostate{};
      96       26962 : }
      97             : 
      98        1833 : bool InstantSendSigner::IsInstantSendMempoolSigningEnabled() const
      99             : {
     100        1833 :     return !fReindex && !fImporting && m_sporkman.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED) == 0;
     101             : }
     102             : 
     103        1726 : void InstantSendSigner::HandleNewInputLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const uint256& txid)
     104             : {
     105        1726 :     if (g_txindex) {
     106        1726 :         g_txindex->BlockUntilSyncedToCurrentChain();
     107        1726 :     }
     108             : 
     109        1726 :     uint256 _hashBlock{};
     110        1726 :     const auto tx = GetTransaction(nullptr, &m_mempool, txid, Params().GetConsensus(), _hashBlock);
     111        1726 :     if (!tx) {
     112           0 :         return;
     113             :     }
     114             : 
     115        1726 :     if (LogAcceptDebug(BCLog::INSTANTSEND)) {
     116       10294 :         for (const auto& in : tx->vin) {
     117       10294 :             if (GenInputLockRequestId(in.prevout) == recoveredSig.getId()) {
     118        1726 :                 LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got recovered sig for input %s\n", __func__,
     119             :                          txid.ToString(), in.prevout.ToStringShort());
     120        1726 :                 break;
     121             :             }
     122             :         }
     123        1726 :     }
     124             : 
     125        1726 :     TrySignInstantSendLock(*tx);
     126        1726 : }
     127             : 
     128      154880 : void InstantSendSigner::ProcessPendingRetryLockTxs(const std::vector<CTransactionRef>& retryTxs)
     129             : {
     130      154880 :     if (!m_isman.IsInstantSendEnabled()) {
     131           0 :         return;
     132             :     }
     133             : 
     134      154880 :     int retryCount = 0;
     135      155119 :     for (const auto& tx : retryTxs) {
     136             :         {
     137         387 :             if (LOCK(cs_creating); txToCreatingInstantSendLocks.count(tx->GetHash())) {
     138             :                 // we're already in the middle of locking this one
     139         148 :                 continue;
     140             :             }
     141          91 :             if (m_isman.IsLocked(tx->GetHash())) {
     142           0 :                 continue;
     143             :             }
     144          91 :             if (m_isman.GetConflictingLock(*tx) != nullptr) {
     145             :                 // should not really happen as we have already filtered these out
     146           0 :                 continue;
     147             :             }
     148             :         }
     149             : 
     150             :         // CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam
     151             :         // the logs when retrying TXs that are not ready yet.
     152          91 :         if (LogAcceptDebug(BCLog::INSTANTSEND)) {
     153          91 :             if (!CheckCanLock(*tx, false, Params().GetConsensus())) {
     154           0 :                 continue;
     155             :             }
     156          91 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: retrying to lock\n", __func__, tx->GetHash().ToString());
     157          91 :         }
     158             : 
     159          91 :         ProcessTx(*tx, false, Params().GetConsensus());
     160          91 :         retryCount++;
     161             :     }
     162             : 
     163      154880 :     if (retryCount != 0) {
     164          91 :         LogPrint(BCLog::INSTANTSEND, "%s -- retried %d TXs.\n", __func__, retryCount);
     165          91 :     }
     166      154880 : }
     167             : 
     168        2358 : bool InstantSendSigner::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const
     169             : {
     170        2358 :     if (tx.vin.empty()) {
     171             :         // can't lock TXs without inputs (e.g. quorum commitments)
     172           0 :         return false;
     173             :     }
     174             : 
     175        8639 :     return std::ranges::all_of(tx.vin, [&](const auto& in) {
     176        6281 :         return CheckCanLock(in.prevout, printDebug, tx.GetHash(), params);
     177             :     });
     178        2358 : }
     179             : 
     180        6281 : bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash,
     181             :                                      const Consensus::Params& params) const
     182             : {
     183        6281 :     int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
     184             : 
     185        6281 :     if (m_isman.IsLocked(outpoint.hash)) {
     186             :         // if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined)
     187         570 :         return true;
     188             :     }
     189             : 
     190        5711 :     auto mempoolTx = m_mempool.get(outpoint.hash);
     191        5711 :     if (mempoolTx) {
     192          45 :         if (printDebug) {
     193          45 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: parent mempool TX %s is not locked\n", __func__,
     194             :                      txHash.ToString(), outpoint.hash.ToString());
     195          45 :         }
     196          45 :         return false;
     197             :     }
     198             : 
     199        5666 :     uint256 hashBlock{};
     200        5666 :     const auto tx = GetTransaction(nullptr, &m_mempool, outpoint.hash, params, hashBlock);
     201             :     // this relies on enabled txindex and won't work if we ever try to remove the requirement for txindex for masternodes
     202        5666 :     if (!tx || hashBlock.IsNull()) {
     203          68 :         if (printDebug) {
     204          68 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to find parent TX %s in mined block\n", __func__, txHash.ToString(),
     205             :                      outpoint.hash.ToString());
     206          68 :         }
     207          68 :         return false;
     208             :     }
     209             : 
     210        5598 :     int blockHeight{0};
     211        5598 :     if (auto ret = m_isman.GetCachedHeight(hashBlock)) {
     212        4091 :         blockHeight = *ret;
     213        4091 :     } else {
     214        3014 :         const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
     215        1507 :         if (pindex == nullptr) {
     216           0 :             if (printDebug) {
     217           0 :                 LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n",
     218             :                          __func__, txHash.ToString(), outpoint.hash.ToString());
     219           0 :             }
     220           0 :             return false;
     221             :         }
     222        1507 :         m_isman.CacheBlockHeight(pindex);
     223        1507 :         blockHeight = pindex->nHeight;
     224             :     }
     225             : 
     226        5598 :     const int tipHeight = m_isman.GetTipHeight();
     227             : 
     228        5598 :     if (tipHeight < blockHeight) {
     229           0 :         if (printDebug) {
     230           0 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: cached tip height %d is below block height %d for parent TX %s\n",
     231             :                      __func__, txHash.ToString(), tipHeight, blockHeight, outpoint.hash.ToString());
     232           0 :         }
     233           0 :         return false;
     234             :     }
     235             : 
     236        5598 :     const int nTxAge = tipHeight - blockHeight + 1;
     237             : 
     238        5598 :     if (nTxAge < nInstantSendConfirmationsRequired && !m_chainlocks.HasChainLock(blockHeight, hashBlock)) {
     239         321 :         if (printDebug) {
     240         321 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__,
     241             :                      txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired);
     242         321 :         }
     243         321 :         return false;
     244             :     }
     245             : 
     246        5277 :     return true;
     247        6281 : }
     248             : 
     249         538 : void InstantSendSigner::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
     250             : {
     251         538 :     InstantSendLockPtr islock;
     252             : 
     253             :     {
     254         538 :         LOCK(cs_creating);
     255         538 :         auto it = creatingInstantSendLocks.find(recoveredSig.getId());
     256         538 :         if (it == creatingInstantSendLocks.end()) {
     257           0 :             return;
     258             :         }
     259             : 
     260         538 :         islock = std::make_shared<InstantSendLock>(std::move(it->second));
     261         538 :         creatingInstantSendLocks.erase(it);
     262         538 :         txToCreatingInstantSendLocks.erase(islock->txid);
     263         538 :     }
     264             : 
     265         538 :     if (islock->txid != recoveredSig.getMsgHash()) {
     266           0 :         LogPrintf("%s -- txid=%s: islock conflicts with %s, dropping own version\n", __func__, islock->txid.ToString(),
     267             :                   recoveredSig.getMsgHash().ToString());
     268           0 :         return;
     269             :     }
     270             : 
     271         538 :     islock->sig = recoveredSig.sig;
     272         538 :     m_isman.TryEmplacePendingLock(/*hash=*/::SerializeHash(*islock), /*id=*/-1, islock);
     273         538 : }
     274             : 
     275        2267 : void InstantSendSigner::ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params)
     276             : {
     277        2267 :     if (!m_isman.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced()) {
     278           0 :         return;
     279             :     }
     280             : 
     281        2267 :     if (params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
     282           0 :         return;
     283             :     }
     284             : 
     285        2267 :     if (!CheckCanLock(tx, true, params)) {
     286         434 :         LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: CheckCanLock returned false\n", __func__, tx.GetHash().ToString());
     287         434 :         return;
     288             :     }
     289             : 
     290        1833 :     auto conflictingLock = m_isman.GetConflictingLock(tx);
     291        1833 :     if (conflictingLock != nullptr) {
     292           0 :         auto conflictingLockHash = ::SerializeHash(*conflictingLock);
     293           0 :         LogPrintf("%s -- txid=%s: conflicts with islock %s, txid=%s\n", __func__, tx.GetHash().ToString(),
     294             :                   conflictingLockHash.ToString(), conflictingLock->txid.ToString());
     295           0 :         return;
     296             :     }
     297             : 
     298             :     // Only sign for inlocks or islocks if mempool IS signing is enabled.
     299             :     // However, if we are processing a tx because it was included in a block we should
     300             :     // sign even if mempool IS signing is disabled. This allows a ChainLock to happen on this
     301             :     // block after we retroactively locked all transactions.
     302        1833 :     if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return;
     303             : 
     304        1824 :     if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) {
     305           8 :         return;
     306             :     }
     307             : 
     308             :     // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the
     309             :     // islock now instead of waiting for the input locks.
     310        1816 :     TrySignInstantSendLock(tx);
     311        2267 : }
     312             : 
     313        1824 : bool InstantSendSigner::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType,
     314             :                                           const Consensus::Params& params)
     315             : {
     316        1824 :     std::vector<uint256> ids;
     317        1824 :     ids.reserve(tx.vin.size());
     318             : 
     319        1824 :     size_t alreadyVotedCount = 0;
     320        7252 :     for (const auto& in : tx.vin) {
     321        5436 :         auto id = GenInputLockRequestId(in.prevout);
     322        5436 :         ids.emplace_back(id);
     323             : 
     324        5436 :         uint256 otherTxHash;
     325        5436 :         if (m_sigman.GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash)) {
     326         224 :             if (otherTxHash != tx.GetHash()) {
     327           8 :                 LogPrintf("%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__,
     328             :                           tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString());
     329           8 :                 return false;
     330             :             }
     331         216 :             alreadyVotedCount++;
     332         216 :         }
     333             : 
     334             :         // don't even try the actual signing if any input is conflicting
     335        5428 :         if (m_sigman.IsConflicting(params.llmqTypeDIP0024InstantSend, id, tx.GetHash())) {
     336           0 :             LogPrintf("%s -- txid=%s: m_sigman.IsConflicting returned true. id=%s\n", __func__, tx.GetHash().ToString(),
     337             :                       id.ToString());
     338           0 :             return false;
     339             :         }
     340             :     }
     341        1816 :     if (!fRetroactive && alreadyVotedCount == ids.size()) {
     342         110 :         LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: already voted on all inputs, bailing out\n", __func__,
     343             :                  tx.GetHash().ToString());
     344         110 :         return true;
     345             :     }
     346             : 
     347        1706 :     LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on %d inputs\n", __func__, tx.GetHash().ToString(),
     348             :              tx.vin.size());
     349             : 
     350        7015 :     for (const auto i : util::irange(tx.vin.size())) {
     351        5309 :         const auto& in = tx.vin[i];
     352        5309 :         auto& id = ids[i];
     353       10618 :         WITH_LOCK(cs_input_requests, inputRequestIds.emplace(id));
     354        5309 :         LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on input %s with id %s. fRetroactive=%d\n",
     355             :                  __func__, tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), fRetroactive);
     356        5309 :         if (m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), {}, fRetroactive)) {
     357        1816 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: voted on input %s with id %s\n", __func__,
     358             :                      tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
     359        1816 :         }
     360             :     }
     361             : 
     362        1706 :     return true;
     363        1824 : }
     364             : 
     365        3542 : void InstantSendSigner::TrySignInstantSendLock(const CTransaction& tx)
     366             : {
     367        3542 :     const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     368             : 
     369        7103 :     for (const auto& in : tx.vin) {
     370        6517 :         auto id = GenInputLockRequestId(in.prevout);
     371        6517 :         if (!m_sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) {
     372        2956 :             return;
     373             :         }
     374             :     }
     375             : 
     376         586 :     LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got all recovered sigs, creating InstantSendLock\n", __func__,
     377             :              tx.GetHash().ToString());
     378             : 
     379         586 :     InstantSendLock islock;
     380         586 :     islock.txid = tx.GetHash();
     381        2274 :     for (const auto& in : tx.vin) {
     382        1688 :         islock.inputs.emplace_back(in.prevout);
     383             :     }
     384             : 
     385         586 :     auto id = islock.GetRequestId();
     386             : 
     387         586 :     if (m_sigman.HasRecoveredSigForId(llmqType, id)) {
     388          11 :         return;
     389             :     }
     390             : 
     391         575 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     392         575 :     assert(llmq_params_opt);
     393         575 :     const auto quorum = llmq::SelectQuorumForSigning(llmq_params_opt.value(), m_chainstate.m_chain, m_qman, id);
     394             : 
     395         575 :     if (!quorum) {
     396           6 :         LogPrint(BCLog::INSTANTSEND, "%s -- failed to select quorum. islock id=%s, txid=%s\n", __func__, id.ToString(),
     397             :                  tx.GetHash().ToString());
     398           6 :         return;
     399             :     }
     400             : 
     401        1138 :     const int cycle_height = quorum->m_quorum_base_block_index->nHeight -
     402         569 :                              quorum->m_quorum_base_block_index->nHeight % llmq_params_opt->dkgInterval;
     403         569 :     islock.cycleHash = quorum->m_quorum_base_block_index->GetAncestor(cycle_height)->GetBlockHash();
     404             : 
     405             :     {
     406         569 :         LOCK(cs_creating);
     407         569 :         auto e = creatingInstantSendLocks.emplace(id, std::move(islock));
     408         569 :         if (!e.second) {
     409          12 :             return;
     410             :         }
     411         557 :         txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second);
     412         569 :     }
     413             : 
     414         557 :     m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), quorum->m_quorum_base_block_index->GetBlockHash());
     415        3542 : }
     416             : } // namespace instantsend

Generated by: LCOV version 1.16