diff --git a/src/blockassembler.cpp b/src/blockassembler.cpp index 7e8da0663db5..e500ffccc1ce 100644 --- a/src/blockassembler.cpp +++ b/src/blockassembler.cpp @@ -66,9 +66,22 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam return nNewTime - nOldTime; } +static CMutableTransaction NewCoinbase(const int nHeight, const CScript* pScriptPubKey = nullptr) +{ + CMutableTransaction txCoinbase; + txCoinbase.vout.emplace_back(); + txCoinbase.vout[0].SetEmpty(); + if (pScriptPubKey) txCoinbase.vout[0].scriptPubKey = *pScriptPubKey; + txCoinbase.vin.emplace_back(); + txCoinbase.vin[0].scriptSig = CScript() << nHeight << OP_0; + return txCoinbase; +} + bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CWallet* pwallet, std::vector* availableCoins) { boost::this_thread::interruption_point(); + + assert(pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock); // Sync wallet before create coinstake @@ -76,23 +89,25 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CWallet* pwallet CMutableTransaction txCoinStake; int64_t nTxNewTime = 0; - if (!pwallet->CreateCoinStake(*pwallet, pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, availableCoins)) { + if (!pwallet->CreateCoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, availableCoins)) { LogPrint(BCLog::STAKING, "%s : stake not found\n", __func__); return false; } // Stake found - pblock->nTime = nTxNewTime; - CMutableTransaction emptyTx; - emptyTx.vout.emplace_back(); - emptyTx.vout[0].SetEmpty(); - emptyTx.vin.emplace_back(); - emptyTx.vin[0].scriptSig = CScript() << pindexPrev->nHeight + 1 << OP_0; - pblock->vtx.emplace_back( - std::make_shared(emptyTx)); - // stake - pblock->vtx.emplace_back( - std::make_shared(txCoinStake)); + // Create coinbase tx and add masternode/budget payments + CMutableTransaction txCoinbase = NewCoinbase(pindexPrev->nHeight + 1); + FillBlockPayee(txCoinbase, txCoinStake, pindexPrev, true); + + // Sign coinstake + if (!pwallet->SignCoinStake(txCoinStake)) { + const COutPoint& stakeIn = txCoinStake.vin[0].prevout; + return error("Unable to sign coinstake with input %s-%d", stakeIn.hash.ToString(), stakeIn.n); + } + + pblock->vtx.emplace_back(MakeTransactionRef(txCoinbase)); + pblock->vtx.emplace_back(MakeTransactionRef(txCoinStake)); + pblock->nTime = nTxNewTime; return true; } @@ -102,23 +117,18 @@ bool CreateCoinbaseTx(CBlock* pblock, const CScript& scriptPubKeyIn, CBlockIndex const int nHeight = pindexPrev->nHeight + 1; // Create coinbase tx - CMutableTransaction txNew; - txNew.vin.resize(1); - txNew.vin[0].prevout.SetNull(); - txNew.vout.resize(1); - txNew.vout[0].scriptPubKey = scriptPubKeyIn; + CMutableTransaction txCoinbase = NewCoinbase(nHeight, &scriptPubKeyIn); //Masternode and general budget payments - FillBlockPayee(txNew, nHeight, false); + CMutableTransaction txDummy; // POW blocks have no coinstake + FillBlockPayee(txCoinbase, txDummy, pindexPrev, false); - txNew.vin[0].scriptSig = CScript() << nHeight << OP_0; // If no payee was detected, then the whole block value goes to the first output. - if (txNew.vout.size() == 1) { - txNew.vout[0].nValue = GetBlockValue(nHeight); + if (txCoinbase.vout.size() == 1) { + txCoinbase.vout[0].nValue = GetBlockValue(nHeight); } - pblock->vtx.emplace_back( - std::make_shared(CTransaction(txNew))); + pblock->vtx.emplace_back(MakeTransactionRef(txCoinbase)); return true; } @@ -493,8 +503,10 @@ void IncrementExtraNonce(std::shared_ptr& pblock, const CBlockIndex* pin int32_t ComputeBlockVersion(const Consensus::Params& consensus, int nHeight) { - if (NetworkUpgradeActive(nHeight, consensus, Consensus::UPGRADE_V5_0)) { - return CBlockHeader::CURRENT_VERSION; // v9 + if (NetworkUpgradeActive(nHeight, consensus, Consensus::UPGRADE_V6_0)) { + return CBlockHeader::CURRENT_VERSION; // v10 + } else if (NetworkUpgradeActive(nHeight, consensus, Consensus::UPGRADE_V5_0)) { + return 9; } else if (consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V4_0)) { return 7; } else if (consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V3_4)) { diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 5056784e5400..383fc210a637 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -415,7 +415,7 @@ bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet); } -bool CBudgetManager::FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake) const +bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const { if (nHeight <= 0) return false; @@ -427,19 +427,29 @@ bool CBudgetManager::FillBlockPayee(CMutableTransaction& txNew, const int nHeigh CAmount blockValue = GetBlockValue(nHeight); + // Starting from PIVX v6.0 masternode and budgets are paid in the coinbase tx of PoS blocks (block v10) + const bool fPayCoinstake = fProofOfStake && + !Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0); + if (fProofOfStake) { - unsigned int i = txNew.vout.size(); - txNew.vout.resize(i + 1); - txNew.vout[i].scriptPubKey = payee; - txNew.vout[i].nValue = nAmount; + if (fPayCoinstake) { + unsigned int i = txCoinstake.vout.size(); + txCoinstake.vout.resize(i + 1); + txCoinstake.vout[i].scriptPubKey = payee; + txCoinstake.vout[i].nValue = nAmount; + } else { + txCoinbase.vout.resize(1); + txCoinbase.vout[0].scriptPubKey = payee; + txCoinbase.vout[0].nValue = nAmount; + } } else { //miners get the full amount on these blocks - txNew.vout[0].nValue = blockValue; - txNew.vout.resize(2); + txCoinbase.vout[0].nValue = blockValue; + txCoinbase.vout.resize(2); //these are super blocks, so their value can be much larger than normal - txNew.vout[1].scriptPubKey = payee; - txNew.vout[1].nValue = nAmount; + txCoinbase.vout[1].scriptPubKey = payee; + txCoinbase.vout[1].nValue = nAmount; } CTxDestination address; diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index 04e775502bd7..8245962250f3 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -125,7 +125,7 @@ class CBudgetManager bool UpdateFinalizedBudget(CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError); TrxValidationStatus IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const; std::string GetRequiredPaymentsString(int nBlockHeight); - bool FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake) const; + bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; // Only initialized masternodes: sign and submit votes on valid finalized budgets void VoteOnFinalizedBudgets(); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c31665b595a5..4fb77e9ff720 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -142,7 +142,6 @@ class CMainParams : public CChainParams consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; consensus.nFutureTimeDriftPoS = 180; - consensus.nMasternodeCountDrift = 20; // num of MN we allow the see-saw payments to be off by consensus.nMaxMoneyOut = 21000000 * COIN; consensus.nPoolMaxTransactions = 3; consensus.nProposalEstablishmentTime = 60 * 60 * 24; // must be at least a day old to make it into a budget @@ -280,7 +279,6 @@ class CTestNetParams : public CChainParams consensus.nCoinbaseMaturity = 15; consensus.nFutureTimeDriftPoW = 7200; consensus.nFutureTimeDriftPoS = 180; - consensus.nMasternodeCountDrift = 20; // num of MN we allow the see-saw payments to be off by consensus.nMaxMoneyOut = 21000000 * COIN; consensus.nPoolMaxTransactions = 3; consensus.nProposalEstablishmentTime = 60 * 5; // at least 5 min old to make it into a budget @@ -402,7 +400,6 @@ class CRegTestParams : public CChainParams consensus.nCoinbaseMaturity = 100; consensus.nFutureTimeDriftPoW = 7200; consensus.nFutureTimeDriftPoS = 180; - consensus.nMasternodeCountDrift = 4; // num of MN we allow the see-saw payments to be off by consensus.nMaxMoneyOut = 43199500 * COIN; consensus.nPoolMaxTransactions = 2; consensus.nProposalEstablishmentTime = 60 * 5; // at least 5 min old to make it into a budget diff --git a/src/consensus/params.h b/src/consensus/params.h index b57d7a687486..302ed30d3c05 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -96,7 +96,6 @@ struct Params { int nCoinbaseMaturity; int nFutureTimeDriftPoW; int nFutureTimeDriftPoS; - int nMasternodeCountDrift; CAmount nMaxMoneyOut; int nPoolMaxTransactions; int64_t nProposalEstablishmentTime; diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 05039e9097ae..768e370c0579 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -751,6 +751,10 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlock(const CBlockIndex* { LOCK(cs); + if (!IsDIP3Enforced(pindex->nHeight)) { + return {}; + } + CDeterministicMNList snapshot; std::list listDiffIndexes; diff --git a/src/kernel.cpp b/src/kernel.cpp index 26d9503ea609..d04d0d0ed795 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -87,19 +87,8 @@ bool CStakeKernel::CheckKernelHash(bool fSkipLog) const */ // helper function for CheckProofOfStake and GetStakeKernelHash -bool LoadStakeInput(const CBlock& block, const CBlockIndex* pindexPrev, std::unique_ptr& stake) +static bool LoadStakeInput(const CBlock& block, std::unique_ptr& stake) { - // If previous index is not provided, look for it in the blockmap - if (!pindexPrev) { - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi != mapBlockIndex.end() && (*mi).second) pindexPrev = (*mi).second; - else return error("%s : couldn't find previous block", __func__); - } else { - // check that is the actual parent block - if (block.hashPrevBlock != pindexPrev->GetBlockHash()) - return error("%s : previous block mismatch", __func__); - } - // Check that this is a PoS block if (!block.IsProofOfStake()) return error("called on non PoS block"); @@ -153,7 +142,7 @@ bool CheckProofOfStake(const CBlock& block, std::string& strError, const CBlockI const int nHeight = pindexPrev->nHeight + 1; // Initialize stake input std::unique_ptr stakeInput; - if (!LoadStakeInput(block, pindexPrev, stakeInput)) { + if (!LoadStakeInput(block, stakeInput)) { strError = "stake input initialization failed"; return false; } @@ -207,7 +196,7 @@ bool GetStakeKernelHash(uint256& hashRet, const CBlock& block, const CBlockIndex { // Initialize stake input std::unique_ptr stakeInput; - if (!LoadStakeInput(block, pindexPrev, stakeInput)) + if (!LoadStakeInput(block, stakeInput)) return error("%s : stake input initialization failed", __func__); CStakeKernel stakeKernel(pindexPrev, stakeInput.get(), block.nBits, block.nTime); diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 61780cf7abe7..b06d8fb741b7 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -6,6 +6,7 @@ #include "masternode-payments.h" #include "addrman.h" #include "chainparams.h" +#include "evo/deterministicmns.h" #include "fs.h" #include "budget/budgetmanager.h" #include "masternode-sync.h" @@ -178,7 +179,7 @@ bool CMasternodePaymentWinner::IsValid(CNode* pnode, std::string& strError) return false; } - int n = mnodeman.GetMasternodeRank(vinMasternode, nBlockHeight - 100, ActiveProtocol()); + int n = mnodeman.GetMasternodeRank(vinMasternode, nBlockHeight - 100); if (n > MNPAYMENTS_SIGNATURES_TOTAL) { //It's common to have masternodes mistakenly think they are in the top 10 @@ -211,7 +212,7 @@ void DumpMasternodePayments() LogPrint(BCLog::MASTERNODE,"Budget dump finished %dms\n", GetTimeMillis() - nStart); } -bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted) +bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) { if (!masternodeSync.IsSynced()) { //there is no budget data to use to check anything @@ -227,9 +228,8 @@ bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted) // if the superblock spork is enabled if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { // add current payee amount to the expected block value - CAmount expectedPayAmount; - if (g_budgetman.GetExpectedPayeeAmount(nHeight, expectedPayAmount)) { - nExpectedValue += expectedPayAmount; + if (g_budgetman.GetExpectedPayeeAmount(nHeight, nBudgetAmt)) { + nExpectedValue += nBudgetAmt; } } } @@ -237,8 +237,9 @@ bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted) return nMinted <= nExpectedValue; } -bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) +bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) { + int nBlockHeight = pindexPrev->nHeight + 1; TrxValidationStatus transactionStatus = TrxValidationStatus::InValid; if (!masternodeSync.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain @@ -246,8 +247,9 @@ bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) return true; } - const bool isPoSActive = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS); - const CTransaction& txNew = *(isPoSActive ? block.vtx[1] : block.vtx[0]); + const bool fPayCoinstake = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS) && + !Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0); + const CTransaction& txNew = *(fPayCoinstake ? block.vtx[1] : block.vtx[0]); //check if it's a budget block if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { @@ -273,7 +275,7 @@ bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) // In all cases a masternode will get the payment for this block //check for masternode payee - if (masternodePayments.IsTransactionValid(txNew, nBlockHeight)) + if (masternodePayments.IsTransactionValid(txNew, pindexPrev)) return true; LogPrint(BCLog::MASTERNODE,"Invalid mn payment detected %s\n", txNew.ToString().c_str()); @@ -284,14 +286,13 @@ bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) } -void FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake) +void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) { - if (nHeight == 0) return; - if (!sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) || // if superblocks are not enabled - !g_budgetman.FillBlockPayee(txNew, nHeight, fProofOfStake) ) { // or this is not a superblock, + // ... or this is not a superblock + !g_budgetman.FillBlockPayee(txCoinbase, txCoinstake, pindexPrev->nHeight + 1, fProofOfStake) ) { // ... or there's no budget with enough votes, then pay a masternode - masternodePayments.FillBlockPayee(txNew, nHeight, fProofOfStake); + masternodePayments.FillBlockPayee(txCoinbase, txCoinstake, pindexPrev, fProofOfStake); } } @@ -304,66 +305,92 @@ std::string GetRequiredPaymentsString(int nBlockHeight) } } -void CMasternodePayments::FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake) +bool CMasternodePayments::GetMasternodeTxOuts(const CBlockIndex* pindexPrev, std::vector& voutMasternodePaymentsRet) const { - if (nHeight == 0) return; + if (deterministicMNManager->LegacyMNObsolete(pindexPrev->nHeight + 1)) { + // New payment logic (!TODO) + return false; + } - bool hasPayment = true; - CScript payee; + // Legacy payment logic. !TODO: remove when transition to DMN is complete + return GetLegacyMasternodeTxOut(pindexPrev->nHeight + 1, voutMasternodePaymentsRet); +} - //spork - if (!masternodePayments.GetBlockPayee(nHeight, payee)) { +bool CMasternodePayments::GetLegacyMasternodeTxOut(int nHeight, std::vector& voutMasternodePaymentsRet) const +{ + if (nHeight == 0) return false; + voutMasternodePaymentsRet.clear(); + + CScript payee; + if (!GetBlockPayee(nHeight, payee)) { //no masternode detected - const CMasternode* winningNode = mnodeman.GetCurrentMasterNode(1); + MasternodeRef winningNode = mnodeman.GetCurrentMasterNode(nHeight, UINT256_ZERO); if (winningNode) { payee = GetScriptForDestination(winningNode->pubKeyCollateralAddress.GetID()); } else { LogPrint(BCLog::MASTERNODE,"CreateNewBlock: Failed to detect masternode to pay\n"); - hasPayment = false; + return false; } } + voutMasternodePaymentsRet.emplace_back(GetMasternodePayment(), payee); + return true; +} - if (hasPayment) { - CAmount masternodePayment = GetMasternodePayment(); - if (fProofOfStake) { - /**For Proof Of Stake vout[0] must be null - * Stake reward can be split into many different outputs, so we must - * use vout.size() to align with several different cases. - * An additional output is appended as the masternode payment - */ - unsigned int i = txNew.vout.size(); - txNew.vout.resize(i + 1); - txNew.vout[i].scriptPubKey = payee; - txNew.vout[i].nValue = masternodePayment; - - //subtract mn payment from the stake reward - if (!txNew.vout[1].IsZerocoinMint()) { - if (i == 2) { - // Majority of cases; do it quick and move on - txNew.vout[i - 1].nValue -= masternodePayment; - } else if (i > 2) { - // special case, stake is split between (i-1) outputs - unsigned int outputs = i-1; - CAmount mnPaymentSplit = masternodePayment / outputs; - CAmount mnPaymentRemainder = masternodePayment - (mnPaymentSplit * outputs); - for (unsigned int j=1; j<=outputs; j++) { - txNew.vout[j].nValue -= mnPaymentSplit; - } - // in case it's not an even division, take the last bit of dust from the last one - txNew.vout[outputs].nValue -= mnPaymentRemainder; - } - } - } else { - txNew.vout.resize(2); - txNew.vout[1].scriptPubKey = payee; - txNew.vout[1].nValue = masternodePayment; - txNew.vout[0].nValue = GetBlockValue(nHeight) - masternodePayment; +static void SubtractMnPaymentFromCoinstake(CMutableTransaction& txCoinstake, CAmount masternodePayment, int stakerOuts) +{ + assert (stakerOuts >= 2); + //subtract mn payment from the stake reward + if (stakerOuts == 2) { + // Majority of cases; do it quick and move on + txCoinstake.vout[1].nValue -= masternodePayment; + } else { + // special case, stake is split between (stakerOuts-1) outputs + unsigned int outputs = stakerOuts-1; + CAmount mnPaymentSplit = masternodePayment / outputs; + CAmount mnPaymentRemainder = masternodePayment - (mnPaymentSplit * outputs); + for (unsigned int j=1; j<=outputs; j++) { + txCoinstake.vout[j].nValue -= mnPaymentSplit; } + // in case it's not an even division, take the last bit of dust from the last one + txCoinstake.vout[outputs].nValue -= mnPaymentRemainder; + } +} - CTxDestination address1; - ExtractDestination(payee, address1); +void CMasternodePayments::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) const +{ + std::vector vecMnOuts; + if (!GetMasternodeTxOuts(pindexPrev, vecMnOuts)) { + return; + } + + // Starting from PIVX v6.0 masternode and budgets are paid in the coinbase tx (block v10) + const int nHeight = pindexPrev->nHeight + 1; + bool fPayCoinstake = fProofOfStake && !Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0); + + // if PoS block pays the coinbase, clear it first + if (fProofOfStake && !fPayCoinstake) txCoinbase.vout.clear(); - LogPrint(BCLog::MASTERNODE,"Masternode payment of %s to %s\n", FormatMoney(masternodePayment).c_str(), EncodeDestination(address1).c_str()); + const int initial_cstake_outs = txCoinstake.vout.size(); + + CAmount masternodePayment{0}; + for (const CTxOut& mnOut: vecMnOuts) { + // Add the mn payment to the coinstake/coinbase tx + if (fPayCoinstake) { + txCoinstake.vout.emplace_back(mnOut); + } else { + txCoinbase.vout.emplace_back(mnOut); + } + masternodePayment += mnOut.nValue; + CTxDestination payeeDest; + ExtractDestination(mnOut.scriptPubKey, payeeDest); + LogPrint(BCLog::MASTERNODE,"Masternode payment of %s to %s\n", FormatMoney(mnOut.nValue), EncodeDestination(payeeDest)); + } + + // Subtract mn payment value from the block reward + if (fProofOfStake) { + SubtractMnPaymentFromCoinstake(txCoinstake, masternodePayment, initial_cstake_outs); + } else { + txCoinbase.vout[0].nValue = GetBlockValue(nHeight) - masternodePayment; } } @@ -454,10 +481,11 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st } } -bool CMasternodePayments::GetBlockPayee(int nBlockHeight, CScript& payee) +bool CMasternodePayments::GetBlockPayee(int nBlockHeight, CScript& payee) const { - if (mapMasternodeBlocks.count(nBlockHeight)) { - return mapMasternodeBlocks[nBlockHeight].GetPayee(payee); + const auto it = mapMasternodeBlocks.find(nBlockHeight); + if (it != mapMasternodeBlocks.end()) { + return it->second.GetPayee(payee); } return false; @@ -493,7 +521,7 @@ bool CMasternodePayments::AddWinningMasternode(CMasternodePaymentWinner& winnerI { // check winner height if (winnerIn.nBlockHeight - 100 > mnodeman.GetBestHeight() + 1) { - return false; + return error("%s: mnw - invalid height %d > %d", __func__, winnerIn.nBlockHeight - 100, mnodeman.GetBestHeight() + 1); } { @@ -590,8 +618,15 @@ std::string CMasternodePayments::GetRequiredPaymentsString(int nBlockHeight) return "Unknown"; } -bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight) +bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, const CBlockIndex* pindexPrev) { + const int nBlockHeight = pindexPrev->nHeight + 1; + if (deterministicMNManager->LegacyMNObsolete(nBlockHeight)) { + // !TODO + return false; + } + + // Legacy payment logic. !TODO: remove when transition to DMN is complete LOCK(cs_mapMasternodeBlocks); if (mapMasternodeBlocks.count(nBlockHeight)) { @@ -631,7 +666,7 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) return error("%s: Active Masternode not initialized.", __func__); //reference node - hybrid mode - int n = mnodeman.GetMasternodeRank(*(activeMasternode.vin), nBlockHeight - 100, ActiveProtocol()); + int n = mnodeman.GetMasternodeRank(*(activeMasternode.vin), nBlockHeight - 100); if (n == -1) { LogPrint(BCLog::MASTERNODE, "CMasternodePayments::ProcessBlock - Unknown Masternode\n"); @@ -645,49 +680,36 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) if (nBlockHeight <= nLastBlockHeight) return false; - CMasternodePaymentWinner newWinner(*(activeMasternode.vin)); - if (g_budgetman.IsBudgetPaymentBlock(nBlockHeight)) { //is budget payment block -- handled by the budgeting software - } else { - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() Start nHeight %d - vin %s. \n", nBlockHeight, activeMasternode.vin->prevout.hash.ToString()); - - // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough - int nCount = 0; - const CMasternode* pmn = mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount); - - if (pmn != nullptr) { - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() Found by FindOldestNotInVec \n"); - - newWinner.nBlockHeight = nBlockHeight; - - CScript payee = GetScriptForDestination(pmn->pubKeyCollateralAddress.GetID()); - newWinner.AddPayee(payee); + return false; + } - CTxDestination address1; - ExtractDestination(payee, address1); + CMasternodePaymentWinner newWinner(*(activeMasternode.vin), nBlockHeight); + // 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; + MasternodeRef pmn = mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount); - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() Winner payee %s nHeight %d. \n", EncodeDestination(address1).c_str(), newWinner.nBlockHeight); - } else { - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() Failed to find masternode to pay\n"); - } + if (pmn == nullptr) { + LogPrint(BCLog::MASTERNODE,"%s: Failed to find masternode to pay\n", __func__); + return false; } + const CScript& payee = GetScriptForDestination(pmn->pubKeyCollateralAddress.GetID()); + newWinner.AddPayee(payee); + CPubKey pubKeyMasternode; CKey keyMasternode; activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); - - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() - Signing Winner\n"); - if (newWinner.Sign(keyMasternode, pubKeyMasternode.GetID())) { - LogPrint(BCLog::MASTERNODE,"CMasternodePayments::ProcessBlock() - AddWinningMasternode\n"); - - if (AddWinningMasternode(newWinner)) { - newWinner.Relay(); - nLastBlockHeight = nBlockHeight; - return true; - } + if (!newWinner.Sign(keyMasternode, pubKeyMasternode.GetID())) { + LogPrintf("%s: Failed to sign masternode winner\n", __func__); + return false; } - - return false; + if (!AddWinningMasternode(newWinner)) { + return false; + } + newWinner.Relay(); + nLastBlockHeight = nBlockHeight; + return true; } void CMasternodePayments::Sync(CNode* node, int nCountNeeded) @@ -719,3 +741,35 @@ std::string CMasternodePayments::ToString() const return info.str(); } + +bool IsCoinbaseValueValid(const CTransactionRef& tx, CAmount nBudgetAmt, CValidationState& _state) +{ + assert(tx->IsCoinBase()); + if (masternodeSync.IsSynced()) { + const CAmount nCBaseOutAmt = tx->GetValueOut(); + if (nBudgetAmt > 0) { + // Superblock + if (nCBaseOutAmt != nBudgetAmt) { + const std::string strError = strprintf("%s: invalid coinbase payment for budget (%s vs expected=%s)", + __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nBudgetAmt)); + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-superblock-cb-amt"); + } + return true; + } else { + // regular block + CAmount nMnAmt = GetMasternodePayment(); + // if enforcement is disabled, there could be no masternode payment + bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); + const std::string strError = strprintf("%s: invalid coinbase payment for masternode (%s vs expected=%s)", + __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nMnAmt)); + if (sporkEnforced && nCBaseOutAmt != nMnAmt) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt"); + } + if (!sporkEnforced && nCBaseOutAmt > nMnAmt) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt-spork8-disabled"); + } + return true; + } + } + return true; +} diff --git a/src/masternode-payments.h b/src/masternode-payments.h index 3e042dadb2db..a231841a4d33 100644 --- a/src/masternode-payments.h +++ b/src/masternode-payments.h @@ -17,6 +17,7 @@ extern RecursiveMutex cs_mapMasternodePayeeVotes; class CMasternodePayments; class CMasternodePaymentWinner; class CMasternodeBlockPayees; +class CValidationState; extern CMasternodePayments masternodePayments; @@ -24,10 +25,16 @@ extern CMasternodePayments masternodePayments; #define MNPAYMENTS_SIGNATURES_TOTAL 10 void ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); -bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight); +bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev); std::string GetRequiredPaymentsString(int nBlockHeight); -bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted); -void FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake); +bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt); +void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake); + +/** + * Check coinbase output value for blocks v10+. + * It must pay the masternode for regular blocks and a proposal during superblocks. + */ +bool IsCoinbaseValueValid(const CTransactionRef& tx, CAmount nBudgetAmt, CValidationState& _state); void DumpMasternodePayments(); @@ -116,12 +123,12 @@ class CMasternodeBlockPayees vecPayments.push_back(c); } - bool GetPayee(CScript& payee) + bool GetPayee(CScript& payee) const { LOCK(cs_vecPayments); int nVotes = -1; - for (CMasternodePayee& p : vecPayments) { + for (const CMasternodePayee& p : vecPayments) { if (p.nVotes > nVotes) { payee = p.scriptPubKey; nVotes = p.nVotes; @@ -170,10 +177,10 @@ class CMasternodePaymentWinner : public CSignedMessage payee() {} - CMasternodePaymentWinner(CTxIn vinIn) : + CMasternodePaymentWinner(CTxIn vinIn, int nHeight) : CSignedMessage(), vinMasternode(vinIn), - nBlockHeight(0), + nBlockHeight(nHeight), payee() {} @@ -253,8 +260,14 @@ class CMasternodePayments void Sync(CNode* node, int nCountNeeded); void CleanPaymentList(int mnCount, int nHeight); - bool GetBlockPayee(int nBlockHeight, CScript& payee); - bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight); + // get the masternode payment outs for block built on top of pindexPrev + bool GetMasternodeTxOuts(const CBlockIndex* pindexPrev, std::vector& voutMasternodePaymentsRet) const; + + // can be removed after transition to DMN + bool GetLegacyMasternodeTxOut(int nHeight, std::vector& voutMasternodePaymentsRet) const; + bool GetBlockPayee(int nBlockHeight, CScript& payee) const; + + bool IsTransactionValid(const CTransaction& txNew, const CBlockIndex* pindexPrev); bool IsScheduled(const CMasternode& mn, int nNotBlockHeight); bool CanVote(const COutPoint& outMasternode, int nBlockHeight) @@ -274,7 +287,7 @@ class CMasternodePayments void ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); std::string GetRequiredPaymentsString(int nBlockHeight); - void FillBlockPayee(CMutableTransaction& txNew, const int nHeight, bool fProofOfStake); + void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) const; std::string ToString() const; ADD_SERIALIZE_METHODS; diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 3a45f25fb85a..48e1b82ca6b1 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -26,25 +26,10 @@ CMasternodeMan mnodeman; /** Keep track of the active Masternode */ CActiveMasternode activeMasternode; -struct CompareLastPaid { - bool operator()(const std::pair& t1, - const std::pair& t2) const - { - return t1.first < t2.first; - } -}; - -struct CompareScoreTxIn { - bool operator()(const std::pair& t1, - const std::pair& t2) const - { - return t1.first < t2.first; - } -}; - struct CompareScoreMN { - bool operator()(const std::pair& t1, - const std::pair& t2) const + template + bool operator()(const std::pair& t1, + const std::pair& t2) const { return t1.first < t2.first; } @@ -338,8 +323,6 @@ int CMasternodeMan::stable_size() const { int nStable_size = 0; int nMinProtocol = ActiveProtocol(); - int64_t nMasternode_Min_Age = MN_WINNER_MINIMUM_AGE; - int64_t nMasternode_Age = 0; for (const auto& it : mapMasternodes) { const MasternodeRef& mn = it.second; @@ -347,8 +330,7 @@ int CMasternodeMan::stable_size() const continue; // Skip obsolete versions } if (sporkManager.IsSporkActive (SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { - nMasternode_Age = GetAdjustedTime() - mn->sigTime; - if ((nMasternode_Age) < nMasternode_Min_Age) { + if (GetAdjustedTime() - mn->sigTime < MN_WINNER_MINIMUM_AGE) { continue; // Skip masternodes younger than (default) 8000 sec (MUST be > MASTERNODE_REMOVAL_SECONDS) } } @@ -462,41 +444,50 @@ void CMasternodeMan::CheckSpentCollaterals(const std::vector& v } } +static bool canScheduleMN(bool fFilterSigTime, const MasternodeRef& mn, int minProtocol, + int nMnCount, int nBlockHeight) +{ + // check protocol version + if (mn->protocolVersion < minProtocol) return false; + + // it's in the list (up to 8 entries ahead of current block to allow propagation) -- so let's skip it + if (masternodePayments.IsScheduled(*mn, nBlockHeight)) return false; + + // it's too new, wait for a cycle + if (fFilterSigTime && mn->sigTime + (nMnCount * 2.6 * 60) > GetAdjustedTime()) return false; + + // make sure it has as many confirmations as there are masternodes + if (pcoinsTip->GetCoinDepthAtHeight(mn->vin.prevout, nBlockHeight) < nMnCount) return false; + + return true; +} + // // Deterministically select the oldest/best masternode to pay on the network // -const CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip) const +MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip) const { AssertLockNotHeld(cs_main); const CBlockIndex* BlockReading = (pChainTip == nullptr ? GetChainTip() : pChainTip); if (!BlockReading) return nullptr; - LOCK(cs); - const CMasternode* pBestMasternode = nullptr; - std::vector > vecMasternodeLastPaid; + MasternodeRef pBestMasternode = nullptr; + std::vector > vecMasternodeLastPaid; /* Make a vector with all of the last paid times */ - - int nMnCount = CountEnabled(); - for (const auto& it : mapMasternodes) { - const MasternodeRef& mn = it.second; - if (!mn->IsEnabled()) continue; - - // //check protocol version - if (mn->protocolVersion < ActiveProtocol()) continue; - - //it's in the list (up to 8 entries ahead of current block to allow propagation) -- so let's skip it - if (masternodePayments.IsScheduled(*mn, nBlockHeight)) continue; - - //it's too new, wait for a cycle - if (fFilterSigTime && mn->sigTime + (nMnCount * 2.6 * 60) > GetAdjustedTime()) continue; - - //make sure it has as many confirmations as there are masternodes - if (pcoinsTip->GetCoinDepthAtHeight(mn->vin.prevout, nBlockHeight) < nMnCount) continue; - - vecMasternodeLastPaid.emplace_back(SecondsSincePayment(mn, BlockReading), mn->vin); + int minProtocol = ActiveProtocol(); + int nMnCount{0}; + { + LOCK(cs); + nMnCount = CountEnabled(); + for (const auto& it : mapMasternodes) { + if (!it.second->IsEnabled()) continue; + if (canScheduleMN(fFilterSigTime, it.second, minProtocol, nMnCount, nBlockHeight)) { + vecMasternodeLastPaid.emplace_back(SecondsSincePayment(it.second, BlockReading), it.second); + } + } } nCount = (int)vecMasternodeLastPaid.size(); @@ -505,18 +496,18 @@ const CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlock if (fFilterSigTime && nCount < nMnCount / 3) return GetNextMasternodeInQueueForPayment(nBlockHeight, false, nCount, BlockReading); // Sort them high to low - sort(vecMasternodeLastPaid.rbegin(), vecMasternodeLastPaid.rend(), CompareLastPaid()); + sort(vecMasternodeLastPaid.rbegin(), vecMasternodeLastPaid.rend(), CompareScoreMN()); // Look at 1/10 of the oldest nodes (by last payment), calculate their scores and pay the best one // -- This doesn't look at who is being paid in the +8-10 blocks, allowing for double payments very rarely // -- 1/100 payments should be a double payment on mainnet - (1/(3000/10))*2 // -- (chance per block * chances before IsScheduled will fire) - int nTenthNetwork = CountEnabled() / 10; + int nTenthNetwork = nMnCount / 10; int nCountTenth = 0; uint256 nHigh; const uint256& hash = GetHashAtHeight(nBlockHeight - 101); - for (std::pair & s : vecMasternodeLastPaid) { - const CMasternode* pmn = Find(s.second.prevout); + for (const auto& s: vecMasternodeLastPaid) { + const MasternodeRef pmn = s.second; if (!pmn) break; const uint256& n = pmn->CalculateScore(hash); @@ -530,25 +521,22 @@ const CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlock return pBestMasternode; } -const CMasternode* CMasternodeMan::GetCurrentMasterNode(int mod, int64_t nBlockHeight, int minProtocol) const +MasternodeRef CMasternodeMan::GetCurrentMasterNode(int nHeight, const uint256& hash) const { + int minProtocol = ActiveProtocol(); int64_t score = 0; - const CMasternode* winner = nullptr; - const uint256& hash = GetHashAtHeight(nBlockHeight - 1); + MasternodeRef winner = nullptr; // scan for winner for (const auto& it : mapMasternodes) { const MasternodeRef& mn = it.second; if (mn->protocolVersion < minProtocol || !mn->IsEnabled()) continue; - - // calculate the score for each Masternode - uint256 n = mn->CalculateScore(hash); - int64_t n2 = n.GetCompact(false); - + // calculate the score of the masternode + const int64_t n = mn->CalculateScore(hash).GetCompact(false); // determine the winner - if (n2 > score) { - score = n2; - winner = mn.get(); + if (n > score) { + score = n; + winner = mn; } } @@ -563,57 +551,43 @@ std::vector> CMasternodeMan::GetMnScores(int nLast for (int nHeight = nChainHeight - nLast; nHeight < nChainHeight + 20; nHeight++) { const uint256& hash = GetHashAtHeight(nHeight - 101); - uint256 nHigh = UINT256_ZERO; - MasternodeRef pBestMasternode; - for (const auto& it : mapMasternodes) { - const uint256& n = it.second->CalculateScore(hash); - if (n > nHigh) { - nHigh = n; - pBestMasternode = it.second; - } - } - if (nHigh > UINT256_ZERO) { - ret.emplace_back(pBestMasternode, nHeight); + MasternodeRef winner = GetCurrentMasterNode(nHeight, hash); + if (winner) { + ret.emplace_back(winner, nHeight); } } return ret; } -int CMasternodeMan::GetMasternodeRank(const CTxIn& vin, int64_t nBlockHeight, int minProtocol, bool fOnlyActive) const +int CMasternodeMan::GetMasternodeRank(const CTxIn& vin, int64_t nBlockHeight) const { - std::vector > vecMasternodeScores; - int64_t nMasternode_Min_Age = MN_WINNER_MINIMUM_AGE; - int64_t nMasternode_Age = 0; - const uint256& hash = GetHashAtHeight(nBlockHeight - 1); // height outside range if (!hash) return -1; // scan for winner - for (const auto& it : mapMasternodes) { - const MasternodeRef& mn = it.second; - if (mn->protocolVersion < minProtocol) { - LogPrint(BCLog::MASTERNODE,"Skipping Masternode with obsolete version %d\n", mn->protocolVersion); - continue; // Skip obsolete versions - } - - if (sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) { - nMasternode_Age = GetAdjustedTime() - mn->sigTime; - if ((nMasternode_Age) < nMasternode_Min_Age) { - LogPrint(BCLog::MASTERNODE,"Skipping just activated Masternode. Age: %ld\n", nMasternode_Age); - continue; // Skip masternodes younger than (default) 1 hour + int minProtocol = ActiveProtocol(); + std::vector > vecMasternodeScores; + { + LOCK(cs); + for (const auto& it : mapMasternodes) { + const MasternodeRef& mn = it.second; + if (!mn->IsEnabled()) { + continue; // Skip not enabled } + if (mn->protocolVersion < minProtocol) { + LogPrint(BCLog::MASTERNODE,"Skipping Masternode with obsolete version %d\n", mn->protocolVersion); + continue; // Skip obsolete versions + } + if (sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) && + GetAdjustedTime() - mn->sigTime < MN_WINNER_MINIMUM_AGE) { + continue; // Skip masternodes younger than (default) 1 hour + } + vecMasternodeScores.emplace_back(mn->CalculateScore(hash).GetCompact(false), mn->vin); } - if (fOnlyActive) { - if (!mn->IsEnabled()) continue; - } - uint256 n = mn->CalculateScore(hash); - int64_t n2 = n.GetCompact(false); - - vecMasternodeScores.emplace_back(n2, mn->vin); } - sort(vecMasternodeScores.rbegin(), vecMasternodeScores.rend(), CompareScoreTxIn()); + sort(vecMasternodeScores.rbegin(), vecMasternodeScores.rend(), CompareScoreMN()); int rank = 0; for (std::pair & s : vecMasternodeScores) { @@ -636,14 +610,12 @@ std::vector> CMasternodeMan::GetMasternodeRank LOCK(cs); // scan for winner for (const auto& it : mapMasternodes) { - const MasternodeRef& mn = it.second; + const MasternodeRef mn = it.second; if (!mn->IsEnabled()) { vecMasternodeScores.emplace_back(9999, mn); - continue; + } else { + vecMasternodeScores.emplace_back(mn->CalculateScore(hash).GetCompact(false), mn); } - - int64_t n2 = mn->CalculateScore(hash).GetCompact(false); - vecMasternodeScores.emplace_back(n2, mn); } } sort(vecMasternodeScores.rbegin(), vecMasternodeScores.rend(), CompareScoreMN()); diff --git a/src/masternodeman.h b/src/masternodeman.h index 8ba11158e962..e675ad0f6684 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -144,17 +144,17 @@ class CMasternodeMan void CheckSpentCollaterals(const std::vector& vtx); /// Find an entry in the masternode list that is next to be paid - const CMasternode* GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip = nullptr) const; + MasternodeRef GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip = nullptr) const; /// Get the current winner for this block - const CMasternode* GetCurrentMasterNode(int mod = 1, int64_t nBlockHeight = 0, int minProtocol = 0) const; + MasternodeRef GetCurrentMasterNode(int nHeight, const uint256& hash) const; /// vector of pairs std::vector> GetMnScores(int nLast) const; // Retrieve the known masternodes ordered by scoring without checking them. (Only used for listmasternodes RPC call) std::vector> GetMasternodeRanks(int nBlockHeight) const; - int GetMasternodeRank(const CTxIn& vin, int64_t nBlockHeight, int minProtocol = 0, bool fOnlyActive = true) const; + int GetMasternodeRank(const CTxIn& vin, int64_t nBlockHeight) const; void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); diff --git a/src/primitives/block.h b/src/primitives/block.h index 8e28bc6b7278..786a6ab7eeba 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -23,7 +23,7 @@ class CBlockHeader { public: // header - static const int32_t CURRENT_VERSION=9; + static const int32_t CURRENT_VERSION=10; //!> Version 10 Masternode/Budget payments in the coinbase int32_t nVersion; uint256 hashPrevBlock; uint256 hashMerkleRoot; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index d467be23f462..de315faf77fc 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -389,7 +389,7 @@ class CTransaction bool IsCoinBase() const { - return (vin.size() == 1 && vin[0].prevout.IsNull() && !ContainsZerocoins()); + return (vin.size() == 1 && vin[0].prevout.IsNull() && !vin[0].scriptSig.IsZerocoinSpend()); } bool IsCoinStake() const; diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 9878597d2603..ad611ee98545 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -243,7 +243,7 @@ UniValue masternodecurrent (const JSONRPCRequest& request) if (!pChainTip) return "unknown"; int nCount = 0; - const CMasternode* winner = mnodeman.GetNextMasternodeInQueueForPayment(pChainTip->nHeight + 1, true, nCount, pChainTip); + MasternodeRef winner = mnodeman.GetNextMasternodeInQueueForPayment(pChainTip->nHeight + 1, true, nCount, pChainTip); if (winner) { UniValue obj(UniValue::VOBJ); obj.pushKV("protocol", (int64_t)winner->protocolVersion); diff --git a/src/spork.cpp b/src/spork.cpp index 0ae712b900ae..62939f1964e3 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -211,15 +211,20 @@ bool CSporkManager::UpdateSpork(SporkId nSporkID, int64_t nValue) if(spork.Sign(strMasterPrivKey)){ spork.Relay(); - LOCK(cs); - mapSporks[spork.GetHash()] = spork; - mapSporksActive[nSporkID] = spork; + AddSporkMessage(spork); return true; } return false; } +void CSporkManager::AddSporkMessage(const CSporkMessage& spork) +{ + LOCK(cs); + mapSporks[spork.GetHash()] = spork; + mapSporksActive[spork.nSporkID] = spork; +} + // grab the spork value, and see if it's off bool CSporkManager::IsSporkActive(SporkId nSporkID) { diff --git a/src/spork.h b/src/spork.h index a69c06b32d92..97b6ecbbc336 100644 --- a/src/spork.h +++ b/src/spork.h @@ -109,7 +109,10 @@ class CSporkManager void ProcessSpork(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); int64_t GetSporkValue(SporkId nSporkID); void ExecuteSpork(SporkId nSporkID, int nValue); + // Create/Sign/Relay the spork message, and update the maps bool UpdateSpork(SporkId nSporkID, int64_t nValue); + // Add spork message to mapSporks and mapSporksActive + void AddSporkMessage(const CSporkMessage& spork); bool IsSporkActive(SporkId nSporkID); std::string GetSporkNameByID(SporkId id); diff --git a/src/stakeinput.cpp b/src/stakeinput.cpp index a89e4920bf87..5ec724eb9579 100644 --- a/src/stakeinput.cpp +++ b/src/stakeinput.cpp @@ -46,10 +46,9 @@ bool CPivStake::GetTxOutFrom(CTxOut& out) const return true; } -bool CPivStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) +CTxIn CPivStake::GetTxIn() const { - txIn = CTxIn(outpointFrom.hash, outpointFrom.n); - return true; + return CTxIn(outpointFrom.hash, outpointFrom.n); } CAmount CPivStake::GetValue() const @@ -57,7 +56,7 @@ CAmount CPivStake::GetValue() const return outputFrom.nValue; } -bool CPivStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) +bool CPivStake::CreateTxOuts(const CWallet* pwallet, std::vector& vout, CAmount nTotal) const { std::vector vSolutions; txnouttype whichType; diff --git a/src/stakeinput.h b/src/stakeinput.h index 66b2c110bf34..b8ab2ffae34c 100644 --- a/src/stakeinput.h +++ b/src/stakeinput.h @@ -23,10 +23,8 @@ class CStakeInput virtual ~CStakeInput(){}; virtual bool InitFromTxIn(const CTxIn& txin) = 0; virtual const CBlockIndex* GetIndexFrom() const = 0; - virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = UINT256_ZERO) = 0; virtual bool GetTxOutFrom(CTxOut& out) const = 0; virtual CAmount GetValue() const = 0; - virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) = 0; virtual bool IsZPIV() const = 0; virtual CDataStream GetUniqueness() const = 0; virtual bool ContextCheck(int nHeight, uint32_t nTime) = 0; @@ -50,8 +48,8 @@ class CPivStake : public CStakeInput bool GetTxOutFrom(CTxOut& out) const override; CAmount GetValue() const override; CDataStream GetUniqueness() const override; - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = UINT256_ZERO) override; - bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) override; + CTxIn GetTxIn() const; + bool CreateTxOuts(const CWallet* pwallet, std::vector& vout, CAmount nTotal) const; bool IsZPIV() const override { return false; } bool ContextCheck(int nHeight, uint32_t nTime) override; }; diff --git a/src/test/budget_tests.cpp b/src/test/budget_tests.cpp index 0cf309efdc40..2ab42dcf9a51 100644 --- a/src/test/budget_tests.cpp +++ b/src/test/budget_tests.cpp @@ -5,6 +5,9 @@ #include "test_pivx.h" #include "budget/budgetmanager.h" +#include "masternode-payments.h" +#include "masternode-sync.h" +#include "spork.h" #include "tinyformat.h" #include "utilmoneystr.h" #include "validation.h" @@ -98,4 +101,131 @@ BOOST_AUTO_TEST_CASE(block_value) BOOST_CHECK(!t_budgetman.IsBlockValueValid(nHeight, nExpected, nExpected+propAmt, false)); } +static CScript GetRandomP2PKH() +{ + CKey key; + key.MakeNewKey(false); + return GetScriptForDestination(key.GetPubKey().GetID()); +} + +static CMutableTransaction NewCoinBase(int nHeight, CAmount cbaseAmt, const CScript& cbaseScript) +{ + CMutableTransaction tx; + tx.vout.emplace_back(cbaseAmt, cbaseScript); + tx.vin.emplace_back(); + tx.vin[0].scriptSig = CScript() << nHeight << OP_0; + return tx; +} + +BOOST_AUTO_TEST_CASE(IsCoinbaseValueValid_test) +{ + const CAmount mnAmt = GetMasternodePayment(); + const CScript& cbaseScript = GetRandomP2PKH(); + CValidationState state; + + // force mnsync complete + masternodeSync.RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED; + + // -- Regular blocks + + // Exact + CMutableTransaction cbase = NewCoinBase(1, mnAmt, cbaseScript); + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + cbase.vout[0].nValue /= 2; + cbase.vout.emplace_back(cbase.vout[0]); + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + + // Underpaying with SPORK_8 disabled (good) + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt - 1, cbaseScript); + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + cbase.vout[0].nValue = mnAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = mnAmt/2 - 1; + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + + // Overpaying with SPORK_8 disabled + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt + 1, cbaseScript); + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt-spork8-disabled"); + state = CValidationState(); + cbase.vout[0].nValue = mnAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = mnAmt/2 + 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt-spork8-disabled"); + state = CValidationState(); + + // enable SPORK_8 + int64_t nTime = GetTime() - 10; + const CSporkMessage& spork = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime); + sporkManager.AddSporkMessage(spork); + BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)); + + // Underpaying with SPORK_8 enabled + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt - 1, cbaseScript); + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt"); + state = CValidationState(); + cbase.vout[0].nValue = mnAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = mnAmt/2 - 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt"); + state = CValidationState(); + + // Overpaying with SPORK_8 enabled + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt + 1, cbaseScript); + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt"); + state = CValidationState(); + cbase.vout[0].nValue = mnAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = mnAmt/2 + 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt"); + state = CValidationState(); + + const CAmount budgAmt = 200 * COIN; + + // -- Superblocks + + // Exact + cbase.vout.clear(); + cbase.vout.emplace_back(budgAmt, cbaseScript); + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + cbase.vout[0].nValue /= 2; + cbase.vout.emplace_back(cbase.vout[0]); + BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + + // Underpaying + cbase.vout.clear(); + cbase.vout.emplace_back(budgAmt - 1, cbaseScript); + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout[0].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = budgAmt/2 - 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + + // Overpaying + cbase.vout.clear(); + cbase.vout.emplace_back(budgAmt + 1, cbaseScript); + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout[0].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue = budgAmt/2 + 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index 863fb3bbd481..d25fc03168a6 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1740,10 +1740,17 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd nExpectedMint += nFees; //Check that the block does not overmint - if (!IsBlockValueValid(pindex->nHeight, nExpectedMint, nMint)) { - return state.DoS(100, error("ConnectBlock() : reward pays too much (actual=%s vs limit=%s)", - FormatMoney(nMint), FormatMoney(nExpectedMint)), - REJECT_INVALID, "bad-cb-amount"); + CAmount nBudgetAmt = 0; // If this is a superblock, amount to be paid to the winning proposal, otherwise 0 + if (!IsBlockValueValid(pindex->nHeight, nExpectedMint, nMint, nBudgetAmt)) { + return state.DoS(100, error("%s: reward pays too much (actual=%s vs limit=%s)", + __func__, FormatMoney(nMint), FormatMoney(nExpectedMint)), + REJECT_INVALID, "bad-blk-amount"); + } + + // For blocks v10+: Check that the coinbase pays the exact amount + if (isPoSActive && pindex->nVersion >= 10 && !IsCoinbaseValueValid(block.vtx[0], nBudgetAmt, state)) { + // pass the state returned by the function above + return false; } if (!control.Wait()) @@ -2814,8 +2821,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); if (IsPoS) { - // Coinbase output should be empty if proof-of-stake block - if (block.vtx[0]->vout.size() != 1 || !block.vtx[0]->vout[0].IsEmpty()) + // Coinbase output should be empty if proof-of-stake block (before block v10) + if (block.nVersion < 10 && (block.vtx[0]->vout.size() != 1 || !block.vtx[0]->vout[0].IsEmpty())) return state.DoS(100, false, REJECT_INVALID, "bad-cb-pos", false, "coinbase output not empty for proof-of-stake block"); // Second transaction must be coinstake, the rest must not be @@ -2835,14 +2842,16 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo // masternode payments / budgets CBlockIndex* pindexPrev = chainActive.Tip(); int nHeight = 0; - if (pindexPrev != NULL) { - if (pindexPrev->GetBlockHash() == block.hashPrevBlock) { - nHeight = pindexPrev->nHeight + 1; - } else { //out of order - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi != mapBlockIndex.end() && (*mi).second) - nHeight = (*mi).second->nHeight + 1; + if (pindexPrev != nullptr && block.hashPrevBlock != UINT256_ZERO) { + if (pindexPrev->GetBlockHash() != block.hashPrevBlock) { + //out of order + auto mi = mapBlockIndex.find(block.hashPrevBlock); + if (mi == mapBlockIndex.end()) { + return false; + } + pindexPrev = mi->second; } + nHeight = pindexPrev->nHeight + 1; // PIVX // It is entierly possible that we don't have enough data and this could fail @@ -2861,7 +2870,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo fColdStakingActive = !sporkManager.IsSporkActive(SPORK_19_COLDSTAKING_MAINTENANCE); // check masternode/budget payment - if (!IsBlockPayeeValid(block, nHeight)) { + // !TODO: after transition to DMN is complete, check this also during IBD + if (!IsBlockPayeeValid(block, pindexPrev)) { mapRejectedBlocks.emplace(block.GetHash(), GetTime()); return state.DoS(0, false, REJECT_INVALID, "bad-cb-payee", false, "Couldn't find masternode/budget payment"); } @@ -3009,7 +3019,8 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta (block.nVersion < 5 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_BIP65)) || (block.nVersion < 6 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V3_4)) || (block.nVersion < 7 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V4_0)) || - (block.nVersion < 8 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_0))) + (block.nVersion < 8 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_0)) || + (block.nVersion < 10 && consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0))) { std::string stringErr = strprintf("rejected block version %d at height %d", block.nVersion, nHeight); return state.Invalid(false, REJECT_OBSOLETE, "bad-version", stringErr); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a76a52013d67..535d7fd60749 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2335,25 +2335,29 @@ UniValue ListReceived(const UniValue& params, bool by_label, int nBlockHeight) for (std::map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || !IsFinalTx(wtx.tx, nBlockHeight)) + if (!IsFinalTx(wtx.tx, nBlockHeight)) { continue; + } int nDepth = wtx.GetDepthInMainChain(); - if (nDepth < nMinDepth) + if (nDepth < nMinDepth) { continue; + } for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; - if (!ExtractDestination(txout.scriptPubKey, address)) + if (!ExtractDestination(txout.scriptPubKey, address)) { continue; + } if (has_filtered_address && !(filtered_address == address)) { continue; } isminefilter mine = IsMine(*pwalletMain, address); - if (!(mine & filter)) + if (!(mine & filter)) { continue; + } tallyitem& item = mapTally[address]; item.nAmount += txout.nValue; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8222975c8144..c9da05607615 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3208,16 +3208,12 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CTr } bool CWallet::CreateCoinStake( - const CKeyStore& keystore, const CBlockIndex* pindexPrev, unsigned int nBits, CMutableTransaction& txNew, int64_t& nTxNewTime, - std::vector* availableCoins) + std::vector* availableCoins) const { - - const Consensus::Params& consensus = Params().GetConsensus(); - // Mark coin stake transaction txNew.vin.clear(); txNew.vout.clear(); @@ -3296,32 +3292,23 @@ bool CWallet::CreateCoinStake( // put the remaining on the last output (which all into the first if only one output) txNew.vout[outputs].nValue += nRemaining; + // Set coinstake input + txNew.vin.emplace_back(stakeInput.GetTxIn()); + // Limit size unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= DEFAULT_BLOCK_MAX_SIZE / 5) return error("%s : exceeded coinstake size limit", __func__); - // Masternode payment - FillBlockPayee(txNew, pindexPrev->nHeight + 1, true); - - const uint256& hashTxOut = txNew.GetHash(); - CTxIn in; - if (!stakeInput.CreateTxIn(this, in, hashTxOut)) { - LogPrintf("%s : failed to create TxIn\n", __func__); - txNew.vin.clear(); - txNew.vout.clear(); - it++; - continue; - } - txNew.vin.emplace_back(in); - break; } LogPrint(BCLog::STAKING, "%s: attempted staking %d times\n", __func__, nAttempts); - if (!fKernelFound) - return false; + return fKernelFound; +} +bool CWallet::SignCoinStake(CMutableTransaction& txNew) const +{ // Sign it int nIn = 0; for (const CTxIn& txIn : txNew.vin) { @@ -3330,7 +3317,7 @@ bool CWallet::CreateCoinStake( return error("%s : failed to sign coinstake", __func__); } - // Successfully generated coinstake + // Successfully signed coinstake return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7c1c809d20b6..8cfb57b62e83 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1032,12 +1032,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface }; CWallet::CommitResult CommitTransaction(CTransactionRef tx, CReserveKey& opReservekey, CConnman* connman); CWallet::CommitResult CommitTransaction(CTransactionRef tx, CReserveKey* reservekey, CConnman* connman); - bool CreateCoinStake(const CKeyStore& keystore, - const CBlockIndex* pindexPrev, + bool CreateCoinStake(const CBlockIndex* pindexPrev, unsigned int nBits, CMutableTransaction& txNew, int64_t& nTxNewTime, - std::vector* availableCoins); + std::vector* availableCoins) const; + bool SignCoinStake(CMutableTransaction& txNew) const; void AutoCombineDust(CConnman* connman); // Shielded balances diff --git a/src/zpiv/zpos.h b/src/zpiv/zpos.h index b8dcfd7baed0..10442ce376e7 100644 --- a/src/zpiv/zpos.h +++ b/src/zpiv/zpos.h @@ -25,8 +25,6 @@ class CLegacyZPivStake : public CStakeInput const CBlockIndex* GetIndexFrom() const override; CAmount GetValue() const override; CDataStream GetUniqueness() const override; - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = UINT256_ZERO) override { return false; /* creation disabled */} - bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) override { return false; /* creation disabled */} bool GetTxOutFrom(CTxOut& out) const override { return false; /* not available */ } virtual bool ContextCheck(int nHeight, uint32_t nTime) override; };