mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 13:32:47 +01:00
1278 lines
51 KiB
C++
1278 lines
51 KiB
C++
// 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 <string>
|
|
|
|
/** 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<CTxOut>& voutMasternodePaymentsRet, std::vector<CTxOut>& 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<int, std::string> GetRequiredPaymentsStrings(int nStartHeight, int nEndHeight)
|
|
{
|
|
std::map<int, std::string> 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<CTxOut>& 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<CTxOut>& 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<CTxOut> 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<CTxOut> 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<uint256, CMasternodePaymentVote>::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<uint256> 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<CInv> 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);
|
|
}
|