LCOV - code coverage report
Current view: top level - src/instantsend - signing.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 0 230 0.0 %
Date: 2026-06-25 07:23:51 Functions: 0 23 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 <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           0 : 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           0 :     m_chainstate{chainstate},
      38           0 :     m_chainlocks{chainlocks},
      39           0 :     m_isman{isman},
      40           0 :     m_sigman{sigman},
      41           0 :     m_shareman{shareman},
      42           0 :     m_qman{qman},
      43           0 :     m_sporkman{sporkman},
      44           0 :     m_mempool{mempool},
      45           0 :     m_mn_sync{mn_sync}
      46           0 : {
      47           0 : }
      48             : 
      49           0 : InstantSendSigner::~InstantSendSigner() = default;
      50             : 
      51           0 : void InstantSendSigner::RegisterRecoveryInterface()
      52             : {
      53           0 :     m_sigman.RegisterRecoveredSigsListener(this);
      54           0 : }
      55             : 
      56           0 : void InstantSendSigner::UnregisterRecoveryInterface()
      57             : {
      58           0 :     m_sigman.UnregisterRecoveredSigsListener(this);
      59           0 : }
      60             : 
      61           0 : void InstantSendSigner::ClearInputsFromQueue(const Uint256HashSet& ids)
      62             : {
      63           0 :     LOCK(cs_input_requests);
      64           0 :     for (const auto& id : ids) {
      65           0 :         inputRequestIds.erase(id);
      66             :     }
      67           0 : }
      68             : 
      69           0 : void InstantSendSigner::ClearLockFromQueue(const InstantSendLockPtr& islock)
      70             : {
      71           0 :     LOCK(cs_creating);
      72           0 :     creatingInstantSendLocks.erase(islock->GetRequestId());
      73           0 :     txToCreatingInstantSendLocks.erase(islock->txid);
      74           0 : }
      75             : 
      76           0 : llmq::RecoveredSigResult InstantSendSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
      77             : {
      78           0 :     if (!m_isman.IsInstantSendEnabled()) {
      79           0 :         return std::monostate{};
      80             :     }
      81             : 
      82           0 :     if (Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
      83           0 :         return std::monostate{};
      84             :     }
      85             : 
      86           0 :     uint256 txid;
      87           0 :     if (LOCK(cs_input_requests); inputRequestIds.count(recoveredSig.getId())) {
      88           0 :         txid = recoveredSig.getMsgHash();
      89           0 :     }
      90           0 :     if (!txid.IsNull()) {
      91           0 :         HandleNewInputLockRecoveredSig(recoveredSig, txid);
      92           0 :     } else if (/*isInstantSendLock=*/WITH_LOCK(cs_creating, return creatingInstantSendLocks.count(recoveredSig.getId()))) {
      93           0 :         HandleNewInstantSendLockRecoveredSig(recoveredSig);
      94           0 :     }
      95           0 :     return std::monostate{};
      96           0 : }
      97             : 
      98           0 : bool InstantSendSigner::IsInstantSendMempoolSigningEnabled() const
      99             : {
     100           0 :     return !fReindex && !fImporting && m_sporkman.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED) == 0;
     101             : }
     102             : 
     103           0 : void InstantSendSigner::HandleNewInputLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const uint256& txid)
     104             : {
     105           0 :     if (g_txindex) {
     106           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
     107           0 :     }
     108             : 
     109           0 :     uint256 _hashBlock{};
     110           0 :     const auto tx = GetTransaction(nullptr, &m_mempool, txid, Params().GetConsensus(), _hashBlock);
     111           0 :     if (!tx) {
     112           0 :         return;
     113             :     }
     114             : 
     115           0 :     if (LogAcceptDebug(BCLog::INSTANTSEND)) {
     116           0 :         for (const auto& in : tx->vin) {
     117           0 :             if (GenInputLockRequestId(in.prevout) == recoveredSig.getId()) {
     118           0 :                 LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got recovered sig for input %s\n", __func__,
     119             :                          txid.ToString(), in.prevout.ToStringShort());
     120           0 :                 break;
     121             :             }
     122             :         }
     123           0 :     }
     124             : 
     125           0 :     TrySignInstantSendLock(*tx);
     126           0 : }
     127             : 
     128           0 : void InstantSendSigner::ProcessPendingRetryLockTxs(const std::vector<CTransactionRef>& retryTxs)
     129             : {
     130           0 :     if (!m_isman.IsInstantSendEnabled()) {
     131           0 :         return;
     132             :     }
     133             : 
     134           0 :     int retryCount = 0;
     135           0 :     for (const auto& tx : retryTxs) {
     136             :         {
     137           0 :             if (LOCK(cs_creating); txToCreatingInstantSendLocks.count(tx->GetHash())) {
     138             :                 // we're already in the middle of locking this one
     139           0 :                 continue;
     140             :             }
     141           0 :             if (m_isman.IsLocked(tx->GetHash())) {
     142           0 :                 continue;
     143             :             }
     144           0 :             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           0 :         if (LogAcceptDebug(BCLog::INSTANTSEND)) {
     153           0 :             if (!CheckCanLock(*tx, false, Params().GetConsensus())) {
     154           0 :                 continue;
     155             :             }
     156           0 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: retrying to lock\n", __func__, tx->GetHash().ToString());
     157           0 :         }
     158             : 
     159           0 :         ProcessTx(*tx, false, Params().GetConsensus());
     160           0 :         retryCount++;
     161             :     }
     162             : 
     163           0 :     if (retryCount != 0) {
     164           0 :         LogPrint(BCLog::INSTANTSEND, "%s -- retried %d TXs.\n", __func__, retryCount);
     165           0 :     }
     166           0 : }
     167             : 
     168           0 : bool InstantSendSigner::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const
     169             : {
     170           0 :     if (tx.vin.empty()) {
     171             :         // can't lock TXs without inputs (e.g. quorum commitments)
     172           0 :         return false;
     173             :     }
     174             : 
     175           0 :     return std::ranges::all_of(tx.vin, [&](const auto& in) {
     176           0 :         return CheckCanLock(in.prevout, printDebug, tx.GetHash(), params);
     177             :     });
     178           0 : }
     179             : 
     180           0 : bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash,
     181             :                                      const Consensus::Params& params) const
     182             : {
     183           0 :     int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
     184             : 
     185           0 :     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           0 :         return true;
     188             :     }
     189             : 
     190           0 :     auto mempoolTx = m_mempool.get(outpoint.hash);
     191           0 :     if (mempoolTx) {
     192           0 :         if (printDebug) {
     193           0 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: parent mempool TX %s is not locked\n", __func__,
     194             :                      txHash.ToString(), outpoint.hash.ToString());
     195           0 :         }
     196           0 :         return false;
     197             :     }
     198             : 
     199           0 :     uint256 hashBlock{};
     200           0 :     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           0 :     if (!tx || hashBlock.IsNull()) {
     203           0 :         if (printDebug) {
     204           0 :             LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to find parent TX %s in mined block\n", __func__, txHash.ToString(),
     205             :                      outpoint.hash.ToString());
     206           0 :         }
     207           0 :         return false;
     208             :     }
     209             : 
     210           0 :     int blockHeight{0};
     211           0 :     if (auto ret = m_isman.GetCachedHeight(hashBlock)) {
     212           0 :         blockHeight = *ret;
     213           0 :     } else {
     214           0 :         const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
     215           0 :         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           0 :         m_isman.CacheBlockHeight(pindex);
     223           0 :         blockHeight = pindex->nHeight;
     224             :     }
     225             : 
     226           0 :     const int tipHeight = m_isman.GetTipHeight();
     227             : 
     228           0 :     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           0 :     const int nTxAge = tipHeight - blockHeight + 1;
     237             : 
     238           0 :     if (nTxAge < nInstantSendConfirmationsRequired && !m_chainlocks.HasChainLock(blockHeight, hashBlock)) {
     239           0 :         if (printDebug) {
     240           0 :             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           0 :         }
     243           0 :         return false;
     244             :     }
     245             : 
     246           0 :     return true;
     247           0 : }
     248             : 
     249           0 : void InstantSendSigner::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
     250             : {
     251           0 :     InstantSendLockPtr islock;
     252             : 
     253             :     {
     254           0 :         LOCK(cs_creating);
     255           0 :         auto it = creatingInstantSendLocks.find(recoveredSig.getId());
     256           0 :         if (it == creatingInstantSendLocks.end()) {
     257           0 :             return;
     258             :         }
     259             : 
     260           0 :         islock = std::make_shared<InstantSendLock>(std::move(it->second));
     261           0 :         creatingInstantSendLocks.erase(it);
     262           0 :         txToCreatingInstantSendLocks.erase(islock->txid);
     263           0 :     }
     264             : 
     265           0 :     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           0 :     islock->sig = recoveredSig.sig;
     272           0 :     m_isman.TryEmplacePendingLock(/*hash=*/::SerializeHash(*islock), /*id=*/-1, islock);
     273           0 : }
     274             : 
     275           0 : void InstantSendSigner::ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params)
     276             : {
     277           0 :     if (!m_isman.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced()) {
     278           0 :         return;
     279             :     }
     280             : 
     281           0 :     if (params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
     282           0 :         return;
     283             :     }
     284             : 
     285           0 :     if (!CheckCanLock(tx, true, params)) {
     286           0 :         LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: CheckCanLock returned false\n", __func__, tx.GetHash().ToString());
     287           0 :         return;
     288             :     }
     289             : 
     290           0 :     auto conflictingLock = m_isman.GetConflictingLock(tx);
     291           0 :     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           0 :     if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return;
     303             : 
     304           0 :     if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) {
     305           0 :         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           0 :     TrySignInstantSendLock(tx);
     311           0 : }
     312             : 
     313           0 : bool InstantSendSigner::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType,
     314             :                                           const Consensus::Params& params)
     315             : {
     316           0 :     std::vector<uint256> ids;
     317           0 :     ids.reserve(tx.vin.size());
     318             : 
     319           0 :     size_t alreadyVotedCount = 0;
     320           0 :     for (const auto& in : tx.vin) {
     321           0 :         auto id = GenInputLockRequestId(in.prevout);
     322           0 :         ids.emplace_back(id);
     323             : 
     324           0 :         uint256 otherTxHash;
     325           0 :         if (m_sigman.GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash)) {
     326           0 :             if (otherTxHash != tx.GetHash()) {
     327           0 :                 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           0 :                 return false;
     330             :             }
     331           0 :             alreadyVotedCount++;
     332           0 :         }
     333             : 
     334             :         // don't even try the actual signing if any input is conflicting
     335           0 :         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           0 :     if (!fRetroactive && alreadyVotedCount == ids.size()) {
     342           0 :         LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: already voted on all inputs, bailing out\n", __func__,
     343             :                  tx.GetHash().ToString());
     344           0 :         return true;
     345             :     }
     346             : 
     347           0 :     LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on %d inputs\n", __func__, tx.GetHash().ToString(),
     348             :              tx.vin.size());
     349             : 
     350           0 :     for (const auto i : util::irange(tx.vin.size())) {
     351           0 :         const auto& in = tx.vin[i];
     352           0 :         auto& id = ids[i];
     353           0 :         WITH_LOCK(cs_input_requests, inputRequestIds.emplace(id));
     354           0 :         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           0 :         if (m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), {}, fRetroactive)) {
     357           0 :             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           0 :         }
     360             :     }
     361             : 
     362           0 :     return true;
     363           0 : }
     364             : 
     365           0 : void InstantSendSigner::TrySignInstantSendLock(const CTransaction& tx)
     366             : {
     367           0 :     const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     368             : 
     369           0 :     for (const auto& in : tx.vin) {
     370           0 :         auto id = GenInputLockRequestId(in.prevout);
     371           0 :         if (!m_sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) {
     372           0 :             return;
     373             :         }
     374             :     }
     375             : 
     376           0 :     LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got all recovered sigs, creating InstantSendLock\n", __func__,
     377             :              tx.GetHash().ToString());
     378             : 
     379           0 :     InstantSendLock islock;
     380           0 :     islock.txid = tx.GetHash();
     381           0 :     for (const auto& in : tx.vin) {
     382           0 :         islock.inputs.emplace_back(in.prevout);
     383             :     }
     384             : 
     385           0 :     auto id = islock.GetRequestId();
     386             : 
     387           0 :     if (m_sigman.HasRecoveredSigForId(llmqType, id)) {
     388           0 :         return;
     389             :     }
     390             : 
     391           0 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     392           0 :     assert(llmq_params_opt);
     393           0 :     const auto quorum = llmq::SelectQuorumForSigning(llmq_params_opt.value(), m_chainstate.m_chain, m_qman, id);
     394             : 
     395           0 :     if (!quorum) {
     396           0 :         LogPrint(BCLog::INSTANTSEND, "%s -- failed to select quorum. islock id=%s, txid=%s\n", __func__, id.ToString(),
     397             :                  tx.GetHash().ToString());
     398           0 :         return;
     399             :     }
     400             : 
     401           0 :     const int cycle_height = quorum->m_quorum_base_block_index->nHeight -
     402           0 :                              quorum->m_quorum_base_block_index->nHeight % llmq_params_opt->dkgInterval;
     403           0 :     islock.cycleHash = quorum->m_quorum_base_block_index->GetAncestor(cycle_height)->GetBlockHash();
     404             : 
     405             :     {
     406           0 :         LOCK(cs_creating);
     407           0 :         auto e = creatingInstantSendLocks.emplace(id, std::move(islock));
     408           0 :         if (!e.second) {
     409           0 :             return;
     410             :         }
     411           0 :         txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second);
     412           0 :     }
     413             : 
     414           0 :     m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), quorum->m_quorum_base_block_index->GetBlockHash());
     415           0 : }
     416             : } // namespace instantsend

Generated by: LCOV version 1.16