2f21e55514
* 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
728 lines
24 KiB
C++
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();
|
|
}
|
|
|
|
}
|