diff --git a/src/blockassembler.cpp b/src/blockassembler.cpp index f3edbc65e4f6..bf6c3d371a1e 100644 --- a/src/blockassembler.cpp +++ b/src/blockassembler.cpp @@ -111,7 +111,7 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CWallet* pwallet return true; } -bool CreateCoinbaseTx(CBlock* pblock, const CScript& scriptPubKeyIn, CBlockIndex* pindexPrev) +CMutableTransaction CreateCoinbaseTx(const CScript& scriptPubKeyIn, CBlockIndex* pindexPrev) { assert(pindexPrev); const int nHeight = pindexPrev->nHeight + 1; @@ -128,7 +128,12 @@ bool CreateCoinbaseTx(CBlock* pblock, const CScript& scriptPubKeyIn, CBlockIndex txCoinbase.vout[0].nValue = GetBlockValue(nHeight); } - pblock->vtx.emplace_back(MakeTransactionRef(txCoinbase)); + return txCoinbase; +} + +bool CreateCoinbaseTx(CBlock* pblock, const CScript& scriptPubKeyIn, CBlockIndex* pindexPrev) +{ + pblock->vtx.emplace_back(MakeTransactionRef(CreateCoinbaseTx(scriptPubKeyIn, pindexPrev))); return true; } @@ -165,7 +170,8 @@ void BlockAssembler::resetBlock() std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, bool fProofOfStake, - std::vector* availableCoins) + std::vector* availableCoins, + bool fNoMempoolTx) { resetBlock(); @@ -194,7 +200,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc return nullptr; } - { + if (!fNoMempoolTx) { // Add transactions from mempool LOCK2(cs_main,mempool.cs); addPriorityTxs(); diff --git a/src/blockassembler.h b/src/blockassembler.h index 9014167326e1..d927feca2b76 100644 --- a/src/blockassembler.h +++ b/src/blockassembler.h @@ -68,7 +68,8 @@ class BlockAssembler std::unique_ptr CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet = nullptr, bool fProofOfStake = false, - std::vector* availableCoins = nullptr); + std::vector* availableCoins = nullptr, + bool fNoMempoolTx = false); private: // utility functions @@ -100,6 +101,7 @@ int32_t ComputeBlockVersion(const Consensus::Params& consensusParams, int nHeigh // Visible for testing purposes only bool CreateCoinbaseTx(CBlock* pblock, const CScript& scriptPubKeyIn, CBlockIndex* pindexPrev); +CMutableTransaction CreateCoinbaseTx(const CScript& scriptPubKeyIn, CBlockIndex* pindexPrev); // Visible for testing purposes only uint256 CalculateSaplingTreeRoot(CBlock* pblock, int nHeight, const CChainParams& chainparams); diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 9ae558cfa265..1f2715ea2fb0 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -308,8 +308,23 @@ std::string GetRequiredPaymentsString(int nBlockHeight) bool CMasternodePayments::GetMasternodeTxOuts(const CBlockIndex* pindexPrev, std::vector& voutMasternodePaymentsRet) const { if (deterministicMNManager->LegacyMNObsolete(pindexPrev->nHeight + 1)) { - // New payment logic (!TODO) - return false; + CAmount masternodeReward = GetMasternodePayment(); + auto dmnPayee = deterministicMNManager->GetListForBlock(pindexPrev).GetMNPayee(); + if (!dmnPayee) { + return error("%s: Failed to get payees for block at height %d", __func__, pindexPrev->nHeight + 1); + } + CAmount operatorReward = 0; + if (dmnPayee->nOperatorReward != 0 && !dmnPayee->pdmnState->scriptOperatorPayout.empty()) { + 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; } // Legacy payment logic. !TODO: remove when transition to DMN is complete @@ -318,7 +333,6 @@ bool CMasternodePayments::GetMasternodeTxOuts(const CBlockIndex* pindexPrev, std bool CMasternodePayments::GetLegacyMasternodeTxOut(int nHeight, std::vector& voutMasternodePaymentsRet) const { - if (nHeight == 0) return false; voutMasternodePaymentsRet.clear(); CScript payee; @@ -622,8 +636,24 @@ bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, const CB { const int nBlockHeight = pindexPrev->nHeight + 1; if (deterministicMNManager->LegacyMNObsolete(nBlockHeight)) { - // !TODO - return false; + std::vector vecMnOuts; + if (!GetMasternodeTxOuts(pindexPrev, vecMnOuts)) { + // No masternode scheduled to be paid. + return true; + } + + for (const CTxOut& o : vecMnOuts) { + if (std::find(txNew.vout.begin(), txNew.vout.end(), o) == txNew.vout.end()) { + CTxDestination mnDest; + const std::string& payee = ExtractDestination(o.scriptPubKey, mnDest) ? EncodeDestination(mnDest) + : HexStr(o.scriptPubKey); + LogPrint(BCLog::MASTERNODE, "%s: Failed to find expected payee %s in block at height %d (tx %s)", + __func__, payee, pindexPrev->nHeight + 1, txNew.GetHash().ToString()); + return false; + } + } + // all the expected payees have been found in txNew outputs + return true; } // Legacy payment logic. !TODO: remove when transition to DMN is complete diff --git a/src/spork.cpp b/src/spork.cpp index ba286c28fc73..73609b96bdc5 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -202,9 +202,9 @@ void CSporkManager::ProcessGetSporks(CNode* pfrom, std::string& strCommand, CDat bool CSporkManager::UpdateSpork(SporkId nSporkID, int64_t nValue) { - CSporkMessage spork = CSporkMessage(nSporkID, nValue, GetTime()); + CSporkMessage spork(nSporkID, nValue, GetTime()); - if(spork.Sign(strMasterPrivKey)){ + if (spork.Sign(strMasterPrivKey)) { spork.Relay(); AddOrUpdateSporkMessage(spork); return true; diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index c6d18f91130e..87e13136667e 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -5,14 +5,19 @@ #include "test/test_pivx.h" +#include "blockassembler.h" +#include "consensus/merkle.h" #include "evo/specialtx.h" #include "evo/providertx.h" #include "evo/deterministicmns.h" +#include "masternode-payments.h" +#include "masternode-sync.h" #include "messagesigner.h" #include "netbase.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "script/sign.h" +#include "spork.h" #include "validation.h" #include @@ -36,7 +41,7 @@ static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) return utxos; } -static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet) +static std::vector SelectUTXOs(SimpleUTXOMap& utxos, CAmount amount, CAmount& changeRet) { changeRet = 0; amount += fee; @@ -44,9 +49,9 @@ static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, std::vector selectedUtxos; CAmount selectedAmount = 0; int chainHeight = chainActive.Height(); - while (!utoxs.empty()) { + while (!utxos.empty()) { bool found = false; - for (auto it = utoxs.begin(); it != utoxs.end(); ++it) { + for (auto it = utxos.begin(); it != utxos.end(); ++it) { if (chainHeight - it->second.first < 100) { continue; } @@ -54,7 +59,7 @@ static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, found = true; selectedAmount += it->second.second; selectedUtxos.emplace_back(it->first); - utoxs.erase(it); + utxos.erase(it); break; } BOOST_ASSERT(found); @@ -67,10 +72,10 @@ static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, return selectedUtxos; } -static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, const CScript& scriptChange, CAmount amount) +static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utxos, const CScript& scriptPayout, const CScript& scriptChange, CAmount amount) { CAmount change; - auto inputs = SelectUTXOs(utoxs, amount, change); + auto inputs = SelectUTXOs(utxos, amount, change); for (size_t i = 0; i < inputs.size(); i++) { tx.vin.emplace_back(inputs[i]); } @@ -161,6 +166,24 @@ static bool CheckTransactionSignature(const CMutableTransaction& tx) return true; } +static bool IsMNPayeeInBlock(const CBlock& block, const CScript& expected) +{ + for (const auto& txout : block.vtx[0]->vout) { + if (txout.scriptPubKey == expected) return true; + } + return false; +} + +static void CheckPayments(std::map mp, size_t mapSize, int minCount) +{ + BOOST_CHECK_EQUAL(mp.size(), mapSize); + for (const auto& it : mp) { + BOOST_CHECK_MESSAGE(it.second >= minCount, + strprintf("MN %s didn't receive expected num of payments (%d<%d)",it.first.ToString(), it.second, minCount) + ); + } +} + BOOST_AUTO_TEST_SUITE(deterministicmns_tests) BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) @@ -170,9 +193,14 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) CBlockIndex* chainTip = chainActive.Tip(); int nHeight = chainTip->nHeight; UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight); + masternodeSync.RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED; + // enable SPORK_8 + int64_t nTime = GetTime() - 10; + const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime); + sporkManager.AddOrUpdateSporkMessage(sporkMnPayment); + BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)); int port = 1; - // these maps are only populated, but not used for now. They will be needed later on, in the next commits. std::vector dmnHashes; std::map ownerKeys; std::map operatorKeys; @@ -215,48 +243,57 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) nHeight++; } - // Mine 30 more blocks - for (size_t i = 0; i < 30; i++) { - CreateAndProcessBlock({}, coinbaseKey); + + // enable SPORK_21 + const CSporkMessage& spork = CSporkMessage(SPORK_21_LEGACY_MNS_MAX_HEIGHT, nHeight, GetTime()); + sporkManager.AddOrUpdateSporkMessage(spork); + BOOST_CHECK(deterministicMNManager->LegacyMNObsolete(nHeight + 1)); + + // Mine 20 blocks, checking MN reward payments + std::map mapPayments; + for (size_t i = 0; i < 20; i++) { + auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(); + CBlock block = CreateAndProcessBlock({}, coinbaseKey); chainTip = chainActive.Tip(); - BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight); deterministicMNManager->UpdatedBlockTip(chainTip); + BOOST_ASSERT(!block.vtx.empty()); + BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout)); + mapPayments[dmnExpectedPayee->proTxHash]++; + BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight); } + // 20 blocks, 6 masternodes. Must have been paid at least 3 times each. + CheckPayments(mapPayments, 6, 3); // Try to register used owner key { - SimpleUTXOMap utxos_copy(utxos); const CKey& ownerKey = ownerKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]); - auto tx = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey, GetRandomKey()); + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, GetRandomKey()); CValidationState state; BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key"); } // Try to register used operator key { - SimpleUTXOMap utxos_copy(utxos); const CKey& operatorKey = operatorKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]); - auto tx = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKey); + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKey); CValidationState state; BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key"); } // Try to register used IP address { - SimpleUTXOMap utxos_copy(utxos); - auto tx = CreateProRegTx(nullopt, utxos_copy, 1 + InsecureRandRange(port-1), GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + auto tx = CreateProRegTx(nullopt, utxos, 1 + InsecureRandRange(port-1), GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); CValidationState state; BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address"); } // Block with two ProReg txes using same owner key { - SimpleUTXOMap utxos_copy(utxos); const CKey& ownerKey = GetRandomKey(); const CKey& operatorKey1 = GetRandomKey(); const CKey& operatorKey2 = GetRandomKey(); - auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey1); - auto tx2 = CreateProRegTx(nullopt, utxos_copy, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey2); + auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey1); + auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey2); CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); CBlockIndex indexFake(block); indexFake.nHeight = nHeight; @@ -269,12 +306,11 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) } // Block with two ProReg txes using same operator key { - SimpleUTXOMap utxos_copy(utxos); const CKey& ownerKey1 = GetRandomKey(); const CKey& ownerKey2 = GetRandomKey(); const CKey& operatorKey = GetRandomKey(); - auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey); - auto tx2 = CreateProRegTx(nullopt, utxos_copy, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey2, operatorKey); + auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey); + auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey2, operatorKey); CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); CBlockIndex indexFake(block); indexFake.nHeight = nHeight; @@ -287,9 +323,8 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) } // Block with two ProReg txes using ip address { - SimpleUTXOMap utxos_copy(utxos); - auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); - auto tx2 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + auto tx2 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); CBlockIndex indexFake(block); indexFake.nHeight = nHeight; @@ -301,6 +336,76 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) BOOST_CHECK_EQUAL(chainActive.Height(), nHeight); // bad block not connected } + // register multiple MNs per block + for (size_t i = 0; i < 3; i++) { + std::vector txns; + for (size_t j = 0; j < 3; j++) { + const CKey& ownerKey = GetRandomKey(); + const CKey& operatorKey = GetRandomKey(); + auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey); + const uint256& txid = tx.GetHash(); + dmnHashes.emplace_back(txid); + ownerKeys.emplace(txid, ownerKey); + operatorKeys.emplace(txid, operatorKey); + + CValidationState dummyState; + BOOST_CHECK(CheckSpecialTx(tx, chainActive.Tip(), dummyState)); + BOOST_CHECK(CheckTransactionSignature(tx)); + txns.emplace_back(tx); + } + CreateAndProcessBlock(txns, coinbaseKey); + chainTip = chainActive.Tip(); + BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1); + + deterministicMNManager->UpdatedBlockTip(chainTip); + auto mnList = deterministicMNManager->GetListAtChainTip(); + for (size_t j = 0; j < 3; j++) { + BOOST_CHECK(mnList.HasMN(txns[j].GetHash())); + } + + nHeight++; + } + + // Mine 30 blocks, checking MN reward payments + mapPayments.clear(); + for (size_t i = 0; i < 30; i++) { + auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(); + CBlock block = CreateAndProcessBlock({}, coinbaseKey); + chainTip = chainActive.Tip(); + deterministicMNManager->UpdatedBlockTip(chainTip); + BOOST_ASSERT(!block.vtx.empty()); + BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout)); + mapPayments[dmnExpectedPayee->proTxHash]++; + + nHeight++; + } + // 30 blocks, 15 masternodes. Must have been paid exactly 2 times each. + CheckPayments(mapPayments, 15, 2); + + // Check that the prev DMN winner is different that the tip one + std::vector vecMnOutsPrev; + BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip->pprev, vecMnOutsPrev)); + std::vector vecMnOutsNow; + BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip, vecMnOutsNow)); + BOOST_CHECK(vecMnOutsPrev != vecMnOutsNow); + + // Craft an invalid block paying to the previous block DMN again + CBlock invalidBlock = CreateBlock({}, coinbaseKey); + std::shared_ptr pblock = std::make_shared(invalidBlock); + CMutableTransaction invalidCoinbaseTx = CreateCoinbaseTx(CScript(), chainTip); + invalidCoinbaseTx.vout.clear(); + for (const CTxOut& mnOut: vecMnOutsPrev) { + invalidCoinbaseTx.vout.emplace_back(mnOut); + } + invalidCoinbaseTx.vout.emplace_back( + CTxOut(GetBlockValue(nHeight + 1) - GetMasternodePayment(), + GetScriptForDestination(coinbaseKey.GetPubKey().GetID()))); + pblock->vtx[0] = MakeTransactionRef(invalidCoinbaseTx); + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + CValidationState state; + BOOST_CHECK_MESSAGE(!ProcessNewBlock(state, nullptr, pblock, nullptr), "Error, invalid block paying to an already paid DMN passed"); + BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Height()) == nHeight); // no block connected + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index 5f3e4dc79cc4..46f811cdffcb 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -160,24 +160,16 @@ CBlock TestChainSetup::CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey, bool fNoMempoolTx) { std::unique_ptr pblocktemplate = BlockAssembler( - Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(scriptPubKey, nullptr, false); + Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(scriptPubKey, nullptr, false, nullptr, fNoMempoolTx); std::shared_ptr pblock = std::make_shared(pblocktemplate->block); - const int nHeight = WITH_LOCK(cs_main, return chainActive.Height()) + 1; - - // Replace mempool-selected txns with just coinbase plus passed-in txns: - if (fNoMempoolTx) { - pblock->vtx.resize(1); - - // Replace coinbase output amount (could have included fee in CreateNewBlock) - CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.vout[0].nValue = GetBlockValue(nHeight); - pblock->vtx[0] = MakeTransactionRef(txCoinbase); - } + // Add passed-in txns: for (const CMutableTransaction& tx : txns) { pblock->vtx.push_back(MakeTransactionRef(tx)); } + const int nHeight = WITH_LOCK(cs_main, return chainActive.Height()) + 1; + // Re-compute sapling root pblock->hashFinalSaplingRoot = CalculateSaplingTreeRoot(pblock.get(), nHeight, Params());