<NME> quorums_chainlocks.cpp
<BEF> // Copyright (c) 2019 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "quorums.h"
#include "quorums_chainlocks.h"
#include "quorums_instantsend.h"
#include "quorums_signing.h"
#include "quorums_utils.h"
#include "evo/spork.h"

#include "chain.h"
#include "masternode-sync.h"
#include "net_processing.h"
#include "scheduler.h"
#include "txmempool.h"
#include "validation.h"

namespace llmq
{

static const std::string CLSIG_REQUESTID_PREFIX = "clsig";

CChainLocksHandler* chainLocksHandler;

std::string CChainLockSig::ToString() const
{
    return strprintf("CChainLockSig(nHeight=%d, blockHash=%s)", nHeight, blockHash.ToString());
}

CChainLocksHandler::CChainLocksHandler(CScheduler* _scheduler) :
    scheduler(_scheduler)
{
}

CChainLocksHandler::~CChainLocksHandler()
{
}

void CChainLocksHandler::Start()
{
    {
        LOCK(cs_main);
        if (chainActive.Tip())
            isChainlocksEnabled = CSporkManager::GetSporkManager()->IsFeatureEnabled(CSporkAction::featureChainlocks, chainActive.Tip());
    }
    quorumSigningManager->RegisterRecoveredSigsListener(this);
    scheduler->scheduleEvery([&]() {
        CheckActiveState();
        EnforceBestChainLock();
        // regularly retry signing the current chaintip as it might have failed before due to missing ixlocks
        TrySignChainTip();
    }, 5);
}

void CChainLocksHandler::Stop()
{
    quorumSigningManager->UnregisterRecoveredSigsListener(this);
}

bool CChainLocksHandler::AlreadyHave(const CInv& inv)
{
    LOCK(cs);
    return seenChainLocks.count(inv.hash) != 0;
}

bool CChainLocksHandler::GetChainLockByHash(const uint256& hash, llmq::CChainLockSig& ret)
{
    LOCK(cs);

    if (hash != bestChainLockHash) {
        // we only propagate the best one and ditch all the old ones
        return false;
    }

    ret = bestChainLock;
    return true;
}

void CChainLocksHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
{
    if(!IsChainlocksEnabled())
        return;

    if (strCommand == NetMsgType::CLSIG) {
        CChainLockSig clsig;
        vRecv >> clsig;

        auto hash = ::SerializeHash(clsig);

        ProcessNewChainLock(pfrom->id, clsig, hash);
    }
}

void CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLockSig& clsig, const uint256& hash)
{
    {
        LOCK(cs_main);
        g_connman->RemoveAskFor(hash);
    }

    {
        LOCK(cs);
        if (!seenChainLocks.emplace(hash, GetTimeMillis()).second) {
            return;
        }

        if (bestChainLock.nHeight != -1 && clsig.nHeight <= bestChainLock.nHeight) {
            // no need to process/relay older CLSIGs
            return;
        }
    }

    uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, clsig.nHeight));
    uint256 msgHash = clsig.blockHash;
    if (!quorumSigningManager->VerifyRecoveredSig(Params().GetConsensus().llmqChainLocks, clsig.nHeight, requestId, msgHash, clsig.sig)) {
        LogPrintf("CChainLocksHandler::%s -- invalid CLSIG (%s), peer=%d\n", __func__, clsig.ToString(), from);
        if (from != -1) {
            LOCK(cs_main);
            Misbehaving(from, 10);
        }
        return;
    }

    {
        LOCK2(cs_main, cs);

        if (InternalHasConflictingChainLock(clsig.nHeight, clsig.blockHash)) {
            // This should not happen. If it happens, it means that a malicious entity controls a large part of the MN
            // network. In this case, we don't allow him to reorg older chainlocks.
            LogPrintf("CChainLocksHandler::%s -- new CLSIG (%s) tries to reorg previous CLSIG (%s), peer=%d\n",
                      __func__, clsig.ToString(), bestChainLock.ToString(), from);
            return;
        }

        bestChainLockHash = hash;
        bestChainLock = clsig;

        CInv inv(MSG_CLSIG, hash);
        g_connman->RelayInv(inv, LLMQS_PROTO_VERSION);

        auto blockIt = mapBlockIndex.find(clsig.blockHash);
        if (blockIt == mapBlockIndex.end()) {
            // we don't know the block/header for this CLSIG yet, so bail out for now
            // when the block or the header later comes in, we will enforce the correct chain
            return;
        }

        if (blockIt->second->nHeight != clsig.nHeight) {
            // Should not happen, same as the conflict check from above.
            LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n",
                    __func__, clsig.ToString(), blockIt->second->nHeight);
            return;
        }

        const CBlockIndex* pindex = blockIt->second;
        bestChainLockWithKnownBlock = bestChainLock;
        bestChainLockBlockIndex = pindex;
    }

    scheduler->scheduleFromNow([&]() {
        CheckActiveState();
        EnforceBestChainLock();
    }, 0);

    LogPrint("chainlocks", "CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n",
              __func__, clsig.ToString(), from);
}

void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
{
    LOCK2(cs_main, cs);

    if (pindexNew->GetBlockHash() == bestChainLock.blockHash) {
        LogPrintf("CChainLocksHandler::%s -- block header %s came in late, updating and enforcing\n", __func__, pindexNew->GetBlockHash().ToString());

        if (bestChainLock.nHeight != pindexNew->nHeight) {
            // Should not happen, same as the conflict check from ProcessNewChainLock.
            LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n",
                      __func__, bestChainLock.ToString(), pindexNew->nHeight);
            return;
        }

        // when EnforceBestChainLock is called later, it might end up invalidating other chains but not activating the
        // CLSIG locked chain. This happens when only the header is known but the block is still missing yet. The usual
        // block processing logic will handle this when the block arrives
        bestChainLockWithKnownBlock = bestChainLock;
        bestChainLockBlockIndex = pindexNew;
    }
}

void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew)
{
    // don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is
    // never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to
    // EnforceBestChainLock switching chains.
    isChainlocksEnabled = CSporkManager::GetSporkManager()->IsFeatureEnabled(CSporkAction::featureChainlocks, pindexNew);

    LOCK(cs);
    if (tryLockChainTipScheduled) {
        return;
    }
    tryLockChainTipScheduled = true;
    scheduler->scheduleFromNow([&]() {
        CheckActiveState();
        EnforceBestChainLock();
        TrySignChainTip();
        LOCK(cs);
        tryLockChainTipScheduled = false;
    }, 0);
}

void CChainLocksHandler::CheckActiveState()
    LOCK(cs);
    bool oldIsEnforced = isEnforced;
    isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);
    isEnforced = fDIP0008Active && isSporkActive;


    if (!oldIsEnforced && isEnforced) {
        // ChainLocks got activated just recently, but it's possible that it was already running before, leaving
        // us with some stale values which we should not try to enforce anymore (there probably was a good reason to
        // to disable spork19)
        bestChainLockHash = uint256();
        bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();
    isEnforced = (fDIP0008Active && isChainLocksActive) || fEnforcedBySpork;

    if (!oldIsEnforced && isEnforced) {
        // ChainLocks got activated just recently, but it's possible that it was already running before, leaving
        // us with some stale values which we should not try to enforce anymore (there probably was a good reason
        // to disable spork19)
        bestChainLockHash = uint256();
        bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();
        bestChainLockBlockIndex = lastNotifyChainLockBlockIndex = nullptr;
    }
}

void CChainLocksHandler::TrySignChainTip()
{
    Cleanup();

    if (!fMasternodeMode) {
        return;
    }

    if (!masternodeSync.IsBlockchainSynced()) {
        return;
    }

    const CBlockIndex* pindex;
    {
        LOCK(cs_main);
        pindex = chainActive.Tip();
    }

    if (!pindex->pprev) {
        return;
    }

    // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
    // To simplify the initial implementation, we skip this process and directly try to create a CLSIG
    // This will fail when multiple blocks compete, but we accept this for the initial implementation.
    // Later, we'll add the multiple attempts process.

    {
        LOCK(cs);

        if (!isChainLocksActive) {
            return;
        }

        if (pindex->nHeight == lastSignedHeight) {
            // already signed this one
            return;
        }

        if (bestChainLock.nHeight >= pindex->nHeight) {
            // already got the same CLSIG or a better one
            return;
        }

        if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
            // don't sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce
            // the correct chain.
            return;
        }
    }

    LogPrint("chainlocks", "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()) {
        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
                LogPrint("chainlocks", "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
                LogPrint("chainlocks", "CChainLocksHandler::%s -- chainlock at height %d \n", __func__, pindexWalk->nHeight);
                break;
            }

            auto txids = GetBlockTxs(pindexWalk->GetBlockHash());
            if (!txids) {
                pindexWalk = pindexWalk->pprev;
                continue;
            }

            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_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {
                    LogPrint("chainlocks", "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();

    {
        LOCK(cs);
        if (bestChainLock.nHeight >= pindex->nHeight) {
            // might have happened while we didn't hold cs
            return;
        }
        lastSignedHeight = pindex->nHeight;
        lastSignedRequestId = requestId;
        lastSignedMsgHash = msgHash;
    }

    quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash);
}

void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock)
{
    bool handleTx = true;
    if (tx.IsCoinBase() || tx.vin.empty()) {
        handleTx = false;
    }

    if (!masternodeSync.IsBlockchainSynced()) {
        if (handleTx && posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {
            auto info = mempool.info(tx.GetHash());
            if (!info.tx) {
                return;
            }
            LOCK(cs);
            txFirstSeenTime.emplace(tx.GetHash(), info.nTime);
        }
        return;
    }

    LOCK(cs);

    if (handleTx) {
        int64_t curTime = GetAdjustedTime();
        txFirstSeenTime.emplace(tx.GetHash(), curTime);
    }

    // We listen for SyncTransaction so that we can collect all TX ids of all included TXs of newly received blocks
    // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are
    // safe.
    if (pindex && posInBlock != CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {
        auto it = blockTxs.find(pindex->GetBlockHash());
        if (it == blockTxs.end()) {
            // we want this to be run even if handleTx == false, so that the coinbase TX triggers creation of an empty entry
            it = blockTxs.emplace(pindex->GetBlockHash(), std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>()).first;
        }
        if (handleTx) {
            auto& txs = *it->second;
            txs.emplace(tx.GetHash());
        }
    }
}

CChainLocksHandler::BlockTxs::mapped_type CChainLocksHandler::GetBlockTxs(const uint256& blockHash)
{
    AssertLockNotHeld(cs);
    AssertLockNotHeld(cs_main);

    CChainLocksHandler::BlockTxs::mapped_type ret;

    {
        LOCK(cs);
        auto it = blockTxs.find(blockHash);
        if (it != blockTxs.end()) {
            ret = it->second;
        }
    }
    if (!ret) {
        // This should only happen when freshly started.
        // If running for some time, SyncTransaction should have been called before which fills blockTxs.
        LogPrint("chainlocks", "CChainLocksHandler::%s -- blockTxs for %s not found. Trying ReadBlockFromDisk\n", __func__,
                 blockHash.ToString());

        uint32_t blockTime;
        {
            LOCK(cs_main);
            auto pindex = mapBlockIndex.at(blockHash);
            CBlock block;
            if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
                return nullptr;
            }

            ret = std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>();
            for (auto& tx : block.vtx) {
                if (tx->IsCoinBase() || tx->vin.empty()) {
                    continue;
                }
                ret->emplace(tx->GetHash());
            }

            blockTime = block.nTime;
        }

        LOCK(cs);
        blockTxs.emplace(blockHash, ret);
        for (auto& txid : *ret) {
            txFirstSeenTime.emplace(txid, blockTime);
        }
    }
    return ret;
}

bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)
{
    AssertLockHeld(cs_main);
    if (!IsNewInstantSendEnabled()) {
        return true;
    }

    if (chainActive.Height() < Params().GetConsensus().nInstantSendBlockFilteringStartHeight) {
        return true;
    }

    int64_t txAge = 0;
    {
        LOCK(cs);
        if (!isChainLocksActive) {
            return true;
        }
        auto it = txFirstSeenTime.find(txid);
        if (it != txFirstSeenTime.end()) {
            txAge = GetAdjustedTime() - it->second;
        }
    }

    if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {
        return false;
    }
    return true;
}

bool CChainLocksHandler::IsChainlocksEnabled() const
{
    return isChainlocksEnabled;
}

// WARNING: cs_main and cs should not be held!
// This should also not be called from validation signals, as this might result in recursive calls
void CChainLocksHandler::EnforceBestChainLock()
{
    CChainLockSig clsig;
    const CBlockIndex* pindex;
    const CBlockIndex* currentBestChainLockBlockIndex;
    {
        LOCK(cs);

        if (!isEnforced) {
            return;
        }

        clsig = bestChainLockWithKnownBlock;
        pindex = currentBestChainLockBlockIndex = this->bestChainLockBlockIndex;

        if (!currentBestChainLockBlockIndex) {
            // we don't have the header/block, so we can't do anything right now
            return;
        }
    }

    bool activateNeeded;
    {
        LOCK(cs_main);

        // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain.
        // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig
        // and invalidate each of them.
        while (pindex && !chainActive.Contains(pindex)) {
            // Invalidate all blocks that have the same prevBlockHash but are not equal to blockHash
            auto itp = mapPrevBlockIndex.equal_range(pindex->pprev->GetBlockHash());
            for (auto jt = itp.first; jt != itp.second; ++jt) {
                if (jt->second == pindex) {
                    continue;
                }
                LogPrintf("CChainLocksHandler::%s -- CLSIG (%s) invalidates block %s\n",
                          __func__, clsig.ToString(), jt->second->GetBlockHash().ToString());
                DoInvalidateBlock(jt->second, false);
            }

            pindex = pindex->pprev;
        }
        // In case blocks from the correct chain are invalid at the moment, reconsider them. The only case where this
        // can happen right now is when missing superblock triggers caused the main chain to be dismissed first. When
        // the trigger later appears, this should bring us to the correct chain eventually. Please note that this does
        // NOT enforce invalid blocks in any way, it just causes re-validation.
        if (!currentBestChainLockBlockIndex->IsValid()) {
            ResetBlockFailureFlags(mapBlockIndex.at(currentBestChainLockBlockIndex->GetBlockHash()));
        }

        activateNeeded = chainActive.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex;
    }

    CValidationState state;
    if (activateNeeded) {
        LogPrintf("CChainLocksHandler::%s -- Activated best chain tip: %s\n", __func__, currentBestChainLockBlockIndex->GetBlockHash().ToString());
        if (!ActivateBestChain(state, Params()))
            LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, FormatStateMessage(state));
    }

    const CBlockIndex* pindexNotify = nullptr;
    {
        LOCK(cs_main);
        if (lastNotifyChainLockBlockIndex != currentBestChainLockBlockIndex &&
            chainActive.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) == currentBestChainLockBlockIndex) {
            lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;
            pindexNotify = currentBestChainLockBlockIndex;
        }
    }

    if (pindexNotify) {
        GetMainSignals().NotifyChainLock(pindexNotify);
    }
}

void CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
{
    CChainLockSig clsig;
    {
        LOCK(cs);

        if (!isChainLocksActive) {
            return;
        }

        if (recoveredSig.id != lastSignedRequestId || recoveredSig.msgHash != lastSignedMsgHash) {
            // this is not what we signed, so lets not create a CLSIG for it
            return;
        }
        if (bestChainLock.nHeight >= lastSignedHeight) {
            // already got the same or a better CLSIG through the CLSIG message
            return;
        }

        clsig.nHeight = lastSignedHeight;
        clsig.blockHash = lastSignedMsgHash;
        clsig.sig = recoveredSig.sig.Get();
    }
    ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));
}

// WARNING, do not hold cs while calling this method as we'll otherwise run into a deadlock
void CChainLocksHandler::DoInvalidateBlock(const CBlockIndex* pindex, bool activateBestChain)
{
    auto& params = Params();

    {
        LOCK(cs_main);

        // get the non-const pointer
        CBlockIndex* pindex2 = mapBlockIndex[pindex->GetBlockHash()];

        CValidationState state;
        if (!InvalidateBlock(state, params, pindex2)) {
            LogPrintf("CChainLocksHandler::%s -- InvalidateBlock failed: %s\n", __func__, FormatStateMessage(state));
            // This should not have happened and we are in a state were it's not safe to continue anymore
            assert(false);
        }
    }

    CValidationState state;
    if (activateBestChain && !ActivateBestChain(state, params)) {
        LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, FormatStateMessage(state));
        // This should not have happened and we are in a state were it's not safe to continue anymore
        assert(false);
    }
}

bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash)
{
    LOCK(cs);
    return InternalHasChainLock(nHeight, blockHash);
}

bool CChainLocksHandler::InternalHasChainLock(int nHeight, const uint256& blockHash)
{
    AssertLockHeld(cs);

    if (!isEnforced) {
        return false;
    }

    if (!bestChainLockBlockIndex) {
        return false;
    }

    if (nHeight > bestChainLockBlockIndex->nHeight) {
        return false;
    }

    if (nHeight == bestChainLockBlockIndex->nHeight) {
        return blockHash == bestChainLockBlockIndex->GetBlockHash();
    }

    auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);
    return pAncestor && pAncestor->GetBlockHash() == blockHash;
}

bool CChainLocksHandler::HasConflictingChainLock(int nHeight, const uint256& blockHash)
{
    LOCK(cs);
    return InternalHasConflictingChainLock(nHeight, blockHash);
}

bool CChainLocksHandler::InternalHasConflictingChainLock(int nHeight, const uint256& blockHash)
{
    AssertLockHeld(cs);

    if (!isEnforced) {
        return false;
    }

    if (!bestChainLockBlockIndex) {
        return false;
    }

    if (nHeight > bestChainLockBlockIndex->nHeight) {
        return false;
    }

    if (nHeight == bestChainLockBlockIndex->nHeight) {
        return blockHash != bestChainLockBlockIndex->GetBlockHash();
    }

    auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);
    assert(pAncestor);
    return pAncestor->GetBlockHash() != blockHash;
}

void CChainLocksHandler::Cleanup()
{
    if (!masternodeSync.IsBlockchainSynced()) {
        return;
    }

    {
        LOCK(cs);
        if (GetTimeMillis() - lastCleanupTime < CLEANUP_INTERVAL) {
            return;
        }
    }

    // need mempool.cs due to GetTransaction calls
    LOCK2(cs_main, mempool.cs);
    LOCK(cs);

    for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {
        if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {
            it = seenChainLocks.erase(it);
        } else {
            ++it;
        }
    }

    for (auto it = blockTxs.begin(); it != blockTxs.end(); ) {
        auto pindex = mapBlockIndex.at(it->first);
        if (InternalHasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
            for (auto& txid : *it->second) {
                txFirstSeenTime.erase(txid);
            }
            it = blockTxs.erase(it);
        } else if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
            it = blockTxs.erase(it);
        } else {
            ++it;
        }
    }
    for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end(); ) {
        CTransactionRef tx;
        uint256 hashBlock;
        if (!GetTransaction(it->first, tx, Params().GetConsensus(), hashBlock)) {
            // tx has vanished, probably due to conflicts
            it = txFirstSeenTime.erase(it);
        } else if (!hashBlock.IsNull()) {
            auto pindex = mapBlockIndex.at(hashBlock);
            if (chainActive.Tip()->GetAncestor(pindex->nHeight) == pindex && chainActive.Height() - pindex->nHeight >= 6) {
                // tx got confirmed >= 6 times, so we can stop keeping track of it
                it = txFirstSeenTime.erase(it);
            } else {
                ++it;
            }
        } else {
            ++it;
        }
    }

    lastCleanupTime = GetTimeMillis();
}

}

bool IsChainlocksEnabled()
{
    if (!llmq::chainLocksHandler) return false;
    return llmq::chainLocksHandler->IsChainlocksEnabled();
}


<MSG> Allow testing ChainLocks enforcement with spork19 == 1 (#2854)

This is only for testnet.
<DFF> @@ -214,12 +214,13 @@ void CChainLocksHandler::CheckActiveState()
     LOCK(cs);
     bool oldIsEnforced = isEnforced;
     isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);
-    isEnforced = fDIP0008Active && isSporkActive;
-
+    // TODO remove this after DIP8 is active
+    bool fEnforcedBySpork = (Params().NetworkIDString() == CBaseChainParams::TESTNET) && (sporkManager.GetSporkValue(SPORK_19_CHAINLOCKS_ENABLED) == 1);
+    isEnforced = (fDIP0008Active && isSporkActive) || fEnforcedBySpork;
 
     if (!oldIsEnforced && isEnforced) {
         // ChainLocks got activated just recently, but it's possible that it was already running before, leaving
-        // us with some stale values which we should not try to enforce anymore (there probably was a good reason to
+        // us with some stale values which we should not try to enforce anymore (there probably was a good reason
         // to disable spork19)
         bestChainLockHash = uint256();
         bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();

{'commit': "diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp\nindex eb6a05eb2..21c85239d 100644\n--- a/src/llmq/quorums_chainlocks.cpp\n+++ b/src/llmq/quorums_chainlocks.cpp\n@@ -214,12 +214,13 @@ void CChainLocksHandler::CheckActiveState()\n     LOCK(cs);\n     bool oldIsEnforced = isEnforced;\n     isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);\n-    isEnforced = fDIP0008Active && isSporkActive;\n-\n+    // TODO remove this after DIP8 is active\n+    bool fEnforcedBySpork = (Params().NetworkIDString() == CBaseChainParams::TESTNET) && (sporkManager.GetSporkValue(SPORK_19_CHAINLOCKS_ENABLED) == 1);\n+    isEnforced = (fDIP0008Active && isSporkActive) || fEnforcedBySpork;\n \n     if (!oldIsEnforced && isEnforced) {\n         // ChainLocks got activated just recently, but it's possible that it was already running before, leaving\n-        // us with some stale values which we should not try to enforce anymore (there probably was a good reason to\n+        // us with some stale values which we should not try to enforce anymore (there probably was a good reason\n         // to disable spork19)\n         bestChainLockHash = uint256();\n         bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();\n", 'commit_subject': 'Allow testing ChainLocks enforcement with spork19 == 1 (#2854)', 'repo_name': 'zcoinofficial/zcoin', 'committer': {'name': 'UdjinM6', 'email': '0105d54f6b1f0756b3fdc004130a6506b53843f9@users.noreply.github.com', 'time_sec': '1554986511', 'tz_offset': '180', 'date': {'seconds': '1554986511'}}, 'commit_tree': 'e137b379de9e7b1a2904f55fa35b3f5f6b4b1123', 'commit_message': 'Allow testing ChainLocks enforcement with spork19 == 1 (#2854)\n\nThis is only for testnet.', 'language_name': 'Shell', 'license': 'mit', 'hunks': "@@ -214,12 +214,13 @@ void CChainLocksHandler::CheckActiveState()\n     LOCK(cs);\n     bool oldIsEnforced = isEnforced;\n     isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);\n-    isEnforced = fDIP0008Active && isSporkActive;\n-\n+    // TODO remove this after DIP8 is active\n+    bool fEnforcedBySpork = (Params().NetworkIDString() == CBaseChainParams::TESTNET) && (sporkManager.GetSporkValue(SPORK_19_CHAINLOCKS_ENABLED) == 1);\n+    isEnforced = (fDIP0008Active && isSporkActive) || fEnforcedBySpork;\n \n     if (!oldIsEnforced && isEnforced) {\n         // ChainLocks got activated just recently, but it's possible that it was already running before, leaving\n-        // us with some stale values which we should not try to enforce anymore (there probably was a good reason to\n+        // us with some stale values which we should not try to enforce anymore (there probably was a good reason\n         // to disable spork19)\n         bestChainLockHash = uint256();\n         bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();\n", 'addition_count': 4, 'deletion_count': 3, 'src_file': 'a/src/llmq/quorums_chainlocks.cpp', 'tgt_file': 'b/src/llmq/quorums_chainlocks.cpp', 'file_extension': '.cpp', 'before_file': '// Copyright (c) 2019 The Dash Core developers\n// Distributed under the MIT/X11 software license, see the accompanying\n// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n\n#include "quorums.h"\n#include "quorums_chainlocks.h"\n#include "quorums_instantsend.h"\n#include "quorums_signing.h"\n#include "quorums_utils.h"\n#include "evo/spork.h"\n\n#include "chain.h"\n#include "masternode-sync.h"\n#include "net_processing.h"\n#include "scheduler.h"\n#include "txmempool.h"\n#include "validation.h"\n\nnamespace llmq\n{\n\nstatic const std::string CLSIG_REQUESTID_PREFIX = "clsig";\n\nCChainLocksHandler* chainLocksHandler;\n\nstd::string CChainLockSig::ToString() const\n{\n    return strprintf("CChainLockSig(nHeight=%d, blockHash=%s)", nHeight, blockHash.ToString());\n}\n\nCChainLocksHandler::CChainLocksHandler(CScheduler* _scheduler) :\n    scheduler(_scheduler)\n{\n}\n\nCChainLocksHandler::~CChainLocksHandler()\n{\n}\n\nvoid CChainLocksHandler::Start()\n{\n    {\n        LOCK(cs_main);\n        if (chainActive.Tip())\n            isChainlocksEnabled = CSporkManager::GetSporkManager()->IsFeatureEnabled(CSporkAction::featureChainlocks, chainActive.Tip());\n    }\n    quorumSigningManager->RegisterRecoveredSigsListener(this);\n    scheduler->scheduleEvery([&]() {\n        CheckActiveState();\n        EnforceBestChainLock();\n        // regularly retry signing the current chaintip as it might have failed before due to missing ixlocks\n        TrySignChainTip();\n    }, 5);\n}\n\nvoid CChainLocksHandler::Stop()\n{\n    quorumSigningManager->UnregisterRecoveredSigsListener(this);\n}\n\nbool CChainLocksHandler::AlreadyHave(const CInv& inv)\n{\n    LOCK(cs);\n    return seenChainLocks.count(inv.hash) != 0;\n}\n\nbool CChainLocksHandler::GetChainLockByHash(const uint256& hash, llmq::CChainLockSig& ret)\n{\n    LOCK(cs);\n\n    if (hash != bestChainLockHash) {\n        // we only propagate the best one and ditch all the old ones\n        return false;\n    }\n\n    ret = bestChainLock;\n    return true;\n}\n\nvoid CChainLocksHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)\n{\n    if(!IsChainlocksEnabled())\n        return;\n\n    if (strCommand == NetMsgType::CLSIG) {\n        CChainLockSig clsig;\n        vRecv >> clsig;\n\n        auto hash = ::SerializeHash(clsig);\n\n        ProcessNewChainLock(pfrom->id, clsig, hash);\n    }\n}\n\nvoid CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLockSig& clsig, const uint256& hash)\n{\n    {\n        LOCK(cs_main);\n        g_connman->RemoveAskFor(hash);\n    }\n\n    {\n        LOCK(cs);\n        if (!seenChainLocks.emplace(hash, GetTimeMillis()).second) {\n            return;\n        }\n\n        if (bestChainLock.nHeight != -1 && clsig.nHeight <= bestChainLock.nHeight) {\n            // no need to process/relay older CLSIGs\n            return;\n        }\n    }\n\n    uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, clsig.nHeight));\n    uint256 msgHash = clsig.blockHash;\n    if (!quorumSigningManager->VerifyRecoveredSig(Params().GetConsensus().llmqChainLocks, clsig.nHeight, requestId, msgHash, clsig.sig)) {\n        LogPrintf("CChainLocksHandler::%s -- invalid CLSIG (%s), peer=%d\\n", __func__, clsig.ToString(), from);\n        if (from != -1) {\n            LOCK(cs_main);\n            Misbehaving(from, 10);\n        }\n        return;\n    }\n\n    {\n        LOCK2(cs_main, cs);\n\n        if (InternalHasConflictingChainLock(clsig.nHeight, clsig.blockHash)) {\n            // This should not happen. If it happens, it means that a malicious entity controls a large part of the MN\n            // network. In this case, we don\'t allow him to reorg older chainlocks.\n            LogPrintf("CChainLocksHandler::%s -- new CLSIG (%s) tries to reorg previous CLSIG (%s), peer=%d\\n",\n                      __func__, clsig.ToString(), bestChainLock.ToString(), from);\n            return;\n        }\n\n        bestChainLockHash = hash;\n        bestChainLock = clsig;\n\n        CInv inv(MSG_CLSIG, hash);\n        g_connman->RelayInv(inv, LLMQS_PROTO_VERSION);\n\n        auto blockIt = mapBlockIndex.find(clsig.blockHash);\n        if (blockIt == mapBlockIndex.end()) {\n            // we don\'t know the block/header for this CLSIG yet, so bail out for now\n            // when the block or the header later comes in, we will enforce the correct chain\n            return;\n        }\n\n        if (blockIt->second->nHeight != clsig.nHeight) {\n            // Should not happen, same as the conflict check from above.\n            LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block\'s height (%d)\\n",\n                    __func__, clsig.ToString(), blockIt->second->nHeight);\n            return;\n        }\n\n        const CBlockIndex* pindex = blockIt->second;\n        bestChainLockWithKnownBlock = bestChainLock;\n        bestChainLockBlockIndex = pindex;\n    }\n\n    scheduler->scheduleFromNow([&]() {\n        CheckActiveState();\n        EnforceBestChainLock();\n    }, 0);\n\n    LogPrint("chainlocks", "CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\\n",\n              __func__, clsig.ToString(), from);\n}\n\nvoid CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)\n{\n    LOCK2(cs_main, cs);\n\n    if (pindexNew->GetBlockHash() == bestChainLock.blockHash) {\n        LogPrintf("CChainLocksHandler::%s -- block header %s came in late, updating and enforcing\\n", __func__, pindexNew->GetBlockHash().ToString());\n\n        if (bestChainLock.nHeight != pindexNew->nHeight) {\n            // Should not happen, same as the conflict check from ProcessNewChainLock.\n            LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block\'s height (%d)\\n",\n                      __func__, bestChainLock.ToString(), pindexNew->nHeight);\n            return;\n        }\n\n        // when EnforceBestChainLock is called later, it might end up invalidating other chains but not activating the\n        // CLSIG locked chain. This happens when only the header is known but the block is still missing yet. The usual\n        // block processing logic will handle this when the block arrives\n        bestChainLockWithKnownBlock = bestChainLock;\n        bestChainLockBlockIndex = pindexNew;\n    }\n}\n\nvoid CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew)\n{\n    // don\'t call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is\n    // never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to\n    // EnforceBestChainLock switching chains.\n    isChainlocksEnabled = CSporkManager::GetSporkManager()->IsFeatureEnabled(CSporkAction::featureChainlocks, pindexNew);\n\n    LOCK(cs);\n    if (tryLockChainTipScheduled) {\n        return;\n    }\n    tryLockChainTipScheduled = true;\n    scheduler->scheduleFromNow([&]() {\n        CheckActiveState();\n        EnforceBestChainLock();\n        TrySignChainTip();\n        LOCK(cs);\n        tryLockChainTipScheduled = false;\n    }, 0);\n}\n\nvoid CChainLocksHandler::CheckActiveState()\n    LOCK(cs);\n    bool oldIsEnforced = isEnforced;\n    isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);\n    isEnforced = fDIP0008Active && isSporkActive;\n\n\n    if (!oldIsEnforced && isEnforced) {\n        // ChainLocks got activated just recently, but it\'s possible that it was already running before, leaving\n        // us with some stale values which we should not try to enforce anymore (there probably was a good reason to\n        // to disable spork19)\n        bestChainLockHash = uint256();\n        bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();\n    isEnforced = (fDIP0008Active && isChainLocksActive) || fEnforcedBySpork;\n\n    if (!oldIsEnforced && isEnforced) {\n        // ChainLocks got activated just recently, but it\'s possible that it was already running before, leaving\n        // us with some stale values which we should not try to enforce anymore (there probably was a good reason\n        // to disable spork19)\n        bestChainLockHash = uint256();\n        bestChainLock = bestChainLockWithKnownBlock = CChainLockSig();\n        bestChainLockBlockIndex = lastNotifyChainLockBlockIndex = nullptr;\n    }\n}\n\nvoid CChainLocksHandler::TrySignChainTip()\n{\n    Cleanup();\n\n    if (!fMasternodeMode) {\n        return;\n    }\n\n    if (!masternodeSync.IsBlockchainSynced()) {\n        return;\n    }\n\n    const CBlockIndex* pindex;\n    {\n        LOCK(cs_main);\n        pindex = chainActive.Tip();\n    }\n\n    if (!pindex->pprev) {\n        return;\n    }\n\n    // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized\n    // To simplify the initial implementation, we skip this process and directly try to create a CLSIG\n    // This will fail when multiple blocks compete, but we accept this for the initial implementation.\n    // Later, we\'ll add the multiple attempts process.\n\n    {\n        LOCK(cs);\n\n        if (!isChainLocksActive) {\n            return;\n        }\n\n        if (pindex->nHeight == lastSignedHeight) {\n            // already signed this one\n            return;\n        }\n\n        if (bestChainLock.nHeight >= pindex->nHeight) {\n            // already got the same CLSIG or a better one\n            return;\n        }\n\n        if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {\n            // don\'t sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce\n            // the correct chain.\n            return;\n        }\n    }\n\n    LogPrint("chainlocks", "CChainLocksHandler::%s -- trying to sign %s, height=%d\\n", __func__, pindex->GetBlockHash().ToString(), pindex->nHeight);\n\n    // When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is\n    // considered safe when it is ixlocked or at least known since 10 minutes (from mempool or block). These checks are\n    // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the\n    // way down, we consider all TXs to be safe.\n    if (IsNewInstantSendEnabled()) {\n        auto pindexWalk = pindex;\n        while (pindexWalk) {\n            if (pindex->nHeight - pindexWalk->nHeight > 5) {\n                // no need to check further down, 6 confs is safe to assume that TXs below this height won\'t be\n                // ixlocked anymore if they aren\'t already\n                LogPrint("chainlocks", "CChainLocksHandler::%s -- tip and previous 5 blocks all safe\\n", __func__);\n                break;\n            }\n            if (HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {\n                // we don\'t care about ixlocks for TXs that are ChainLocked already\n                LogPrint("chainlocks", "CChainLocksHandler::%s -- chainlock at height %d \\n", __func__, pindexWalk->nHeight);\n                break;\n            }\n\n            auto txids = GetBlockTxs(pindexWalk->GetBlockHash());\n            if (!txids) {\n                pindexWalk = pindexWalk->pprev;\n                continue;\n            }\n\n            for (auto& txid : *txids) {\n                int64_t txAge = 0;\n                {\n                    LOCK(cs);\n                    auto it = txFirstSeenTime.find(txid);\n                    if (it != txFirstSeenTime.end()) {\n                        txAge = GetAdjustedTime() - it->second;\n                    }\n                }\n\n                if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {\n                    LogPrint("chainlocks", "CChainLocksHandler::%s -- not signing block %s due to TX %s not being ixlocked and not old enough. age=%d\\n", __func__,\n                              pindexWalk->GetBlockHash().ToString(), txid.ToString(), txAge);\n                    return;\n                }\n            }\n\n            pindexWalk = pindexWalk->pprev;\n        }\n    }\n\n    uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight));\n    uint256 msgHash = pindex->GetBlockHash();\n\n    {\n        LOCK(cs);\n        if (bestChainLock.nHeight >= pindex->nHeight) {\n            // might have happened while we didn\'t hold cs\n            return;\n        }\n        lastSignedHeight = pindex->nHeight;\n        lastSignedRequestId = requestId;\n        lastSignedMsgHash = msgHash;\n    }\n\n    quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash);\n}\n\nvoid CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock)\n{\n    bool handleTx = true;\n    if (tx.IsCoinBase() || tx.vin.empty()) {\n        handleTx = false;\n    }\n\n    if (!masternodeSync.IsBlockchainSynced()) {\n        if (handleTx && posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {\n            auto info = mempool.info(tx.GetHash());\n            if (!info.tx) {\n                return;\n            }\n            LOCK(cs);\n            txFirstSeenTime.emplace(tx.GetHash(), info.nTime);\n        }\n        return;\n    }\n\n    LOCK(cs);\n\n    if (handleTx) {\n        int64_t curTime = GetAdjustedTime();\n        txFirstSeenTime.emplace(tx.GetHash(), curTime);\n    }\n\n    // We listen for SyncTransaction so that we can collect all TX ids of all included TXs of newly received blocks\n    // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are\n    // safe.\n    if (pindex && posInBlock != CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {\n        auto it = blockTxs.find(pindex->GetBlockHash());\n        if (it == blockTxs.end()) {\n            // we want this to be run even if handleTx == false, so that the coinbase TX triggers creation of an empty entry\n            it = blockTxs.emplace(pindex->GetBlockHash(), std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>()).first;\n        }\n        if (handleTx) {\n            auto& txs = *it->second;\n            txs.emplace(tx.GetHash());\n        }\n    }\n}\n\nCChainLocksHandler::BlockTxs::mapped_type CChainLocksHandler::GetBlockTxs(const uint256& blockHash)\n{\n    AssertLockNotHeld(cs);\n    AssertLockNotHeld(cs_main);\n\n    CChainLocksHandler::BlockTxs::mapped_type ret;\n\n    {\n        LOCK(cs);\n        auto it = blockTxs.find(blockHash);\n        if (it != blockTxs.end()) {\n            ret = it->second;\n        }\n    }\n    if (!ret) {\n        // This should only happen when freshly started.\n        // If running for some time, SyncTransaction should have been called before which fills blockTxs.\n        LogPrint("chainlocks", "CChainLocksHandler::%s -- blockTxs for %s not found. Trying ReadBlockFromDisk\\n", __func__,\n                 blockHash.ToString());\n\n        uint32_t blockTime;\n        {\n            LOCK(cs_main);\n            auto pindex = mapBlockIndex.at(blockHash);\n            CBlock block;\n            if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {\n                return nullptr;\n            }\n\n            ret = std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>();\n            for (auto& tx : block.vtx) {\n                if (tx->IsCoinBase() || tx->vin.empty()) {\n                    continue;\n                }\n                ret->emplace(tx->GetHash());\n            }\n\n            blockTime = block.nTime;\n        }\n\n        LOCK(cs);\n        blockTxs.emplace(blockHash, ret);\n        for (auto& txid : *ret) {\n            txFirstSeenTime.emplace(txid, blockTime);\n        }\n    }\n    return ret;\n}\n\nbool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)\n{\n    AssertLockHeld(cs_main);\n    if (!IsNewInstantSendEnabled()) {\n        return true;\n    }\n\n    if (chainActive.Height() < Params().GetConsensus().nInstantSendBlockFilteringStartHeight) {\n        return true;\n    }\n\n    int64_t txAge = 0;\n    {\n        LOCK(cs);\n        if (!isChainLocksActive) {\n            return true;\n        }\n        auto it = txFirstSeenTime.find(txid);\n        if (it != txFirstSeenTime.end()) {\n            txAge = GetAdjustedTime() - it->second;\n        }\n    }\n\n    if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {\n        return false;\n    }\n    return true;\n}\n\nbool CChainLocksHandler::IsChainlocksEnabled() const\n{\n    return isChainlocksEnabled;\n}\n\n// WARNING: cs_main and cs should not be held!\n// This should also not be called from validation signals, as this might result in recursive calls\nvoid CChainLocksHandler::EnforceBestChainLock()\n{\n    CChainLockSig clsig;\n    const CBlockIndex* pindex;\n    const CBlockIndex* currentBestChainLockBlockIndex;\n    {\n        LOCK(cs);\n\n        if (!isEnforced) {\n            return;\n        }\n\n        clsig = bestChainLockWithKnownBlock;\n        pindex = currentBestChainLockBlockIndex = this->bestChainLockBlockIndex;\n\n        if (!currentBestChainLockBlockIndex) {\n            // we don\'t have the header/block, so we can\'t do anything right now\n            return;\n        }\n    }\n\n    bool activateNeeded;\n    {\n        LOCK(cs_main);\n\n        // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain.\n        // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig\n        // and invalidate each of them.\n        while (pindex && !chainActive.Contains(pindex)) {\n            // Invalidate all blocks that have the same prevBlockHash but are not equal to blockHash\n            auto itp = mapPrevBlockIndex.equal_range(pindex->pprev->GetBlockHash());\n            for (auto jt = itp.first; jt != itp.second; ++jt) {\n                if (jt->second == pindex) {\n                    continue;\n                }\n                LogPrintf("CChainLocksHandler::%s -- CLSIG (%s) invalidates block %s\\n",\n                          __func__, clsig.ToString(), jt->second->GetBlockHash().ToString());\n                DoInvalidateBlock(jt->second, false);\n            }\n\n            pindex = pindex->pprev;\n        }\n        // In case blocks from the correct chain are invalid at the moment, reconsider them. The only case where this\n        // can happen right now is when missing superblock triggers caused the main chain to be dismissed first. When\n        // the trigger later appears, this should bring us to the correct chain eventually. Please note that this does\n        // NOT enforce invalid blocks in any way, it just causes re-validation.\n        if (!currentBestChainLockBlockIndex->IsValid()) {\n            ResetBlockFailureFlags(mapBlockIndex.at(currentBestChainLockBlockIndex->GetBlockHash()));\n        }\n\n        activateNeeded = chainActive.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex;\n    }\n\n    CValidationState state;\n    if (activateNeeded) {\n        LogPrintf("CChainLocksHandler::%s -- Activated best chain tip: %s\\n", __func__, currentBestChainLockBlockIndex->GetBlockHash().ToString());\n        if (!ActivateBestChain(state, Params()))\n            LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\\n", __func__, FormatStateMessage(state));\n    }\n\n    const CBlockIndex* pindexNotify = nullptr;\n    {\n        LOCK(cs_main);\n        if (lastNotifyChainLockBlockIndex != currentBestChainLockBlockIndex &&\n            chainActive.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) == currentBestChainLockBlockIndex) {\n            lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;\n            pindexNotify = currentBestChainLockBlockIndex;\n        }\n    }\n\n    if (pindexNotify) {\n        GetMainSignals().NotifyChainLock(pindexNotify);\n    }\n}\n\nvoid CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)\n{\n    CChainLockSig clsig;\n    {\n        LOCK(cs);\n\n        if (!isChainLocksActive) {\n            return;\n        }\n\n        if (recoveredSig.id != lastSignedRequestId || recoveredSig.msgHash != lastSignedMsgHash) {\n            // this is not what we signed, so lets not create a CLSIG for it\n            return;\n        }\n        if (bestChainLock.nHeight >= lastSignedHeight) {\n            // already got the same or a better CLSIG through the CLSIG message\n            return;\n        }\n\n        clsig.nHeight = lastSignedHeight;\n        clsig.blockHash = lastSignedMsgHash;\n        clsig.sig = recoveredSig.sig.Get();\n    }\n    ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));\n}\n\n// WARNING, do not hold cs while calling this method as we\'ll otherwise run into a deadlock\nvoid CChainLocksHandler::DoInvalidateBlock(const CBlockIndex* pindex, bool activateBestChain)\n{\n    auto& params = Params();\n\n    {\n        LOCK(cs_main);\n\n        // get the non-const pointer\n        CBlockIndex* pindex2 = mapBlockIndex[pindex->GetBlockHash()];\n\n        CValidationState state;\n        if (!InvalidateBlock(state, params, pindex2)) {\n            LogPrintf("CChainLocksHandler::%s -- InvalidateBlock failed: %s\\n", __func__, FormatStateMessage(state));\n            // This should not have happened and we are in a state were it\'s not safe to continue anymore\n            assert(false);\n        }\n    }\n\n    CValidationState state;\n    if (activateBestChain && !ActivateBestChain(state, params)) {\n        LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\\n", __func__, FormatStateMessage(state));\n        // This should not have happened and we are in a state were it\'s not safe to continue anymore\n        assert(false);\n    }\n}\n\nbool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash)\n{\n    LOCK(cs);\n    return InternalHasChainLock(nHeight, blockHash);\n}\n\nbool CChainLocksHandler::InternalHasChainLock(int nHeight, const uint256& blockHash)\n{\n    AssertLockHeld(cs);\n\n    if (!isEnforced) {\n        return false;\n    }\n\n    if (!bestChainLockBlockIndex) {\n        return false;\n    }\n\n    if (nHeight > bestChainLockBlockIndex->nHeight) {\n        return false;\n    }\n\n    if (nHeight == bestChainLockBlockIndex->nHeight) {\n        return blockHash == bestChainLockBlockIndex->GetBlockHash();\n    }\n\n    auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);\n    return pAncestor && pAncestor->GetBlockHash() == blockHash;\n}\n\nbool CChainLocksHandler::HasConflictingChainLock(int nHeight, const uint256& blockHash)\n{\n    LOCK(cs);\n    return InternalHasConflictingChainLock(nHeight, blockHash);\n}\n\nbool CChainLocksHandler::InternalHasConflictingChainLock(int nHeight, const uint256& blockHash)\n{\n    AssertLockHeld(cs);\n\n    if (!isEnforced) {\n        return false;\n    }\n\n    if (!bestChainLockBlockIndex) {\n        return false;\n    }\n\n    if (nHeight > bestChainLockBlockIndex->nHeight) {\n        return false;\n    }\n\n    if (nHeight == bestChainLockBlockIndex->nHeight) {\n        return blockHash != bestChainLockBlockIndex->GetBlockHash();\n    }\n\n    auto pAncestor = bestChainLockBlockIndex->GetAncestor(nHeight);\n    assert(pAncestor);\n    return pAncestor->GetBlockHash() != blockHash;\n}\n\nvoid CChainLocksHandler::Cleanup()\n{\n    if (!masternodeSync.IsBlockchainSynced()) {\n        return;\n    }\n\n    {\n        LOCK(cs);\n        if (GetTimeMillis() - lastCleanupTime < CLEANUP_INTERVAL) {\n            return;\n        }\n    }\n\n    // need mempool.cs due to GetTransaction calls\n    LOCK2(cs_main, mempool.cs);\n    LOCK(cs);\n\n    for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {\n        if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {\n            it = seenChainLocks.erase(it);\n        } else {\n            ++it;\n        }\n    }\n\n    for (auto it = blockTxs.begin(); it != blockTxs.end(); ) {\n        auto pindex = mapBlockIndex.at(it->first);\n        if (InternalHasChainLock(pindex->nHeight, pindex->GetBlockHash())) {\n            for (auto& txid : *it->second) {\n                txFirstSeenTime.erase(txid);\n            }\n            it = blockTxs.erase(it);\n        } else if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {\n            it = blockTxs.erase(it);\n        } else {\n            ++it;\n        }\n    }\n    for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end(); ) {\n        CTransactionRef tx;\n        uint256 hashBlock;\n        if (!GetTransaction(it->first, tx, Params().GetConsensus(), hashBlock)) {\n            // tx has vanished, probably due to conflicts\n            it = txFirstSeenTime.erase(it);\n        } else if (!hashBlock.IsNull()) {\n            auto pindex = mapBlockIndex.at(hashBlock);\n            if (chainActive.Tip()->GetAncestor(pindex->nHeight) == pindex && chainActive.Height() - pindex->nHeight >= 6) {\n                // tx got confirmed >= 6 times, so we can stop keeping track of it\n                it = txFirstSeenTime.erase(it);\n            } else {\n                ++it;\n            }\n        } else {\n            ++it;\n        }\n    }\n\n    lastCleanupTime = GetTimeMillis();\n}\n\n}\n\nbool IsChainlocksEnabled()\n{\n    if (!llmq::chainLocksHandler) return false;\n    return llmq::chainLocksHandler->IsChainlocksEnabled();\n}\n\n', 'message': 'Allow testing ChainLocks enforcement with spork19 == 1 (#2854)\n\nThis is only for testnet.', 'author_name': 'UdjinM6', 'lang': 'cpp'}