// Copyright (c) 2014-2015 The Dash developers // Copyright (c) 2015-2019 The PIVX developers // Copyright (c) 2022-2036 Agrarian Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "masternode-payments.h" #include "addrman.h" #include "chainparams.h" #include "masternode-budget.h" #include "masternode-sync.h" #include "masternodeman.h" #include "obfuscation.h" #include "spork.h" #include "sync.h" #include "util.h" #include "utilmoneystr.h" #include #include #include #include #include #include /** Object for who's going to get paid on which blocks */ CMasternodePayments masternodePayments; CCriticalSection cs_vecPayments; CCriticalSection cs_mapMasternodeBlocks; CCriticalSection cs_mapMasternodePayeeVotes; namespace { // Select the correct "newly created" transaction for payee validation based on block type. // - PoW blocks: coinbase is vtx[0] // - PoS blocks: coinstake is vtx[1] (with a null coinbase at vtx[0]) // // This must NOT be derived from height-era assumptions, because Agrarian runs PoW+PoS concurrently // from Params().FIRST_POS_BLOCK() through Params().LAST_POW_BLOCK(). const CTransaction& GetBlockNewTxForPayeeChecks(const CBlock& block) { // Defensive: preserve old behavior if vector is unexpectedly small. // A valid PoS block should have at least 2 tx; a valid PoW block at least 1 tx. if (block.IsProofOfStake() && block.vtx.size() > 1) { return block.vtx[1]; } return block.vtx[0]; } } // namespace // // CMasternodePaymentDB // CMasternodePaymentDB::CMasternodePaymentDB() { pathDB = GetDataDir() / "mnpayments.dat"; strMagicMessage = "MasternodePayments"; } bool CMasternodePaymentDB::Write(const CMasternodePayments& objToSave) { const int64_t nStart = GetTimeMillis(); // serialize, checksum data up to that point, then append checksum CDataStream ssObj(SER_DISK, CLIENT_VERSION); ssObj << strMagicMessage; // masternode cache file specific magic message ssObj << FLATDATA(Params().MessageStart()); // network specific magic number ssObj << objToSave; const uint256 hash = Hash(ssObj.begin(), ssObj.end()); ssObj << hash; // open output file, and associate with CAutoFile FILE* file = std::fopen(pathDB.string().c_str(), "wb"); if (file == nullptr) { return error("%s : Failed to open file %s", __func__, pathDB.string()); } CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); if (fileout.IsNull()) { return error("%s : Failed to open file %s", __func__, pathDB.string()); } // Write and commit header, data try { fileout << ssObj; } catch (const std::exception& e) { return error("%s : Serialize or I/O error - %s", __func__, e.what()); } fileout.fclose(); LogPrint("masternode", "Written info to mnpayments.dat %dms\n", GetTimeMillis() - nStart); return true; } CMasternodePaymentDB::ReadResult CMasternodePaymentDB::Read(CMasternodePayments& objToLoad, bool fDryRun) { const int64_t nStart = GetTimeMillis(); // open input file, and associate with CAutoFile FILE* file = std::fopen(pathDB.string().c_str(), "rb"); if (file == nullptr) { error("%s : Failed to open file %s", __func__, pathDB.string()); return FileError; } CAutoFile filein(file, SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { error("%s : Failed to open file %s", __func__, pathDB.string()); return FileError; } int fileSize = 0; try { fileSize = static_cast(boost::filesystem::file_size(pathDB)); } catch (const std::exception& e) { error("%s : Unable to read file size %s (%s)", __func__, pathDB.string(), e.what()); return FileError; } int dataSize = fileSize - static_cast(sizeof(uint256)); // Don't try to resize to a negative number if file is small if (dataSize < 0) { dataSize = 0; } std::vector vchData; vchData.resize(static_cast(dataSize)); uint256 hashIn; // read data and checksum from file try { if (dataSize > 0) { filein.read(reinterpret_cast(&vchData[0]), dataSize); } filein >> hashIn; } catch (const std::exception& e) { error("%s : Deserialize or I/O error - %s", __func__, e.what()); return HashReadError; } filein.fclose(); CDataStream ssObj(vchData, SER_DISK, CLIENT_VERSION); // verify stored checksum matches input data const uint256 hashTmp = Hash(ssObj.begin(), ssObj.end()); if (hashIn != hashTmp) { error("%s : Checksum mismatch, data corrupted", __func__); return IncorrectHash; } unsigned char pchMsgTmp[4]; std::string strMagicMessageTmp; try { // de-serialize file header (masternode cache file specific magic message) and .. ssObj >> strMagicMessageTmp; // ... verify the message matches predefined one if (strMagicMessage != strMagicMessageTmp) { error("%s : Invalid masternode payment cache magic message", __func__); return IncorrectMagicMessage; } // de-serialize file header (network specific magic number) and .. ssObj >> FLATDATA(pchMsgTmp); // ... verify the network matches ours if (std::memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) { error("%s : Invalid network magic number", __func__); return IncorrectMagicNumber; } // de-serialize data into CMasternodePayments object ssObj >> objToLoad; } catch (const std::exception& e) { objToLoad.Clear(); error("%s : Deserialize or I/O error - %s", __func__, e.what()); return IncorrectFormat; } LogPrint("masternode", "Loaded info from mnpayments.dat %dms\n", GetTimeMillis() - nStart); LogPrint("masternode", " %s\n", objToLoad.ToString()); if (!fDryRun) { LogPrint("masternode", "Masternode payments manager - cleaning....\n"); objToLoad.CleanPaymentList(); LogPrint("masternode", "Masternode payments manager - result:\n"); LogPrint("masternode", " %s\n", objToLoad.ToString()); } return Ok; } void DumpMasternodePayments() { const int64_t nStart = GetTimeMillis(); CMasternodePaymentDB paymentdb; CMasternodePayments tempPayments; LogPrint("masternode", "Verifying mnpayments.dat format...\n"); const CMasternodePaymentDB::ReadResult readResult = paymentdb.Read(tempPayments, true); // there was an error and it was not an error on file opening => do not proceed if (readResult == CMasternodePaymentDB::FileError) { LogPrint("masternode", "Missing payments file - mnpayments.dat, will try to recreate\n"); } else if (readResult != CMasternodePaymentDB::Ok) { LogPrint("masternode", "Error reading mnpayments.dat: "); if (readResult == CMasternodePaymentDB::IncorrectFormat) { LogPrint("masternode", "magic is ok but data has invalid format, will try to recreate\n"); } else { LogPrint("masternode", "file format is unknown or invalid, please fix it manually\n"); return; } } LogPrint("masternode", "Writing info to mnpayments.dat...\n"); paymentdb.Write(masternodePayments); LogPrint("masternode", "Payments dump finished %dms\n", GetTimeMillis() - nStart); } bool IsBlockValueValid(const CBlock& block, CAmount nExpectedValue, CAmount nMinted) { CBlockIndex* pindexPrev = chainActive.Tip(); if (pindexPrev == nullptr) { return true; } int nHeight = 0; if (pindexPrev->GetBlockHash() == block.hashPrevBlock) { nHeight = pindexPrev->nHeight + 1; } else { // out of order BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi != mapBlockIndex.end() && (*mi).second) { nHeight = (*mi).second->nHeight + 1; } } if (nHeight == 0) { LogPrint("masternode", "IsBlockValueValid() : WARNING: Couldn't find previous block\n"); } if (!masternodeSync.IsSynced()) { // there is no budget data to use to check anything // super blocks will always be on these blocks, max 100 per budgeting if (nHeight % Params().GetBudgetCycleBlocks() < 100) { return true; } return nMinted <= nExpectedValue; } // we're synced and have data so check the budget schedule // are these blocks even enabled if (!IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { return nMinted <= nExpectedValue; } if (budget.IsBudgetPaymentBlock(nHeight)) { // the value of the block is evaluated in CheckBlock return true; } return nMinted <= nExpectedValue; } bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) { TrxValidationStatus transactionStatus = TrxValidationStatus::InValid; if (!masternodeSync.IsSynced()) { // there is no budget data to use to check anything -- find the longest chain LogPrint("mnpayments", "Client not synced, skipping block payee checks\n"); return true; } const CTransaction& txNew = GetBlockNewTxForPayeeChecks(block); // check if it's a budget block if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { if (budget.IsBudgetPaymentBlock(nBlockHeight)) { transactionStatus = budget.IsTransactionValid(txNew, nBlockHeight); if (transactionStatus == TrxValidationStatus::Valid) { return true; } if (transactionStatus == TrxValidationStatus::InValid) { LogPrint("masternode", "Invalid budget payment detected %s\n", txNew.ToString().c_str()); if (IsSporkActive(SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT)) { return false; } LogPrint("masternode", "Budget enforcement is disabled, accepting block\n"); } } } // If we end here the transaction was either InValid and Budget enforcement is disabled, or // a double budget payment (DoublePayment) was detected, or no/not enough masternode // votes (VoteThreshold) for a finalized budget were found. // In all cases a masternode will get the payment for this block. // check for masternode payee if (masternodePayments.IsTransactionValid(txNew, nBlockHeight)) { return true; } LogPrint("masternode", "Invalid mn payment detected %s\n", txNew.ToString().c_str()); if (IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { return false; } LogPrint("masternode", "Masternode payment enforcement is disabled, accepting block\n"); return true; } void FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, bool fProofOfStake, bool fZAGRStake) { CBlockIndex* pindexPrev = chainActive.Tip(); if (!pindexPrev) { return; } const int nextHeight = pindexPrev->nHeight + 1; if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(nextHeight)) { budget.FillBlockPayee(txNew, nFees, fProofOfStake); } else { masternodePayments.FillBlockPayee(txNew, nFees, fProofOfStake, fZAGRStake); } } std::string GetRequiredPaymentsString(int nBlockHeight) { if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(nBlockHeight)) { return budget.GetRequiredPaymentsString(nBlockHeight); } return masternodePayments.GetRequiredPaymentsString(nBlockHeight); } void CMasternodePayments::FillBlockPayee(CMutableTransaction& txNew, int64_t /*nFees*/, bool fProofOfStake, bool fZAGRStake) { CBlockIndex* pindexPrev = chainActive.Tip(); if (!pindexPrev) { return; } bool hasPayment = true; CScript payee; // spork / schedule if (!masternodePayments.GetBlockPayee(pindexPrev->nHeight + 1, payee)) { // no masternode detected CMasternode* winningNode = mnodeman.GetCurrentMasterNode(1); if (winningNode) { payee = GetScriptForDestination(winningNode->pubKeyCollateralAddress.GetID()); } else { LogPrint("masternode", "CreateNewBlock: Failed to detect masternode to pay\n"); hasPayment = false; } } const int payHeight = pindexPrev->nHeight + 1; const CAmount blockValue = GetBlockValue(payHeight); const CAmount masternodePayment = GetMasternodePayment(payHeight, blockValue, 0, fZAGRStake); if (hasPayment) { if (fProofOfStake) { /** For Proof Of Stake vout[0] must be null. * Stake reward can be split into many different outputs, so we must * use vout.size() to align with several different cases. * An additional output is appended as the masternode payment. */ const unsigned int i = txNew.vout.size(); txNew.vout.resize(i + 1); txNew.vout[i].scriptPubKey = payee; txNew.vout[i].nValue = masternodePayment; // subtract mn payment from the stake reward // NOTE: original code used vout[1] and (i-1); keep behavior, but guard bounds. if (i > 0) { if (!txNew.vout[1].IsZerocoinMint() && (i - 1) < txNew.vout.size()) { txNew.vout[i - 1].nValue -= masternodePayment; } } } else { txNew.vout.resize(2); txNew.vout[1].scriptPubKey = payee; txNew.vout[1].nValue = masternodePayment; txNew.vout[0].nValue = blockValue - masternodePayment; } CTxDestination address1; ExtractDestination(payee, address1); CBitcoinAddress address2(address1); LogPrint("masternode", "Masternode payment of %s to %s\n", FormatMoney(masternodePayment).c_str(), address2.ToString().c_str()); } } int CMasternodePayments::GetMinMasternodePaymentsProto() { if (IsSporkActive(SPORK_10_MASTERNODE_PAY_UPDATED_NODES)) return ActiveProtocol(); // Allow only updated peers return MIN_PEER_PROTO_VERSION_BEFORE_ENFORCEMENT; // Also allow old peers as long as they are allowed to run } void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) { if (!masternodeSync.IsBlockchainSynced()) { return; } if (fLiteMode) { return; // disable all Obfuscation/Masternode related functionality } if (strCommand == "mnget") { // Masternode Payments Request Sync int nCountNeeded; vRecv >> nCountNeeded; if (Params().NetworkID() == CBaseChainParams::MAIN) { if (pfrom->HasFulfilledRequest("mnget")) { LogPrintf("CMasternodePayments::ProcessMessageMasternodePayments() : mnget - peer already asked me for the list\n"); Misbehaving(pfrom->GetId(), 20); return; } } pfrom->FulfilledRequest("mnget"); masternodePayments.Sync(pfrom, nCountNeeded); LogPrint("mnpayments", "mnget - Sent Masternode winners to peer %i\n", pfrom->GetId()); return; } if (strCommand == "mnw") { // Masternode Payments Declare Winner CMasternodePaymentWinner winner; vRecv >> winner; if (pfrom->nVersion < ActiveProtocol()) { return; } int nHeight; { TRY_LOCK(cs_main, locked); if (!locked || chainActive.Tip() == nullptr) { return; } nHeight = chainActive.Tip()->nHeight; } if (masternodePayments.mapMasternodePayeeVotes.count(winner.GetHash())) { LogPrint("mnpayments", "mnw - Already seen - %s bestHeight %d\n", winner.GetHash().ToString().c_str(), nHeight); masternodeSync.AddedMasternodeWinner(winner.GetHash()); return; } const int nFirstBlock = nHeight - static_cast(mnodeman.CountEnabled() * 1.25); if (winner.nBlockHeight < nFirstBlock || winner.nBlockHeight > nHeight + 20) { LogPrint("mnpayments", "mnw - winner out of range - FirstBlock %d Height %d bestHeight %d\n", nFirstBlock, winner.nBlockHeight, nHeight); return; } std::string strError; if (!winner.IsValid(pfrom, strError)) { return; } if (!masternodePayments.CanVote(winner.vinMasternode.prevout, winner.nBlockHeight)) { return; } if (!winner.SignatureValid()) { if (masternodeSync.IsSynced()) { LogPrintf("CMasternodePayments::ProcessMessageMasternodePayments() : mnw - invalid signature\n"); Misbehaving(pfrom->GetId(), 20); } // it could just be a non-synced masternode mnodeman.AskForMN(pfrom, winner.vinMasternode); return; } if (masternodePayments.AddWinningMasternode(winner)) { winner.Relay(); masternodeSync.AddedMasternodeWinner(winner.GetHash()); } return; } } bool CMasternodePaymentWinner::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode) { std::string errorMessage; const std::string strMessage = vinMasternode.prevout.ToStringShort() + std::to_string(nBlockHeight) + payee.ToString(); if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyMasternode)) { LogPrint("masternode", "CMasternodePing::Sign() - Error: %s\n", errorMessage.c_str()); return false; } if (!obfuScationSigner.VerifyMessage(pubKeyMasternode, vchSig, strMessage, errorMessage)) { LogPrint("masternode", "CMasternodePing::Sign() - Error: %s\n", errorMessage.c_str()); return false; } return true; } bool CMasternodePayments::GetBlockPayee(int nBlockHeight, CScript& payee) { if (mapMasternodeBlocks.count(nBlockHeight)) { return mapMasternodeBlocks[nBlockHeight].GetPayee(payee); } return false; } // Is this masternode scheduled to get paid soon? // -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 winners bool CMasternodePayments::IsScheduled(CMasternode& mn, int nNotBlockHeight) { LOCK(cs_mapMasternodeBlocks); int nHeight; { TRY_LOCK(cs_main, locked); if (!locked || chainActive.Tip() == nullptr) { return false; } nHeight = chainActive.Tip()->nHeight; } const CScript mnpayee = GetScriptForDestination(mn.pubKeyCollateralAddress.GetID()); CScript payee; for (int64_t h = nHeight; h <= nHeight + 8; h++) { if (h == nNotBlockHeight) { continue; } if (mapMasternodeBlocks.count(h)) { if (mapMasternodeBlocks[h].GetPayee(payee)) { if (mnpayee == payee) { return true; } } } } return false; } bool CMasternodePayments::AddWinningMasternode(CMasternodePaymentWinner& winnerIn) { uint256 blockHash = 0; if (!GetBlockHash(blockHash, winnerIn.nBlockHeight - 100)) { return false; } { LOCK2(cs_mapMasternodePayeeVotes, cs_mapMasternodeBlocks); if (mapMasternodePayeeVotes.count(winnerIn.GetHash())) { return false; } mapMasternodePayeeVotes[winnerIn.GetHash()] = winnerIn; if (!mapMasternodeBlocks.count(winnerIn.nBlockHeight)) { CMasternodeBlockPayees blockPayees(winnerIn.nBlockHeight); mapMasternodeBlocks[winnerIn.nBlockHeight] = blockPayees; } } mapMasternodeBlocks[winnerIn.nBlockHeight].AddPayee(winnerIn.payee, 1); return true; } bool CMasternodeBlockPayees::IsTransactionValid(const CTransaction& txNew) { LOCK(cs_vecPayments); int nMaxSignatures = 0; int nMasternode_Drift_Count = 0; std::string strPayeesPossible; const CAmount nReward = GetBlockValue(nBlockHeight); if (IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { // Get a stable number of masternodes by ignoring newly activated (< 8000 sec old) masternodes nMasternode_Drift_Count = mnodeman.stable_size() + Params().MasternodeCountDrift(); } else { // Account for the fact that all peers do not see the same masternode count. // An allowance of being off our masternode count is given. // We only need to look at an increased masternode count because as count increases, the reward decreases. // This code only checks for mnPayment >= required, so it only makes sense to check the max node count allowed. nMasternode_Drift_Count = mnodeman.size() + Params().MasternodeCountDrift(); } const CAmount requiredMasternodePayment = GetMasternodePayment(nBlockHeight, nReward, nMasternode_Drift_Count, txNew.HasZerocoinSpendInputs()); // require at least 6 signatures for (CMasternodePayee& payee : vecPayments) { if (payee.nVotes >= nMaxSignatures && payee.nVotes >= MNPAYMENTS_SIGNATURES_REQUIRED) { nMaxSignatures = payee.nVotes; } } // If we don't have at least 6 signatures on a payee, approve whichever is the longest chain if (nMaxSignatures < MNPAYMENTS_SIGNATURES_REQUIRED) { return true; } for (CMasternodePayee& payee : vecPayments) { bool found = false; for (const CTxOut& out : txNew.vout) { if (payee.scriptPubKey == out.scriptPubKey) { if (out.nValue >= requiredMasternodePayment) { found = true; } else { LogPrint("masternode", "Masternode payment is out of drift range. Paid=%s Min=%s\n", FormatMoney(out.nValue).c_str(), FormatMoney(requiredMasternodePayment).c_str()); } } } if (payee.nVotes >= MNPAYMENTS_SIGNATURES_REQUIRED) { if (found) { return true; } CTxDestination address1; ExtractDestination(payee.scriptPubKey, address1); CBitcoinAddress address2(address1); if (strPayeesPossible.empty()) { strPayeesPossible += address2.ToString(); } else { strPayeesPossible += "," + address2.ToString(); } } } LogPrint("masternode", "CMasternodePayments::IsTransactionValid - Missing required payment of %s to %s\n", FormatMoney(requiredMasternodePayment).c_str(), strPayeesPossible.c_str()); return false; } std::string CMasternodeBlockPayees::GetRequiredPaymentsString() { LOCK(cs_vecPayments); std::string ret = "Unknown"; for (CMasternodePayee& payee : vecPayments) { CTxDestination address1; ExtractDestination(payee.scriptPubKey, address1); CBitcoinAddress address2(address1); if (ret != "Unknown") { ret += ", " + address2.ToString() + ":" + std::to_string(payee.nVotes); } else { ret = address2.ToString() + ":" + std::to_string(payee.nVotes); } } return ret; } std::string CMasternodePayments::GetRequiredPaymentsString(int nBlockHeight) { LOCK(cs_mapMasternodeBlocks); if (mapMasternodeBlocks.count(nBlockHeight)) { return mapMasternodeBlocks[nBlockHeight].GetRequiredPaymentsString(); } return "Unknown"; } bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight) { LOCK(cs_mapMasternodeBlocks); if (mapMasternodeBlocks.count(nBlockHeight)) { return mapMasternodeBlocks[nBlockHeight].IsTransactionValid(txNew); } return true; } void CMasternodePayments::CleanPaymentList() { LOCK2(cs_mapMasternodePayeeVotes, cs_mapMasternodeBlocks); int nHeight; { TRY_LOCK(cs_main, locked); if (!locked || chainActive.Tip() == nullptr) { return; } nHeight = chainActive.Tip()->nHeight; } // keep up to five cycles for historical sake const int nLimit = std::max(static_cast(mnodeman.size() * 1.25), 1000); std::map::iterator it = mapMasternodePayeeVotes.begin(); while (it != mapMasternodePayeeVotes.end()) { const CMasternodePaymentWinner& winner = (*it).second; if (nHeight - winner.nBlockHeight > nLimit) { LogPrint("mnpayments", "CMasternodePayments::CleanPaymentList - Removing old Masternode payment - block %d\n", winner.nBlockHeight); masternodeSync.mapSeenSyncMNW.erase((*it).first); mapMasternodePayeeVotes.erase(it++); mapMasternodeBlocks.erase(winner.nBlockHeight); } else { ++it; } } } bool CMasternodePaymentWinner::IsValid(CNode* pnode, std::string& strError) { CMasternode* pmn = mnodeman.Find(vinMasternode); if (!pmn) { strError = strprintf("Unknown Masternode %s", vinMasternode.prevout.hash.ToString()); LogPrint("masternode", "CMasternodePaymentWinner::IsValid - %s\n", strError); mnodeman.AskForMN(pnode, vinMasternode); return false; } if (pmn->protocolVersion < ActiveProtocol()) { strError = strprintf("Masternode protocol too old %d - req %d", pmn->protocolVersion, ActiveProtocol()); LogPrint("masternode", "CMasternodePaymentWinner::IsValid - %s\n", strError); return false; } const int n = mnodeman.GetMasternodeRank(vinMasternode, nBlockHeight - 100, ActiveProtocol()); if (n > 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, or punish them unless they're way off. if (n > MNPAYMENTS_SIGNATURES_TOTAL * 2) { strError = strprintf("Masternode not in the top %d (%d)", MNPAYMENTS_SIGNATURES_TOTAL * 2, n); LogPrint("masternode", "CMasternodePaymentWinner::IsValid - %s\n", strError); // if (masternodeSync.IsSynced()) Misbehaving(pnode->GetId(), 20); } return false; } return true; } bool CMasternodePayments::ProcessBlock(int nBlockHeight) { if (!fMasterNode) { return false; } // reference node - hybrid mode const int n = mnodeman.GetMasternodeRank(activeMasternode.vin, nBlockHeight - 100, ActiveProtocol()); if (n == -1) { LogPrint("mnpayments", "CMasternodePayments::ProcessBlock - Unknown Masternode\n"); return false; } if (n > MNPAYMENTS_SIGNATURES_TOTAL) { LogPrint("mnpayments", "CMasternodePayments::ProcessBlock - Masternode not in the top %d (%d)\n", MNPAYMENTS_SIGNATURES_TOTAL, n); return false; } if (nBlockHeight <= nLastBlockHeight) { return false; } CMasternodePaymentWinner newWinner(activeMasternode.vin); if (budget.IsBudgetPaymentBlock(nBlockHeight)) { // budget payment block -- handled by the budgeting software } else { LogPrint("masternode", "CMasternodePayments::ProcessBlock() Start nHeight %d - vin %s. \n", nBlockHeight, activeMasternode.vin.prevout.hash.ToString()); // 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; CMasternode* pmn = mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount); if (pmn != nullptr) { LogPrint("masternode", "CMasternodePayments::ProcessBlock() Found by FindOldestNotInVec \n"); newWinner.nBlockHeight = nBlockHeight; const CScript payee = GetScriptForDestination(pmn->pubKeyCollateralAddress.GetID()); newWinner.AddPayee(payee); CTxDestination address1; ExtractDestination(payee, address1); CBitcoinAddress address2(address1); LogPrint("masternode", "CMasternodePayments::ProcessBlock() Winner payee %s nHeight %d. \n", address2.ToString().c_str(), newWinner.nBlockHeight); } else { LogPrint("masternode", "CMasternodePayments::ProcessBlock() Failed to find masternode to pay\n"); } } std::string errorMessage; CPubKey pubKeyMasternode; CKey keyMasternode; if (!obfuScationSigner.SetKey(strMasterNodePrivKey, errorMessage, keyMasternode, pubKeyMasternode)) { LogPrint("masternode", "CMasternodePayments::ProcessBlock() - Error upon calling SetKey: %s\n", errorMessage.c_str()); return false; } LogPrint("masternode", "CMasternodePayments::ProcessBlock() - Signing Winner\n"); if (newWinner.Sign(keyMasternode, pubKeyMasternode)) { LogPrint("masternode", "CMasternodePayments::ProcessBlock() - AddWinningMasternode\n"); if (AddWinningMasternode(newWinner)) { newWinner.Relay(); nLastBlockHeight = nBlockHeight; return true; } } return false; } void CMasternodePaymentWinner::Relay() { CInv inv(MSG_MASTERNODE_WINNER, GetHash()); RelayInv(inv); } bool CMasternodePaymentWinner::SignatureValid() { CMasternode* pmn = mnodeman.Find(vinMasternode); if (pmn != nullptr) { const std::string strMessage = vinMasternode.prevout.ToStringShort() + std::to_string(nBlockHeight) + payee.ToString(); std::string errorMessage; if (!obfuScationSigner.VerifyMessage(pmn->pubKeyMasternode, vchSig, strMessage, errorMessage)) { return error("CMasternodePaymentWinner::SignatureValid() - Got bad Masternode address signature %s\n", vinMasternode.prevout.hash.ToString()); } return true; } return false; } void CMasternodePayments::Sync(CNode* node, int nCountNeeded) { LOCK(cs_mapMasternodePayeeVotes); int nHeight; { TRY_LOCK(cs_main, locked); if (!locked || chainActive.Tip() == nullptr) { return; } nHeight = chainActive.Tip()->nHeight; } int nCount = static_cast(mnodeman.CountEnabled() * 1.25); if (nCountNeeded > nCount) { nCountNeeded = nCount; } int nInvCount = 0; std::map::iterator it = mapMasternodePayeeVotes.begin(); while (it != mapMasternodePayeeVotes.end()) { const CMasternodePaymentWinner& winner = (*it).second; if (winner.nBlockHeight >= nHeight - nCountNeeded && winner.nBlockHeight <= nHeight + 20) { node->PushInventory(CInv(MSG_MASTERNODE_WINNER, winner.GetHash())); nInvCount++; } ++it; } node->PushMessage("ssc", MASTERNODE_SYNC_MNW, nInvCount); } std::string CMasternodePayments::ToString() const { std::ostringstream info; info << "Votes: " << (int)mapMasternodePayeeVotes.size() << ", Blocks: " << (int)mapMasternodeBlocks.size(); return info.str(); } int CMasternodePayments::GetOldestBlock() { LOCK(cs_mapMasternodeBlocks); int nOldestBlock = std::numeric_limits::max(); std::map::iterator it = mapMasternodeBlocks.begin(); while (it != mapMasternodeBlocks.end()) { if ((*it).first < nOldestBlock) { nOldestBlock = (*it).first; } ++it; } return nOldestBlock; } int CMasternodePayments::GetNewestBlock() { LOCK(cs_mapMasternodeBlocks); int nNewestBlock = 0; std::map::iterator it = mapMasternodeBlocks.begin(); while (it != mapMasternodeBlocks.end()) { if ((*it).first > nNewestBlock) { nNewestBlock = (*it).first; } ++it; } return nNewestBlock; }