neobytes/src/masternode-payments.cpp
Alexander Block 25545fc1e7 Split keyIDMasternode into keyIDOwner/keyIDOperator/keyIDVoting (#2248)
* 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
2018-08-31 16:31:59 +03:00

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);
}