LCOV - code coverage report
Current view: top level - src/test - mnpayments_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 175 176 99.4 %
Date: 2025-02-23 09:33:43 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2021 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "test/test_pivx.h"
       6             : 
       7             : #include "blockassembler.h"
       8             : #include "consensus/merkle.h"
       9             : #include "masternode-payments.h"
      10             : #include "masternodeman.h"
      11             : #include "spork.h"
      12             : #include "tiertwo/tiertwo_sync_state.h"
      13             : #include "primitives/transaction.h"
      14             : #include "utilmoneystr.h"
      15             : #include "util/blockstatecatcher.h"
      16             : #include "validation.h"
      17             : 
      18             : #include <boost/test/unit_test.hpp>
      19             : 
      20             : BOOST_AUTO_TEST_SUITE(mnpayments_tests)
      21             : 
      22           1 : void enableMnSyncAndMNPayments()
      23             : {
      24             :     // force mnsync complete
      25           1 :     g_tiertwo_sync_state.SetCurrentSyncPhase(MASTERNODE_SYNC_FINISHED);
      26             : 
      27             :     // enable SPORK_13
      28           1 :     int64_t nTime = GetTime() - 10;
      29           1 :     CSporkMessage spork(SPORK_13_ENABLE_SUPERBLOCKS, nTime + 1, nTime);
      30           1 :     sporkManager.AddOrUpdateSporkMessage(spork);
      31           2 :     BOOST_CHECK(sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS));
      32             : 
      33           1 :     spork = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
      34           1 :     sporkManager.AddOrUpdateSporkMessage(spork);
      35           2 :     BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));
      36           1 : }
      37             : 
      38         900 : static bool CreateMNWinnerPayment(const CTxIn& mnVinVoter, int paymentBlockHeight, const CScript& payeeScript,
      39             :                                   const CKey& signerKey, const CPubKey& signerPubKey, CValidationState& state)
      40             : {
      41        1800 :     CMasternodePaymentWinner mnWinner(mnVinVoter, paymentBlockHeight);
      42         900 :     mnWinner.AddPayee(payeeScript);
      43        1800 :     BOOST_CHECK(mnWinner.Sign(signerKey, signerPubKey.GetID()));
      44        1800 :     return masternodePayments.ProcessMNWinner(mnWinner, nullptr, state);
      45             : }
      46             : 
      47             : class MNdata
      48             : {
      49             : public:
      50             :     COutPoint collateralOut;
      51             :     CKey mnPrivKey;
      52             :     CPubKey mnPubKey;
      53             :     CPubKey collateralPubKey;
      54             :     CScript mnPayeeScript;
      55             : 
      56          40 :     MNdata(const COutPoint& collateralOut, const CKey& mnPrivKey, const CPubKey& mnPubKey,
      57          40 :            const CPubKey& collateralPubKey, const CScript& mnPayeeScript) :
      58             :            collateralOut(collateralOut), mnPrivKey(mnPrivKey), mnPubKey(mnPubKey),
      59          40 :            collateralPubKey(collateralPubKey), mnPayeeScript(mnPayeeScript) {}
      60             : 
      61             : 
      62             : };
      63             : 
      64          40 : CMasternode buildMN(const MNdata& data, const uint256& tipHash, uint64_t tipTime)
      65             : {
      66          40 :     CMasternode mn;
      67          40 :     mn.vin = CTxIn(data.collateralOut);
      68          40 :     mn.pubKeyCollateralAddress = data.mnPubKey;
      69          40 :     mn.pubKeyMasternode = data.collateralPubKey;
      70          40 :     mn.sigTime = GetTime() - 8000 - 1; // MN_WINNER_MINIMUM_AGE = 8000.
      71          40 :     mn.lastPing = CMasternodePing(mn.vin, tipHash, tipTime);
      72          40 :     return mn;
      73             : }
      74             : 
      75         999 : class FakeMasternode {
      76             : public:
      77          40 :     explicit FakeMasternode(CMasternode& mn, const MNdata& data) : mn(mn), data(data) {}
      78             :     CMasternode mn;
      79             :     MNdata data;
      80             : };
      81             : 
      82           1 : std::vector<FakeMasternode> buildMNList(const uint256& tipHash, uint64_t tipTime, int size)
      83             : {
      84           1 :     std::vector<FakeMasternode> ret;
      85          41 :     for (int i=0; i < size; i++) {
      86          80 :         CKey mnKey;
      87          40 :         mnKey.MakeNewKey(true);
      88          40 :         const CPubKey& mnPubKey = mnKey.GetPubKey();
      89          80 :         const CScript& mnPayeeScript = GetScriptForDestination(mnPubKey.GetID());
      90             :         // Fake collateral out and key for now
      91          40 :         COutPoint mnCollateral(GetRandHash(), 0);
      92          40 :         const CPubKey& collateralPubKey = mnPubKey;
      93             : 
      94             :         // Now add the MN
      95          80 :         MNdata mnData(mnCollateral, mnKey, mnPubKey, collateralPubKey, mnPayeeScript);
      96          80 :         CMasternode mn = buildMN(mnData, tipHash, tipTime);
      97          80 :         BOOST_CHECK(mnodeman.Add(mn));
      98          40 :         ret.emplace_back(mn, mnData);
      99             :     }
     100           1 :     return ret;
     101             : }
     102             : 
     103         896 : FakeMasternode findMNData(std::vector<FakeMasternode>& mnList, const MasternodeRef& ref)
     104             : {
     105       18595 :     for (const auto& item : mnList) {
     106       18595 :         if (item.data.mnPubKey == ref->pubKeyMasternode) {
     107         896 :             return item;
     108             :         }
     109             :     }
     110           0 :     throw std::runtime_error("MN not found");
     111             : }
     112             : 
     113           7 : bool findStrError(CValidationState& state, const std::string& str)
     114             : {
     115           7 :     return state.GetRejectReason().find(str) != std::string::npos;
     116             : }
     117             : 
     118           2 : BOOST_FIXTURE_TEST_CASE(mnwinner_test, TestChain100Setup)
     119             : {
     120           1 :     CreateAndProcessBlock({}, coinbaseKey);
     121           2 :     CBlock tipBlock = CreateAndProcessBlock({}, coinbaseKey);
     122           1 :     enableMnSyncAndMNPayments();
     123           1 :     int nextBlockHeight = 103;
     124           1 :     UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_3, nextBlockHeight - 1);
     125             : 
     126             :     // MN list.
     127           2 :     std::vector<FakeMasternode> mnList = buildMNList(tipBlock.GetHash(), tipBlock.GetBlockTime(), 40);
     128           1 :     std::vector<std::pair<int64_t, MasternodeRef>> mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
     129             : 
     130             :     // Test mnwinner failure for non-existent MN voter.
     131           2 :     CTxIn dummyVoter;
     132           1 :     CScript dummyPayeeScript;
     133           2 :     CKey dummyKey;
     134           1 :     dummyKey.MakeNewKey(true);
     135           2 :     CValidationState state0;
     136           2 :     BOOST_CHECK(!CreateMNWinnerPayment(dummyVoter, nextBlockHeight, dummyPayeeScript,
     137             :                                        dummyKey, dummyKey.GetPubKey(), state0));
     138           5 :     BOOST_CHECK_MESSAGE(findStrError(state0, "Non-existent mnwinner voter"), state0.GetRejectReason());
     139             : 
     140             :     // Take the first MN
     141           2 :     auto firstMN = findMNData(mnList, mnRank[0].second);
     142           2 :     CTxIn mnVinVoter(firstMN.mn.vin);
     143           1 :     int paymentBlockHeight = nextBlockHeight;
     144           2 :     CScript payeeScript = firstMN.data.mnPayeeScript;
     145           1 :     CMasternode* pFirstMN = mnodeman.Find(firstMN.mn.vin.prevout);
     146           1 :     pFirstMN->sigTime += 8000 + 1; // MN_WINNER_MINIMUM_AGE = 8000.
     147             :     // Voter MN1, fail because the sigTime - GetAdjustedTime() is not greater than MN_WINNER_MINIMUM_AGE.
     148           2 :     CValidationState state1;
     149           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
     150             :                                        firstMN.data.mnPrivKey, firstMN.data.mnPubKey, state1));
     151             :     // future: add specific error cause
     152           5 :     BOOST_CHECK_MESSAGE(findStrError(state1, "Masternode not in the top"), state1.GetRejectReason());
     153             : 
     154             :     // Voter MN2, fail because MN2 doesn't match with the signing keys.
     155           2 :     auto secondMn = findMNData(mnList, mnRank[1].second);
     156           1 :     CMasternode* pSecondMN = mnodeman.Find(secondMn.mn.vin.prevout);
     157           1 :     mnVinVoter = CTxIn(pSecondMN->vin);
     158           1 :     payeeScript = secondMn.data.mnPayeeScript;
     159           2 :     CValidationState state2;
     160           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
     161             :                                        firstMN.data.mnPrivKey, firstMN.data.mnPubKey, state2));
     162           5 :     BOOST_CHECK_MESSAGE(findStrError(state2, "invalid voter mnwinner signature"), state2.GetRejectReason());
     163             : 
     164             :     // Voter MN2, fail because mnwinner height is too far in the future.
     165           1 :     mnVinVoter = CTxIn(pSecondMN->vin);
     166           2 :     CValidationState state2_5;
     167           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight + 20, payeeScript,
     168             :                                        secondMn.data.mnPrivKey, secondMn.data.mnPubKey, state2_5));
     169           5 :     BOOST_CHECK_MESSAGE(findStrError(state2_5, "block height out of range"), state2_5.GetRejectReason());
     170             : 
     171             : 
     172             :     // Voter MN2, fail because MN2 is not enabled
     173           1 :     pSecondMN->SetSpent();
     174           2 :     BOOST_CHECK(!pSecondMN->IsEnabled());
     175           1 :     CValidationState state3;
     176           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
     177             :                                        secondMn.data.mnPrivKey, secondMn.data.mnPubKey, state3));
     178             :     // future: could add specific error cause.
     179           5 :     BOOST_CHECK_MESSAGE(findStrError(state3, "Masternode not in the top"), state3.GetRejectReason());
     180             : 
     181             :     // Voter MN3, fail because the payeeScript is not a P2PKH
     182           2 :     auto thirdMn = findMNData(mnList, mnRank[2].second);
     183           1 :     CMasternode* pThirdMN = mnodeman.Find(thirdMn.mn.vin.prevout);
     184           1 :     mnVinVoter = CTxIn(pThirdMN->vin);
     185           2 :     CScript scriptDummy = CScript() << OP_TRUE;
     186           2 :     CValidationState state4;
     187           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, scriptDummy,
     188             :                                        thirdMn.data.mnPrivKey, thirdMn.data.mnPubKey, state4));
     189           5 :     BOOST_CHECK_MESSAGE(findStrError(state4, "payee must be a P2PKH"), state4.GetRejectReason());
     190             : 
     191             :     // Voter MN15 pays to MN3, fail because the voter is not in the top ten.
     192           2 :     auto voterPos15 = findMNData(mnList, mnRank[14].second);
     193           1 :     CMasternode* p15dMN = mnodeman.Find(voterPos15.mn.vin.prevout);
     194           1 :     mnVinVoter = CTxIn(p15dMN->vin);
     195           1 :     payeeScript = thirdMn.data.mnPayeeScript;
     196           2 :     CValidationState state6;
     197           2 :     BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
     198             :                                        voterPos15.data.mnPrivKey, voterPos15.data.mnPubKey, state6));
     199           5 :     BOOST_CHECK_MESSAGE(findStrError(state6, "Masternode not in the top"), state6.GetRejectReason());
     200             : 
     201             :     // Voter MN3, passes
     202           1 :     mnVinVoter = CTxIn(pThirdMN->vin);
     203           1 :     CValidationState state7;
     204           2 :     BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
     205             :                                       thirdMn.data.mnPrivKey, thirdMn.data.mnPubKey, state7));
     206           3 :     BOOST_CHECK_MESSAGE(state7.IsValid(), state7.GetRejectReason());
     207             : 
     208             :     // Create block and check that is being paid properly.
     209           1 :     tipBlock = CreateAndProcessBlock({}, coinbaseKey);
     210           2 :     BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == payeeScript, "error: block not paying to proper MN");
     211           1 :     nextBlockHeight++;
     212             : 
     213             :     // Now let's push two valid winner payments and make every MN in the top ten vote for them (having more votes in mnwinnerA than in mnwinnerB).
     214           2 :     mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
     215           2 :     CScript firstRankedPayee = GetScriptForDestination(mnRank[0].second->pubKeyCollateralAddress.GetID());
     216           2 :     CScript secondRankedPayee = GetScriptForDestination(mnRank[1].second->pubKeyCollateralAddress.GetID());
     217             : 
     218             :     // Let's vote with the first 6 nodes for MN ranked 1
     219             :     // And with the last 4 nodes for MN ranked 2
     220           1 :     payeeScript = firstRankedPayee;
     221          11 :     for (int i=0; i<10; i++) {
     222          10 :         if (i > 5) {
     223           4 :             payeeScript = secondRankedPayee;
     224             :         }
     225          20 :         auto voterMn = findMNData(mnList, mnRank[i].second);
     226          10 :         CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
     227          10 :         mnVinVoter = CTxIn(pVoterMN->vin);
     228          20 :         CValidationState stateInternal;
     229          20 :         BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
     230             :                                                              voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
     231          30 :         BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
     232             :     }
     233             : 
     234             :     // Check the votes count for each mnwinner.
     235           2 :     CMasternodeBlockPayees blockPayees = masternodePayments.mapMasternodeBlocks.at(nextBlockHeight);
     236           2 :     BOOST_CHECK_MESSAGE(blockPayees.HasPayeeWithVotes(firstRankedPayee, 6), "first ranked payee with no enough votes");
     237           2 :     BOOST_CHECK_MESSAGE(blockPayees.HasPayeeWithVotes(secondRankedPayee, 4), "second ranked payee with no enough votes");
     238             : 
     239             :     // let's try to create a bad block paying to the second most voted MN.
     240           2 :     CBlock badBlock = CreateBlock({}, coinbaseKey);
     241           1 :     CMutableTransaction coinbase(*badBlock.vtx[0]);
     242           1 :     coinbase.vout[coinbase.vout.size() - 1].scriptPubKey = secondRankedPayee;
     243           2 :     badBlock.vtx[0] = MakeTransactionRef(coinbase);
     244           1 :     badBlock.hashMerkleRoot = BlockMerkleRoot(badBlock);
     245           1 :     {
     246           1 :         auto pBadBlock = std::make_shared<CBlock>(badBlock);
     247           1 :         SolveBlock(pBadBlock, nextBlockHeight);
     248           2 :         BlockStateCatcherWrapper sc(pBadBlock->GetHash());
     249           1 :         sc.registerEvent();
     250           1 :         ProcessNewBlock(pBadBlock, nullptr);
     251           2 :         BOOST_CHECK(sc.get().found && !sc.get().state.IsValid());
     252           2 :         BOOST_CHECK_EQUAL(sc.get().state.GetRejectReason(), "bad-cb-payee");
     253             :     }
     254           4 :     BOOST_CHECK(WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash();) != badBlock.GetHash());
     255             : 
     256             : 
     257             :     // And let's verify that the most voted one is the one being paid.
     258           1 :     tipBlock = CreateAndProcessBlock({}, coinbaseKey);
     259           2 :     BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == firstRankedPayee, "error: block not paying to first ranked MN");
     260           1 :     nextBlockHeight++;
     261             : 
     262             :     //
     263             :     // Generate 125 blocks paying to different MNs to load the payments cache.
     264         126 :     for (int i = 0; i < 125; i++) {
     265         250 :         mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
     266         125 :         payeeScript = GetScriptForDestination(mnRank[0].second->pubKeyCollateralAddress.GetID());
     267        1000 :         for (int j=0; j<7; j++) { // votes
     268        1750 :             auto voterMn = findMNData(mnList, mnRank[j].second);
     269         875 :             CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
     270         875 :             mnVinVoter = CTxIn(pVoterMN->vin);
     271        1750 :             CValidationState stateInternal;
     272        1750 :             BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
     273             :                                               voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
     274        2625 :             BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
     275             :         }
     276             :         // Create block and check that is being paid properly.
     277         125 :         tipBlock = CreateAndProcessBlock({}, coinbaseKey);
     278         250 :         BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == payeeScript, "error: block not paying to proper MN");
     279         125 :         nextBlockHeight++;
     280             :     }
     281             :     // Check chain height.
     282           2 :     BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height();), nextBlockHeight - 1);
     283             : 
     284             :     // Let's now verify what happen if a previously paid MN goes offline but still have scheduled a payment in the future.
     285             :     // The current system allows it (up to a certain point) as payments are scheduled ahead of time and a MN can go down in the
     286             :     // [proposedWinnerHeightTime < currentHeight < currentHeight + 20] window.
     287             : 
     288             :     // 1) Schedule payment and vote for it with the first 6 MNs.
     289           2 :     mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
     290           2 :     MasternodeRef mnToPay = mnRank[0].second;
     291           1 :     payeeScript = GetScriptForDestination(mnToPay->pubKeyCollateralAddress.GetID());
     292           7 :     for (int i=0; i<6; i++) {
     293          12 :         auto voterMn = findMNData(mnList, mnRank[i].second);
     294           6 :         CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
     295           6 :         mnVinVoter = CTxIn(pVoterMN->vin);
     296          12 :         CValidationState stateInternal;
     297          12 :         BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
     298             :                                           voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
     299          18 :         BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
     300             :     }
     301             : 
     302             :     // 2) Remove payee MN from the MN list and try to emit a vote from MN7 to the same payee.
     303             :     // it should still be accepted because the MN was scheduled when it was online.
     304           1 :     mnodeman.Remove(mnToPay->vin.prevout);
     305           2 :     BOOST_CHECK_MESSAGE(!mnodeman.Find(mnToPay->vin.prevout), "error: removed MN is still available");
     306             : 
     307             :     // Now emit the vote from MN7
     308           2 :     auto voterMn = findMNData(mnList, mnRank[7].second);
     309           1 :     CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
     310           1 :     mnVinVoter = CTxIn(pVoterMN->vin);
     311           2 :     CValidationState stateInternal;
     312           2 :     BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
     313             :                                       voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
     314           3 :     BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
     315           1 : }
     316             : 
     317             : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14