diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 0415a19d53776..68766bf0c71fa 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -145,25 +145,32 @@ std::variant CInstantSendManager::Proc uint256 hashBlock{}; const auto tx = GetTransaction(nullptr, &mempool, islock->txid, Params().GetConsensus(), hashBlock); - const CBlockIndex* pindexMined{nullptr}; const bool found_transaction{tx != nullptr}; // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally - if (found_transaction && !hashBlock.IsNull()) { - pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); - + std::optional minedHeight = GetBlockHeight(hashBlock); + if (found_transaction) { + if (!minedHeight.has_value()) { + const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); + if (pindexMined != nullptr) { + CacheBlockHeight(pindexMined); + minedHeight = pindexMined->nHeight; + } + } // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain - if (pindexMined != nullptr && clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a ChainLock in block %s, peer=%d\n", __func__, - islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); + if (minedHeight.has_value() && clhandler.HasChainLock(*minedHeight, hashBlock)) { + LogPrint(BCLog::INSTANTSEND, /* Continued */ + "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " + "ChainLock in block %s, peer=%d\n", + __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); return std::monostate{}; } } if (found_transaction) { db.WriteNewInstantSendLock(hash, islock); - if (pindexMined) { - db.WriteInstantSendLockMined(hash, pindexMined->nHeight); + if (minedHeight.has_value()) { + db.WriteInstantSendLockMined(hash, *minedHeight); } } else { // put it in a separate pending map and try again later @@ -251,6 +258,8 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr& pb return; } + CacheTipHeight(pindex); + if (m_mn_sync.IsBlockchainSynced()) { const bool has_chainlock = clhandler.HasChainLock(pindex->nHeight, pindex->GetBlockHash()); for (const auto& tx : pblock->vtx) { @@ -278,6 +287,13 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr& pb void CInstantSendManager::BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) { + { + LOCK(cs_height_cache); + m_cached_block_heights.erase(pindexDisconnected->GetBlockHash()); + } + + CacheTipHeight(pindexDisconnected->pprev); + db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected); } @@ -417,6 +433,8 @@ void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock) void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) { + CacheTipHeight(pindexNew); + bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; if (AreChainLocksEnabled(spork_manager) && fDIP0008Active) { @@ -597,7 +615,7 @@ void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const { LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: Removing ISLOCK and its chained children\n", __func__, islock.txid.ToString(), islockHash.ToString()); - int tipHeight = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Height()); + const int tipHeight = GetTipHeight(); auto removedIslocks = db.RemoveChainedInstantSendLocks(islockHash, islock.txid, tipHeight); for (const auto& h : removedIslocks) { @@ -703,6 +721,64 @@ size_t CInstantSendManager::GetInstantSendLockCount() const return db.GetInstantSendLockCount(); } +void CInstantSendManager::CacheBlockHeightInternal(const CBlockIndex* const block_index) const +{ + AssertLockHeld(cs_height_cache); + m_cached_block_heights.insert(block_index->GetBlockHash(), block_index->nHeight); +} + +void CInstantSendManager::CacheBlockHeight(const CBlockIndex* const block_index) const +{ + LOCK(cs_height_cache); + CacheBlockHeightInternal(block_index); +} + +std::optional CInstantSendManager::GetBlockHeight(const uint256& hash) const +{ + if (hash.IsNull()) { + return std::nullopt; + } + { + LOCK(cs_height_cache); + int cached_height{0}; + if (m_cached_block_heights.get(hash, cached_height)) return cached_height; + } + + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hash)); + if (pindex == nullptr) { + return std::nullopt; + } + + CacheBlockHeight(pindex); + return pindex->nHeight; +} + +void CInstantSendManager::CacheTipHeight(const CBlockIndex* const tip) const +{ + LOCK(cs_height_cache); + if (tip) { + CacheBlockHeightInternal(tip); + m_cached_tip_height = tip->nHeight; + } else { + m_cached_tip_height = -1; + } +} + +int CInstantSendManager::GetTipHeight() const +{ + { + LOCK(cs_height_cache); + if (m_cached_tip_height >= 0) { + return m_cached_tip_height; + } + } + + const CBlockIndex* tip = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip()); + + CacheTipHeight(tip); + return tip ? tip->nHeight : -1; +} + bool CInstantSendManager::IsInstantSendEnabled() const { return !fReindex && !fImporting && spork_manager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 30e8ba4a8ebfd..9b93198e85f44 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -17,11 +17,13 @@ #include #include -#include +#include #include #include #include +#include + class CBlockIndex; class CChainState; class CDataStream; @@ -91,6 +93,14 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent mutable Mutex cs_timingsTxSeen; Uint256HashMap timingsTxSeen GUARDED_BY(cs_timingsTxSeen); + mutable Mutex cs_height_cache; + static constexpr size_t MAX_BLOCK_HEIGHT_CACHE{16384}; + mutable unordered_lru_cache m_cached_block_heights + GUARDED_BY(cs_height_cache); + mutable int m_cached_tip_height GUARDED_BY(cs_height_cache){-1}; + + void CacheBlockHeightInternal(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(cs_height_cache); + public: CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; @@ -122,7 +132,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); void HandleFullyConfirmedBlock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); @@ -142,14 +152,15 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CSigningManager& Sigman() { return sigman; } [[nodiscard]] std::variant ProcessInstantSendLock( NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); void TransactionAddedToMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen); - void TransactionRemovedFromMempool(const CTransactionRef& tx); + void TransactionRemovedFromMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen); - void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected); + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen, !cs_height_cache); + void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) + EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); bool GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const @@ -159,14 +170,20 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void NotifyChainLock(const CBlockIndex* pindexChainLock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); void UpdatedBlockTip(const CBlockIndex* pindexNew) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry, !cs_height_cache); - void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock); + void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) override EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); size_t GetInstantSendLockCount() const; + void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + std::optional GetBlockHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + void CacheTipHeight(const CBlockIndex* const tip) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + int GetTipHeight() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + bool IsInstantSendEnabled() const override; /** * If true, MN should sign all transactions, if false, MN should not sign diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index f4eeb5300ce5a..0e4b4b38caf52 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -34,18 +34,18 @@ void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, C return; } - int block_height = [this, islock] { + auto cycleHeightOpt = m_is_manager.GetBlockHeight(islock->cycleHash); + if (!cycleHeightOpt) { const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); if (blockIndex == nullptr) { - return -1; + // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 1); + return; } - return blockIndex->nHeight; - }(); - if (block_height < 0) { - // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash - m_peer_manager->PeerMisbehaving(pfrom.GetId(), 1); - return; + m_is_manager.CacheBlockHeight(blockIndex); + cycleHeightOpt = blockIndex->nHeight; } + const int block_height = *cycleHeightOpt; // Deterministic islocks MUST use rotation based llmq auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; @@ -125,16 +125,18 @@ Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks( continue; } - const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); - if (blockIndex == nullptr) { + auto cycleHeightOpt = m_is_manager.GetBlockHeight(islock->cycleHash); + if (!cycleHeightOpt) { batchVerifier.badSources.emplace(nodeId); continue; } int nSignHeight{-1}; const auto dkgInterval = llmq_params.dkgInterval; - if (blockIndex->nHeight + dkgInterval < m_chainstate.m_chain.Height()) { - nSignHeight = blockIndex->nHeight + dkgInterval - 1; + const int tipHeight = m_is_manager.GetTipHeight(); + const int cycleHeight = *cycleHeightOpt; + if (cycleHeight + dkgInterval < tipHeight) { + nSignHeight = cycleHeight + dkgInterval - 1; } // For RegTest non-rotating quorum cycleHash has directly quorum hash auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman, diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index 789c0135f9685..984968d9c53da 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -204,16 +204,28 @@ bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, return false; } - const CBlockIndex* pindexMined; - int nTxAge; - { - LOCK(::cs_main); - pindexMined = m_chainstate.m_blockman.LookupBlockIndex(hashBlock); - nTxAge = m_chainstate.m_chain.Height() - pindexMined->nHeight + 1; + const auto blockHeight = m_isman.GetBlockHeight(hashBlock); + if (!blockHeight) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n", __func__, + txHash.ToString(), outpoint.hash.ToString()); + } + return false; + } + + const int tipHeight = m_isman.GetTipHeight(); + + if (tipHeight < *blockHeight) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: cached tip height %d is below block height %d for parent TX %s\n", + __func__, txHash.ToString(), tipHeight, *blockHeight, outpoint.hash.ToString()); + } + return false; } - if (nTxAge < nInstantSendConfirmationsRequired && - !m_clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { + const int nTxAge = tipHeight - *blockHeight + 1; + + if (nTxAge < nInstantSendConfirmationsRequired && !m_clhandler.HasChainLock(*blockHeight, hashBlock)) { if (printDebug) { LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__, txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired); diff --git a/src/instantsend/signing.h b/src/instantsend/signing.h index f62ea90c4a185..cf878e8c21e91 100644 --- a/src/instantsend/signing.h +++ b/src/instantsend/signing.h @@ -8,6 +8,8 @@ #include #include +#include + class CMasternodeSync; class CSporkManager; class CTxMemPool; @@ -34,6 +36,8 @@ class InstantSendSignerParent virtual bool IsLocked(const uint256& txHash) const = 0; virtual InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const = 0; virtual void TryEmplacePendingLock(const uint256& hash, const NodeId id, const InstantSendLockPtr& islock) = 0; + virtual std::optional GetBlockHeight(const uint256& hash) const = 0; + virtual int GetTipHeight() const = 0; }; class InstantSendSigner final : public llmq::CRecoveredSigsListener