LCOV - code coverage report
Current view: top level - src/instantsend - net_instantsend.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 0 364 0.0 %
Date: 2026-06-25 07:23:51 Functions: 0 37 0.0 %

          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           0 : static std::optional<int> GetBlockHeight(llmq::CInstantSendManager& is_manager, const CChainState& chainstate,
      41             :                                          const uint256& hash)
      42             : {
      43           0 :     if (hash.IsNull()) {
      44           0 :         return std::nullopt;
      45             :     }
      46           0 :     auto ret = is_manager.GetCachedHeight(hash);
      47           0 :     if (ret) return ret;
      48             : 
      49           0 :     const CBlockIndex* pindex = WITH_LOCK(::cs_main, return chainstate.m_blockman.LookupBlockIndex(hash));
      50           0 :     if (pindex == nullptr) {
      51           0 :         return std::nullopt;
      52             :     }
      53           0 :     is_manager.CacheBlockHeight(pindex);
      54           0 :     return pindex->nHeight;
      55           0 : }
      56             : 
      57           0 : struct NetInstantSend::BatchVerificationData {
      58           0 :     CBLSBatchVerifier<NodeId, uint256> batchVerifier{false, true, BATCH_VERIFIER_SOURCE_THRESHOLD};
      59             :     Uint256HashMap<llmq::CRecoveredSig> recSigs;
      60           0 :     size_t verifyCount{0};
      61           0 :     size_t alreadyVerified{0};
      62             : };
      63             : 
      64           0 : bool NetInstantSend::ValidateIncomingISLock(const instantsend::InstantSendLock& islock, NodeId node_id)
      65             : {
      66           0 :     if (!islock.TriviallyValid()) {
      67           0 :         m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
      68           0 :         return false;
      69             :     }
      70             : 
      71           0 :     return true;
      72           0 : }
      73             : 
      74           0 : std::optional<int> NetInstantSend::ResolveCycleHeight(const uint256& cycle_hash)
      75             : {
      76           0 :     auto cycle_height = GetBlockHeight(m_is_manager, m_chainstate, cycle_hash);
      77           0 :     if (cycle_height) {
      78           0 :         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           0 : }
      89             : 
      90           0 : 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           0 :     if (cycle_height % llmq_params.dkgInterval == 0) {
      97           0 :         return true;
      98             :     }
      99             : 
     100           0 :     m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
     101           0 :     return false;
     102           0 : }
     103             : 
     104           0 : 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           0 :     auto data = std::make_unique<BatchVerificationData>();
     110             : 
     111           0 :     for (const auto& pending : pend) {
     112           0 :         const auto& hash = pending.islock_hash;
     113           0 :         auto nodeId = pending.node_id;
     114           0 :         const auto& islock = pending.islock;
     115             : 
     116           0 :         if (data->batchVerifier.badSources.count(nodeId)) {
     117           0 :             continue;
     118             :         }
     119             : 
     120           0 :         CBLSSignature sig = islock->sig.Get();
     121           0 :         if (!sig.IsValid()) {
     122           0 :             data->batchVerifier.badSources.emplace(nodeId);
     123           0 :             continue;
     124             :         }
     125             : 
     126           0 :         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           0 :         if (m_sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) {
     130           0 :             data->alreadyVerified++;
     131           0 :             continue;
     132             :         }
     133             : 
     134           0 :         auto cycleHeightOpt = GetBlockHeight(m_is_manager, m_chainstate, islock->cycleHash);
     135           0 :         if (!cycleHeightOpt) {
     136           0 :             data->batchVerifier.badSources.emplace(nodeId);
     137           0 :             continue;
     138             :         }
     139             : 
     140           0 :         int nSignHeight{-1};
     141           0 :         const auto dkgInterval = llmq_params.dkgInterval;
     142           0 :         const int tipHeight = m_is_manager.GetTipHeight();
     143           0 :         const int cycleHeight = *cycleHeightOpt;
     144           0 :         if (cycleHeight + dkgInterval < tipHeight) {
     145           0 :             nSignHeight = cycleHeight + dkgInterval - 1;
     146           0 :         }
     147             :         // For RegTest non-rotating quorum cycleHash has directly quorum hash
     148           0 :         auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman,
     149           0 :                                                                              id, nSignHeight, signOffset)
     150           0 :                                               : m_qman.GetQuorum(llmq_params.type, islock->cycleHash);
     151             : 
     152           0 :         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           0 :         uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get();
     157           0 :         data->batchVerifier.PushMessage(nodeId, hash, signHash, sig, quorum->qc->quorumPublicKey);
     158           0 :         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           0 :         if (!m_sigman.HasRecoveredSigForId(llmq_params.type, id)) {
     164           0 :             data->recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid,
     165           0 :                                                                 islock->sig));
     166           0 :         }
     167           0 :     }
     168             : 
     169           0 :     return data;
     170           0 : }
     171             : 
     172           0 : Uint256HashSet NetInstantSend::ApplyVerificationResults(
     173             :     const Consensus::LLMQParams& llmq_params,
     174             :     bool ban,
     175             :     BatchVerificationData& data,
     176             :     const std::vector<instantsend::PendingISLockEntry>& pend)
     177             : {
     178           0 :     Uint256HashSet badISLocks;
     179           0 :     std::set<NodeId> penalized;
     180             : 
     181           0 :     for (const auto& pending : pend) {
     182           0 :         const auto& hash = pending.islock_hash;
     183           0 :         auto nodeId = pending.node_id;
     184           0 :         const auto& islock = pending.islock;
     185             : 
     186           0 :         const bool source_bad = data.batchVerifier.badSources.count(nodeId);
     187           0 :         const bool message_bad = data.batchVerifier.badMessages.count(hash);
     188             : 
     189           0 :         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           0 :         ProcessInstantSendLock(nodeId, hash, islock);
     204             : 
     205             :         // Pass a reconstructed recovered sig to the signing manager to avoid double-verification of the sig.
     206           0 :         auto it = data.recSigs.find(hash);
     207           0 :         if (it != data.recSigs.end()) {
     208           0 :             auto recSig = std::make_shared<llmq::CRecoveredSig>(std::move(it->second));
     209           0 :             if (!m_sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) {
     210           0 :                 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           0 :                 m_sigman.PushReconstructedRecoveredSig(recSig);
     215           0 :             }
     216           0 :         }
     217             :     }
     218             : 
     219           0 :     return badISLocks;
     220           0 : }
     221             : 
     222             : namespace {
     223             : template <typename T>
     224             :     requires std::same_as<T, CTxIn> || std::same_as<T, COutPoint>
     225           0 : Uint256HashSet GetIdsFromLockable(const std::vector<T>& vec)
     226             : {
     227           0 :     Uint256HashSet ret{};
     228           0 :     if (vec.empty()) return ret;
     229           0 :     ret.reserve(vec.size());
     230           0 :     for (const auto& in : vec) {
     231             :         if constexpr (std::is_same_v<T, COutPoint>) {
     232           0 :             ret.emplace(instantsend::GenInputLockRequestId(in));
     233             :         } else if constexpr (std::is_same_v<T, CTxIn>) {
     234           0 :             ret.emplace(instantsend::GenInputLockRequestId(in.prevout));
     235             :         } else {
     236             :             assert(false);
     237             :         }
     238             :     }
     239           0 :     return ret;
     240           0 : }
     241             : } // anonymous namespace
     242             : 
     243           0 : void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv)
     244             : {
     245           0 :     if (msg_type != NetMsgType::ISDLOCK) {
     246           0 :         return;
     247             :     }
     248             : 
     249           0 :     if (!m_is_manager.IsInstantSendEnabled()) return;
     250             : 
     251           0 :     auto islock = std::make_shared<instantsend::InstantSendLock>();
     252           0 :     vRecv >> *islock;
     253             : 
     254           0 :     const NodeId from = pfrom.GetId();
     255           0 :     uint256 hash = ::SerializeHash(*islock);
     256             : 
     257           0 :     WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(from, CInv{MSG_ISDLOCK, hash}));
     258             : 
     259           0 :     if (!ValidateIncomingISLock(*islock, from)) {
     260           0 :         return;
     261             :     }
     262             : 
     263           0 :     auto cycle_height = ResolveCycleHeight(islock->cycleHash);
     264           0 :     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           0 :     auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     271           0 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     272           0 :     assert(llmq_params_opt);
     273           0 :     if (!ValidateDeterministicCycleHeight(*cycle_height, *llmq_params_opt, from)) {
     274           0 :         return;
     275             :     }
     276             : 
     277           0 :     if (!m_is_manager.AlreadyHave(CInv{MSG_ISDLOCK, hash})) {
     278           0 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend -- ISDLOCK txid=%s, islock=%s: received islock, peer=%d\n",
     279             :                  islock->txid.ToString(), hash.ToString(), from);
     280             : 
     281           0 :         m_is_manager.EnqueueInstantSendLock(from, hash, std::move(islock));
     282           0 :     }
     283           0 : }
     284             : 
     285           0 : void NetInstantSend::Start()
     286             : {
     287             :     // can't start new thread if we have one running already
     288           0 :     if (workThread.joinable()) {
     289           0 :         assert(false);
     290             :     }
     291             : 
     292           0 :     workThread = std::thread(&util::TraceThread, "isman", [this] { WorkThreadMain(); });
     293           0 : }
     294             : 
     295           0 : void NetInstantSend::Stop()
     296             : {
     297             :     // make sure to call Interrupt() first
     298           0 :     if (!workInterrupt) {
     299           0 :         assert(false);
     300             :     }
     301             : 
     302           0 :     if (workThread.joinable()) {
     303           0 :         workThread.join();
     304           0 :     }
     305           0 : }
     306             : 
     307           0 : Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks(
     308             :     const Consensus::LLMQParams& llmq_params, int signOffset, bool ban,
     309             :     const std::vector<instantsend::PendingISLockEntry>& pend)
     310             : {
     311           0 :     auto batch = BuildVerificationBatch(llmq_params, signOffset, pend);
     312           0 :     if (!batch) return {};
     313             : 
     314           0 :     cxxtimer::Timer verifyTimer(true);
     315           0 :     batch->batchVerifier.Verify();
     316           0 :     verifyTimer.stop();
     317             : 
     318           0 :     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           0 :     return ApplyVerificationResults(llmq_params, ban, *batch, pend);
     323           0 : }
     324             : 
     325           0 : void NetInstantSend::ProcessPendingISLocks(std::vector<instantsend::PendingISLockEntry>&& locks_to_process)
     326             : {
     327             :     // TODO Investigate if leaving this is ok
     328           0 :     auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
     329           0 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     330           0 :     assert(llmq_params_opt);
     331           0 :     const auto& llmq_params = llmq_params_opt.value();
     332           0 :     auto dkgInterval = llmq_params.dkgInterval;
     333             : 
     334             :     // First check against the current active set and don't ban
     335           0 :     auto bad_is_locks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, locks_to_process);
     336           0 :     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           0 :     uiInterface.NotifyInstantSendChanged();
     351           0 : }
     352             : 
     353           0 : void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock)
     354             : {
     355           0 :     LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__,
     356             :              islock->txid.ToString(), hash.ToString(), from);
     357             : 
     358           0 :     if (m_signer) {
     359           0 :         m_signer->ClearLockFromQueue(islock);
     360           0 :     }
     361           0 :     if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return;
     362             : 
     363           0 :     uint256 hashBlock{};
     364           0 :     auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock);
     365           0 :     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           0 :     const auto minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock);
     368           0 :     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           0 :         if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) {
     372           0 :             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           0 :             return;
     377             :         }
     378           0 :         m_is_manager.WriteNewISLock(hash, islock, minedHeight);
     379           0 :     } else {
     380           0 :         m_is_manager.AddPendingISLock(hash, islock, from);
     381             :     }
     382             : 
     383             :     // This will also add children TXs to pendingRetryTxs
     384           0 :     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           0 :     TruncateRecoveredSigsForInputs(*islock);
     388           0 :     ResolveBlockConflicts(hash, *islock);
     389             : 
     390           0 :     if (found_transaction) {
     391           0 :         RemoveMempoolConflictsForLock(hash, *islock);
     392           0 :         LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- notify about lock %s for tx %s\n", __func__,
     393             :                  hash.ToString(), tx->GetHash().ToString());
     394           0 :         GetMainSignals().NotifyTransactionLock(tx, islock);
     395             :         // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate
     396           0 :         m_mempool.AddTransactionsUpdated(1);
     397           0 :     }
     398             : 
     399           0 :     CInv inv(MSG_ISDLOCK, hash);
     400           0 :     if (found_transaction) {
     401           0 :         m_peer_manager->PeerRelayInvFiltered(inv, *tx);
     402           0 :     } else {
     403           0 :         m_peer_manager->PeerRelayInvFiltered(inv, islock->txid);
     404           0 :         m_peer_manager->PeerAskPeersForTransaction(islock->txid);
     405             :     }
     406           0 : }
     407             : 
     408           0 : void NetInstantSend::WorkThreadMain()
     409             : {
     410           0 :     while (!workInterrupt) {
     411           0 :         bool fMoreWork = [&]() -> bool {
     412           0 :             if (!m_is_manager.IsInstantSendEnabled()) return false;
     413             : 
     414           0 :             auto [more_work, locks] = m_is_manager.FetchPendingLocks();
     415           0 :             if (!locks.empty()) {
     416           0 :                 ProcessPendingISLocks(std::move(locks));
     417           0 :             }
     418           0 :             if (m_signer) {
     419           0 :                 m_signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry());
     420           0 :             }
     421           0 :             return more_work;
     422           0 :         }();
     423             : 
     424           0 :         if (!fMoreWork && !workInterrupt.sleep_for(WORK_THREAD_SLEEP_INTERVAL)) {
     425           0 :             return;
     426             :         }
     427             :     }
     428           0 : }
     429             : 
     430           0 : void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_t, uint64_t mempool_sequence)
     431             : {
     432           0 :     if (!m_is_manager.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) {
     433           0 :         return;
     434             :     }
     435             : 
     436           0 :     instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx);
     437           0 :     if (islock == nullptr) {
     438           0 :         if (m_signer) {
     439           0 :             m_signer->ProcessTx(*tx, false, Params().GetConsensus());
     440           0 :         }
     441             :         // TX is not locked, so make sure it is tracked
     442           0 :         m_is_manager.AddNonLockedTx(tx, nullptr);
     443           0 :     } else {
     444           0 :         RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock);
     445             :     }
     446           0 : }
     447             : 
     448           0 : void NetInstantSend::ClearConflicting(const Uint256HashMap<CTransactionRef>& to_delete)
     449             : {
     450           0 :     for (const auto& [_, tx] : to_delete) {
     451           0 :         m_is_manager.RemoveNonLockedTx(tx->GetHash(), false);
     452           0 :         if (m_signer) {
     453           0 :             m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx->vin));
     454           0 :         }
     455             :     }
     456           0 : }
     457             : 
     458           0 : void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock)
     459             : {
     460           0 :     Uint256HashMap<CTransactionRef> toDelete;
     461             : 
     462             :     {
     463           0 :         LOCK(m_mempool.cs);
     464             : 
     465           0 :         for (const auto& in : islock.inputs) {
     466           0 :             auto it = m_mempool.mapNextTx.find(in);
     467           0 :             if (it == m_mempool.mapNextTx.end()) {
     468           0 :                 continue;
     469             :             }
     470           0 :             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           0 :         for (const auto& p : toDelete) {
     479           0 :             m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT);
     480             :         }
     481           0 :     }
     482           0 :     ClearConflicting(toDelete);
     483           0 : }
     484             : 
     485           0 : void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork,
     486             :                                                 bool fInitialDownload)
     487             : {
     488           0 :     m_is_manager.CacheTipHeight(pindexNew);
     489           0 : }
     490             : 
     491           0 : void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
     492             : {
     493           0 :     bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height;
     494             : 
     495           0 :     if (m_chainlocks.IsEnabled() && fDIP0008Active) {
     496             :         // Nothing to do here. We should keep all islocks and let chainlocks handle them.
     497           0 :         return;
     498             :     }
     499             : 
     500           0 :     int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock;
     501           0 :     const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight);
     502             : 
     503           0 :     if (pindex) {
     504           0 :         HandleFullyConfirmedBlock(pindex);
     505           0 :     }
     506           0 : }
     507             : 
     508           0 : void NetInstantSend::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason,
     509             :                                                    uint64_t mempool_sequence)
     510             : {
     511           0 :     m_is_manager.TransactionIsRemoved(tx);
     512           0 : }
     513             : 
     514           0 : void NetInstantSend::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
     515             : {
     516           0 :     if (!m_is_manager.IsInstantSendEnabled()) {
     517           0 :         return;
     518             :     }
     519             : 
     520           0 :     m_is_manager.CacheTipHeight(pindex);
     521             : 
     522           0 :     if (m_mn_sync.IsBlockchainSynced()) {
     523           0 :         const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash());
     524           0 :         for (const auto& tx : pblock->vtx) {
     525           0 :             if (tx->IsCoinBase() || tx->vin.empty()) {
     526             :                 // coinbase and TXs with no inputs can't be locked
     527           0 :                 continue;
     528             :             }
     529             : 
     530           0 :             if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) {
     531           0 :                 if (m_signer) {
     532           0 :                     m_signer->ProcessTx(*tx, true, Params().GetConsensus());
     533           0 :                 }
     534             :                 // TX is not locked, so make sure it is tracked
     535           0 :                 m_is_manager.AddNonLockedTx(tx, pindex);
     536           0 :             } else {
     537             :                 // TX is locked, so make sure we don't track it anymore
     538           0 :                 m_is_manager.RemoveNonLockedTx(tx->GetHash(), true);
     539             :             }
     540             :         }
     541           0 :     }
     542           0 :     m_is_manager.WriteBlockISLocks(pblock, pindex);
     543           0 : }
     544             : 
     545           0 : void NetInstantSend::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
     546             : {
     547           0 :     m_is_manager.CacheDisconnectBlock(pindexDisconnected);
     548           0 :     m_is_manager.CacheTipHeight(pindexDisconnected->pprev);
     549           0 :     m_is_manager.RemoveBlockISLocks(pblock, pindexDisconnected);
     550           0 : }
     551             : 
     552           0 : void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr<const chainlock::ChainLockSig>& clsig)
     553             : {
     554           0 :     HandleFullyConfirmedBlock(pindex);
     555           0 : }
     556             : 
     557           0 : void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock)
     558             : {
     559           0 :     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           0 :     bool hasChainLockedConflict = false;
     563           0 :     for (const auto& p : conflicts) {
     564           0 :         const auto* pindex = p.first;
     565           0 :         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           0 :     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           0 :     bool hasTxForLock = m_is_manager.HasTxForLock(islockHash);
     583             : 
     584           0 :     bool activateBestChain = false;
     585           0 :     for (const auto& p : conflicts) {
     586           0 :         const auto* pindex = p.first;
     587           0 :         ClearConflicting(p.second);
     588             : 
     589           0 :         LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString());
     590             : 
     591           0 :         BlockValidationState state;
     592             :         // need non-const pointer
     593           0 :         auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash()));
     594           0 :         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           0 :         if (hasTxForLock) {
     600           0 :             activateBestChain = true;
     601           0 :         } else {
     602           0 :             LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString());
     603           0 :             LOCK(::cs_main);
     604           0 :             m_chainstate.ResetBlockFailureFlags(pindex2);
     605           0 :         }
     606           0 :     }
     607             : 
     608           0 :     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           0 : }
     617             : 
     618           0 : void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock)
     619             : {
     620           0 :     auto ids = GetIdsFromLockable(islock.inputs);
     621           0 :     if (m_signer) {
     622           0 :         m_signer->ClearInputsFromQueue(ids);
     623           0 :     }
     624           0 :     for (const auto& id : ids) {
     625           0 :         m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id);
     626             :     }
     627           0 : }
     628             : 
     629           0 : void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
     630             : {
     631           0 :     if (!m_is_manager.IsInstantSendEnabled()) {
     632           0 :         return;
     633             :     }
     634             : 
     635           0 :     auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex);
     636           0 :     for (const auto& [islockHash, islock] : removeISLocks) {
     637           0 :         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           0 :         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           0 :         TruncateRecoveredSigsForInputs(*islock);
     647             :     }
     648           0 : }

Generated by: LCOV version 1.16