LCOV - code coverage report
Current view: top level - src/instantsend - net_instantsend.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 307 364 84.3 %
Date: 2026-06-25 07:23:43 Functions: 36 37 97.3 %

          Line data    Source code
       1             : // Copyright (c) 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/net_instantsend.h>
       6             : 
       7             : #include <bls/bls_batchverifier.h>
       8             : #include <chainlock/chainlock.h>
       9             : #include <consensus/params.h>
      10             : #include <cxxtimer.hpp>
      11             : #include <instantsend/instantsend.h>
      12             : #include <instantsend/signing.h>
      13             : #include <llmq/commitment.h>
      14             : #include <llmq/quorumsman.h>
      15             : #include <llmq/signhash.h>
      16             : #include <llmq/signing.h>
      17             : #include <masternode/sync.h>
      18             : #include <node/interface_ui.h>
      19             : #include <util/thread.h>
      20             : #include <validation.h>
      21             : 
      22             : #include <chrono>
      23             : #include <set>
      24             : 
      25             : // Forward declaration to break dependency over node/transaction.h
      26             : namespace node {
      27             : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
      28             :                                const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
      29             : } // namespace node
      30             : 
      31             : using node::GetTransaction;
      32             : namespace {
      33             : constexpr int BATCH_VERIFIER_SOURCE_THRESHOLD{8};
      34             : constexpr int INVALID_ISLOCK_MISBEHAVIOR_SCORE{100};
      35             : constexpr int UNKNOWN_CYCLE_HASH_MISBEHAVIOR_SCORE{1};
      36             : constexpr int OLD_ACTIVE_SET_FAILURE_MISBEHAVIOR_SCORE{20};
      37             : constexpr auto WORK_THREAD_SLEEP_INTERVAL{std::chrono::milliseconds{100}};
      38             : } // namespace
      39             : 
      40        1448 : static std::optional<int> GetBlockHeight(llmq::CInstantSendManager& is_manager, const CChainState& chainstate,
      41             :                                          const uint256& hash)
      42             : {
      43        1448 :     if (hash.IsNull()) {
      44         545 :         return std::nullopt;
      45             :     }
      46         903 :     auto ret = is_manager.GetCachedHeight(hash);
      47         903 :     if (ret) return ret;
      48             : 
      49          54 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return chainstate.m_blockman.LookupBlockIndex(hash));
      50          27 :     if (pindex == nullptr) {
      51           0 :         return std::nullopt;
      52             :     }
      53          27 :     is_manager.CacheBlockHeight(pindex);
      54          27 :     return pindex->nHeight;
      55        1448 : }
      56             : 
      57        1610 : struct NetInstantSend::BatchVerificationData {
      58         805 :     CBLSBatchVerifier<NodeId, uint256> batchVerifier{false, true, BATCH_VERIFIER_SOURCE_THRESHOLD};
      59             :     Uint256HashMap<llmq::CRecoveredSig> recSigs;
      60         805 :     size_t verifyCount{0};
      61         805 :     size_t alreadyVerified{0};
      62             : };
      63             : 
      64         300 : bool NetInstantSend::ValidateIncomingISLock(const instantsend::InstantSendLock& islock, NodeId node_id)
      65             : {
      66         300 :     if (!islock.TriviallyValid()) {
      67           0 :         m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
      68           0 :         return false;
      69             :     }
      70             : 
      71         300 :     return true;
      72         300 : }
      73             : 
      74         300 : std::optional<int> NetInstantSend::ResolveCycleHeight(const uint256& cycle_hash)
      75             : {
      76         300 :     auto cycle_height = GetBlockHeight(m_is_manager, m_chainstate, cycle_hash);
      77         300 :     if (cycle_height) {
      78         300 :         return cycle_height;
      79             :     }
      80             : 
      81           0 :     const auto block_index = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(cycle_hash));
      82           0 :     if (block_index == nullptr) {
      83           0 :         return std::nullopt;
      84             :     }
      85             : 
      86           0 :     m_is_manager.CacheBlockHeight(block_index);
      87           0 :     return block_index->nHeight;
      88         300 : }
      89             : 
      90         300 : bool NetInstantSend::ValidateDeterministicCycleHeight(
      91             :     int cycle_height,
      92             :     const Consensus::LLMQParams& llmq_params,
      93             :     NodeId node_id)
      94             : {
      95             :     // Deterministic islocks MUST use rotation based llmq
      96         300 :     if (cycle_height % llmq_params.dkgInterval == 0) {
      97         300 :         return true;
      98             :     }
      99             : 
     100           0 :     m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
     101           0 :     return false;
     102         300 : }
     103             : 
     104         805 : std::unique_ptr<NetInstantSend::BatchVerificationData> NetInstantSend::BuildVerificationBatch(
     105             :     const Consensus::LLMQParams& llmq_params,
     106             :     int signOffset,
     107             :     const std::vector<instantsend::PendingISLockEntry>& pend)
     108             : {
     109         805 :     auto data = std::make_unique<BatchVerificationData>();
     110             : 
     111        1670 :     for (const auto& pending : pend) {
     112         865 :         const auto& hash = pending.islock_hash;
     113         865 :         auto nodeId = pending.node_id;
     114         865 :         const auto& islock = pending.islock;
     115             : 
     116         865 :         if (data->batchVerifier.badSources.count(nodeId)) {
     117           0 :             continue;
     118             :         }
     119             : 
     120         865 :         CBLSSignature sig = islock->sig.Get();
     121         865 :         if (!sig.IsValid()) {
     122           0 :             data->batchVerifier.badSources.emplace(nodeId);
     123           0 :             continue;
     124             :         }
     125             : 
     126         865 :         auto id = islock->GetRequestId();
     127             : 
     128             :         // no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it
     129         865 :         if (m_sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) {
     130         582 :             data->alreadyVerified++;
     131         582 :             continue;
     132             :         }
     133             : 
     134         283 :         auto cycleHeightOpt = GetBlockHeight(m_is_manager, m_chainstate, islock->cycleHash);
     135         283 :         if (!cycleHeightOpt) {
     136           0 :             data->batchVerifier.badSources.emplace(nodeId);
     137           0 :             continue;
     138             :         }
     139             : 
     140         283 :         int nSignHeight{-1};
     141         283 :         const auto dkgInterval = llmq_params.dkgInterval;
     142         283 :         const int tipHeight = m_is_manager.GetTipHeight();
     143         283 :         const int cycleHeight = *cycleHeightOpt;
     144         283 :         if (cycleHeight + dkgInterval < tipHeight) {
     145         143 :             nSignHeight = cycleHeight + dkgInterval - 1;
     146         143 :         }
     147             :         // For RegTest non-rotating quorum cycleHash has directly quorum hash
     148         295 :         auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman,
     149         271 :                                                                              id, nSignHeight, signOffset)
     150          12 :                                               : m_qman.GetQuorum(llmq_params.type, islock->cycleHash);
     151             : 
     152         283 :         if (!quorum) {
     153             :             // should not happen, but if one fails to select, all others will also fail to select
     154           0 :             return nullptr;
     155             :         }
     156         283 :         uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get();
     157         283 :         data->batchVerifier.PushMessage(nodeId, hash, signHash, sig, quorum->qc->quorumPublicKey);
     158         283 :         data->verifyCount++;
     159             : 
     160             :         // We can reconstruct the CRecoveredSig objects from the islock and pass it to the signing manager, which
     161             :         // avoids unnecessary double-verification of the signature. We however only do this when verification here
     162             :         // turns out to be good (which is checked further down)
     163         283 :         if (!m_sigman.HasRecoveredSigForId(llmq_params.type, id)) {
     164         566 :             data->recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid,
     165         283 :                                                                 islock->sig));
     166         283 :         }
     167         283 :     }
     168             : 
     169         805 :     return data;
     170         805 : }
     171             : 
     172         805 : Uint256HashSet NetInstantSend::ApplyVerificationResults(
     173             :     const Consensus::LLMQParams& llmq_params,
     174             :     bool ban,
     175             :     BatchVerificationData& data,
     176             :     const std::vector<instantsend::PendingISLockEntry>& pend)
     177             : {
     178         805 :     Uint256HashSet badISLocks;
     179         805 :     std::set<NodeId> penalized;
     180             : 
     181        1670 :     for (const auto& pending : pend) {
     182         865 :         const auto& hash = pending.islock_hash;
     183         865 :         auto nodeId = pending.node_id;
     184         865 :         const auto& islock = pending.islock;
     185             : 
     186         865 :         const bool source_bad = data.batchVerifier.badSources.count(nodeId);
     187         865 :         const bool message_bad = data.batchVerifier.badMessages.count(hash);
     188             : 
     189         865 :         if (source_bad || message_bad) {
     190           0 :             LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: verification failed, peer=%d\n",
     191             :                      __func__, islock->txid.ToString(), hash.ToString(), nodeId);
     192           0 :             if (ban && source_bad && penalized.emplace(nodeId).second) {
     193             :                 // Let's not be too harsh, as the peer might simply be unlucky and might have sent us
     194             :                 // an old lock which does not validate anymore due to changed quorums
     195           0 :                 m_peer_manager->PeerMisbehaving(nodeId, OLD_ACTIVE_SET_FAILURE_MISBEHAVIOR_SCORE);
     196           0 :             }
     197           0 :             if (message_bad) {
     198           0 :                 badISLocks.emplace(hash);
     199           0 :             }
     200           0 :             continue;
     201             :         }
     202             : 
     203         865 :         ProcessInstantSendLock(nodeId, hash, islock);
     204             : 
     205             :         // Pass a reconstructed recovered sig to the signing manager to avoid double-verification of the sig.
     206         865 :         auto it = data.recSigs.find(hash);
     207         865 :         if (it != data.recSigs.end()) {
     208         283 :             auto recSig = std::make_shared<llmq::CRecoveredSig>(std::move(it->second));
     209         283 :             if (!m_sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) {
     210         281 :                 LogPrint(BCLog::INSTANTSEND, /* Continued */
     211             :                          "NetInstantSend::%s -- txid=%s, islock=%s: "
     212             :                          "passing reconstructed recSig to signing mgr, peer=%d\n",
     213             :                          __func__, islock->txid.ToString(), hash.ToString(), nodeId);
     214         281 :                 m_sigman.PushReconstructedRecoveredSig(recSig);
     215         281 :             }
     216         283 :         }
     217             :     }
     218             : 
     219         805 :     return badISLocks;
     220         805 : }
     221             : 
     222             : namespace {
     223             : template <typename T>
     224             :     requires std::same_as<T, CTxIn> || std::same_as<T, COutPoint>
     225        1467 : Uint256HashSet GetIdsFromLockable(const std::vector<T>& vec)
     226             : {
     227        1467 :     Uint256HashSet ret{};
     228        1467 :     if (vec.empty()) return ret;
     229        1467 :     ret.reserve(vec.size());
     230        5890 :     for (const auto& in : vec) {
     231             :         if constexpr (std::is_same_v<T, COutPoint>) {
     232        4415 :             ret.emplace(instantsend::GenInputLockRequestId(in));
     233             :         } else if constexpr (std::is_same_v<T, CTxIn>) {
     234           8 :             ret.emplace(instantsend::GenInputLockRequestId(in.prevout));
     235             :         } else {
     236             :             assert(false);
     237             :         }
     238             :     }
     239        1467 :     return ret;
     240        1467 : }
     241             : } // anonymous namespace
     242             : 
     243       96785 : void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv)
     244             : {
     245       96785 :     if (msg_type != NetMsgType::ISDLOCK) {
     246       96485 :         return;
     247             :     }
     248             : 
     249         300 :     if (!m_is_manager.IsInstantSendEnabled()) return;
     250             : 
     251         300 :     auto islock = std::make_shared<instantsend::InstantSendLock>();
     252         300 :     vRecv >> *islock;
     253             : 
     254         300 :     const NodeId from = pfrom.GetId();
     255         300 :     uint256 hash = ::SerializeHash(*islock);
     256             : 
     257         600 :     WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(from, CInv{MSG_ISDLOCK, hash}));
     258             : 
     259         300 :     if (!ValidateIncomingISLock(*islock, from)) {
     260           0 :         return;
     261             :     }
     262             : 
     263         300 :     auto cycle_height = ResolveCycleHeight(islock->cycleHash);
     264         300 :     if (!cycle_height) {
     265             :         // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
     266           0 :         m_peer_manager->PeerMisbehaving(from, UNKNOWN_CYCLE_HASH_MISBEHAVIOR_SCORE);
     267           0 :         return;
     268             :     }
     269             : 
     270         300 :     auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     271         300 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     272         300 :     assert(llmq_params_opt);
     273         300 :     if (!ValidateDeterministicCycleHeight(*cycle_height, *llmq_params_opt, from)) {
     274           0 :         return;
     275             :     }
     276             : 
     277         300 :     if (!m_is_manager.AlreadyHave(CInv{MSG_ISDLOCK, hash})) {
     278         300 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend -- ISDLOCK txid=%s, islock=%s: received islock, peer=%d\n",
     279             :                  islock->txid.ToString(), hash.ToString(), from);
     280             : 
     281         300 :         m_is_manager.EnqueueInstantSendLock(from, hash, std::move(islock));
     282         300 :     }
     283       96785 : }
     284             : 
     285        2831 : void NetInstantSend::Start()
     286             : {
     287             :     // can't start new thread if we have one running already
     288        2831 :     if (workThread.joinable()) {
     289           0 :         assert(false);
     290             :     }
     291             : 
     292        5662 :     workThread = std::thread(&util::TraceThread, "isman", [this] { WorkThreadMain(); });
     293        2831 : }
     294             : 
     295        5714 : void NetInstantSend::Stop()
     296             : {
     297             :     // make sure to call Interrupt() first
     298        5714 :     if (!workInterrupt) {
     299           0 :         assert(false);
     300             :     }
     301             : 
     302        5714 :     if (workThread.joinable()) {
     303        2831 :         workThread.join();
     304        2831 :     }
     305        5714 : }
     306             : 
     307         805 : Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks(
     308             :     const Consensus::LLMQParams& llmq_params, int signOffset, bool ban,
     309             :     const std::vector<instantsend::PendingISLockEntry>& pend)
     310             : {
     311         805 :     auto batch = BuildVerificationBatch(llmq_params, signOffset, pend);
     312         805 :     if (!batch) return {};
     313             : 
     314         805 :     cxxtimer::Timer verifyTimer(true);
     315         805 :     batch->batchVerifier.Verify();
     316         805 :     verifyTimer.stop();
     317             : 
     318         805 :     LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- verified locks. count=%d, alreadyVerified=%d, vt=%d, nodes=%d\n",
     319             :              __func__, batch->verifyCount, batch->alreadyVerified,
     320             :              verifyTimer.count(), batch->batchVerifier.GetUniqueSourceCount());
     321             : 
     322         805 :     return ApplyVerificationResults(llmq_params, ban, *batch, pend);
     323         805 : }
     324             : 
     325         805 : void NetInstantSend::ProcessPendingISLocks(std::vector<instantsend::PendingISLockEntry>&& locks_to_process)
     326             : {
     327             :     // TODO Investigate if leaving this is ok
     328         805 :     auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     329         805 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     330         805 :     assert(llmq_params_opt);
     331         805 :     const auto& llmq_params = llmq_params_opt.value();
     332         805 :     auto dkgInterval = llmq_params.dkgInterval;
     333             : 
     334             :     // First check against the current active set and don't ban
     335         805 :     auto bad_is_locks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, locks_to_process);
     336         805 :     if (!bad_is_locks.empty()) {
     337           0 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- doing verification on old active set\n", __func__);
     338             : 
     339             :         // filter out valid IS locks from "pend" - keep only bad ones
     340           0 :         std::vector<instantsend::PendingISLockEntry> still_pending;
     341           0 :         still_pending.reserve(bad_is_locks.size());
     342           0 :         for (auto& pending : locks_to_process) {
     343           0 :             if (bad_is_locks.contains(pending.islock_hash)) {
     344           0 :                 still_pending.emplace_back(std::move(pending));
     345           0 :             }
     346             :         }
     347             :         // Now check against the previous active set and perform banning if this fails
     348           0 :         ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, still_pending);
     349           0 :     }
     350         805 :     uiInterface.NotifyInstantSendChanged();
     351         805 : }
     352             : 
     353         865 : void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock)
     354             : {
     355         865 :     LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__,
     356             :              islock->txid.ToString(), hash.ToString(), from);
     357             : 
     358         865 :     if (m_signer) {
     359         601 :         m_signer->ClearLockFromQueue(islock);
     360         601 :     }
     361         865 :     if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return;
     362             : 
     363         865 :     uint256 hashBlock{};
     364         865 :     auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock);
     365         865 :     const bool found_transaction{tx != nullptr};
     366             :     // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
     367         865 :     const auto minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock);
     368         865 :     if (found_transaction) {
     369             :         // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
     370             :         // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
     371         834 :         if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) {
     372          14 :             LogPrint(BCLog::INSTANTSEND, /* Continued */
     373             :                      "NetInstantSend::%s -- txlock=%s, islock=%s: dropping islock as it already got a "
     374             :                      "ChainLock in block %s, peer=%d\n",
     375             :                      __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
     376          14 :             return;
     377             :         }
     378         820 :         m_is_manager.WriteNewISLock(hash, islock, minedHeight);
     379         820 :     } else {
     380          31 :         m_is_manager.AddPendingISLock(hash, islock, from);
     381             :     }
     382             : 
     383             :     // This will also add children TXs to pendingRetryTxs
     384         851 :     m_is_manager.RemoveNonLockedTx(islock->txid, true);
     385             :     // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs.
     386             :     // We only need the ISLOCK from now on to detect conflicts
     387         851 :     TruncateRecoveredSigsForInputs(*islock);
     388         851 :     ResolveBlockConflicts(hash, *islock);
     389             : 
     390         851 :     if (found_transaction) {
     391         820 :         RemoveMempoolConflictsForLock(hash, *islock);
     392         820 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- notify about lock %s for tx %s\n", __func__,
     393             :                  hash.ToString(), tx->GetHash().ToString());
     394         820 :         GetMainSignals().NotifyTransactionLock(tx, islock);
     395             :         // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate
     396         820 :         m_mempool.AddTransactionsUpdated(1);
     397         820 :     }
     398             : 
     399         851 :     CInv inv(MSG_ISDLOCK, hash);
     400         851 :     if (found_transaction) {
     401         820 :         m_peer_manager->PeerRelayInvFiltered(inv, *tx);
     402         820 :     } else {
     403          31 :         m_peer_manager->PeerRelayInvFiltered(inv, islock->txid);
     404          31 :         m_peer_manager->PeerAskPeersForTransaction(islock->txid);
     405             :     }
     406         865 : }
     407             : 
     408        2831 : void NetInstantSend::WorkThreadMain()
     409             : {
     410      378587 :     while (!workInterrupt) {
     411      757170 :         bool fMoreWork = [&]() -> bool {
     412      378585 :             if (!m_is_manager.IsInstantSendEnabled()) return false;
     413             : 
     414      378106 :             auto [more_work, locks] = m_is_manager.FetchPendingLocks();
     415      189053 :             if (!locks.empty()) {
     416         805 :                 ProcessPendingISLocks(std::move(locks));
     417         805 :             }
     418      189053 :             if (m_signer) {
     419      154880 :                 m_signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry());
     420      154880 :             }
     421      189053 :             return more_work;
     422      378585 :         }();
     423             : 
     424      378585 :         if (!fMoreWork && !workInterrupt.sleep_for(WORK_THREAD_SLEEP_INTERVAL)) {
     425        2829 :             return;
     426             :         }
     427             :     }
     428        2831 : }
     429             : 
     430       36798 : void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_t, uint64_t mempool_sequence)
     431             : {
     432       36798 :     if (!m_is_manager.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) {
     433       35389 :         return;
     434             :     }
     435             : 
     436        1409 :     instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx);
     437        1409 :     if (islock == nullptr) {
     438        1385 :         if (m_signer) {
     439         934 :             m_signer->ProcessTx(*tx, false, Params().GetConsensus());
     440         934 :         }
     441             :         // TX is not locked, so make sure it is tracked
     442        1385 :         m_is_manager.AddNonLockedTx(tx, nullptr);
     443        1385 :     } else {
     444          24 :         RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock);
     445             :     }
     446       36798 : }
     447             : 
     448         854 : void NetInstantSend::ClearConflicting(const Uint256HashMap<CTransactionRef>& to_delete)
     449             : {
     450         864 :     for (const auto& [_, tx] : to_delete) {
     451          20 :         m_is_manager.RemoveNonLockedTx(tx->GetHash(), false);
     452          10 :         if (m_signer) {
     453          16 :             m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx->vin));
     454           8 :         }
     455             :     }
     456         854 : }
     457             : 
     458         844 : void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock)
     459             : {
     460         844 :     Uint256HashMap<CTransactionRef> toDelete;
     461             : 
     462             :     {
     463         844 :         LOCK(m_mempool.cs);
     464             : 
     465        3156 :         for (const auto& in : islock.inputs) {
     466        2312 :             auto it = m_mempool.mapNextTx.find(in);
     467        2312 :             if (it == m_mempool.mapNextTx.end()) {
     468         960 :                 continue;
     469             :             }
     470        1352 :             if (it->second->GetHash() != islock.txid) {
     471           0 :                 toDelete.emplace(it->second->GetHash(), m_mempool.get(it->second->GetHash()));
     472             : 
     473           0 :                 LogPrintf("%s -- txid=%s, mempool TX %s with input %s conflicts with islock=%s\n", __func__,
     474             :                           islock.txid.ToString(), it->second->GetHash().ToString(), in.ToStringShort(), hash.ToString());
     475           0 :             }
     476             :         }
     477             : 
     478         844 :         for (const auto& p : toDelete) {
     479           0 :             m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT);
     480             :         }
     481         844 :     }
     482         844 :     ClearConflicting(toDelete);
     483         844 : }
     484             : 
     485      220712 : void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork,
     486             :                                                 bool fInitialDownload)
     487             : {
     488      220712 :     m_is_manager.CacheTipHeight(pindexNew);
     489      220712 : }
     490             : 
     491      220685 : void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
     492             : {
     493      220685 :     bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height;
     494             : 
     495      220685 :     if (m_chainlocks.IsEnabled() && fDIP0008Active) {
     496             :         // Nothing to do here. We should keep all islocks and let chainlocks handle them.
     497       71254 :         return;
     498             :     }
     499             : 
     500      149431 :     int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock;
     501      149431 :     const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight);
     502             : 
     503      149431 :     if (pindex) {
     504      144995 :         HandleFullyConfirmedBlock(pindex);
     505      144995 :     }
     506      220685 : }
     507             : 
     508         317 : void NetInstantSend::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason,
     509             :                                                    uint64_t mempool_sequence)
     510             : {
     511         317 :     m_is_manager.TransactionIsRemoved(tx);
     512         317 : }
     513             : 
     514      227998 : void NetInstantSend::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
     515             : {
     516      227998 :     if (!m_is_manager.IsInstantSendEnabled()) {
     517      161139 :         return;
     518             :     }
     519             : 
     520       66859 :     m_is_manager.CacheTipHeight(pindex);
     521             : 
     522       66859 :     if (m_mn_sync.IsBlockchainSynced()) {
     523       65834 :         const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash());
     524      211318 :         for (const auto& tx : pblock->vtx) {
     525      145484 :             if (tx->IsCoinBase() || tx->vin.empty()) {
     526             :                 // coinbase and TXs with no inputs can't be locked
     527      143572 :                 continue;
     528             :             }
     529             : 
     530        1912 :             if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) {
     531        1456 :                 if (m_signer) {
     532        1242 :                     m_signer->ProcessTx(*tx, true, Params().GetConsensus());
     533        1242 :                 }
     534             :                 // TX is not locked, so make sure it is tracked
     535        1456 :                 m_is_manager.AddNonLockedTx(tx, pindex);
     536        1456 :             } else {
     537             :                 // TX is locked, so make sure we don't track it anymore
     538         456 :                 m_is_manager.RemoveNonLockedTx(tx->GetHash(), true);
     539             :             }
     540             :         }
     541       65834 :     }
     542       66859 :     m_is_manager.WriteBlockISLocks(pblock, pindex);
     543      227998 : }
     544             : 
     545       14229 : void NetInstantSend::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
     546             : {
     547       14229 :     m_is_manager.CacheDisconnectBlock(pindexDisconnected);
     548       14229 :     m_is_manager.CacheTipHeight(pindexDisconnected->pprev);
     549       14229 :     m_is_manager.RemoveBlockISLocks(pblock, pindexDisconnected);
     550       14229 : }
     551             : 
     552       13548 : void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr<const chainlock::ChainLockSig>& clsig)
     553             : {
     554       13548 :     HandleFullyConfirmedBlock(pindex);
     555       13548 : }
     556             : 
     557         851 : void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock)
     558             : {
     559         851 :     auto conflicts = m_is_manager.RetrieveISConflicts(islockHash, islock);
     560             : 
     561             :     // Lets see if any of the conflicts was already mined into a ChainLocked block
     562         851 :     bool hasChainLockedConflict = false;
     563         861 :     for (const auto& p : conflicts) {
     564          10 :         const auto* pindex = p.first;
     565          10 :         if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
     566           0 :             hasChainLockedConflict = true;
     567           0 :             break;
     568             :         }
     569             :     }
     570             : 
     571             :     // If a conflict was mined into a ChainLocked block, then we have no other choice and must prune the ISLOCK and all
     572             :     // chained ISLOCKs that build on top of this one. The probability of this is practically zero and can only happen
     573             :     // when large parts of the masternode network are controlled by an attacker. In this case we must still find
     574             :     // consensus and its better to sacrifice individual ISLOCKs then to sacrifice whole ChainLocks.
     575         851 :     if (hasChainLockedConflict) {
     576           0 :         LogPrintf("NetInstantSend::%s -- txid=%s, islock=%s: at least one conflicted TX already got a ChainLock\n",
     577             :                   __func__, islock.txid.ToString(), islockHash.ToString());
     578           0 :         m_is_manager.RemoveConflictingLock(islockHash, islock);
     579           0 :         return;
     580             :     }
     581             : 
     582         851 :     bool hasTxForLock = m_is_manager.HasTxForLock(islockHash);
     583             : 
     584         851 :     bool activateBestChain = false;
     585         861 :     for (const auto& p : conflicts) {
     586          10 :         const auto* pindex = p.first;
     587          10 :         ClearConflicting(p.second);
     588             : 
     589          10 :         LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString());
     590             : 
     591          10 :         BlockValidationState state;
     592             :         // need non-const pointer
     593          20 :         auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash()));
     594          10 :         if (!m_chainstate.InvalidateBlock(state, pindex2)) {
     595           0 :             LogPrintf("NetInstantSend::%s -- InvalidateBlock failed: %s\n", __func__, state.ToString());
     596             :             // This should not have happened and we are in a state were it's not safe to continue anymore
     597           0 :             assert(false);
     598             :         }
     599          10 :         if (hasTxForLock) {
     600           0 :             activateBestChain = true;
     601           0 :         } else {
     602          10 :             LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString());
     603          10 :             LOCK(::cs_main);
     604          10 :             m_chainstate.ResetBlockFailureFlags(pindex2);
     605          10 :         }
     606          10 :     }
     607             : 
     608         851 :     if (activateBestChain) {
     609           0 :         BlockValidationState state;
     610           0 :         if (!m_chainstate.ActivateBestChain(state)) {
     611           0 :             LogPrintf("NetInstantSend::%s -- ActivateBestChain failed: %s\n", __func__, state.ToString());
     612             :             // This should not have happened and we are in a state were it's not safe to continue anymore
     613           0 :             assert(false);
     614             :         }
     615           0 :     }
     616         851 : }
     617             : 
     618        1459 : void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock)
     619             : {
     620        1459 :     auto ids = GetIdsFromLockable(islock.inputs);
     621        1459 :     if (m_signer) {
     622        1014 :         m_signer->ClearInputsFromQueue(ids);
     623        1014 :     }
     624        5874 :     for (const auto& id : ids) {
     625        4415 :         m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id);
     626             :     }
     627        1459 : }
     628             : 
     629      158543 : void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
     630             : {
     631      158543 :     if (!m_is_manager.IsInstantSendEnabled()) {
     632      142962 :         return;
     633             :     }
     634             : 
     635       15581 :     auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex);
     636       17405 :     for (const auto& [islockHash, islock] : removeISLocks) {
     637         608 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n",
     638             :                  __func__, islock->txid.ToString(), islockHash.ToString());
     639             : 
     640             :         // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered
     641             :         // fully confirmed now
     642         608 :         m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId());
     643             : 
     644             :         // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts
     645             :         // from now on. All inputs are spent now and can't be spend in any other TX.
     646         608 :         TruncateRecoveredSigsForInputs(*islock);
     647             :     }
     648      158543 : }

Generated by: LCOV version 1.16