// 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 "consensus/validation.h" #include "governance-classes.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "netfulfilledman.h" #include "netmessagemaker.h" #include "spork.h" #include "util.h" #include "evo/deterministicmns.h" #include /** Object for who's going to get paid on which blocks */ CMasternodePayments mnpayments; CCriticalSection cs_vecPayees; CCriticalSection cs_mapMasternodeBlocks; CCriticalSection cs_mapMasternodePaymentVotes; bool IsOldBudgetBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet) { const Consensus::Params& consensusParams = Params().GetConsensus(); if (nBlockHeight >= consensusParams.nSuperblockStartBlock) { // switched to new budget system (superblocks) return true; } bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward); // we are still using budgets, but we have no data about them anymore, // all we know is predefined budget cycle and window int nOffset = nBlockHeight % consensusParams.nBudgetPaymentsCycleBlocks; if(nBlockHeight >= consensusParams.nBudgetPaymentsStartBlock && nOffset < consensusParams.nBudgetPaymentsWindowBlocks) { // NOTE: old budget system is disabled since 12.1 if(masternodeSync.IsSynced()) { // no old budget blocks should be accepted here on mainnet, // testnet/devnet/regtest should produce regular blocks only LogPrint("gobject", "%s -- WARNING: Client synced but old budget system is disabled, checking block value against block reward\n", __func__); if(!isBlockRewardValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, old budgets are disabled", nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); } return isBlockRewardValueMet; } // when not synced, rely on online nodes (all networks) LogPrint("gobject", "%s -- WARNING: Skipping old budget block value checks, accepting block\n", __func__); return true; } // LogPrint("gobject", "%s -- Block is not in budget cycle window, checking block value against block reward\n", __func__); if(!isBlockRewardValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, block is not in old budget cycle window", nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); } return isBlockRewardValueMet; } /** * IsBlockValueValid * * Determine if coinbase outgoing created money is the correct value * * Why is this needed? * - In Dash some blocks are superblocks, which output much higher amounts of coins * - Otherblocks are 10% lower in outgoing value, so in total, no extra coins are created * - When non-superblocks are detected, the normal schedule should be maintained */ bool IsBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet) { strErrorRet = ""; if (!IsOldBudgetBlockValueValid(block, nBlockHeight, blockReward, strErrorRet)) { return false; } bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward); if(fDebug) LogPrintf("block.vtx[0]->GetValueOut() %lld <= blockReward %lld\n", block.vtx[0]->GetValueOut(), blockReward); CAmount nSuperblockMaxValue = blockReward + CSuperblock::GetPaymentsLimit(nBlockHeight); bool isSuperblockMaxValueMet = (block.vtx[0]->GetValueOut() <= nSuperblockMaxValue); LogPrint("gobject", "block.vtx[0]->GetValueOut() %lld <= nSuperblockMaxValue %lld\n", block.vtx[0]->GetValueOut(), nSuperblockMaxValue); if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) { // can't possibly be a superblock, so lets just check for block reward limits if (!isBlockRewardValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, only regular blocks are allowed at this height", nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); } return isBlockRewardValueMet; } // bail out in case superblock limits were exceeded if (!isSuperblockMaxValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded superblock max value", nBlockHeight, block.vtx[0]->GetValueOut(), nSuperblockMaxValue); return false; } if(!masternodeSync.IsSynced() || fLiteMode) { if(fDebug) LogPrintf("%s -- WARNING: Not enough data, checked superblock max bounds only\n", __func__); // not enough data for full checks but at least we know that the superblock limits were honored. // We rely on the network to have followed the correct chain in this case return true; } // we are synced and possibly on a superblock now if (!sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) { // should NOT allow superblocks at all, when superblocks are disabled // revert to block reward limits in this case LogPrint("gobject", "%s -- Superblocks are disabled, no superblocks allowed\n", __func__); if(!isBlockRewardValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, superblocks are disabled", nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); } return isBlockRewardValueMet; } if (!CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { // we are on a valid superblock height but a superblock was not triggered // revert to block reward limits in this case if(!isBlockRewardValueMet) { strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, no triggered superblock detected", nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); } return isBlockRewardValueMet; } // this actually also checks for correct payees and not only amount if (!CSuperblockManager::IsValid(*block.vtx[0], nBlockHeight, blockReward)) { // triggered but invalid? that's weird LogPrintf("%s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, block.vtx[0]->ToString()); // should NOT allow invalid superblocks, when superblocks are enabled strErrorRet = strprintf("invalid superblock detected at height %d", nBlockHeight); return false; } // we got a valid superblock return true; } bool IsBlockPayeeValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) { if((!masternodeSync.IsSynced() && !deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) || fLiteMode) { //there is no budget data to use to check anything, let's just accept the longest chain if(fDebug) LogPrintf("%s -- WARNING: Not enough data, skipping block payee checks\n", __func__); return true; } // we are still using budgets, but we have no data about them anymore, // we can only check masternode payments const Consensus::Params& consensusParams = Params().GetConsensus(); if(nBlockHeight < consensusParams.nSuperblockStartBlock) { // NOTE: old budget system is disabled since 12.1 and we should never enter this branch // anymore when sync is finished (on mainnet). We have no old budget data but these blocks // have tons of confirmations and can be safely accepted without payee verification LogPrint("gobject", "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__); return true; } // superblocks started // SEE IF THIS IS A VALID SUPERBLOCK if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) { if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { if(CSuperblockManager::IsValid(txNew, nBlockHeight, blockReward)) { LogPrint("gobject", "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString()); // only allow superblock and masternode payments in the same block after spork15 activation if (!deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) return true; // continue validation, should also pay MN } else { LogPrintf("%s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); // should NOT allow such superblocks, when superblocks are enabled return false; } } else { LogPrint("gobject", "%s -- No triggered superblock detected at height %d\n", __func__, nBlockHeight); } } else { // should NOT allow superblocks at all, when superblocks are disabled LogPrint("gobject", "%s -- Superblocks are disabled, no superblocks allowed\n", __func__); } // If this isn't a superblock or spork15 is activated, check for correct masternode payment if(mnpayments.IsTransactionValid(txNew, nBlockHeight, blockReward)) { LogPrint("mnpayments", "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); return true; } if (deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) { // always enforce masternode payments when spork15 is active return false; } else { if(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); return false; } LogPrintf("%s-- WARNING: Masternode payment enforcement is disabled, accepting any payee\n", __func__); return true; } } void FillBlockPayments(CMutableTransaction& txNew, int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet) { // only create superblocks if spork is enabled AND if superblock is actually triggered // (height should be validated inside) if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED) && CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { LogPrint("gobject", "%s -- triggered superblock creation at height %d\n", __func__, nBlockHeight); CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblockPaymentsRet); } bool allowSuperblockAndMNReward = deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight); // don't allow payments to superblocks AND masternodes before spork15 activation if (!voutSuperblockPaymentsRet.empty() && !allowSuperblockAndMNReward) { txNew.vout.insert(txNew.vout.end(), voutSuperblockPaymentsRet.begin(), voutSuperblockPaymentsRet.end()); return; } if (!mnpayments.GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) { // no idea whom to pay (MN list empty?), lets hope for the best return; } txNew.vout.insert(txNew.vout.end(), voutMasternodePaymentsRet.begin(), voutMasternodePaymentsRet.end()); txNew.vout.insert(txNew.vout.end(), voutSuperblockPaymentsRet.begin(), voutSuperblockPaymentsRet.end()); std::string voutMasternodeStr; for (const auto& txout : voutMasternodePaymentsRet) { // subtract MN payment from miner reward txNew.vout[0].nValue -= txout.nValue; if (!voutMasternodeStr.empty()) voutMasternodeStr += ","; voutMasternodeStr += txout.ToString(); } LogPrint("mnpayments", "%s -- nBlockHeight %d blockReward %lld voutMasternodePaymentsRet \"%s\" txNew %s", __func__, nBlockHeight, blockReward, voutMasternodeStr, txNew.ToString()); } std::string GetLegacyRequiredPaymentsString(int nBlockHeight) { // IF WE HAVE A ACTIVATED TRIGGER FOR THIS HEIGHT - IT IS A SUPERBLOCK, GET THE REQUIRED PAYEES if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { return CSuperblockManager::GetRequiredPaymentsString(nBlockHeight); } // OTHERWISE, PAY MASTERNODE return mnpayments.GetRequiredPaymentsString(nBlockHeight); } std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCPtr &payee) { std::string strPayee = "Unknown"; if (payee) { CTxDestination dest; if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) assert(false); strPayee = CBitcoinAddress(dest).ToString(); } if (CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { strPayee += ", " + CSuperblockManager::GetRequiredPaymentsString(nBlockHeight); } return strPayee; } std::map GetRequiredPaymentsStrings(int nStartHeight, int nEndHeight) { std::map mapPayments; LOCK(cs_main); int nChainTipHeight = chainActive.Height(); bool doProjection = false; for(int h = nStartHeight; h < nEndHeight; h++) { if (deterministicMNManager->IsDeterministicMNsSporkActive(h)) { if (h <= nChainTipHeight) { auto payee = deterministicMNManager->GetListForBlock(chainActive[h - 1]->GetBlockHash()).GetMNPayee(); mapPayments.emplace(h, GetRequiredPaymentsString(h, payee)); } else { doProjection = true; break; } } else { mapPayments.emplace(h, GetLegacyRequiredPaymentsString(h)); } } if (doProjection) { auto projection = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(nEndHeight - nChainTipHeight); for (size_t i = 0; i < projection.size(); i++) { auto payee = projection[i]; int h = nChainTipHeight + 1 + i; mapPayments.emplace(h, GetRequiredPaymentsString(h, payee)); } } return mapPayments; } void CMasternodePayments::Clear() { LOCK2(cs_mapMasternodeBlocks, cs_mapMasternodePaymentVotes); mapMasternodeBlocks.clear(); mapMasternodePaymentVotes.clear(); } bool CMasternodePayments::UpdateLastVote(const CMasternodePaymentVote& vote) { if (deterministicMNManager->IsDeterministicMNsSporkActive()) return false; LOCK(cs_mapMasternodePaymentVotes); const auto it = mapMasternodesLastVote.find(vote.masternodeOutpoint); if (it != mapMasternodesLastVote.end()) { if (it->second == vote.nBlockHeight) return false; it->second = vote.nBlockHeight; return true; } //record this masternode voted mapMasternodesLastVote.emplace(vote.masternodeOutpoint, vote.nBlockHeight); return true; } /** * GetMasternodeTxOuts * * Get masternode payment tx outputs */ bool CMasternodePayments::GetMasternodeTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const { // make sure it's not filled yet voutMasternodePaymentsRet.clear(); if(!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) { assert(!deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)); // no masternode detected... int nCount = 0; masternode_info_t mnInfo; if(!mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount, mnInfo)) { // ...and we can't calculate it on our own LogPrintf("CMasternodePayments::%s -- Failed to detect masternode to pay\n", __func__); return false; } // fill payee with locally calculated winner and hope for the best CScript payee = GetScriptForDestination(mnInfo.keyIDCollateralAddress); CAmount masternodePayment = GetMasternodePayment(nBlockHeight, blockReward); voutMasternodePaymentsRet.emplace_back(masternodePayment, payee); } for (const auto& txout : voutMasternodePaymentsRet) { CTxDestination address1; ExtractDestination(txout.scriptPubKey, address1); CBitcoinAddress address2(address1); LogPrintf("CMasternodePayments::%s -- Masternode payment %lld to %s\n", __func__, txout.nValue, address2.ToString()); } return true; } int CMasternodePayments::GetMinMasternodePaymentsProto() const { return sporkManager.IsSporkActive(SPORK_10_MASTERNODE_PAY_UPDATED_NODES) ? MIN_MASTERNODE_PAYMENT_PROTO_VERSION_2 : MIN_MASTERNODE_PAYMENT_PROTO_VERSION_1; } void CMasternodePayments::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if (deterministicMNManager->IsDeterministicMNsSporkActive()) return; if(fLiteMode) return; // disable all Dash specific functionality if (strCommand == NetMsgType::MASTERNODEPAYMENTSYNC) { //Masternode Payments Request Sync if(pfrom->nVersion < GetMinMasternodePaymentsProto()) { LogPrint("mnpayments", "MASTERNODEPAYMENTSYNC -- peer=%d using obsolete version %i\n", pfrom->id, pfrom->nVersion); connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", GetMinMasternodePaymentsProto()))); return; } // Ignore such requests until we are fully synced. // We could start processing this after masternode list is synced // but this is a heavy one so it's better to finish sync first. if (!masternodeSync.IsSynced()) return; // DEPRECATED, should be removed on next protocol bump if(pfrom->nVersion == 70208) { int nCountNeeded; vRecv >> nCountNeeded; } if(netfulfilledman.HasFulfilledRequest(pfrom->addr, NetMsgType::MASTERNODEPAYMENTSYNC)) { LOCK(cs_main); // Asking for the payments list multiple times in a short period of time is no good LogPrintf("MASTERNODEPAYMENTSYNC -- peer already asked me for the list, peer=%d\n", pfrom->id); Misbehaving(pfrom->GetId(), 20); return; } netfulfilledman.AddFulfilledRequest(pfrom->addr, NetMsgType::MASTERNODEPAYMENTSYNC); Sync(pfrom, connman); LogPrintf("MASTERNODEPAYMENTSYNC -- Sent Masternode payment votes to peer=%d\n", pfrom->id); } else if (strCommand == NetMsgType::MASTERNODEPAYMENTVOTE) { // Masternode Payments Vote for the Winner CMasternodePaymentVote vote; vRecv >> vote; if(pfrom->nVersion < GetMinMasternodePaymentsProto()) { LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- peer=%d using obsolete version %i\n", pfrom->id, pfrom->nVersion); connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", GetMinMasternodePaymentsProto()))); return; } uint256 nHash = vote.GetHash(); pfrom->setAskFor.erase(nHash); // TODO: clear setAskFor for MSG_MASTERNODE_PAYMENT_BLOCK too // Ignore any payments messages until masternode list is synced if(!masternodeSync.IsMasternodeListSynced()) return; { LOCK(cs_mapMasternodePaymentVotes); auto res = mapMasternodePaymentVotes.emplace(nHash, vote); // Avoid processing same vote multiple times if it was already verified earlier if(!res.second && res.first->second.IsVerified()) { LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- hash=%s, nBlockHeight=%d/%d seen\n", nHash.ToString(), vote.nBlockHeight, nCachedBlockHeight); return; } // Mark vote as non-verified when it's seen for the first time, // AddOrUpdatePaymentVote() below should take care of it if vote is actually ok res.first->second.MarkAsNotVerified(); } int nFirstBlock = nCachedBlockHeight - GetStorageLimit(); if(vote.nBlockHeight < nFirstBlock || vote.nBlockHeight > nCachedBlockHeight+20) { LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- vote out of range: nFirstBlock=%d, nBlockHeight=%d, nHeight=%d\n", nFirstBlock, vote.nBlockHeight, nCachedBlockHeight); return; } std::string strError = ""; if(!vote.IsValid(pfrom, nCachedBlockHeight, strError, connman)) { LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- invalid message, error: %s\n", strError); return; } masternode_info_t mnInfo; if(!mnodeman.GetMasternodeInfo(vote.masternodeOutpoint, mnInfo)) { // mn was not found, so we can't check vote, some info is probably missing LogPrintf("MASTERNODEPAYMENTVOTE -- masternode is missing %s\n", vote.masternodeOutpoint.ToStringShort()); mnodeman.AskForMN(pfrom, vote.masternodeOutpoint, connman); return; } int nDos = 0; if(!vote.CheckSignature(mnInfo.keyIDOperator, nCachedBlockHeight, nDos)) { if(nDos) { LOCK(cs_main); LogPrintf("MASTERNODEPAYMENTVOTE -- ERROR: invalid signature\n"); Misbehaving(pfrom->GetId(), nDos); } else { // only warn about anything non-critical (i.e. nDos == 0) in debug mode LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- WARNING: invalid signature\n"); } // Either our info or vote info could be outdated. // In case our info is outdated, ask for an update, mnodeman.AskForMN(pfrom, vote.masternodeOutpoint, connman); // but there is nothing we can do if vote info itself is outdated // (i.e. it was signed by a mn which changed its key), // so just quit here. return; } if(!UpdateLastVote(vote)) { LogPrintf("MASTERNODEPAYMENTVOTE -- masternode already voted, masternode=%s\n", vote.masternodeOutpoint.ToStringShort()); return; } CTxDestination address1; ExtractDestination(vote.payee, address1); CBitcoinAddress address2(address1); LogPrint("mnpayments", "MASTERNODEPAYMENTVOTE -- vote: address=%s, nBlockHeight=%d, nHeight=%d, prevout=%s, hash=%s new\n", address2.ToString(), vote.nBlockHeight, nCachedBlockHeight, vote.masternodeOutpoint.ToStringShort(), nHash.ToString()); if(AddOrUpdatePaymentVote(vote)){ vote.Relay(connman); masternodeSync.BumpAssetLastTime("MASTERNODEPAYMENTVOTE"); } } } uint256 CMasternodePaymentVote::GetHash() const { // Note: doesn't match serialization CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << *(CScriptBase*)(&payee); ss << nBlockHeight; ss << masternodeOutpoint; return ss.GetHash(); } uint256 CMasternodePaymentVote::GetSignatureHash() const { return SerializeHash(*this); } bool CMasternodePaymentVote::Sign() { std::string strError; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if(!CHashSigner::SignHash(hash, activeMasternodeInfo.keyOperator, vchSig)) { LogPrintf("CMasternodePaymentVote::%s -- SignHash() failed\n", __func__); return false; } if (!CHashSigner::VerifyHash(hash, activeMasternodeInfo.keyIDOperator, vchSig, strError)) { LogPrintf("CMasternodePaymentVote::%s -- VerifyHash() failed, error: %s\n", __func__, strError); return false; } } else { std::string strMessage = masternodeOutpoint.ToStringShort() + std::to_string(nBlockHeight) + ScriptToAsmStr(payee); if(!CMessageSigner::SignMessage(strMessage, vchSig, activeMasternodeInfo.keyOperator)) { LogPrintf("CMasternodePaymentVote::%s -- SignMessage() failed\n", __func__); return false; } if(!CMessageSigner::VerifyMessage(activeMasternodeInfo.keyIDOperator, vchSig, strMessage, strError)) { LogPrintf("CMasternodePaymentVote::%s -- VerifyMessage() failed, error: %s\n", __func__, strError); return false; } } return true; } bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const { voutMasternodePaymentsRet.clear(); CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward); if (deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) { uint256 blockHash; { LOCK(cs_main); blockHash = chainActive[nBlockHeight - 1]->GetBlockHash(); } uint256 proTxHash; auto dmnPayee = deterministicMNManager->GetListForBlock(blockHash).GetMNPayee(); if (!dmnPayee) { return false; } if (dmnPayee->nOperatorReward == 0 || dmnPayee->pdmnState->scriptOperatorPayout == CScript()) { voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout); } else { CAmount operatorReward = (masternodeReward * dmnPayee->nOperatorReward) / 10000; masternodeReward -= operatorReward; voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout); voutMasternodePaymentsRet.emplace_back(operatorReward, dmnPayee->pdmnState->scriptOperatorPayout); } return true; } else { LOCK(cs_mapMasternodeBlocks); auto it = mapMasternodeBlocks.find(nBlockHeight); CScript payee; if (it == mapMasternodeBlocks.end() || !it->second.GetBestPayee(payee)) { return false; } voutMasternodePaymentsRet.emplace_back(masternodeReward, payee); return true; } } // Is this masternode scheduled to get paid soon? // -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 blocks of votes bool CMasternodePayments::IsScheduled(const masternode_info_t& mnInfo, int nNotBlockHeight) const { LOCK(cs_mapMasternodeBlocks); if (deterministicMNManager->IsDeterministicMNsSporkActive()) { auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8); for (const auto &dmn : projectedPayees) { if (dmn->proTxHash == mnInfo.outpoint.hash) { return true; } } return false; } if(!masternodeSync.IsMasternodeListSynced()) return false; CScript mnpayee; mnpayee = GetScriptForDestination(mnInfo.keyIDCollateralAddress); for(int64_t h = nCachedBlockHeight; h <= nCachedBlockHeight + 8; h++){ if(h == nNotBlockHeight) continue; std::vector voutMasternodePayments; if(GetBlockTxOuts(h, 0, voutMasternodePayments)) { for (const auto& txout : voutMasternodePayments) { if (txout.scriptPubKey == mnpayee) return true; } } } return false; } bool CMasternodePayments::AddOrUpdatePaymentVote(const CMasternodePaymentVote& vote) { uint256 blockHash = uint256(); if(!GetBlockHash(blockHash, vote.nBlockHeight - 101)) return false; uint256 nVoteHash = vote.GetHash(); if(HasVerifiedPaymentVote(nVoteHash)) return false; LOCK2(cs_mapMasternodeBlocks, cs_mapMasternodePaymentVotes); mapMasternodePaymentVotes[nVoteHash] = vote; auto it = mapMasternodeBlocks.emplace(vote.nBlockHeight, CMasternodeBlockPayees(vote.nBlockHeight)).first; it->second.AddPayee(vote); LogPrint("mnpayments", "CMasternodePayments::%s -- added, hash=%s\n", __func__, nVoteHash.ToString()); return true; } bool CMasternodePayments::HasVerifiedPaymentVote(const uint256& hashIn) const { LOCK(cs_mapMasternodePaymentVotes); const auto it = mapMasternodePaymentVotes.find(hashIn); return it != mapMasternodePaymentVotes.end() && it->second.IsVerified(); } void CMasternodeBlockPayees::AddPayee(const CMasternodePaymentVote& vote) { LOCK(cs_vecPayees); uint256 nVoteHash = vote.GetHash(); for (auto& payee : vecPayees) { if (payee.GetPayee() == vote.payee) { payee.AddVoteHash(nVoteHash); return; } } CMasternodePayee payeeNew(vote.payee, nVoteHash); vecPayees.push_back(payeeNew); } bool CMasternodeBlockPayees::GetBestPayee(CScript& payeeRet) const { LOCK(cs_vecPayees); if(vecPayees.empty()) { LogPrint("mnpayments", "CMasternodeBlockPayees::%s -- ERROR: couldn't find any payee\n", __func__); return false; } int nVotes = -1; for (const auto& payee : vecPayees) { if (payee.GetVoteCount() > nVotes) { payeeRet = payee.GetPayee(); nVotes = payee.GetVoteCount(); } } return (nVotes > -1); } bool CMasternodeBlockPayees::HasPayeeWithVotes(const CScript& payeeIn, int nVotesReq) const { LOCK(cs_vecPayees); for (const auto& payee : vecPayees) { if (payee.GetVoteCount() >= nVotesReq && payee.GetPayee() == payeeIn) { return true; } } LogPrint("mnpayments", "CMasternodeBlockPayees::%s -- ERROR: couldn't find any payee with %d+ votes\n", __func__, nVotesReq); return false; } bool CMasternodeBlockPayees::IsTransactionValid(const CTransaction& txNew) const { LOCK(cs_vecPayees); int nMaxSignatures = 0; std::string strPayeesPossible = ""; CAmount nMasternodePayment = GetMasternodePayment(nBlockHeight, txNew.GetValueOut()); //require at least MNPAYMENTS_SIGNATURES_REQUIRED signatures for (const auto& payee : vecPayees) { if (payee.GetVoteCount() >= nMaxSignatures) { nMaxSignatures = payee.GetVoteCount(); } } // if we don't have at least MNPAYMENTS_SIGNATURES_REQUIRED signatures on a payee, approve whichever is the longest chain if(nMaxSignatures < MNPAYMENTS_SIGNATURES_REQUIRED) return true; for (const auto& payee : vecPayees) { if (payee.GetVoteCount() >= MNPAYMENTS_SIGNATURES_REQUIRED) { for (const auto& txout : txNew.vout) { if (payee.GetPayee() == txout.scriptPubKey && nMasternodePayment == txout.nValue) { LogPrint("mnpayments", "CMasternodeBlockPayees::%s -- Found required payment\n", __func__); return true; } } CTxDestination address1; ExtractDestination(payee.GetPayee(), address1); CBitcoinAddress address2(address1); if(strPayeesPossible == "") { strPayeesPossible = address2.ToString(); } else { strPayeesPossible += "," + address2.ToString(); } } } LogPrintf("CMasternodeBlockPayees::%s -- ERROR: Missing required payment, possible payees: '%s', amount: %f DASH\n", __func__, strPayeesPossible, (float)nMasternodePayment/COIN); return false; } std::string CMasternodeBlockPayees::GetRequiredPaymentsString() const { LOCK(cs_vecPayees); std::string strRequiredPayments = ""; for (const auto& payee : vecPayees) { CTxDestination address1; ExtractDestination(payee.GetPayee(), address1); CBitcoinAddress address2(address1); if (!strRequiredPayments.empty()) strRequiredPayments += ", "; strRequiredPayments += strprintf("%s:%d", address2.ToString(), payee.GetVoteCount()); } if (strRequiredPayments.empty()) return "Unknown"; return strRequiredPayments; } std::string CMasternodePayments::GetRequiredPaymentsString(int nBlockHeight) const { LOCK(cs_mapMasternodeBlocks); const auto it = mapMasternodeBlocks.find(nBlockHeight); return it == mapMasternodeBlocks.end() ? "Unknown" : it->second.GetRequiredPaymentsString(); } bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) const { if (deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) { std::vector voutMasternodePayments; if (!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePayments)) { LogPrintf("CMasternodePayments::%s -- ERROR failed to get payees for block at height %s\n", __func__, nBlockHeight); return true; } for (const auto& txout : voutMasternodePayments) { bool found = false; for (const auto& txout2 : txNew.vout) { if (txout == txout2) { found = true; break; } } if (!found) { CTxDestination dest; if (!ExtractDestination(txout.scriptPubKey, dest)) assert(false); LogPrintf("CMasternodePayments::%s -- ERROR failed to find expected payee %s in block at height %s\n", __func__, CBitcoinAddress(dest).ToString(), nBlockHeight); return false; } } return true; } else { LOCK(cs_mapMasternodeBlocks); const auto it = mapMasternodeBlocks.find(nBlockHeight); return it == mapMasternodeBlocks.end() ? true : it->second.IsTransactionValid(txNew); } } void CMasternodePayments::CheckAndRemove() { if (deterministicMNManager->IsDeterministicMNsSporkActive()) { return; } if(!masternodeSync.IsBlockchainSynced()) return; LOCK2(cs_mapMasternodeBlocks, cs_mapMasternodePaymentVotes); int nLimit = GetStorageLimit(); std::map::iterator it = mapMasternodePaymentVotes.begin(); while(it != mapMasternodePaymentVotes.end()) { CMasternodePaymentVote vote = (*it).second; if(nCachedBlockHeight - vote.nBlockHeight > nLimit) { LogPrint("mnpayments", "CMasternodePayments::%s -- Removing old Masternode payment: nBlockHeight=%d\n", __func__, vote.nBlockHeight); mapMasternodePaymentVotes.erase(it++); mapMasternodeBlocks.erase(vote.nBlockHeight); } else { ++it; } } LogPrintf("CMasternodePayments::%s -- %s\n", __func__, ToString()); } bool CMasternodePaymentVote::IsValid(CNode* pnode, int nValidationHeight, std::string& strError, CConnman& connman) const { masternode_info_t mnInfo; if(!mnodeman.GetMasternodeInfo(masternodeOutpoint, mnInfo)) { strError = strprintf("Unknown masternode=%s", masternodeOutpoint.ToStringShort()); // Only ask if we are already synced and still have no idea about that Masternode if(masternodeSync.IsMasternodeListSynced()) { mnodeman.AskForMN(pnode, masternodeOutpoint, connman); } return false; } int nMinRequiredProtocol; if(nBlockHeight >= nValidationHeight) { // new votes must comply SPORK_10_MASTERNODE_PAY_UPDATED_NODES rules nMinRequiredProtocol = mnpayments.GetMinMasternodePaymentsProto(); } else { // allow non-updated masternodes for old blocks nMinRequiredProtocol = MIN_MASTERNODE_PAYMENT_PROTO_VERSION_1; } if(mnInfo.nProtocolVersion < nMinRequiredProtocol) { strError = strprintf("Masternode protocol is too old: nProtocolVersion=%d, nMinRequiredProtocol=%d", mnInfo.nProtocolVersion, nMinRequiredProtocol); return false; } // Only masternodes should try to check masternode rank for old votes - they need to pick the right winner for future blocks. // Regular clients (miners included) need to verify masternode rank for future block votes only. if(!fMasternodeMode && nBlockHeight < nValidationHeight) return true; int nRank; if(!mnodeman.GetMasternodeRank(masternodeOutpoint, nRank, nBlockHeight - 101, nMinRequiredProtocol)) { LogPrint("mnpayments", "CMasternodePaymentVote::%s -- Can't calculate rank for masternode %s\n", __func__, masternodeOutpoint.ToStringShort()); return false; } if(nRank > MNPAYMENTS_SIGNATURES_TOTAL) { // It's common to have masternodes mistakenly think they are in the top 10 // We don't want to print all of these messages in normal mode, debug mode should print though strError = strprintf("Masternode %s is not in the top %d (%d)", masternodeOutpoint.ToStringShort(), MNPAYMENTS_SIGNATURES_TOTAL, nRank); // Only ban for new mnw which is out of bounds, for old mnw MN list itself might be way too much off if(nRank > MNPAYMENTS_SIGNATURES_TOTAL*2 && nBlockHeight > nValidationHeight) { LOCK(cs_main); strError = strprintf("Masternode %s is not in the top %d (%d)", masternodeOutpoint.ToStringShort(), MNPAYMENTS_SIGNATURES_TOTAL*2, nRank); LogPrintf("CMasternodePaymentVote::%s -- Error: %s\n", __func__, strError); Misbehaving(pnode->GetId(), 20); } // Still invalid however return false; } return true; } bool CMasternodePayments::ProcessBlock(int nBlockHeight, CConnman& connman) { if (deterministicMNManager->IsDeterministicMNsSporkActive(nBlockHeight)) { return true; } // DETERMINE IF WE SHOULD BE VOTING FOR THE NEXT PAYEE if(fLiteMode || !fMasternodeMode) return false; // We have little chances to pick the right winner if winners list is out of sync // but we have no choice, so we'll try. However it doesn't make sense to even try to do so // if we have not enough data about masternodes. if(!masternodeSync.IsMasternodeListSynced()) return false; int nRank; if (!mnodeman.GetMasternodeRank(activeMasternodeInfo.outpoint, nRank, nBlockHeight - 101, GetMinMasternodePaymentsProto())) { LogPrint("mnpayments", "CMasternodePayments::%s -- Unknown Masternode\n", __func__); return false; } if (nRank > MNPAYMENTS_SIGNATURES_TOTAL) { LogPrint("mnpayments", "CMasternodePayments::%s -- Masternode not in the top %d (%d)\n", __func__, MNPAYMENTS_SIGNATURES_TOTAL, nRank); return false; } // LOCATE THE NEXT MASTERNODE WHICH SHOULD BE PAID LogPrintf("CMasternodePayments::%s -- Start: nBlockHeight=%d, masternode=%s\n", __func__, nBlockHeight, activeMasternodeInfo.outpoint.ToStringShort()); // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough int nCount = 0; masternode_info_t mnInfo; if (!mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount, mnInfo)) { LogPrintf("CMasternodePayments::%s -- ERROR: Failed to find masternode to pay\n", __func__); return false; } LogPrintf("CMasternodePayments::%s -- Masternode found by GetNextMasternodeInQueueForPayment(): %s\n", __func__, mnInfo.outpoint.ToStringShort()); CScript payee = GetScriptForDestination(mnInfo.keyIDCollateralAddress); CMasternodePaymentVote voteNew(activeMasternodeInfo.outpoint, nBlockHeight, payee); CTxDestination address1; ExtractDestination(payee, address1); CBitcoinAddress address2(address1); LogPrintf("CMasternodePayments::%s -- vote: payee=%s, nBlockHeight=%d\n", __func__, address2.ToString(), nBlockHeight); // SIGN MESSAGE TO NETWORK WITH OUR MASTERNODE KEYS LogPrintf("CMasternodePayments::%s -- Signing vote\n", __func__); if (voteNew.Sign()) { LogPrintf("CMasternodePayments::%s -- AddOrUpdatePaymentVote()\n", __func__); if (AddOrUpdatePaymentVote(voteNew)) { voteNew.Relay(connman); return true; } } return false; } void CMasternodePayments::CheckBlockVotes(int nBlockHeight) { if (!masternodeSync.IsWinnersListSynced()) return; CMasternodeMan::rank_pair_vec_t mns; if (!mnodeman.GetMasternodeRanks(mns, nBlockHeight - 101, GetMinMasternodePaymentsProto())) { LogPrintf("CMasternodePayments::%s -- nBlockHeight=%d, GetMasternodeRanks failed\n", __func__, nBlockHeight); return; } std::string debugStr; debugStr += strprintf("CMasternodePayments::%s -- nBlockHeight=%d,\n Expected voting MNs:\n", __func__, nBlockHeight); LOCK2(cs_mapMasternodeBlocks, cs_mapMasternodePaymentVotes); int i{0}; for (const auto& mn : mns) { CScript payee; bool found = false; const auto it = mapMasternodeBlocks.find(nBlockHeight); if (it != mapMasternodeBlocks.end()) { for (const auto& p : it->second.vecPayees) { for (const auto& voteHash : p.GetVoteHashes()) { const auto itVote = mapMasternodePaymentVotes.find(voteHash); if (itVote == mapMasternodePaymentVotes.end()) { debugStr += strprintf(" - could not find vote %s\n", voteHash.ToString()); continue; } if (itVote->second.masternodeOutpoint == mn.second.outpoint) { payee = itVote->second.payee; found = true; break; } } } } if (found) { CTxDestination address1; ExtractDestination(payee, address1); CBitcoinAddress address2(address1); debugStr += strprintf(" - %s - voted for %s\n", mn.second.outpoint.ToStringShort(), address2.ToString()); } else { mapMasternodesDidNotVote.emplace(mn.second.outpoint, 0).first->second++; debugStr += strprintf(" - %s - no vote received\n", mn.second.outpoint.ToStringShort()); } if (++i >= MNPAYMENTS_SIGNATURES_TOTAL) break; } if (mapMasternodesDidNotVote.empty()) { LogPrint("mnpayments", "%s", debugStr); return; } debugStr += " Masternodes which missed a vote in the past:\n"; for (const auto& item : mapMasternodesDidNotVote) { debugStr += strprintf(" - %s: %d\n", item.first.ToStringShort(), item.second); } LogPrint("mnpayments", "%s", debugStr); } void CMasternodePaymentVote::Relay(CConnman& connman) const { if (deterministicMNManager->IsDeterministicMNsSporkActive()) { return; } // Do not relay until fully synced if(!masternodeSync.IsSynced()) { LogPrint("mnpayments", "CMasternodePayments::%s -- won't relay until fully synced\n", __func__); return; } CInv inv(MSG_MASTERNODE_PAYMENT_VOTE, GetHash()); connman.RelayInv(inv); } bool CMasternodePaymentVote::CheckSignature(const CKeyID& keyIDOperator, int nValidationHeight, int &nDos) const { // do not ban by default nDos = 0; std::string strError = ""; if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { uint256 hash = GetSignatureHash(); if (!CHashSigner::VerifyHash(hash, keyIDOperator, vchSig, strError)) { // could be a signature in old format std::string strMessage = masternodeOutpoint.ToStringShort() + std::to_string(nBlockHeight) + ScriptToAsmStr(payee); if(!CMessageSigner::VerifyMessage(keyIDOperator, vchSig, strMessage, strError)) { // nope, not in old format either // Only ban for future block vote when we are already synced. // Otherwise it could be the case when MN which signed this vote is using another key now // and we have no idea about the old one. if(masternodeSync.IsMasternodeListSynced() && nBlockHeight > nValidationHeight) { nDos = 20; } return error("CMasternodePaymentVote::CheckSignature -- Got bad Masternode payment signature, masternode=%s, error: %s", masternodeOutpoint.ToStringShort(), strError); } } } else { std::string strMessage = masternodeOutpoint.ToStringShort() + std::to_string(nBlockHeight) + ScriptToAsmStr(payee); if (!CMessageSigner::VerifyMessage(keyIDOperator, vchSig, strMessage, strError)) { // Only ban for future block vote when we are already synced. // Otherwise it could be the case when MN which signed this vote is using another key now // and we have no idea about the old one. if(masternodeSync.IsMasternodeListSynced() && nBlockHeight > nValidationHeight) { nDos = 20; } return error("CMasternodePaymentVote::CheckSignature -- Got bad Masternode payment signature, masternode=%s, error: %s", masternodeOutpoint.ToStringShort(), strError); } } return true; } std::string CMasternodePaymentVote::ToString() const { std::ostringstream info; info << masternodeOutpoint.ToStringShort() << ", " << nBlockHeight << ", " << ScriptToAsmStr(payee) << ", " << (int)vchSig.size(); return info.str(); } // Send only votes for future blocks, node should request every other missing payment block individually void CMasternodePayments::Sync(CNode* pnode, CConnman& connman) const { LOCK(cs_mapMasternodeBlocks); if(!masternodeSync.IsWinnersListSynced()) return; int nInvCount = 0; for(int h = nCachedBlockHeight; h < nCachedBlockHeight + 20; h++) { const auto it = mapMasternodeBlocks.find(h); if(it != mapMasternodeBlocks.end()) { for (const auto& payee : it->second.vecPayees) { std::vector vecVoteHashes = payee.GetVoteHashes(); for (const auto& hash : vecVoteHashes) { if(!HasVerifiedPaymentVote(hash)) continue; pnode->PushInventory(CInv(MSG_MASTERNODE_PAYMENT_VOTE, hash)); nInvCount++; } } } } LogPrintf("CMasternodePayments::%s -- Sent %d votes to peer=%d\n", __func__, nInvCount, pnode->id); CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_MNW, nInvCount)); } // Request low data/unknown payment blocks in batches directly from some node instead of/after preliminary Sync. void CMasternodePayments::RequestLowDataPaymentBlocks(CNode* pnode, CConnman& connman) const { if(!masternodeSync.IsMasternodeListSynced()) return; CNetMsgMaker msgMaker(pnode->GetSendVersion()); LOCK2(cs_main, cs_mapMasternodeBlocks); std::vector vToFetch; int nLimit = GetStorageLimit(); const CBlockIndex *pindex = chainActive.Tip(); while(nCachedBlockHeight - pindex->nHeight < nLimit) { const auto it = mapMasternodeBlocks.find(pindex->nHeight); if(it == mapMasternodeBlocks.end()) { // We have no idea about this block height, let's ask vToFetch.push_back(CInv(MSG_MASTERNODE_PAYMENT_BLOCK, pindex->GetBlockHash())); // We should not violate GETDATA rules if(vToFetch.size() == MAX_INV_SZ) { LogPrintf("CMasternodePayments::%s -- asking peer=%d for %d blocks\n", __func__, pnode->id, MAX_INV_SZ); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, vToFetch)); // Start filling new batch vToFetch.clear(); } } if(!pindex->pprev) break; pindex = pindex->pprev; } for (auto& mnBlockPayees : mapMasternodeBlocks) { int nBlockHeight = mnBlockPayees.first; int nTotalVotes = 0; bool fFound = false; for (const auto& payee : mnBlockPayees.second.vecPayees) { if(payee.GetVoteCount() >= MNPAYMENTS_SIGNATURES_REQUIRED) { fFound = true; break; } nTotalVotes += payee.GetVoteCount(); } // A clear winner (MNPAYMENTS_SIGNATURES_REQUIRED+ votes) was found // or no clear winner was found but there are at least avg number of votes if(fFound || nTotalVotes >= (MNPAYMENTS_SIGNATURES_TOTAL + MNPAYMENTS_SIGNATURES_REQUIRED)/2) { // so just move to the next block continue; } // DEBUG DBG ( // Let's see why this failed for (const auto& payee : mnBlockPayees.second.vecPayees) { CTxDestination address1; ExtractDestination(payee.GetPayee(), address1); CBitcoinAddress address2(address1); printf("payee %s votes %d\n", address2.ToString().c_str(), payee.GetVoteCount()); } printf("block %d votes total %d\n", nBlockHeight, nTotalVotes); ) // END DEBUG // Low data block found, let's try to sync it uint256 hash; if(GetBlockHash(hash, nBlockHeight)) { vToFetch.push_back(CInv(MSG_MASTERNODE_PAYMENT_BLOCK, hash)); } // We should not violate GETDATA rules if(vToFetch.size() == MAX_INV_SZ) { LogPrintf("CMasternodePayments::%s -- asking peer=%d for %d payment blocks\n", __func__, pnode->id, MAX_INV_SZ); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, vToFetch)); // Start filling new batch vToFetch.clear(); } } // Ask for the rest of it if(!vToFetch.empty()) { LogPrintf("CMasternodePayments::%s -- asking peer=%d for %d payment blocks\n", __func__, pnode->id, vToFetch.size()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, vToFetch)); } } std::string CMasternodePayments::ToString() const { std::ostringstream info; info << "Votes: " << (int)mapMasternodePaymentVotes.size() << ", Blocks: " << (int)mapMasternodeBlocks.size(); return info.str(); } bool CMasternodePayments::IsEnoughData() const { float nAverageVotes = (MNPAYMENTS_SIGNATURES_TOTAL + MNPAYMENTS_SIGNATURES_REQUIRED) / 2; int nStorageLimit = GetStorageLimit(); return GetBlockCount() > nStorageLimit && GetVoteCount() > nStorageLimit * nAverageVotes; } int CMasternodePayments::GetStorageLimit() const { return std::max(int(mnodeman.size() * nStorageCoeff), nMinBlocksToStore); } void CMasternodePayments::UpdatedBlockTip(const CBlockIndex *pindex, CConnman& connman) { if(!pindex) return; if (deterministicMNManager->IsDeterministicMNsSporkActive(pindex->nHeight)) { return; } nCachedBlockHeight = pindex->nHeight; LogPrint("mnpayments", "CMasternodePayments::%s -- nCachedBlockHeight=%d\n", __func__, nCachedBlockHeight); int nFutureBlock = nCachedBlockHeight + 10; CheckBlockVotes(nFutureBlock - 1); ProcessBlock(nFutureBlock, connman); }