// Copyright (c) 2014-2017 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 "activemasternode.h" #include "base58.h" #include "clientversion.h" #include "init.h" #include "netbase.h" #include "masternode.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "script/standard.h" #include "util.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif // ENABLE_WALLET #include "evo/deterministicmns.h" #include CMasternode::CMasternode() : masternode_info_t{ MASTERNODE_ENABLED, PROTOCOL_VERSION, GetAdjustedTime()}, fAllowMixingTx(true) {} CMasternode::CMasternode(CService addr, COutPoint outpoint, CPubKey pubKeyCollateralAddressNew, CPubKey pubKeyMasternodeNew, int nProtocolVersionIn) : masternode_info_t{ MASTERNODE_ENABLED, nProtocolVersionIn, GetAdjustedTime(), outpoint, addr, pubKeyCollateralAddressNew, pubKeyMasternodeNew}, fAllowMixingTx(true) {} CMasternode::CMasternode(const CMasternode& other) : masternode_info_t{other}, lastPing(other.lastPing), vchSig(other.vchSig), nCollateralMinConfBlockHash(other.nCollateralMinConfBlockHash), nBlockLastPaid(other.nBlockLastPaid), nPoSeBanScore(other.nPoSeBanScore), nPoSeBanHeight(other.nPoSeBanHeight), fAllowMixingTx(other.fAllowMixingTx), fUnitTest(other.fUnitTest) {} CMasternode::CMasternode(const CMasternodeBroadcast& mnb) : masternode_info_t{ mnb.nActiveState, mnb.nProtocolVersion, mnb.sigTime, mnb.outpoint, mnb.addr, mnb.pubKeyCollateralAddress, mnb.pubKeyMasternode}, lastPing(mnb.lastPing), vchSig(mnb.vchSig), fAllowMixingTx(true) {} CMasternode::CMasternode(const uint256 &proTxHash, const CDeterministicMNCPtr& dmn) : masternode_info_t{ MASTERNODE_ENABLED, DMN_PROTO_VERSION, GetAdjustedTime(), dmn->collateralOutpoint, dmn->pdmnState->addr, CKeyID() /* not valid with DIP3 */, dmn->pdmnState->keyIDOwner, dmn->pdmnState->pubKeyOperator, dmn->pdmnState->keyIDVoting}, fAllowMixingTx(true) { } // // When a new masternode broadcast is sent, update our information // bool CMasternode::UpdateFromNewBroadcast(CMasternodeBroadcast& mnb, CConnman& connman) { if(mnb.sigTime <= sigTime && !mnb.fRecovery) return false; pubKeyMasternode = mnb.pubKeyMasternode; keyIDOwner = mnb.pubKeyMasternode.GetID(); legacyKeyIDOperator = mnb.pubKeyMasternode.GetID(); keyIDVoting = mnb.pubKeyMasternode.GetID(); sigTime = mnb.sigTime; vchSig = mnb.vchSig; nProtocolVersion = mnb.nProtocolVersion; addr = mnb.addr; nPoSeBanScore = 0; nPoSeBanHeight = 0; nTimeLastChecked = 0; int nDos = 0; if(!mnb.lastPing || (mnb.lastPing && mnb.lastPing.CheckAndUpdate(this, true, nDos, connman))) { lastPing = mnb.lastPing; mnodeman.mapSeenMasternodePing.insert(std::make_pair(lastPing.GetHash(), lastPing)); } // if it matches our Masternode privkey... if(fMasternodeMode && legacyKeyIDOperator == activeMasternodeInfo.legacyKeyIDOperator) { nPoSeBanScore = -MASTERNODE_POSE_BAN_MAX_SCORE; if(nProtocolVersion == PROTOCOL_VERSION) { // ... and PROTOCOL_VERSION, then we've been remotely activated ... legacyActiveMasternodeManager.ManageState(connman); } else { // ... otherwise we need to reactivate our node, do not add it to the list and do not relay // but also do not ban the node we get this message from LogPrintf("CMasternode::UpdateFromNewBroadcast -- wrong PROTOCOL_VERSION, re-activate your MN: message nProtocolVersion=%d PROTOCOL_VERSION=%d\n", nProtocolVersion, PROTOCOL_VERSION); return false; } } return true; } // // Deterministically calculate a given "score" for a Masternode depending on how close it's hash is to // the proof of work for that block. The further away they are the better, the furthest will win the election // and get paid this block // arith_uint256 CMasternode::CalculateScore(const uint256& blockHash) const { // NOTE not called when deterministic masternodes (spork15) are activated // Deterministically calculate a "score" for a Masternode based on any given (block)hash CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << outpoint << nCollateralMinConfBlockHash << blockHash; return UintToArith256(ss.GetHash()); } CMasternode::CollateralStatus CMasternode::CheckCollateral(const COutPoint& outpoint, const CKeyID& keyID) { int nHeight; return CheckCollateral(outpoint, keyID, nHeight); } CMasternode::CollateralStatus CMasternode::CheckCollateral(const COutPoint& outpoint, const CKeyID& keyID, int& nHeightRet) { AssertLockHeld(cs_main); Coin coin; if(!GetUTXOCoin(outpoint, coin)) { return COLLATERAL_UTXO_NOT_FOUND; } if(coin.out.nValue != 1000 * COIN) { return COLLATERAL_INVALID_AMOUNT; } if(keyID.IsNull() || coin.out.scriptPubKey != GetScriptForDestination(keyID)) { return COLLATERAL_INVALID_PUBKEY; } nHeightRet = coin.nHeight; return COLLATERAL_OK; } void CMasternode::Check(bool fForce) { AssertLockHeld(cs_main); LOCK(cs); if(ShutdownRequested()) return; if(!fForce && (GetTime() - nTimeLastChecked < MASTERNODE_CHECK_SECONDS)) return; nTimeLastChecked = GetTime(); LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state\n", outpoint.ToStringShort(), GetStateString()); //once spent, stop doing the checks if(IsOutpointSpent()) return; int nHeight = 0; if(!fUnitTest) { Coin coin; if(!GetUTXOCoin(outpoint, coin)) { nActiveState = MASTERNODE_OUTPOINT_SPENT; LogPrint("masternode", "CMasternode::Check -- Failed to find Masternode UTXO, masternode=%s\n", outpoint.ToStringShort()); return; } nHeight = chainActive.Height(); } if(IsPoSeBanned()) { if(nHeight < nPoSeBanHeight) return; // too early? // Otherwise give it a chance to proceed further to do all the usual checks and to change its state. // Masternode still will be on the edge and can be banned back easily if it keeps ignoring mnverify // or connect attempts. Will require few mnverify messages to strengthen its position in mn list. LogPrintf("CMasternode::Check -- Masternode %s is unbanned and back in list now\n", outpoint.ToStringShort()); DecreasePoSeBanScore(); } else if(nPoSeBanScore >= MASTERNODE_POSE_BAN_MAX_SCORE) { nActiveState = MASTERNODE_POSE_BAN; // ban for the whole payment cycle nPoSeBanHeight = nHeight + mnodeman.size(); LogPrintf("CMasternode::Check -- Masternode %s is banned till block %d now\n", outpoint.ToStringShort(), nPoSeBanHeight); return; } int nActiveStatePrev = nActiveState; bool fOurMasternode = fMasternodeMode && activeMasternodeInfo.legacyKeyIDOperator == legacyKeyIDOperator; // masternode doesn't meet payment protocol requirements ... bool fRequireUpdate = nProtocolVersion < mnpayments.GetMinMasternodePaymentsProto() || // or it's our own node and we just updated it to the new protocol but we are still waiting for activation ... (fOurMasternode && nProtocolVersion < PROTOCOL_VERSION); if(fRequireUpdate) { nActiveState = MASTERNODE_UPDATE_REQUIRED; if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } // keep old masternodes on start, give them a chance to receive updates... bool fWaitForPing = !masternodeSync.IsMasternodeListSynced() && !IsPingedWithin(MASTERNODE_MIN_MNP_SECONDS); if(fWaitForPing && !fOurMasternode) { // ...but if it was already expired before the initial check - return right away if(IsExpired() || IsSentinelPingExpired() || IsNewStartRequired()) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state, waiting for ping\n", outpoint.ToStringShort(), GetStateString()); return; } } // don't expire if we are still in "waiting for ping" mode unless it's our own masternode if(!fWaitForPing || fOurMasternode) { if(!IsPingedWithin(MASTERNODE_NEW_START_REQUIRED_SECONDS)) { nActiveState = MASTERNODE_NEW_START_REQUIRED; if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } if(!IsPingedWithin(MASTERNODE_EXPIRATION_SECONDS)) { nActiveState = MASTERNODE_EXPIRED; if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } // part 1: expire based on dashd ping bool fSentinelPingActive = masternodeSync.IsSynced() && mnodeman.IsSentinelPingActive(); bool fSentinelPingExpired = fSentinelPingActive && !IsPingedWithin(MASTERNODE_SENTINEL_PING_MAX_SECONDS); LogPrint("masternode", "CMasternode::Check -- outpoint=%s, GetAdjustedTime()=%d, fSentinelPingExpired=%d\n", outpoint.ToStringShort(), GetAdjustedTime(), fSentinelPingExpired); if(fSentinelPingExpired) { nActiveState = MASTERNODE_SENTINEL_PING_EXPIRED; if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } // We require MNs to be in PRE_ENABLED until they either start to expire or receive a ping and go into ENABLED state // Works on mainnet/testnet only and not the case on regtest/devnet. if (Params().NetworkIDString() != CBaseChainParams::REGTEST && Params().NetworkIDString() != CBaseChainParams::DEVNET) { if (lastPing.sigTime - sigTime < MASTERNODE_MIN_MNP_SECONDS) { nActiveState = MASTERNODE_PRE_ENABLED; if (nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } if(!fWaitForPing || fOurMasternode) { // part 2: expire based on sentinel info bool fSentinelPingActive = masternodeSync.IsSynced() && mnodeman.IsSentinelPingActive(); bool fSentinelPingExpired = fSentinelPingActive && !lastPing.fSentinelIsCurrent; LogPrint("masternode", "CMasternode::Check -- outpoint=%s, GetAdjustedTime()=%d, fSentinelPingExpired=%d\n", outpoint.ToStringShort(), GetAdjustedTime(), fSentinelPingExpired); if(fSentinelPingExpired) { nActiveState = MASTERNODE_SENTINEL_PING_EXPIRED; if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } return; } } nActiveState = MASTERNODE_ENABLED; // OK if(nActiveStatePrev != nActiveState) { LogPrint("masternode", "CMasternode::Check -- Masternode %s is in %s state now\n", outpoint.ToStringShort(), GetStateString()); } } bool CMasternode::IsValidNetAddr() { return IsValidNetAddr(addr); } bool CMasternode::IsValidNetAddr(CService addrIn) { // TODO: regtest is fine with any addresses for now, // should probably be a bit smarter if one day we start to implement tests for this return Params().NetworkIDString() == CBaseChainParams::REGTEST || (addrIn.IsIPv4() && IsReachable(addrIn) && addrIn.IsRoutable()); } masternode_info_t CMasternode::GetInfo() const { masternode_info_t info{*this}; info.nTimeLastPing = lastPing.sigTime; info.fInfoValid = true; return info; } std::string CMasternode::StateToString(int nStateIn) { switch(nStateIn) { case MASTERNODE_PRE_ENABLED: return "PRE_ENABLED"; case MASTERNODE_ENABLED: return "ENABLED"; case MASTERNODE_EXPIRED: return "EXPIRED"; case MASTERNODE_OUTPOINT_SPENT: return "OUTPOINT_SPENT"; case MASTERNODE_UPDATE_REQUIRED: return "UPDATE_REQUIRED"; case MASTERNODE_SENTINEL_PING_EXPIRED: return "SENTINEL_PING_EXPIRED"; case MASTERNODE_NEW_START_REQUIRED: return "NEW_START_REQUIRED"; case MASTERNODE_POSE_BAN: return "POSE_BAN"; default: return "UNKNOWN"; } } std::string CMasternode::GetStateString() const { return StateToString(nActiveState); } std::string CMasternode::GetStatus() const { // TODO: return smth a bit more human readable here return GetStateString(); } void CMasternode::UpdateLastPaid(const CBlockIndex *pindex, int nMaxBlocksToScanBack) { AssertLockHeld(cs_main); if(!pindex) return; if (deterministicMNManager->IsDeterministicMNsSporkActive(pindex->nHeight)) { auto dmn = deterministicMNManager->GetListForBlock(pindex->GetBlockHash()).GetMNByCollateral(outpoint); if (!dmn || dmn->pdmnState->nLastPaidHeight == -1) { LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- not found\n", outpoint.ToStringShort()); } else { nBlockLastPaid = (int)dmn->pdmnState->nLastPaidHeight; nTimeLastPaid = chainActive[nBlockLastPaid]->nTime; LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- found new %d\n", outpoint.ToStringShort(), nBlockLastPaid); } return; } const CBlockIndex *BlockReading = pindex; CScript mnpayee = GetScriptForDestination(keyIDCollateralAddress); // LogPrint("mnpayments", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s\n", outpoint.ToStringShort()); LOCK(cs_mapMasternodeBlocks); for (int i = 0; BlockReading && BlockReading->nHeight > nBlockLastPaid && i < nMaxBlocksToScanBack; i++) { if(mnpayments.mapMasternodeBlocks.count(BlockReading->nHeight) && mnpayments.mapMasternodeBlocks[BlockReading->nHeight].HasPayeeWithVotes(mnpayee, 2)) { CBlock block; if(!ReadBlockFromDisk(block, BlockReading, Params().GetConsensus())) continue; // shouldn't really happen CAmount nMasternodePayment = GetMasternodePayment(BlockReading->nHeight, block.vtx[0]->GetValueOut()); for (const auto& txout : block.vtx[0]->vout) if(mnpayee == txout.scriptPubKey && nMasternodePayment == txout.nValue) { nBlockLastPaid = BlockReading->nHeight; nTimeLastPaid = BlockReading->nTime; LogPrint("mnpayments", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- found new %d\n", outpoint.ToStringShort(), nBlockLastPaid); return; } } if (BlockReading->pprev == nullptr) { assert(BlockReading); break; } BlockReading = BlockReading->pprev; } // Last payment for this masternode wasn't found in latest mnpayments blocks // or it was found in mnpayments blocks but wasn't found in the blockchain. // LogPrint("mnpayments", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- keeping old %d\n", outpoint.ToStringShort(), nBlockLastPaid); } #ifdef ENABLE_WALLET bool CMasternodeBroadcast::Create(const std::string& strService, const std::string& strKeyMasternode, const std::string& strTxHash, const std::string& strOutputIndex, std::string& strErrorRet, CMasternodeBroadcast &mnbRet, bool fOffline) { COutPoint outpoint; CPubKey pubKeyCollateralAddressNew; CKey keyCollateralAddressNew; CPubKey pubKeyMasternodeNew; CKey keyMasternodeNew; auto Log = [&strErrorRet](std::string sErr)->bool { strErrorRet = sErr; LogPrintf("CMasternodeBroadcast::Create -- %s\n", strErrorRet); return false; }; // Wait for sync to finish because mnb simply won't be relayed otherwise if (!fOffline && !masternodeSync.IsSynced()) return Log("Sync in progress. Must wait until sync is complete to start Masternode"); if (!CMessageSigner::GetKeysFromSecret(strKeyMasternode, keyMasternodeNew, pubKeyMasternodeNew)) return Log(strprintf("Invalid masternode key %s", strKeyMasternode)); if (!pwalletMain->GetMasternodeOutpointAndKeys(outpoint, pubKeyCollateralAddressNew, keyCollateralAddressNew, strTxHash, strOutputIndex)) return Log(strprintf("Could not allocate outpoint %s:%s for masternode %s", strTxHash, strOutputIndex, strService)); CService service; if (!Lookup(strService.c_str(), service, 0, false)) return Log(strprintf("Invalid address %s for masternode.", strService)); int mainnetDefaultPort = Params(CBaseChainParams::MAIN).GetDefaultPort(); if (Params().NetworkIDString() == CBaseChainParams::MAIN) { if (service.GetPort() != mainnetDefaultPort) return Log(strprintf("Invalid port %u for masternode %s, only %d is supported on mainnet.", service.GetPort(), strService, mainnetDefaultPort)); } else if (service.GetPort() == mainnetDefaultPort) return Log(strprintf("Invalid port %u for masternode %s, %d is the only supported on mainnet.", service.GetPort(), strService, mainnetDefaultPort)); return Create(outpoint, service, keyCollateralAddressNew, pubKeyCollateralAddressNew, keyMasternodeNew, pubKeyMasternodeNew, strErrorRet, mnbRet); } bool CMasternodeBroadcast::Create(const COutPoint& outpoint, const CService& service, const CKey& keyCollateralAddressNew, const CPubKey& pubKeyCollateralAddressNew, const CKey& keyMasternodeNew, const CPubKey& pubKeyMasternodeNew, std::string &strErrorRet, CMasternodeBroadcast &mnbRet) { // wait for reindex and/or import to finish if (fImporting || fReindex) return false; LogPrint("masternode", "CMasternodeBroadcast::Create -- pubKeyCollateralAddressNew = %s, keyIDOperator = %s\n", CBitcoinAddress(pubKeyCollateralAddressNew.GetID()).ToString(), pubKeyMasternodeNew.GetID().ToString()); auto Log = [&strErrorRet,&mnbRet](std::string sErr)->bool { strErrorRet = sErr; LogPrintf("CMasternodeBroadcast::Create -- %s\n", strErrorRet); mnbRet = CMasternodeBroadcast(); return false; }; CMasternodePing mnp(outpoint); if (!mnp.Sign(keyMasternodeNew, pubKeyMasternodeNew.GetID())) return Log(strprintf("Failed to sign ping, masternode=%s", outpoint.ToStringShort())); mnbRet = CMasternodeBroadcast(service, outpoint, pubKeyCollateralAddressNew, pubKeyMasternodeNew, PROTOCOL_VERSION); if (!mnbRet.IsValidNetAddr()) return Log(strprintf("Invalid IP address, masternode=%s", outpoint.ToStringShort())); mnbRet.lastPing = mnp; if (!mnbRet.Sign(keyCollateralAddressNew)) return Log(strprintf("Failed to sign broadcast, masternode=%s", outpoint.ToStringShort())); return true; } #endif // ENABLE_WALLET bool CMasternodeBroadcast::SimpleCheck(int& nDos) { nDos = 0; AssertLockHeld(cs_main); // make sure addr is valid if(!IsValidNetAddr()) { LogPrintf("CMasternodeBroadcast::SimpleCheck -- Invalid addr, rejected: masternode=%s addr=%s\n", outpoint.ToStringShort(), addr.ToString()); return false; } // make sure signature isn't in the future (past is OK) if (sigTime > GetAdjustedTime() + 60 * 60) { LogPrintf("CMasternodeBroadcast::SimpleCheck -- Signature rejected, too far into the future: masternode=%s\n", outpoint.ToStringShort()); nDos = 1; return false; } // empty ping or incorrect sigTime/unknown blockhash if(!lastPing || !lastPing.SimpleCheck(nDos)) { // one of us is probably forked or smth, just mark it as expired and check the rest of the rules nActiveState = MASTERNODE_EXPIRED; } if(nProtocolVersion < mnpayments.GetMinMasternodePaymentsProto()) { LogPrintf("CMasternodeBroadcast::SimpleCheck -- outdated Masternode: masternode=%s nProtocolVersion=%d\n", outpoint.ToStringShort(), nProtocolVersion); nActiveState = MASTERNODE_UPDATE_REQUIRED; } CScript pubkeyScript; pubkeyScript = GetScriptForDestination(keyIDCollateralAddress); if(pubkeyScript.size() != 25) { LogPrintf("CMasternodeBroadcast::SimpleCheck -- keyIDCollateralAddress has the wrong size\n"); nDos = 100; return false; } CScript pubkeyScript2; pubkeyScript2 = GetScriptForDestination(legacyKeyIDOperator); if(pubkeyScript2.size() != 25) { LogPrintf("CMasternodeBroadcast::SimpleCheck -- keyIDOperator has the wrong size\n"); nDos = 100; return false; } int mainnetDefaultPort = Params(CBaseChainParams::MAIN).GetDefaultPort(); if(Params().NetworkIDString() == CBaseChainParams::MAIN) { if(addr.GetPort() != mainnetDefaultPort) return false; } else if(addr.GetPort() == mainnetDefaultPort) return false; return true; } bool CMasternodeBroadcast::Update(CMasternode* pmn, int& nDos, CConnman& connman) { nDos = 0; AssertLockHeld(cs_main); if(pmn->sigTime == sigTime && !fRecovery) { // mapSeenMasternodeBroadcast in CMasternodeMan::CheckMnbAndUpdateMasternodeList should filter legit duplicates // but this still can happen if we just started, which is ok, just do nothing here. return false; } // this broadcast is older than the one that we already have - it's bad and should never happen // unless someone is doing something fishy if(pmn->sigTime > sigTime) { LogPrintf("CMasternodeBroadcast::Update -- Bad sigTime %d (existing broadcast is at %d) for Masternode %s %s\n", sigTime, pmn->sigTime, outpoint.ToStringShort(), addr.ToString()); return false; } pmn->Check(); // masternode is banned by PoSe if(pmn->IsPoSeBanned()) { LogPrintf("CMasternodeBroadcast::Update -- Banned by PoSe, masternode=%s\n", outpoint.ToStringShort()); return false; } // IsVnAssociatedWithPubkey is validated once in CheckOutpoint, after that they just need to match if(pmn->keyIDCollateralAddress != keyIDCollateralAddress) { LogPrintf("CMasternodeBroadcast::Update -- Got mismatched keyIDCollateralAddress and outpoint\n"); nDos = 33; return false; } if (!CheckSignature(nDos)) { LogPrintf("CMasternodeBroadcast::Update -- CheckSignature() failed, masternode=%s\n", outpoint.ToStringShort()); return false; } // if ther was no masternode broadcast recently or if it matches our Masternode privkey... if(!pmn->IsBroadcastedWithin(MASTERNODE_MIN_MNB_SECONDS) || (fMasternodeMode && legacyKeyIDOperator == activeMasternodeInfo.legacyKeyIDOperator)) { // take the newest entry LogPrintf("CMasternodeBroadcast::Update -- Got UPDATED Masternode entry: addr=%s\n", addr.ToString()); if(pmn->UpdateFromNewBroadcast(*this, connman)) { pmn->Check(); Relay(connman); } masternodeSync.BumpAssetLastTime("CMasternodeBroadcast::Update"); } return true; } bool CMasternodeBroadcast::CheckOutpoint(int& nDos) { // we are a masternode with the same outpoint (i.e. already activated) and this mnb is ours (matches our Masternode privkey) // so nothing to do here for us if(fMasternodeMode && outpoint == activeMasternodeInfo.outpoint && legacyKeyIDOperator == activeMasternodeInfo.legacyKeyIDOperator) { return false; } AssertLockHeld(cs_main); int nHeight; CollateralStatus err = CheckCollateral(outpoint, keyIDCollateralAddress, nHeight); if (err == COLLATERAL_UTXO_NOT_FOUND) { LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Failed to find Masternode UTXO, masternode=%s\n", outpoint.ToStringShort()); return false; } if (err == COLLATERAL_INVALID_AMOUNT) { LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO should have 1000 DASH, masternode=%s\n", outpoint.ToStringShort()); nDos = 33; return false; } if(err == COLLATERAL_INVALID_PUBKEY) { LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO should match keyIDCollateralAddress, masternode=%s\n", outpoint.ToStringShort()); nDos = 33; return false; } if(chainActive.Height() - nHeight + 1 < Params().GetConsensus().nMasternodeMinimumConfirmations) { LogPrintf("CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO must have at least %d confirmations, masternode=%s\n", Params().GetConsensus().nMasternodeMinimumConfirmations, outpoint.ToStringShort()); // UTXO is legit but has not enough confirmations. // Maybe we miss few blocks, let this mnb be checked again later. mnodeman.mapSeenMasternodeBroadcast.erase(GetHash()); return false; } LogPrint("masternode", "CMasternodeBroadcast::CheckOutpoint -- Masternode UTXO verified\n"); // Verify that sig time is legit, should be at least not earlier than the timestamp of the block // at which collateral became nMasternodeMinimumConfirmations blocks deep. // NOTE: this is not accurate because block timestamp is NOT guaranteed to be 100% correct one. CBlockIndex* pRequiredConfIndex = chainActive[nHeight + Params().GetConsensus().nMasternodeMinimumConfirmations - 1]; // block where tx got nMasternodeMinimumConfirmations if(pRequiredConfIndex->GetBlockTime() > sigTime) { LogPrintf("CMasternodeBroadcast::CheckOutpoint -- Bad sigTime %d (%d conf block is at %d) for Masternode %s %s\n", sigTime, Params().GetConsensus().nMasternodeMinimumConfirmations, pRequiredConfIndex->GetBlockTime(), outpoint.ToStringShort(), addr.ToString()); return false; } if (!CheckSignature(nDos)) { LogPrintf("CMasternodeBroadcast::CheckOutpoint -- CheckSignature() failed, masternode=%s\n", outpoint.ToStringShort()); return false; } // remember the block hash when collateral for this masternode had minimum required confirmations nCollateralMinConfBlockHash = pRequiredConfIndex->GetBlockHash(); return true; } uint256 CMasternodeBroadcast::GetHash() const { // Note: doesn't match serialization CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << outpoint << uint8_t{} << 0xffffffff; // adding dummy values here to match old hashing format ss << pubKeyCollateralAddress; ss << sigTime; return ss.GetHash(); } uint256 CMasternodeBroadcast::GetSignatureHash() const { // TODO: replace with "return SerializeHash(*this);" after migration to 70209 CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << outpoint; ss << addr; ss << pubKeyCollateralAddress; ss << pubKeyMasternode; ss << sigTime; ss << nProtocolVersion; return ss.GetHash(); } bool CMasternodeBroadcast::Sign(const CKey& keyCollateralAddress) { std::string strError; sigTime = GetAdjustedTime(); if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::SignHash(hash, keyCollateralAddress, vchSig)) { LogPrintf("CMasternodeBroadcast::Sign -- SignHash() failed\n"); return false; } if (!CHashSigner::VerifyHash(hash, keyIDCollateralAddress, vchSig, strError)) { LogPrintf("CMasternodeBroadcast::Sign -- VerifyMessage() failed, error: %s\n", strError); return false; } } else { std::string strMessage = addr.ToString(false) + std::to_string(sigTime) + keyIDCollateralAddress.ToString() + legacyKeyIDOperator.ToString() + std::to_string(nProtocolVersion); if (!CMessageSigner::SignMessage(strMessage, vchSig, keyCollateralAddress)) { LogPrintf("CMasternodeBroadcast::Sign -- SignMessage() failed\n"); return false; } if(!CMessageSigner::VerifyMessage(keyIDCollateralAddress, vchSig, strMessage, strError)) { LogPrintf("CMasternodeBroadcast::Sign -- VerifyMessage() failed, error: %s\n", strError); return false; } } return true; } bool CMasternodeBroadcast::CheckSignature(int& nDos) const { std::string strError = ""; nDos = 0; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::VerifyHash(hash, keyIDCollateralAddress, vchSig, strError)) { // maybe it's in old format std::string strMessage = addr.ToString(false) + std::to_string(sigTime) + keyIDCollateralAddress.ToString() + legacyKeyIDOperator.ToString() + std::to_string(nProtocolVersion); if (!CMessageSigner::VerifyMessage(keyIDCollateralAddress, vchSig, strMessage, strError)){ // nope, not in old format either LogPrintf("CMasternodeBroadcast::CheckSignature -- Got bad Masternode announce signature, error: %s\n", strError); nDos = 100; return false; } } } else { std::string strMessage = addr.ToString(false) + std::to_string(sigTime) + keyIDCollateralAddress.ToString() + legacyKeyIDOperator.ToString() + std::to_string(nProtocolVersion); if(!CMessageSigner::VerifyMessage(keyIDCollateralAddress, vchSig, strMessage, strError)){ LogPrintf("CMasternodeBroadcast::CheckSignature -- Got bad Masternode announce signature, error: %s\n", strError); nDos = 100; return false; } } return true; } void CMasternodeBroadcast::Relay(CConnman& connman) const { // Do not relay until fully synced if(!masternodeSync.IsSynced()) { LogPrint("masternode", "CMasternodeBroadcast::Relay -- won't relay until fully synced\n"); return; } CInv inv(MSG_MASTERNODE_ANNOUNCE, GetHash()); connman.RelayInv(inv); } uint256 CMasternodePing::GetHash() const { CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { // TODO: replace with "return SerializeHash(*this);" after migration to 70209 ss << masternodeOutpoint; ss << blockHash; ss << sigTime; ss << fSentinelIsCurrent; ss << nSentinelVersion; ss << nDaemonVersion; } else { // Note: doesn't match serialization ss << masternodeOutpoint << uint8_t{} << 0xffffffff; // adding dummy values here to match old hashing format ss << sigTime; } return ss.GetHash(); } uint256 CMasternodePing::GetSignatureHash() const { return GetHash(); } CMasternodePing::CMasternodePing(const COutPoint& outpoint) { LOCK(cs_main); if (!chainActive.Tip() || chainActive.Height() < 12) return; masternodeOutpoint = outpoint; blockHash = chainActive[chainActive.Height() - 12]->GetBlockHash(); sigTime = GetAdjustedTime(); nDaemonVersion = CLIENT_VERSION; } bool CMasternodePing::Sign(const CKey& keyMasternode, const CKeyID& keyIDOperator) { std::string strError; sigTime = GetAdjustedTime(); if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::SignHash(hash, keyMasternode, vchSig)) { LogPrintf("CMasternodePing::Sign -- SignHash() failed\n"); return false; } if (!CHashSigner::VerifyHash(hash, keyIDOperator, vchSig, strError)) { LogPrintf("CMasternodePing::Sign -- VerifyHash() failed, error: %s\n", strError); return false; } } else { std::string strMessage = CTxIn(masternodeOutpoint).ToString() + blockHash.ToString() + std::to_string(sigTime); if (!CMessageSigner::SignMessage(strMessage, vchSig, keyMasternode)) { LogPrintf("CMasternodePing::Sign -- SignMessage() failed\n"); return false; } if(!CMessageSigner::VerifyMessage(keyIDOperator, vchSig, strMessage, strError)) { LogPrintf("CMasternodePing::Sign -- VerifyMessage() failed, error: %s\n", strError); return false; } } return true; } bool CMasternodePing::CheckSignature(CKeyID& keyIDOperator, int &nDos) const { std::string strError = ""; nDos = 0; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::VerifyHash(hash, keyIDOperator, vchSig, strError)) { std::string strMessage = CTxIn(masternodeOutpoint).ToString() + blockHash.ToString() + std::to_string(sigTime); if(!CMessageSigner::VerifyMessage(keyIDOperator, vchSig, strMessage, strError)) { LogPrintf("CMasternodePing::CheckSignature -- Got bad Masternode ping signature, masternode=%s, error: %s\n", masternodeOutpoint.ToStringShort(), strError); nDos = 33; return false; } } } else { std::string strMessage = CTxIn(masternodeOutpoint).ToString() + blockHash.ToString() + std::to_string(sigTime); if (!CMessageSigner::VerifyMessage(keyIDOperator, vchSig, strMessage, strError)) { LogPrintf("CMasternodePing::CheckSignature -- Got bad Masternode ping signature, masternode=%s, error: %s\n", masternodeOutpoint.ToStringShort(), strError); nDos = 33; return false; } } return true; } bool CMasternodePing::SimpleCheck(int& nDos) { // don't ban by default nDos = 0; if (sigTime > GetAdjustedTime() + 60 * 60) { LogPrintf("CMasternodePing::SimpleCheck -- Signature rejected, too far into the future, masternode=%s\n", masternodeOutpoint.ToStringShort()); nDos = 1; return false; } { AssertLockHeld(cs_main); BlockMap::iterator mi = mapBlockIndex.find(blockHash); if (mi == mapBlockIndex.end()) { LogPrint("masternode", "CMasternodePing::SimpleCheck -- Masternode ping is invalid, unknown block hash: masternode=%s blockHash=%s\n", masternodeOutpoint.ToStringShort(), blockHash.ToString()); // maybe we stuck or forked so we shouldn't ban this node, just fail to accept this ping // TODO: or should we also request this block? return false; } } LogPrint("masternode", "CMasternodePing::SimpleCheck -- Masternode ping verified: masternode=%s blockHash=%s sigTime=%d\n", masternodeOutpoint.ToStringShort(), blockHash.ToString(), sigTime); return true; } bool CMasternodePing::CheckAndUpdate(CMasternode* pmn, bool fFromNewBroadcast, int& nDos, CConnman& connman) { AssertLockHeld(cs_main); // don't ban by default nDos = 0; if (!SimpleCheck(nDos)) { return false; } if (pmn == nullptr) { LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- Couldn't find Masternode entry, masternode=%s\n", masternodeOutpoint.ToStringShort()); return false; } if(!fFromNewBroadcast) { if (pmn->IsUpdateRequired()) { LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- masternode protocol is outdated, masternode=%s\n", masternodeOutpoint.ToStringShort()); return false; } if (pmn->IsNewStartRequired()) { LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- masternode is completely expired, new start is required, masternode=%s\n", masternodeOutpoint.ToStringShort()); return false; } } { BlockMap::iterator mi = mapBlockIndex.find(blockHash); if ((*mi).second && (*mi).second->nHeight < chainActive.Height() - 24) { LogPrintf("CMasternodePing::CheckAndUpdate -- Masternode ping is invalid, block hash is too old: masternode=%s blockHash=%s\n", masternodeOutpoint.ToStringShort(), blockHash.ToString()); // nDos = 1; return false; } } LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- New ping: masternode=%s blockHash=%s sigTime=%d\n", masternodeOutpoint.ToStringShort(), blockHash.ToString(), sigTime); // LogPrintf("mnping - Found corresponding mn for outpoint: %s\n", masternodeOutpoint.ToStringShort()); // update only if there is no known ping for this masternode or // last ping was more then MASTERNODE_MIN_MNP_SECONDS-60 ago comparing to this one if (pmn->IsPingedWithin(MASTERNODE_MIN_MNP_SECONDS - 60, sigTime)) { LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- Masternode ping arrived too early, masternode=%s\n", masternodeOutpoint.ToStringShort()); //nDos = 1; //disable, this is happening frequently and causing banned peers return false; } if (!CheckSignature(pmn->legacyKeyIDOperator, nDos)) return false; // so, ping seems to be ok // if we are still syncing and there was no known ping for this mn for quite a while // (NOTE: assuming that MASTERNODE_EXPIRATION_SECONDS/2 should be enough to finish mn list sync) if(!masternodeSync.IsMasternodeListSynced() && !pmn->IsPingedWithin(MASTERNODE_EXPIRATION_SECONDS/2)) { // let's bump sync timeout LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- bumping sync timeout, masternode=%s\n", masternodeOutpoint.ToStringShort()); masternodeSync.BumpAssetLastTime("CMasternodePing::CheckAndUpdate"); } // let's store this ping as the last one LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- Masternode ping accepted, masternode=%s\n", masternodeOutpoint.ToStringShort()); pmn->lastPing = *this; // and update mnodeman.mapSeenMasternodeBroadcast.lastPing which is probably outdated CMasternodeBroadcast mnb(*pmn); uint256 hash = mnb.GetHash(); if (mnodeman.mapSeenMasternodeBroadcast.count(hash)) { mnodeman.mapSeenMasternodeBroadcast[hash].second.lastPing = *this; } // force update, ignoring cache pmn->Check(true); // relay ping for nodes in ENABLED/EXPIRED/SENTINEL_PING_EXPIRED state only, skip everyone else if (!pmn->IsEnabled() && !pmn->IsExpired() && !pmn->IsSentinelPingExpired()) return false; LogPrint("masternode", "CMasternodePing::CheckAndUpdate -- Masternode ping acceepted and relayed, masternode=%s\n", masternodeOutpoint.ToStringShort()); Relay(connman); return true; } void CMasternodePing::Relay(CConnman& connman) { // Do not relay until fully synced if(!masternodeSync.IsSynced()) { LogPrint("masternode", "CMasternodePing::Relay -- won't relay until fully synced\n"); return; } CInv inv(MSG_MASTERNODE_PING, GetHash()); connman.RelayInv(inv); } std::string CMasternodePing::GetSentinelString() const { return nSentinelVersion > DEFAULT_SENTINEL_VERSION ? SafeIntVersionToString(nSentinelVersion) : "Unknown"; } std::string CMasternodePing::GetDaemonString() const { return nDaemonVersion > DEFAULT_DAEMON_VERSION ? FormatVersion(nDaemonVersion) : "Unknown"; } void CMasternode::AddGovernanceVote(uint256 nGovernanceObjectHash) { // Insert a zero value, or not. Then increment the value regardless. This // ensures the value is in the map. const auto& pair = mapGovernanceObjectsVotedOn.emplace(nGovernanceObjectHash, 0); pair.first->second++; } void CMasternode::RemoveGovernanceObject(uint256 nGovernanceObjectHash) { // Whether or not the govobj hash exists in the map first is irrelevant. mapGovernanceObjectsVotedOn.erase(nGovernanceObjectHash); } /** * FLAG GOVERNANCE ITEMS AS DIRTY * * - When masternode come and go on the network, we must flag the items they voted on to recalc it's cached flags * */ void CMasternode::FlagGovernanceItemsAsDirty() { for (auto& govObjHashPair : mapGovernanceObjectsVotedOn) { mnodeman.AddDirtyGovernanceObjectHash(govObjHashPair.first); } }