25545fc1e7
* Split keyIDMasternode into keyIDOwner/keyIDOperator/keyIDVoting keyIDOwner is the key used for things which should stay in control of the collateral owner, like proposal voting. keyIDOperator is the key used for operational things, like signing network messages, signing trigger/watchdog objects and trigger votes. keyIDVoting is the key used for proposal voting Legacy masternodes will always have the same key for all 3 to keep compatibility. Using different keys is only allowed after spork15 activation. * Forbid reusing collateral keys for operator/owner keys and vice versa * Bump SERIALIZATION_VERSION_STRING in CMasternodeMan
1141 lines
46 KiB
C++
1141 lines
46 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 <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() || 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());
|
|
return true;
|
|
} 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(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);
|
|
}
|
|
|
|
// TODO this is a placeholder until DIP3 is merged, which will allow superblock payments and MN reward payments
|
|
// in the same block. We set this to false for now, which means that we'll always get into the next if statement
|
|
// when a superblock payment is present
|
|
bool allowSuperblockAndMNReward = false;
|
|
|
|
// 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 GetRequiredPaymentsString(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);
|
|
}
|
|
|
|
void CMasternodePayments::Clear()
|
|
{
|
|
LOCK2(cs_mapMasternodeBlocks, cs_mapMasternodePaymentVotes);
|
|
mapMasternodeBlocks.clear();
|
|
mapMasternodePaymentVotes.clear();
|
|
}
|
|
|
|
bool CMasternodePayments::UpdateLastVote(const CMasternodePaymentVote& vote)
|
|
{
|
|
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)) {
|
|
// 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(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);
|
|
|
|
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(!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
|
|
{
|
|
LOCK(cs_mapMasternodeBlocks);
|
|
const auto it = mapMasternodeBlocks.find(nBlockHeight);
|
|
return it == mapMasternodeBlocks.end() ? true : it->second.IsTransactionValid(txNew);
|
|
}
|
|
|
|
void CMasternodePayments::CheckAndRemove()
|
|
{
|
|
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)
|
|
{
|
|
// 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
|
|
{
|
|
// 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;
|
|
|
|
nCachedBlockHeight = pindex->nHeight;
|
|
LogPrint("mnpayments", "CMasternodePayments::%s -- nCachedBlockHeight=%d\n", __func__, nCachedBlockHeight);
|
|
|
|
int nFutureBlock = nCachedBlockHeight + 10;
|
|
|
|
CheckBlockVotes(nFutureBlock - 1);
|
|
ProcessBlock(nFutureBlock, connman);
|
|
}
|