PIVX Core  5.6.99
P2P Digital Currency
mnpayments_tests.cpp
Go to the documentation of this file.
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"
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 
23 {
24  // force mnsync complete
26 
27  // enable SPORK_13
28  int64_t nTime = GetTime() - 10;
32 
36 }
37 
38 static bool CreateMNWinnerPayment(const CTxIn& mnVinVoter, int paymentBlockHeight, const CScript& payeeScript,
39  const CKey& signerKey, const CPubKey& signerPubKey, CValidationState& state)
40 {
41  CMasternodePaymentWinner mnWinner(mnVinVoter, paymentBlockHeight);
42  mnWinner.AddPayee(payeeScript);
43  BOOST_CHECK(mnWinner.Sign(signerKey, signerPubKey.GetID()));
44  return masternodePayments.ProcessMNWinner(mnWinner, nullptr, state);
45 }
46 
47 class MNdata
48 {
49 public:
55 
60 
61 
62 };
63 
64 CMasternode buildMN(const MNdata& data, const uint256& tipHash, uint64_t tipTime)
65 {
66  CMasternode mn;
67  mn.vin = CTxIn(data.collateralOut);
70  mn.sigTime = GetTime() - 8000 - 1; // MN_WINNER_MINIMUM_AGE = 8000.
71  mn.lastPing = CMasternodePing(mn.vin, tipHash, tipTime);
72  return mn;
73 }
74 
76 public:
77  explicit FakeMasternode(CMasternode& mn, const MNdata& data) : mn(mn), data(data) {}
80 };
81 
82 std::vector<FakeMasternode> buildMNList(const uint256& tipHash, uint64_t tipTime, int size)
83 {
84  std::vector<FakeMasternode> ret;
85  for (int i=0; i < size; i++) {
86  CKey mnKey;
87  mnKey.MakeNewKey(true);
88  const CPubKey& mnPubKey = mnKey.GetPubKey();
89  const CScript& mnPayeeScript = GetScriptForDestination(mnPubKey.GetID());
90  // Fake collateral out and key for now
91  COutPoint mnCollateral(GetRandHash(), 0);
92  const CPubKey& collateralPubKey = mnPubKey;
93 
94  // Now add the MN
95  MNdata mnData(mnCollateral, mnKey, mnPubKey, collateralPubKey, mnPayeeScript);
96  CMasternode mn = buildMN(mnData, tipHash, tipTime);
98  ret.emplace_back(mn, mnData);
99  }
100  return ret;
101 }
102 
103 FakeMasternode findMNData(std::vector<FakeMasternode>& mnList, const MasternodeRef& ref)
104 {
105  for (const auto& item : mnList) {
106  if (item.data.mnPubKey == ref->pubKeyMasternode) {
107  return item;
108  }
109  }
110  throw std::runtime_error("MN not found");
111 }
112 
113 bool findStrError(CValidationState& state, const std::string& str)
114 {
115  return state.GetRejectReason().find(str) != std::string::npos;
116 }
117 
119 {
120  CreateAndProcessBlock({}, coinbaseKey);
121  CBlock tipBlock = CreateAndProcessBlock({}, coinbaseKey);
123  int nextBlockHeight = 103;
125 
126  // MN list.
127  std::vector<FakeMasternode> mnList = buildMNList(tipBlock.GetHash(), tipBlock.GetBlockTime(), 40);
128  std::vector<std::pair<int64_t, MasternodeRef>> mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
129 
130  // Test mnwinner failure for non-existent MN voter.
131  CTxIn dummyVoter;
132  CScript dummyPayeeScript;
133  CKey dummyKey;
134  dummyKey.MakeNewKey(true);
135  CValidationState state0;
136  BOOST_CHECK(!CreateMNWinnerPayment(dummyVoter, nextBlockHeight, dummyPayeeScript,
137  dummyKey, dummyKey.GetPubKey(), state0));
138  BOOST_CHECK_MESSAGE(findStrError(state0, "Non-existent mnwinner voter"), state0.GetRejectReason());
139 
140  // Take the first MN
141  auto firstMN = findMNData(mnList, mnRank[0].second);
142  CTxIn mnVinVoter(firstMN.mn.vin);
143  int paymentBlockHeight = nextBlockHeight;
144  CScript payeeScript = firstMN.data.mnPayeeScript;
145  CMasternode* pFirstMN = mnodeman.Find(firstMN.mn.vin.prevout);
146  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  CValidationState state1;
149  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
150  firstMN.data.mnPrivKey, firstMN.data.mnPubKey, state1));
151  // future: add specific error cause
152  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  auto secondMn = findMNData(mnList, mnRank[1].second);
156  CMasternode* pSecondMN = mnodeman.Find(secondMn.mn.vin.prevout);
157  mnVinVoter = CTxIn(pSecondMN->vin);
158  payeeScript = secondMn.data.mnPayeeScript;
159  CValidationState state2;
160  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
161  firstMN.data.mnPrivKey, firstMN.data.mnPubKey, state2));
162  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  mnVinVoter = CTxIn(pSecondMN->vin);
166  CValidationState state2_5;
167  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight + 20, payeeScript,
168  secondMn.data.mnPrivKey, secondMn.data.mnPubKey, state2_5));
169  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  pSecondMN->SetSpent();
174  BOOST_CHECK(!pSecondMN->IsEnabled());
175  CValidationState state3;
176  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
177  secondMn.data.mnPrivKey, secondMn.data.mnPubKey, state3));
178  // future: could add specific error cause.
179  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  auto thirdMn = findMNData(mnList, mnRank[2].second);
183  CMasternode* pThirdMN = mnodeman.Find(thirdMn.mn.vin.prevout);
184  mnVinVoter = CTxIn(pThirdMN->vin);
185  CScript scriptDummy = CScript() << OP_TRUE;
186  CValidationState state4;
187  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, scriptDummy,
188  thirdMn.data.mnPrivKey, thirdMn.data.mnPubKey, state4));
189  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  auto voterPos15 = findMNData(mnList, mnRank[14].second);
193  CMasternode* p15dMN = mnodeman.Find(voterPos15.mn.vin.prevout);
194  mnVinVoter = CTxIn(p15dMN->vin);
195  payeeScript = thirdMn.data.mnPayeeScript;
196  CValidationState state6;
197  BOOST_CHECK(!CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
198  voterPos15.data.mnPrivKey, voterPos15.data.mnPubKey, state6));
199  BOOST_CHECK_MESSAGE(findStrError(state6, "Masternode not in the top"), state6.GetRejectReason());
200 
201  // Voter MN3, passes
202  mnVinVoter = CTxIn(pThirdMN->vin);
203  CValidationState state7;
204  BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, paymentBlockHeight, payeeScript,
205  thirdMn.data.mnPrivKey, thirdMn.data.mnPubKey, state7));
206  BOOST_CHECK_MESSAGE(state7.IsValid(), state7.GetRejectReason());
207 
208  // Create block and check that is being paid properly.
209  tipBlock = CreateAndProcessBlock({}, coinbaseKey);
210  BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == payeeScript, "error: block not paying to proper MN");
211  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  mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
215  CScript firstRankedPayee = GetScriptForDestination(mnRank[0].second->pubKeyCollateralAddress.GetID());
216  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  payeeScript = firstRankedPayee;
221  for (int i=0; i<10; i++) {
222  if (i > 5) {
223  payeeScript = secondRankedPayee;
224  }
225  auto voterMn = findMNData(mnList, mnRank[i].second);
226  CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
227  mnVinVoter = CTxIn(pVoterMN->vin);
228  CValidationState stateInternal;
229  BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
230  voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
231  BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
232  }
233 
234  // Check the votes count for each mnwinner.
235  CMasternodeBlockPayees blockPayees = masternodePayments.mapMasternodeBlocks.at(nextBlockHeight);
236  BOOST_CHECK_MESSAGE(blockPayees.HasPayeeWithVotes(firstRankedPayee, 6), "first ranked payee with no enough votes");
237  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  CBlock badBlock = CreateBlock({}, coinbaseKey);
241  CMutableTransaction coinbase(*badBlock.vtx[0]);
242  coinbase.vout[coinbase.vout.size() - 1].scriptPubKey = secondRankedPayee;
243  badBlock.vtx[0] = MakeTransactionRef(coinbase);
244  badBlock.hashMerkleRoot = BlockMerkleRoot(badBlock);
245  {
246  auto pBadBlock = std::make_shared<CBlock>(badBlock);
247  SolveBlock(pBadBlock, nextBlockHeight);
248  BlockStateCatcherWrapper sc(pBadBlock->GetHash());
249  sc.registerEvent();
250  ProcessNewBlock(pBadBlock, nullptr);
251  BOOST_CHECK(sc.get().found && !sc.get().state.IsValid());
252  BOOST_CHECK_EQUAL(sc.get().state.GetRejectReason(), "bad-cb-payee");
253  }
254  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  tipBlock = CreateAndProcessBlock({}, coinbaseKey);
259  BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == firstRankedPayee, "error: block not paying to first ranked MN");
260  nextBlockHeight++;
261 
262  //
263  // Generate 125 blocks paying to different MNs to load the payments cache.
264  for (int i = 0; i < 125; i++) {
265  mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
266  payeeScript = GetScriptForDestination(mnRank[0].second->pubKeyCollateralAddress.GetID());
267  for (int j=0; j<7; j++) { // votes
268  auto voterMn = findMNData(mnList, mnRank[j].second);
269  CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
270  mnVinVoter = CTxIn(pVoterMN->vin);
271  CValidationState stateInternal;
272  BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
273  voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
274  BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
275  }
276  // Create block and check that is being paid properly.
277  tipBlock = CreateAndProcessBlock({}, coinbaseKey);
278  BOOST_CHECK_MESSAGE(tipBlock.vtx[0]->vout.back().scriptPubKey == payeeScript, "error: block not paying to proper MN");
279  nextBlockHeight++;
280  }
281  // Check chain height.
282  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  mnRank = mnodeman.GetMasternodeRanks(nextBlockHeight - 100);
290  MasternodeRef mnToPay = mnRank[0].second;
291  payeeScript = GetScriptForDestination(mnToPay->pubKeyCollateralAddress.GetID());
292  for (int i=0; i<6; i++) {
293  auto voterMn = findMNData(mnList, mnRank[i].second);
294  CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
295  mnVinVoter = CTxIn(pVoterMN->vin);
296  CValidationState stateInternal;
297  BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
298  voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
299  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  mnodeman.Remove(mnToPay->vin.prevout);
305  BOOST_CHECK_MESSAGE(!mnodeman.Find(mnToPay->vin.prevout), "error: removed MN is still available");
306 
307  // Now emit the vote from MN7
308  auto voterMn = findMNData(mnList, mnRank[7].second);
309  CMasternode* pVoterMN = mnodeman.Find(voterMn.mn.vin.prevout);
310  mnVinVoter = CTxIn(pVoterMN->vin);
311  CValidationState stateInternal;
312  BOOST_CHECK(CreateMNWinnerPayment(mnVinVoter, nextBlockHeight, payeeScript,
313  voterMn.data.mnPrivKey, voterMn.data.mnPubKey, stateInternal));
314  BOOST_CHECK_MESSAGE(stateInternal.IsValid(), stateInternal.GetRejectReason());
315 }
316 
bool SolveBlock(std::shared_ptr< CBlock > &pblock, int nHeight)
Modify the nonce/extranonce in a block.
void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivationHeight)
Allows modifying the network upgrade regtest parameters.
CValidationState state
BlockStateCatcher & get() const
uint256 hashMerkleRoot
Definition: block.h:29
uint256 GetHash() const
Definition: block.cpp:15
Definition: block.h:80
std::vector< CTransactionRef > vtx
Definition: block.h:83
uint256 GetBlockHash() const
Definition: chain.h:215
CBlockIndex * Tip(bool fProofOfStake=false) const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:405
int Height() const
Return the maximal height in the chain.
Definition: chain.h:450
An encapsulated private key.
Definition: key.h:30
void MakeNewKey(bool fCompressed)
Generate a new private key using a cryptographic PRNG.
Definition: key.cpp:158
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:186
bool HasPayeeWithVotes(const CScript &payee, int nVotesReq)
void SetSpent()
Definition: masternode.h:180
CMasternodePing lastPing
Definition: masternode.h:104
CTxIn vin
Definition: masternode.h:96
CPubKey pubKeyMasternode
Definition: masternode.h:99
int64_t sigTime
Definition: masternode.h:100
bool IsEnabled() const
Definition: masternode.h:193
CPubKey pubKeyCollateralAddress
Definition: masternode.h:98
std::vector< std::pair< int64_t, MasternodeRef > > GetMasternodeRanks(int nBlockHeight) const
void Remove(const COutPoint &collateralOut)
bool Add(CMasternode &mn)
Add an entry.
CMasternode * Find(const COutPoint &collateralOut)
Find an entry.
std::map< int, CMasternodeBlockPayees > mapMasternodeBlocks
bool ProcessMNWinner(CMasternodePaymentWinner &winner, CNode *pfrom, CValidationState &state)
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:72
An encapsulated public key.
Definition: pubkey.h:44
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
Definition: pubkey.h:167
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:381
bool IsSporkActive(SporkId nSporkID)
Definition: spork.cpp:220
void AddOrUpdateSporkMessage(const CSporkMessage &spork, bool flush=false)
Definition: spork.cpp:206
An input of a transaction.
Definition: transaction.h:94
Capture information about block/transaction validation.
Definition: validation.h:24
bool IsValid() const
Definition: validation.h:69
std::string GetRejectReason() const
Definition: validation.h:94
FakeMasternode(CMasternode &mn, const MNdata &data)
COutPoint collateralOut
MNdata(const COutPoint &collateralOut, const CKey &mnPrivKey, const CPubKey &mnPubKey, const CPubKey &collateralPubKey, const CScript &mnPayeeScript)
CPubKey collateralPubKey
CPubKey mnPubKey
CScript mnPayeeScript
void SetCurrentSyncPhase(int sync_phase)
value_type * data()
Definition: prevector.h:526
256-bit opaque blob.
Definition: uint256.h:138
BOOST_AUTO_TEST_SUITE(cuckoocache_tests)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
CMasternodePayments masternodePayments
Object for who's going to get paid on which blocks.
std::shared_ptr< CMasternode > MasternodeRef
Definition: masternode.h:24
CMasternodeMan mnodeman
Masternode manager.
uint256 BlockMerkleRoot(const CBlock &block, bool *mutated)
Definition: merkle.cpp:154
UniValue spork(const JSONRPCRequest &request)
Definition: misc.cpp:286
bool findStrError(CValidationState &state, const std::string &str)
BOOST_FIXTURE_TEST_CASE(mnwinner_test, TestChain100Setup)
CMasternode buildMN(const MNdata &data, const uint256 &tipHash, uint64_t tipTime)
void enableMnSyncAndMNPayments()
FakeMasternode findMNData(std::vector< FakeMasternode > &mnList, const MasternodeRef &ref)
std::vector< FakeMasternode > buildMNList(const uint256 &tipHash, uint64_t tipTime, int size)
@ UPGRADE_V5_3
Definition: params.h:38
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:80
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
uint256 GetRandHash() noexcept
Definition: random.cpp:596
@ OP_TRUE
Definition: script.h:60
CSporkManager sporkManager
Definition: spork.cpp:29
@ SPORK_13_ENABLE_SUPERBLOCKS
Definition: sporkid.h:20
@ SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT
Definition: sporkid.h:18
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a PIVX scriptPubKey for the given CTxDestination.
Definition: standard.cpp:278
A mutable version of CTransaction.
Definition: transaction.h:409
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:247
TierTwoSyncState g_tiertwo_sync_state
#define MASTERNODE_SYNC_FINISHED
int64_t GetTime()
DEPRECATED Use either GetSystemTimeInSeconds (not mockable) or GetTime<T> (mockable)
Definition: utiltime.cpp:27
bool ProcessNewBlock(const std::shared_ptr< const CBlock > &pblock, const FlatFilePos *dbp)
Process an incoming block.
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84