neobytes/src/llmq/quorums_chainlocks.cpp
Alexander Block 2f21e55514 Remove legacy InstantSend code (#3020)
* Remove ppszTypeName from protocol.cpp and reimplement GetCommand

This removes the need to carefully maintain ppszTypeName, which required
correct order and also did not allow to permanently remove old message
types.

To get the command name for an INV type, GetCommandInternal uses a switch
which needs to be maintained from now on.

The way this is implemented also resembles the way it is implemented in
Bitcoin today, but it's not identical. The original PR that introduced the
switch case in Bitcoin was part of the Segwit changes and thus never got
backported. I decided to implement it in a slightly different way that
avoids throwing exceptions when an unknown INV type is encountered.

IsKnownType will now also leverage GetCommandInternal() to figure out if
the INV type is known locally. This has the side effect of old/legacy
message types to return false from now on. We will depend on this side
effect in later commits when we remove legacy InstantSend code.

* Stop handling/relaying legacy IX messages

When we receive an IX message, we simply treat it as a regular TX and relay
it as such.

We'll however still request IX messages when they are announced to us. We
can't simply revert to requesting TX messages in this case as it might
result in the other peer not answering due to the TX not being in mapRelay
yet. We should at some point in the future completely drop handling of IX
messages instead.

* Remove IsNewInstantSendEnabled() and only use IsInstantSendEnabled()

* Remove legacy InstantSend from GUI

* Remove InstantSend from Bitcoin/Dash URIs

* Remove legacy InstantSend from RPC commands

* Remove legacy InstantSend from wallet

* Remove legacy instantsend.h include

* Remove legacy InstantSend from validation code

* Completely remove remaining legacy InstantSend code

* Remove now unused spork

* Fix InstantSend related test failures

* Remove now obsolete auto IS tests

* Make spork2 and spork3 disabled by default

This should have no influence on mainnet as these sporks are actually set
there. This will however affect regtest, which shouldn't have LLMQ based
InstantSend enabled by default.

* Remove instantsend tests from dip3-deterministicmns.py

These were only testing legacy InstantSend

* Fix .QCheckBox#checkUsePrivateSend styling a bit

* s/TXLEGACYLOCKREQUEST/LEGACYTXLOCKREQUEST/

* Revert "verified via InstantSend" back to "verified via LLMQ based InstantSend"

* Use cmd == nullptr instead of !cmd

* Remove last parameter from AvailableCoins call

This was for fUseInstantSend which is not present anymore since rebase
2019-07-09 17:50:08 +03:00

728 lines
24 KiB
C++

// 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 "chain.h"
#include "masternode/masternode-sync.h"
#include "net_processing.h"
#include "scheduler.h"
#include "spork.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()
{
quorumSigningManager->RegisterRecoveredSigsListener(this);
scheduler->scheduleEvery([&]() {
CheckActiveState();
EnforceBestChainLock();
// regularly retry signing the current chaintip as it might have failed before due to missing ixlocks
TrySignChainTip();
}, 5000);
}
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 (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
return;
}
if (strCommand == NetMsgType::CLSIG) {
CChainLockSig clsig;
vRecv >> clsig;
auto hash = ::SerializeHash(clsig);
ProcessNewChainLock(pfrom->GetId(), 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(BCLog::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.
LOCK(cs);
if (tryLockChainTipScheduled) {
return;
}
tryLockChainTipScheduled = true;
scheduler->scheduleFromNow([&]() {
CheckActiveState();
EnforceBestChainLock();
TrySignChainTip();
LOCK(cs);
tryLockChainTipScheduled = false;
}, 0);
}
void CChainLocksHandler::CheckActiveState()
{
bool fDIP0008Active;
{
LOCK(cs_main);
fDIP0008Active = VersionBitsState(chainActive.Tip()->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == THRESHOLD_ACTIVE;
}
LOCK(cs);
bool oldIsEnforced = isEnforced;
isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);
// 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 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 (!isSporkActive) {
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(BCLog::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 (IsInstantSendEnabled() && 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
LogPrint(BCLog::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(BCLog::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(BCLog::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::TransactionAddedToMempool(const CTransactionRef& tx)
{
if (tx->IsCoinBase() || tx->vin.empty()) {
return;
}
if (!masternodeSync.IsBlockchainSynced()) {
return;
}
LOCK(cs);
int64_t curTime = GetAdjustedTime();
txFirstSeenTime.emplace(tx->GetHash(), curTime);
}
void CChainLocksHandler::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted)
{
if (!masternodeSync.IsBlockchainSynced()) {
return;
}
// We listen for BlockConnected 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.
LOCK(cs);
auto it = blockTxs.find(pindex->GetBlockHash());
if (it == blockTxs.end()) {
// we must create this entry even if there are no lockable transactions in the block, so that TrySignChainTip
// later knows about this block
it = blockTxs.emplace(pindex->GetBlockHash(), std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>()).first;
}
auto& txids = *it->second;
int64_t curTime = GetAdjustedTime();
for (const auto& tx : pblock->vtx) {
if (tx->IsCoinBase() || tx->vin.empty()) {
continue;
}
txids.emplace(tx->GetHash());
txFirstSeenTime.emplace(tx->GetHash(), curTime);
}
}
void CChainLocksHandler::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
{
LOCK(cs);
blockTxs.erase(pindexDisconnected->GetBlockHash());
}
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(BCLog::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)
{
if (!sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
return true;
}
if (!IsInstantSendEnabled()) {
return true;
}
int64_t txAge = 0;
{
LOCK(cs);
if (!isSporkActive) {
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;
}
// 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 && !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, clsig);
}
}
void CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
{
CChainLockSig clsig;
{
LOCK(cs);
if (!isSporkActive) {
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();
}
}