25cb14b615
* Fix confusion between dip3 activation and enforcement * drop DIP0003ActivationHeight for now
395 lines
17 KiB
C++
395 lines
17 KiB
C++
// Copyright (c) 2014-2019 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 "init.h"
|
|
#include "masternode-payments.h"
|
|
#include "masternode-sync.h"
|
|
#include "messagesigner.h"
|
|
#include "netfulfilledman.h"
|
|
#include "netmessagemaker.h"
|
|
#include "spork.h"
|
|
#include "util.h"
|
|
#include "validation.h"
|
|
|
|
#include "evo/deterministicmns.h"
|
|
|
|
#include <string>
|
|
|
|
CMasternodePayments mnpayments;
|
|
|
|
bool IsOldBudgetBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet) {
|
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
|
bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward);
|
|
|
|
if (nBlockHeight < consensusParams.nBudgetPaymentsStartBlock) {
|
|
strErrorRet = strprintf("Incorrect block %d, old budgets are not activated yet", nBlockHeight);
|
|
return false;
|
|
}
|
|
|
|
if (nBlockHeight >= consensusParams.nSuperblockStartBlock) {
|
|
strErrorRet = strprintf("Incorrect block %d, old budgets are no longer active", nBlockHeight);
|
|
return false;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
|
bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward);
|
|
|
|
strErrorRet = "";
|
|
|
|
if (nBlockHeight < consensusParams.nBudgetPaymentsStartBlock) {
|
|
// old budget system is not activated yet, just make sure we do not exceed the regular block reward
|
|
if(!isBlockRewardValueMet) {
|
|
strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, old budgets are not activated yet",
|
|
nBlockHeight, block.vtx[0]->GetValueOut(), blockReward);
|
|
}
|
|
return isBlockRewardValueMet;
|
|
} else if (nBlockHeight < consensusParams.nSuperblockStartBlock) {
|
|
// superblocks are not enabled yet, check if we can pass old budget rules
|
|
return IsOldBudgetBlockValueValid(block, nBlockHeight, blockReward, strErrorRet);
|
|
}
|
|
|
|
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(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());
|
|
// 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__);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString());
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (!mnpayments.GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) {
|
|
LogPrint("mnpayments", "%s -- no masternode to pay (MN list probably empty)\n", __func__);
|
|
}
|
|
|
|
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, 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 (h <= nChainTipHeight) {
|
|
auto payee = deterministicMNManager->GetListForBlock(chainActive[h - 1]->GetBlockHash()).GetMNPayee();
|
|
mapPayments.emplace(h, GetRequiredPaymentsString(h, payee));
|
|
} else {
|
|
doProjection = true;
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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)) {
|
|
LogPrintf("CMasternodePayments::%s -- no payee (deterministic masternode list empty)\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, std::vector<CTxOut>& voutMasternodePaymentsRet) const
|
|
{
|
|
voutMasternodePaymentsRet.clear();
|
|
|
|
CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward);
|
|
|
|
uint256 blockHash;
|
|
{
|
|
LOCK(cs_main);
|
|
blockHash = chainActive[nBlockHeight - 1]->GetBlockHash();
|
|
}
|
|
uint256 proTxHash;
|
|
auto dmnPayee = deterministicMNManager->GetListForBlock(blockHash).GetMNPayee();
|
|
if (!dmnPayee) {
|
|
return false;
|
|
}
|
|
|
|
CAmount operatorReward = 0;
|
|
if (dmnPayee->nOperatorReward != 0 && dmnPayee->pdmnState->scriptOperatorPayout != CScript()) {
|
|
// This calculation might eventually turn out to result in 0 even if an operator reward percentage is given.
|
|
// This will however only happen in a few years when the block rewards drops very low.
|
|
operatorReward = (masternodeReward * dmnPayee->nOperatorReward) / 10000;
|
|
masternodeReward -= operatorReward;
|
|
}
|
|
|
|
if (masternodeReward > 0) {
|
|
voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout);
|
|
}
|
|
if (operatorReward > 0) {
|
|
voutMasternodePaymentsRet.emplace_back(operatorReward, dmnPayee->pdmnState->scriptOperatorPayout);
|
|
}
|
|
|
|
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 CDeterministicMNCPtr& dmnIn, int nNotBlockHeight) const
|
|
{
|
|
auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8);
|
|
for (const auto &dmn : projectedPayees) {
|
|
if (dmn->proTxHash == dmnIn->proTxHash) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) const
|
|
{
|
|
if (!deterministicMNManager->IsDIP3Enforced(nBlockHeight)) {
|
|
// can't verify historical blocks here
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|