2019-01-22 14:20:32 +01:00
|
|
|
// 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_signing.h"
|
|
|
|
#include "quorums_utils.h"
|
|
|
|
|
|
|
|
#include "chain.h"
|
|
|
|
#include "net_processing.h"
|
|
|
|
#include "scheduler.h"
|
2019-01-23 17:40:37 +01:00
|
|
|
#include "spork.h"
|
2019-01-22 14:20:32 +01:00
|
|
|
#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()
|
2019-02-17 12:39:43 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::RegisterAsRecoveredSigsListener()
|
|
|
|
{
|
|
|
|
quorumSigningManager->RegisterRecoveredSigsListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::UnregisterAsRecoveredSigsListener()
|
2019-01-22 14:20:32 +01:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2019-01-23 17:40:37 +01:00
|
|
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
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)
|
|
|
|
{
|
2019-02-27 14:10:12 +01:00
|
|
|
{
|
|
|
|
LOCK(cs_main);
|
|
|
|
g_connman->RemoveAskFor(hash);
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
{
|
|
|
|
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);
|
2019-03-06 08:00:21 +01:00
|
|
|
Misbehaving(from, 10);
|
2019-01-22 14:20:32 +01:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
EnforceBestChainLock();
|
|
|
|
|
|
|
|
LogPrintf("CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n",
|
|
|
|
__func__, clsig.ToString(), from);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
|
|
|
|
{
|
|
|
|
bool doEnforce = false;
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
bestChainLockBlockIndex = pindexNew;
|
|
|
|
doEnforce = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (doEnforce) {
|
|
|
|
EnforceBestChainLock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork)
|
|
|
|
{
|
|
|
|
if (!fMasternodeMode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!pindexNew->pprev) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-23 17:40:37 +01:00
|
|
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-22 14:20:32 +01:00
|
|
|
|
2019-02-27 14:10:12 +01:00
|
|
|
Cleanup();
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindexNew->nHeight));
|
|
|
|
uint256 msgHash = pindexNew->GetBlockHash();
|
|
|
|
|
|
|
|
{
|
|
|
|
LOCK(cs);
|
|
|
|
|
2019-02-27 14:10:12 +01:00
|
|
|
if (bestChainLockBlockIndex == pindexNew) {
|
|
|
|
// we first got the CLSIG, then the header, and then the block was connected.
|
|
|
|
// In this case there is no need to continue here.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
if (InternalHasConflictingChainLock(pindexNew->nHeight, pindexNew->GetBlockHash())) {
|
2019-01-28 12:23:03 +01:00
|
|
|
if (!inEnforceBestChainLock) {
|
2019-01-22 14:20:32 +01:00
|
|
|
// we accepted this block when there was no lock yet, but now a conflicting lock appeared. Invalidate it.
|
|
|
|
LogPrintf("CChainLocksHandler::%s -- conflicting lock after block was accepted, invalidating now\n",
|
|
|
|
__func__);
|
|
|
|
ScheduleInvalidateBlock(pindexNew);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestChainLock.nHeight >= pindexNew->nHeight) {
|
|
|
|
// already got the same CLSIG or a better one
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pindexNew->nHeight == lastSignedHeight) {
|
|
|
|
// already signed this one
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
lastSignedHeight = pindexNew->nHeight;
|
|
|
|
lastSignedRequestId = requestId;
|
|
|
|
lastSignedMsgHash = msgHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
// WARNING: cs_main and cs should not be held!
|
|
|
|
void CChainLocksHandler::EnforceBestChainLock()
|
|
|
|
{
|
|
|
|
CChainLockSig clsig;
|
|
|
|
const CBlockIndex* pindex;
|
|
|
|
{
|
|
|
|
LOCK(cs);
|
|
|
|
clsig = bestChainLockWithKnownBlock;
|
|
|
|
pindex = bestChainLockBlockIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
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.
|
2019-01-28 12:23:03 +01:00
|
|
|
inEnforceBestChainLock = true; // avoid unnecessary ScheduleInvalidateBlock calls inside UpdatedBlockTip
|
2019-01-22 14:20:32 +01:00
|
|
|
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__, bestChainLockWithKnownBlock.ToString(), jt->second->GetBlockHash().ToString());
|
|
|
|
DoInvalidateBlock(jt->second, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
pindex = pindex->pprev;
|
|
|
|
}
|
2019-01-28 12:23:03 +01:00
|
|
|
inEnforceBestChainLock = false;
|
2019-01-22 14:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
CValidationState state;
|
|
|
|
if (!ActivateBestChain(state, Params())) {
|
|
|
|
LogPrintf("CChainLocksHandler::UpdatedBlockTip -- ActivateBestChain failed: %s\n", FormatStateMessage(state));
|
|
|
|
// This should not have happened and we are in a state were it's not safe to continue anymore
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
|
|
|
|
{
|
2019-01-23 17:40:37 +01:00
|
|
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
CChainLockSig clsig;
|
|
|
|
{
|
|
|
|
LOCK(cs);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChainLocksHandler::ScheduleInvalidateBlock(const CBlockIndex* pindex)
|
|
|
|
{
|
|
|
|
// Calls to InvalidateBlock and ActivateBestChain might result in re-invocation of the UpdatedBlockTip and other
|
|
|
|
// signals, so we can't directly call it from signal handlers. We solve this by doing the call from the scheduler
|
|
|
|
|
|
|
|
scheduler->scheduleFromNow([this, pindex]() {
|
|
|
|
DoInvalidateBlock(pindex, true);
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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::UpdatedBlockTip -- InvalidateBlock failed: %s\n", 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::UpdatedBlockTip -- ActivateBestChain failed: %s\n", 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)
|
|
|
|
{
|
2019-01-23 17:40:37 +01:00
|
|
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
LOCK(cs);
|
|
|
|
return InternalHasChainLock(nHeight, blockHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CChainLocksHandler::InternalHasChainLock(int nHeight, const uint256& blockHash)
|
|
|
|
{
|
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2019-01-23 17:40:37 +01:00
|
|
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-22 14:20:32 +01:00
|
|
|
LOCK(cs);
|
|
|
|
return InternalHasConflictingChainLock(nHeight, blockHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CChainLocksHandler::InternalHasConflictingChainLock(int nHeight, const uint256& blockHash)
|
|
|
|
{
|
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
LOCK(cs);
|
|
|
|
if (GetTimeMillis() - lastCleanupTime < CLEANUP_INTERVAL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LOCK2(cs_main, cs);
|
|
|
|
|
|
|
|
for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {
|
|
|
|
if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {
|
|
|
|
it = seenChainLocks.erase(it);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lastCleanupTime = GetTimeMillis();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|