From 2a7a5c6338c3b528a1627faf72a3d39c64a3adb6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 06:46:31 +0100 Subject: [PATCH] Only sign ChainLocks when all included TXs are "safe" Safe means that the TX is either ixlocked or known since at least 10 minutes. Also change miner code to only include safe TXs in block templates. --- src/llmq/quorums_chainlocks.cpp | 80 +++++++++++++++++++++++++++++++++ src/llmq/quorums_chainlocks.h | 5 +++ src/miner.cpp | 10 +++++ 3 files changed, 95 insertions(+) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index eb016ed045795..4d26115caedc7 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -4,6 +4,7 @@ #include "quorums.h" #include "quorums_chainlocks.h" +#include "quorums_instantsend.h" #include "quorums_signing.h" #include "quorums_utils.h" @@ -262,6 +263,61 @@ void CChainLocksHandler::TrySignChainTip() } } + LogPrintf("CChainLocksHandler::%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(), pindex->nHeight); + + // When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is + // considered safe when it is ixlocked or at least known since 10 minutes (from mempool or block). These checks are + // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the + // way down, we consider all TXs to be safe. + if (IsNewInstantSendEnabled() && sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { + auto pindexWalk = pindex; + while (pindexWalk) { + if (pindex->nHeight - pindexWalk->nHeight > 5) { + // no need to check further down, 6 confs is safe to assume that TXs below this height won't be + // ixlocked anymore if they aren't already + LogPrintf("CChainLocksHandler::%s -- tip and previous 5 blocks all safe\n", __func__); + break; + } + if (HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) { + // we don't care about ixlocks for TXs that are ChainLocked already + LogPrintf("CChainLocksHandler::%s -- chainlock at height %d \n", __func__, pindexWalk->nHeight); + break; + } + + decltype(blockTxs.begin()->second) txids; + { + LOCK(cs); + auto it = blockTxs.find(pindexWalk->GetBlockHash()); + if (it == blockTxs.end()) { + // this should actually not happen as NewPoWValidBlock should have been called before + LogPrintf("CChainLocksHandler::%s -- blockTxs for %s not found\n", __func__, + pindexWalk->GetBlockHash().ToString()); + return; + } + txids = it->second; + } + + for (auto& txid : *txids) { + int64_t txAge = 0; + { + LOCK(cs); + auto it = txFirstSeenTime.find(txid); + if (it != txFirstSeenTime.end()) { + txAge = GetAdjustedTime() - it->second; + } + } + + if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + LogPrintf("CChainLocksHandler::%s -- not signing block %s due to TX %s not being ixlocked and not old enough. age=%d\n", __func__, + pindexWalk->GetBlockHash().ToString(), txid.ToString(), txAge); + return; + } + } + + pindexWalk = pindexWalk->pprev; + } + } + uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight)); uint256 msgHash = pindex->GetBlockHash(); @@ -324,6 +380,30 @@ void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockInd txFirstSeenTime.emplace(tx.GetHash(), curTime); } +bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid) +{ + if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED) || !sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { + return true; + } + if (!IsNewInstantSendEnabled()) { + return true; + } + + int64_t txAge = 0; + { + LOCK(cs); + auto it = txFirstSeenTime.find(txid); + if (it != txFirstSeenTime.end()) { + txAge = GetAdjustedTime() - it->second; + } + } + + if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + return false; + } + return true; +} + // WARNING: cs_main and cs should not be held! void CChainLocksHandler::EnforceBestChainLock() { diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index 93ed32404f888..05fbd9a691ad1 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -46,6 +46,9 @@ class CChainLocksHandler : public CRecoveredSigsListener static const int64_t CLEANUP_INTERVAL = 1000 * 30; static const int64_t CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000; + // how long to wait for ixlocks until we consider a block with non-ixlocked TXs to be safe to sign + static const int64_t WAIT_FOR_IXLOCK_TIMEOUT = 10 * 60; + private: CScheduler* scheduler; CCriticalSection cs; @@ -94,6 +97,8 @@ class CChainLocksHandler : public CRecoveredSigsListener bool HasChainLock(int nHeight, const uint256& blockHash); bool HasConflictingChainLock(int nHeight, const uint256& blockHash); + bool IsTxSafeForMining(const uint256& txid); + private: // these require locks to be held already bool InternalHasChainLock(int nHeight, const uint256& blockHash); diff --git a/src/miner.cpp b/src/miner.cpp index c07061f329cae..1e2e8ba2c4598 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -34,6 +34,7 @@ #include "evo/deterministicmns.h" #include "llmq/quorums_blockprocessor.h" +#include "llmq/quorums_chainlocks.h" #include #include @@ -453,6 +454,11 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } + if (!llmq::chainLocksHandler->IsTxSafeForMining(mi->GetTx().GetHash())) { + ++mi; + continue; + } + // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; @@ -601,6 +607,10 @@ void BlockAssembler::addPriorityTxs() continue; } + if (!llmq::chainLocksHandler->IsTxSafeForMining(iter->GetTx().GetHash())) { + continue; + } + // If this tx fits in the block add it, otherwise keep looping if (TestForBlock(iter)) { AddToBlock(iter);