Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 111 additions & 20 deletions src/instantsend/instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <spork.h>
#include <stats/client.h>

#include <cassert>

#include <cxxtimer.hpp>

// Forward declaration to break dependency over node/transaction.h
Expand Down Expand Up @@ -126,18 +128,24 @@ MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::st
return ret;
}

const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash));
if (blockIndex == nullptr) {
// Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
ret.m_error = MisbehavingError{1};
return ret;
auto cycleHeightOpt = GetBlockHeight(islock->cycleHash);
if (!cycleHeightOpt) {
const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash));
if (blockIndex == nullptr) {
// Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
ret.m_error = MisbehavingError{1};
return ret;
}
CacheBlockHeight(blockIndex->GetBlockHash(), blockIndex->nHeight);
cycleHeightOpt = blockIndex->nHeight;
}

// Deterministic islocks MUST use rotation based llmq
auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
assert(llmq_params_opt);
if (blockIndex->nHeight % llmq_params_opt->dkgInterval != 0) {
const int cycleHeight = *cycleHeightOpt;
if (cycleHeight % llmq_params_opt->dkgInterval != 0) {
ret.m_error = MisbehavingError{100};
return ret;
}
Expand Down Expand Up @@ -269,16 +277,18 @@ Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks(
continue;
}

const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash));
if (blockIndex == nullptr) {
auto cycleHeightOpt = 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 = 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, qman,
Expand Down Expand Up @@ -376,25 +386,32 @@ MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from,

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<int> 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->GetBlockHash(), pindexMined->nHeight);
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 " /* Continued */
"ChainLock in block %s, peer=%d\n",
__func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
return {};
}
}

if (found_transaction) {
db.WriteNewInstantSendLock(hash, islock);
if (pindexMined) {
db.WriteInstantSendLockMined(hash, pindexMined->nHeight);
if (minedHeight.has_value()) {
db.WriteInstantSendLockMined(hash, *minedHeight);
}
Comment on lines +391 to 415
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix clang-format issue.

The pipeline indicates clang-format differences at lines 393-395. Please run clang-format to resolve the formatting issue.

🧰 Tools
🪛 GitHub Actions: Clang Diff Format Check

[error] 393-395: Clang format differences found. Apply clang-format changes to src/instantsend/instantsend.cpp. Exit code 1 due to unformatted code.

🤖 Prompt for AI Agents
In src/instantsend/instantsend.cpp around lines 391 to 415, the submitted
changes trigger a clang-format mismatch specifically around lines 393-395;
reformat the file (or at least that region) with the project's clang-format
configuration to fix spacing/line breaks so the code matches style rules, then
stage and commit the formatted file. Ensure no semantic changes are
introduced—only whitespace/formatting adjustments.

} else {
// put it in a separate pending map and try again later
Expand Down Expand Up @@ -489,6 +506,12 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr<const CBlock>& pb
return;
}

{
LOCK(cs_height_cache);
CacheBlockHeightInternal(pindex->GetBlockHash(), pindex->nHeight);
m_cached_tip_height = pindex->nHeight;
}

if (m_mn_sync.IsBlockchainSynced()) {
const bool has_chainlock = clhandler.HasChainLock(pindex->nHeight, pindex->GetBlockHash());
for (const auto& tx : pblock->vtx) {
Expand Down Expand Up @@ -516,6 +539,16 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr<const CBlock>& pb
void CInstantSendManager::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock,
const CBlockIndex* pindexDisconnected)
{
{
LOCK(cs_height_cache);
m_cached_block_heights.erase(pindexDisconnected->GetBlockHash());
const CBlockIndex* const new_tip = pindexDisconnected->pprev;
m_cached_tip_height = new_tip ? new_tip->nHeight : -1;
if (new_tip) {
CacheBlockHeightInternal(new_tip->GetBlockHash(), new_tip->nHeight);
}
}

db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected);
}

Expand Down Expand Up @@ -637,6 +670,12 @@ void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock)

void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew)
{
{
LOCK(cs_height_cache);
CacheBlockHeightInternal(pindexNew->GetBlockHash(), pindexNew->nHeight);
m_cached_tip_height = pindexNew->nHeight;
}

bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height;

if (AreChainLocksEnabled(spork_manager) && fDIP0008Active) {
Expand Down Expand Up @@ -817,7 +856,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) {
Expand Down Expand Up @@ -923,6 +962,58 @@ size_t CInstantSendManager::GetInstantSendLockCount() const
return db.GetInstantSendLockCount();
}

void CInstantSendManager::CacheBlockHeightInternal(const uint256& hash, int height) const
{
AssertLockHeld(cs_height_cache);
m_cached_block_heights.insert(hash, height);
}

void CInstantSendManager::CacheBlockHeight(const uint256& hash, int height) const
{
LOCK(cs_height_cache);
CacheBlockHeightInternal(hash, height);
}

std::optional<int> 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->GetBlockHash(), pindex->nHeight);
return pindex->nHeight;
}

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());
assert(tip != nullptr);

{
LOCK(cs_height_cache);
CacheBlockHeightInternal(tip->GetBlockHash(), tip->nHeight);
m_cached_tip_height = tip->nHeight;
return m_cached_tip_height;
}
}

void CInstantSendManager::WorkThreadMain(PeerManager& peerman)
{
while (!workInterrupt) {
Expand Down
38 changes: 27 additions & 11 deletions src/instantsend/instantsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
#include <unordered_lru_cache.h>

#include <atomic>
#include <unordered_map>
#include <optional>
#include <unordered_set>

#include <saltedhasher.h>

class CBlockIndex;
class CChainState;
class CDataStream;
Expand Down Expand Up @@ -91,6 +93,14 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent
mutable Mutex cs_timingsTxSeen;
Uint256HashMap<int64_t> timingsTxSeen GUARDED_BY(cs_timingsTxSeen);

mutable Mutex cs_height_cache;
static constexpr size_t MAX_BLOCK_HEIGHT_CACHE{16384};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't 16k a bit too much? rotation quorums are not alive that much; also it's very unlikely situation that tx will exist that long before it will be mined.

16k is roughly 28 days

mutable unordered_lru_cache<uint256, int, StaticSaltedHasher, MAX_BLOCK_HEIGHT_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 uint256& hash, int height) const EXCLUSIVE_LOCKS_REQUIRED(cs_height_cache);

public:
CInstantSendManager() = delete;
CInstantSendManager(const CInstantSendManager&) = delete;
Expand All @@ -114,15 +124,15 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent

private:
instantsend::PendingState ProcessPendingInstantSendLocks()
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry);
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache);

Uint256HashSet ProcessPendingInstantSendLocks(const Consensus::LLMQParams& llmq_params, int signOffset, bool ban,
const Uint256HashMap<std::pair<NodeId, instantsend::InstantSendLockPtr>>& pend,
std::vector<std::pair<NodeId, MessageProcessingResult>>& peer_activity)
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry);
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache);
MessageProcessingResult 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 AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined)
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_timingsTxSeen);
Expand All @@ -135,7 +145,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 WorkThreadMain(PeerManager& peerman)
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry);
Expand All @@ -149,14 +159,15 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent
instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const override;

[[nodiscard]] MessageProcessingResult ProcessMessage(NodeId from, std::string_view msg_type, CDataStream& vRecv)
EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks);
EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks, !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<const CBlock>& pblock, const CBlockIndex* pindex)
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen);
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected);
EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen, !cs_height_cache);
void BlockDisconnected(const std::shared_ptr<const CBlock>& 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
Expand All @@ -166,14 +177,19 @@ 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 uint256& hash, int height) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache);
std::optional<int> GetBlockHeight(const uint256& hash) const override 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
Expand Down
28 changes: 20 additions & 8 deletions src/instantsend/signing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/instantsend/signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <instantsend/lock.h>
#include <llmq/signing.h>

#include <optional>

class CMasternodeSync;
class CSporkManager;
class CTxMemPool;
Expand All @@ -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<int> GetBlockHeight(const uint256& hash) const = 0;
virtual int GetTipHeight() const = 0;
};

class InstantSendSigner final : public llmq::CRecoveredSigsListener
Expand Down
Loading