Skip to content
Merged
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
96 changes: 86 additions & 10 deletions src/instantsend/instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,25 +145,32 @@ std::variant<uint256, CTransactionRef, std::monostate> 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<int> minedHeight = GetBlockHeight(hashBlock);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: use snake_case for local variables for new code; it should be mined_height

doc/developer-nodes.md:

- **Symbol naming conventions**. These are preferred in new code, but are not
required when doing so would need changes to significant pieces of existing
code.
  - Variable (including function arguments) and namespace names are all lowercase and may use `_` to
    separate words (snake_case).
    - Class member variables have a `m_` prefix.
    - Global variables have a `g_` prefix.
  - Constant names are all uppercase, and use `_` to separate words.
  - Enumerator constants may be `snake_case`, `PascalCase` or `ALL_CAPS`.
    This is a more tolerant policy than the [C++ Core
    Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-caps),
    which recommend using `snake_case`.  Please use what seems appropriate.
  - Class names, function names, and method names are UpperCamelCase
    (PascalCase). Do not prefix class names with `C`. See [Internal interface
    naming style](#internal-interface-naming-style) for an exception to this
    convention.

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
Expand Down Expand Up @@ -251,6 +258,8 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr<const CBlock>& 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) {
Expand Down Expand Up @@ -278,6 +287,13 @@ 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());
}

CacheTipHeight(pindexDisconnected->pprev);

db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected);
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<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);
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);
Expand Down
33 changes: 25 additions & 8 deletions src/instantsend/instantsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
#include <unordered_lru_cache.h>

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

#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 CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(cs_height_cache);

public:
CInstantSendManager() = delete;
CInstantSendManager(const CInstantSendManager&) = delete;
Expand Down Expand Up @@ -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);
Expand All @@ -142,14 +152,15 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent
CSigningManager& Sigman() { return sigman; }
[[nodiscard]] std::variant<uint256, CTransactionRef, std::monostate> 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<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 @@ -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<int> 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
Expand Down
26 changes: 14 additions & 12 deletions src/instantsend/net_instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
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