Line data Source code
1 : // Copyright (c) 2018-2021 The Dash Core developers
2 : // Copyright (c) 2021-2022 The PIVX Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "test/test_pivx.h"
7 :
8 : #include "blockassembler.h"
9 : #include "consensus/merkle.h"
10 : #include "consensus/params.h"
11 : #include "evo/specialtx_validation.h"
12 : #include "evo/deterministicmns.h"
13 : #include "llmq/quorums_blockprocessor.h"
14 : #include "llmq/quorums_commitment.h"
15 : #include "llmq/quorums_utils.h"
16 : #include "masternode-payments.h"
17 : #include "messagesigner.h"
18 : #include "netbase.h"
19 : #include "policy/policy.h"
20 : #include "primitives/transaction.h"
21 : #include "script/sign.h"
22 : #include "spork.h"
23 : #include "tiertwo/tiertwo_sync_state.h"
24 : #include "util/blocksutil.h"
25 : #include "validation.h"
26 : #include "validationinterface.h"
27 :
28 : #include <boost/test/unit_test.hpp>
29 :
30 : typedef std::map<COutPoint, std::pair<int, CAmount>> SimpleUTXOMap;
31 :
32 : // static 0.1 PIV fee used for the special txes in these tests
33 : static const CAmount fee = 10000000;
34 :
35 2 : static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransaction>& txs)
36 : {
37 2 : SimpleUTXOMap utxos;
38 802 : for (size_t i = 0; i < txs.size(); i++) {
39 800 : auto& tx = txs[i];
40 1600 : for (size_t j = 0; j < tx.vout.size(); j++) {
41 800 : utxos.emplace(std::piecewise_construct,
42 800 : std::forward_as_tuple(tx.GetHash(), j),
43 800 : std::forward_as_tuple((int)i + 1, tx.vout[j].nValue));
44 : }
45 : }
46 2 : return utxos;
47 : }
48 :
49 73 : static std::vector<COutPoint> SelectUTXOs(SimpleUTXOMap& utxos, CAmount amount, CAmount& changeRet)
50 : {
51 73 : changeRet = 0;
52 73 : amount += fee;
53 :
54 73 : std::vector<COutPoint> selectedUtxos;
55 73 : CAmount selectedAmount = 0;
56 146 : int chainHeight = WITH_LOCK(cs_main, return chainActive.Height(); );
57 73 : while (!utxos.empty()) {
58 73 : bool found = false;
59 258 : for (auto it = utxos.begin(); it != utxos.end(); ++it) {
60 258 : if (chainHeight - it->second.first < 100) {
61 185 : continue;
62 : }
63 :
64 73 : found = true;
65 73 : selectedAmount += it->second.second;
66 73 : selectedUtxos.emplace_back(it->first);
67 73 : utxos.erase(it);
68 : break;
69 : }
70 0 : BOOST_ASSERT(found);
71 73 : if (selectedAmount >= amount) {
72 73 : changeRet = selectedAmount - amount;
73 73 : break;
74 : }
75 : }
76 :
77 73 : return selectedUtxos;
78 : }
79 :
80 57 : static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utxos, const CScript& scriptPayout, const CScript& scriptChange, CAmount amount)
81 : {
82 57 : CAmount change;
83 57 : auto inputs = SelectUTXOs(utxos, amount, change);
84 114 : for (size_t i = 0; i < inputs.size(); i++) {
85 57 : tx.vin.emplace_back(inputs[i]);
86 : }
87 114 : tx.vout.emplace_back(CTxOut(amount, scriptPayout));
88 57 : if (change != 0) {
89 57 : tx.vout.emplace_back(change, scriptChange);
90 : }
91 57 : }
92 :
93 58 : static void SignTransaction(CMutableTransaction& tx, const CKey& coinbaseKey)
94 : {
95 116 : CBasicKeyStore tempKeystore;
96 58 : tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
97 :
98 116 : for (size_t i = 0; i < tx.vin.size(); i++) {
99 58 : CTransactionRef txFrom;
100 58 : uint256 hashBlock;
101 58 : BOOST_ASSERT(GetTransaction(tx.vin[i].prevout.hash, txFrom, hashBlock));
102 58 : BOOST_ASSERT(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
103 : }
104 58 : }
105 :
106 : // Makes a new tx with a single out of given amount to given destination (or coinbase address, if nullopt)
107 3 : static COutPoint CreateNewUTXO(SimpleUTXOMap& utxos,
108 : CMutableTransaction& mtx,
109 : const CKey& coinbaseKey, CAmount amount,
110 : Optional<CScript> scriptDest = nullopt)
111 : {
112 4 : const CScript& s = (scriptDest != nullopt ? *scriptDest
113 4 : : GetScriptForDestination(coinbaseKey.GetPubKey().GetID()));
114 3 : FundTransaction(mtx, utxos, s, s, amount);
115 3 : SignTransaction(mtx, coinbaseKey);
116 : int idx = -1;
117 6 : for (size_t i = 0; i < mtx.vout.size() && idx < 0; i++) {
118 3 : if (mtx.vout[i].nValue == amount) idx = i;
119 : }
120 6 : BOOST_CHECK(idx >= 0);
121 3 : return COutPoint(mtx.GetHash(), idx);
122 : }
123 :
124 43 : static CKey GetRandomKey()
125 : {
126 43 : CKey keyRet;
127 43 : keyRet.MakeNewKey(true);
128 43 : return keyRet;
129 : }
130 :
131 42 : static CBLSSecretKey GetRandomBLSKey()
132 : {
133 42 : CBLSSecretKey sk;
134 42 : sk.MakeNewKey();
135 42 : return sk;
136 : }
137 :
138 : // Creates a ProRegTx.
139 : // - if optCollateralOut is nullopt, generate a new collateral in the first output of the tx
140 : // - otherwise reference *optCollateralOut as external collateral
141 38 : static CMutableTransaction CreateProRegTx(Optional<COutPoint> optCollateralOut,
142 : SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey,
143 : const CKey& ownerKey,
144 : const CBLSPublicKey& operatorPubKey,
145 : uint16_t operatorReward = 0,
146 : bool fInvalidCollateral = false)
147 : {
148 38 : ProRegPL pl;
149 38 : pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0));
150 38 : pl.addr = LookupNumeric("1.1.1.1", port);
151 38 : pl.keyIDOwner = ownerKey.GetPubKey().GetID();
152 38 : pl.pubKeyOperator = operatorPubKey;
153 38 : pl.keyIDVoting = ownerKey.GetPubKey().GetID();
154 38 : pl.scriptPayout = scriptPayout;
155 38 : pl.nOperatorReward = operatorReward;
156 :
157 38 : CMutableTransaction tx;
158 38 : tx.nVersion = CTransaction::TxVersion::SAPLING;
159 38 : tx.nType = CTransaction::TxType::PROREG;
160 110 : FundTransaction(tx, utxos, scriptPayout,
161 76 : GetScriptForDestination(coinbaseKey.GetPubKey().GetID()),
162 38 : (optCollateralOut ? 0 : Params().GetConsensus().nMNCollateralAmt - (fInvalidCollateral ? 1 : 0)));
163 :
164 38 : pl.inputsHash = CalcTxInputsHash(tx);
165 38 : SetTxPayload(tx, pl);
166 38 : SignTransaction(tx, coinbaseKey);
167 :
168 38 : return tx;
169 : }
170 :
171 7 : static CMutableTransaction CreateProUpServTx(SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, int port, const CScript& scriptOperatorPayout, const CKey& coinbaseKey)
172 : {
173 7 : CAmount change;
174 7 : auto inputs = SelectUTXOs(utxos, 1 * COIN, change);
175 :
176 14 : ProUpServPL pl;
177 7 : pl.proTxHash = proTxHash;
178 7 : pl.addr = LookupNumeric("1.1.1.1", port);
179 7 : pl.scriptOperatorPayout = scriptOperatorPayout;
180 :
181 7 : CMutableTransaction tx;
182 7 : tx.nVersion = CTransaction::TxVersion::SAPLING;
183 7 : tx.nType = CTransaction::TxType::PROUPSERV;
184 14 : const CScript& s = GetScriptForDestination(coinbaseKey.GetPubKey().GetID());
185 7 : FundTransaction(tx, utxos, s, s, 1 * COIN);
186 7 : pl.inputsHash = CalcTxInputsHash(tx);
187 7 : pl.sig = operatorKey.Sign(::SerializeHash(pl));
188 7 : BOOST_ASSERT(pl.sig.IsValid());
189 7 : SetTxPayload(tx, pl);
190 7 : SignTransaction(tx, coinbaseKey);
191 :
192 14 : return tx;
193 : }
194 :
195 7 : static CMutableTransaction CreateProUpRegTx(SimpleUTXOMap& utxos, const uint256& proTxHash, const CKey& ownerKey, const CBLSPublicKey& operatorPubKey, const CKey& votingKey, const CScript& scriptPayout, const CKey& coinbaseKey)
196 : {
197 7 : CAmount change;
198 7 : auto inputs = SelectUTXOs(utxos, 1 * COIN, change);
199 :
200 14 : ProUpRegPL pl;
201 7 : pl.proTxHash = proTxHash;
202 7 : pl.pubKeyOperator = operatorPubKey;
203 7 : pl.keyIDVoting = votingKey.GetPubKey().GetID();
204 7 : pl.scriptPayout = scriptPayout;
205 :
206 7 : CMutableTransaction tx;
207 7 : tx.nVersion = CTransaction::TxVersion::SAPLING;
208 7 : tx.nType = CTransaction::TxType::PROUPREG;
209 14 : const CScript& s = GetScriptForDestination(coinbaseKey.GetPubKey().GetID());
210 7 : FundTransaction(tx, utxos, s, s, 1 * COIN);
211 7 : pl.inputsHash = CalcTxInputsHash(tx);
212 7 : BOOST_ASSERT(CHashSigner::SignHash(::SerializeHash(pl), ownerKey, pl.vchSig));
213 7 : SetTxPayload(tx, pl);
214 7 : SignTransaction(tx, coinbaseKey);
215 :
216 14 : return tx;
217 : }
218 :
219 2 : static CMutableTransaction CreateProUpRevTx(SimpleUTXOMap& utxos, const uint256& proTxHash, ProUpRevPL::RevocationReason reason, const CBLSSecretKey& operatorKey, const CKey& coinbaseKey)
220 : {
221 2 : CAmount change;
222 2 : auto inputs = SelectUTXOs(utxos, 1 * COIN, change);
223 :
224 2 : ProUpRevPL pl;
225 2 : pl.proTxHash = proTxHash;
226 2 : pl.nReason = reason;
227 :
228 2 : CMutableTransaction tx;
229 2 : tx.nVersion = CTransaction::TxVersion::SAPLING;
230 2 : tx.nType = CTransaction::TxType::PROUPREV;
231 4 : const CScript& s = GetScriptForDestination(coinbaseKey.GetPubKey().GetID());
232 2 : FundTransaction(tx, utxos, s, s, 1 * COIN);
233 2 : pl.inputsHash = CalcTxInputsHash(tx);
234 2 : pl.sig = operatorKey.Sign(::SerializeHash(pl));
235 2 : BOOST_ASSERT(pl.sig.IsValid());
236 2 : SetTxPayload(tx, pl);
237 2 : SignTransaction(tx, coinbaseKey);
238 :
239 4 : return tx;
240 : }
241 :
242 52 : static CScript GenerateRandomAddress()
243 : {
244 52 : CKey key;
245 52 : key.MakeNewKey(false);
246 156 : return GetScriptForDestination(key.GetPubKey().GetID());
247 : }
248 :
249 : template<typename ProPL>
250 7 : static CMutableTransaction MalleateProTxPayout(const CMutableTransaction& tx)
251 : {
252 14 : ProPL pl;
253 7 : GetTxPayload(tx, pl);
254 7 : pl.scriptPayout = GenerateRandomAddress();
255 7 : CMutableTransaction tx2 = tx;
256 7 : SetTxPayload(tx2, pl);
257 7 : return tx2;
258 : }
259 :
260 1 : static CMutableTransaction MalleateProUpServTx(const CMutableTransaction& tx)
261 : {
262 1 : ProUpServPL pl;
263 1 : GetTxPayload(tx, pl);
264 2 : pl.addr = LookupNumeric("1.1.1.1", 1001 + InsecureRandRange(100));
265 1 : if (!pl.scriptOperatorPayout.empty()) {
266 0 : pl.scriptOperatorPayout = GenerateRandomAddress();
267 : }
268 1 : CMutableTransaction tx2 = tx;
269 1 : SetTxPayload(tx2, pl);
270 1 : return tx2;
271 : }
272 :
273 1 : static CMutableTransaction MalleateProUpRevTx(const CMutableTransaction& tx)
274 : {
275 1 : ProUpRevPL pl;
276 1 : GetTxPayload(tx, pl);
277 1 : BOOST_ASSERT(pl.nReason != ProUpRevPL::RevocationReason::REASON_CHANGE_OF_KEYS);
278 1 : pl.nReason = ProUpRevPL::RevocationReason::REASON_CHANGE_OF_KEYS;
279 1 : CMutableTransaction tx2 = tx;
280 1 : SetTxPayload(tx2, pl);
281 1 : return tx2;
282 : }
283 :
284 24 : static bool CheckTransactionSignature(const CMutableTransaction& tx)
285 : {
286 42 : for (unsigned int i = 0; i < tx.vin.size(); i++) {
287 24 : const auto& txin = tx.vin[i];
288 18 : CTransactionRef txFrom;
289 24 : uint256 hashBlock;
290 24 : BOOST_ASSERT(GetTransaction(txin.prevout.hash, txFrom, hashBlock));
291 :
292 24 : CAmount amount = txFrom->vout[txin.prevout.n].nValue;
293 24 : if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount), tx.GetRequiredSigVersion())) {
294 12 : return false;
295 : }
296 : }
297 : return true;
298 : }
299 :
300 82 : static bool IsMNPayeeInBlock(const CBlock& block, const CScript& expected)
301 : {
302 164 : for (const auto& txout : block.vtx[0]->vout) {
303 164 : if (txout.scriptPubKey == expected) return true;
304 : }
305 0 : return false;
306 : }
307 :
308 3 : static void CheckPayments(const std::map<uint256, int>& mp, size_t mapSize, int minCount)
309 : {
310 3 : BOOST_CHECK_EQUAL(mp.size(), mapSize);
311 40 : for (const auto& it : mp) {
312 148 : BOOST_CHECK_MESSAGE(it.second >= minCount,
313 : strprintf("MN %s didn't receive expected num of payments (%d<%d)",it.first.ToString(), it.second, minCount)
314 : );
315 : }
316 3 : }
317 :
318 : BOOST_AUTO_TEST_SUITE(deterministicmns_tests)
319 :
320 2 : BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup)
321 : {
322 1 : auto utxos = BuildSimpleUtxoMap(coinbaseTxns);
323 :
324 1 : CBlockIndex* chainTip = chainActive.Tip();
325 1 : CCoinsViewCache* view = pcoinsTip.get();
326 :
327 1 : int nHeight = chainTip->nHeight;
328 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight + 2);
329 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_POS, nHeight + 200);
330 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V3_4, nHeight + 200);
331 :
332 : // load empty list (last block before enforcement)
333 1 : CreateAndProcessBlock({}, coinbaseKey);
334 1 : chainTip = chainActive.Tip();
335 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
336 :
337 : // force mnsync complete and enable spork 8
338 1 : g_tiertwo_sync_state.SetCurrentSyncPhase(MASTERNODE_SYNC_FINISHED);
339 1 : int64_t nTime = GetTime() - 10;
340 2 : const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
341 1 : sporkManager.AddOrUpdateSporkMessage(sporkMnPayment);
342 2 : BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));
343 :
344 1 : int port = 1;
345 :
346 2 : std::vector<uint256> dmnHashes;
347 2 : std::map<uint256, CKey> ownerKeys;
348 1 : std::map<uint256, CBLSSecretKey> operatorKeys;
349 :
350 : // register one MN per block
351 7 : for (size_t i = 0; i < 6; i++) {
352 12 : const CKey& ownerKey = GetRandomKey();
353 12 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
354 12 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey());
355 6 : const uint256& txid = tx.GetHash();
356 6 : dmnHashes.emplace_back(txid);
357 6 : ownerKeys.emplace(txid, ownerKey);
358 6 : operatorKeys.emplace(txid, operatorKey);
359 :
360 12 : CValidationState dummyState;
361 24 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, dummyState); ));
362 12 : BOOST_CHECK(CheckTransactionSignature(tx));
363 :
364 : // also verify that payloads are not malleable after they have been signed
365 : // the form of ProRegTx we use here is one with a collateral included, so there is no signature inside the
366 : // payload itself. This means, we need to rely on script verification, which takes the hash of the extra payload
367 : // into account
368 6 : auto tx2 = MalleateProTxPayout<ProRegPL>(tx);
369 : // Technically, the payload is still valid...
370 24 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, dummyState); ));
371 : // But the signature should not verify anymore
372 12 : BOOST_CHECK(!CheckTransactionSignature(tx2));
373 :
374 12 : CreateAndProcessBlock({tx}, coinbaseKey);
375 6 : chainTip = chainActive.Tip();
376 6 : BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);
377 12 : BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid));
378 :
379 : // Add change to the utxos map
380 6 : if (tx.vout.size() > 1) {
381 6 : utxos.emplace(COutPoint(tx.GetHash(), 1), std::make_pair(nHeight + 1, tx.vout[1].nValue));
382 : }
383 :
384 6 : nHeight++;
385 : }
386 :
387 : // enable SPORK_21
388 2 : const CSporkMessage& spork = CSporkMessage(SPORK_21_LEGACY_MNS_MAX_HEIGHT, nHeight, GetTime());
389 1 : sporkManager.AddOrUpdateSporkMessage(spork);
390 2 : BOOST_CHECK(deterministicMNManager->LegacyMNObsolete(nHeight + 1));
391 :
392 : // Mine 20 blocks, checking MN reward payments
393 2 : std::map<uint256, int> mapPayments;
394 21 : for (size_t i = 0; i < 20; i++) {
395 20 : auto mnList = deterministicMNManager->GetListAtChainTip();
396 20 : BOOST_CHECK_EQUAL(mnList.GetValidMNsCount(), 6);
397 20 : BOOST_CHECK_EQUAL(mnList.GetHeight(), nHeight);
398 :
399 : // get next payee
400 40 : auto dmnExpectedPayee = mnList.GetMNPayee();
401 40 : CBlock block = CreateAndProcessBlock({}, coinbaseKey);
402 20 : chainTip = chainActive.Tip();
403 20 : BOOST_ASSERT(!block.vtx.empty());
404 40 : BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
405 20 : mapPayments[dmnExpectedPayee->proTxHash]++;
406 20 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
407 : }
408 : // 20 blocks, 6 masternodes. Must have been paid at least 3 times each.
409 1 : CheckPayments(mapPayments, 6, 3);
410 :
411 :
412 : // Try to register with non-existent external collateral
413 1 : {
414 1 : Optional<COutPoint> o = COutPoint(UINT256_ONE, 0);
415 3 : auto tx = CreateProRegTx(o, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
416 2 : CValidationState state;
417 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
418 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral");
419 : }
420 : // Try to register with invalid external collateral
421 1 : {
422 : // create an output of value 1 sat less than the required collateral amount
423 1 : CMutableTransaction mtx;
424 1 : const COutPoint& coll_out = CreateNewUTXO(utxos, mtx, coinbaseKey, Params().GetConsensus().nMNCollateralAmt-1);
425 2 : CreateAndProcessBlock({mtx}, coinbaseKey);
426 1 : chainTip = chainActive.Tip();
427 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
428 2 : Coin coll_coin;
429 2 : BOOST_CHECK(view->GetUTXOCoin(coll_out, coll_coin));
430 1 : BOOST_CHECK_EQUAL(coll_coin.out.nValue, Params().GetConsensus().nMNCollateralAmt-1);
431 :
432 : // create the ProReg tx referencing the invalid collateral
433 3 : auto tx = CreateProRegTx(Optional<COutPoint>(coll_out), utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
434 1 : CValidationState state;
435 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
436 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-amount");
437 :
438 : // add the coin back to the utxo map
439 1 : utxos.emplace(coll_out, std::make_pair(coll_coin.nHeight, coll_coin.out.nValue));
440 : }
441 : // Try to register with spent external collateral
442 1 : {
443 : // create an output of collateral amount
444 1 : CMutableTransaction mtx;
445 1 : const COutPoint& coll_out = CreateNewUTXO(utxos, mtx, coinbaseKey, Params().GetConsensus().nMNCollateralAmt);
446 2 : CreateAndProcessBlock({mtx}, coinbaseKey);
447 1 : chainTip = chainActive.Tip();
448 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
449 2 : Coin coll_coin;
450 2 : BOOST_CHECK(view->GetUTXOCoin(coll_out, coll_coin));
451 1 : BOOST_CHECK_EQUAL(coll_coin.out.nValue, Params().GetConsensus().nMNCollateralAmt);
452 :
453 : // spend it
454 2 : CMutableTransaction spendTx;
455 1 : spendTx.vin.emplace_back(coll_out);
456 1 : spendTx.vout.emplace_back(Params().GetConsensus().nMNCollateralAmt - 1000,
457 2 : GetScriptForDestination(coinbaseKey.GetPubKey().GetID()));
458 1 : SignTransaction(spendTx, coinbaseKey);
459 2 : CreateAndProcessBlock({spendTx}, coinbaseKey);
460 1 : chainTip = chainActive.Tip();
461 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
462 2 : BOOST_CHECK(!view->GetUTXOCoin(coll_out, coll_coin));
463 :
464 : // create the ProReg tx referencing the spent collateral
465 3 : auto tx = CreateProRegTx(Optional<COutPoint>(coll_out), utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
466 2 : CValidationState state;
467 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
468 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral");
469 : }
470 : // Try to register with invalid internal collateral
471 1 : {
472 3 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, true);
473 2 : CValidationState state;
474 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
475 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-amount");
476 : }
477 : // Try to register reusing the collateral key as owner/voting key
478 1 : {
479 1 : const CKey& coll_key = GetRandomKey();
480 : // create a valid collateral
481 2 : CMutableTransaction mtx;
482 1 : const COutPoint& coll_out = CreateNewUTXO(utxos, mtx, coinbaseKey, Params().GetConsensus().nMNCollateralAmt,
483 2 : GetScriptForDestination(coll_key.GetPubKey().GetID()));
484 2 : CreateAndProcessBlock({mtx}, coinbaseKey);
485 1 : chainTip = chainActive.Tip();
486 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
487 2 : Coin coll_coin;
488 2 : BOOST_CHECK(view->GetUTXOCoin(coll_out, coll_coin));
489 1 : BOOST_CHECK_EQUAL(coll_coin.out.nValue, Params().GetConsensus().nMNCollateralAmt);
490 :
491 : // create the ProReg tx reusing the collateral key
492 2 : auto tx = CreateProRegTx(Optional<COutPoint>(coll_out), utxos, port, GenerateRandomAddress(), coinbaseKey, coll_key, GetRandomBLSKey().GetPublicKey());
493 2 : CValidationState state;
494 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
495 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-reuse");
496 : }
497 : // Try to register used owner key
498 1 : {
499 1 : const CKey& ownerKey = ownerKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]);
500 2 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, GetRandomBLSKey().GetPublicKey());
501 2 : CValidationState state;
502 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
503 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key");
504 : }
505 : // Try to register used operator key
506 1 : {
507 1 : const CBLSSecretKey& operatorKey = operatorKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]);
508 2 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKey.GetPublicKey());
509 2 : CValidationState state;
510 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
511 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
512 : }
513 : // Try to register used IP address
514 1 : {
515 3 : auto tx = CreateProRegTx(nullopt, utxos, 1 + InsecureRandRange(port-1), GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
516 2 : CValidationState state;
517 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
518 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address");
519 : }
520 : // Block with two ProReg txes using same owner key
521 1 : {
522 1 : const CKey& ownerKey = GetRandomKey();
523 2 : const CBLSSecretKey& operatorKey1 = GetRandomBLSKey();
524 2 : const CBLSSecretKey& operatorKey2 = GetRandomBLSKey();
525 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey1.GetPublicKey());
526 2 : auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey2.GetPublicKey());
527 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
528 2 : CBlockIndex indexFake(block);
529 1 : indexFake.nHeight = nHeight;
530 1 : indexFake.pprev = chainTip;
531 2 : CValidationState state;
532 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
533 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key");
534 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
535 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
536 : }
537 : // Block with two ProReg txes using same operator key
538 1 : {
539 1 : const CKey& ownerKey1 = GetRandomKey();
540 2 : const CKey& ownerKey2 = GetRandomKey();
541 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
542 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey.GetPublicKey());
543 2 : auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey2, operatorKey.GetPublicKey());
544 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
545 2 : CBlockIndex indexFake(block);
546 1 : indexFake.nHeight = nHeight;
547 1 : indexFake.pprev = chainTip;
548 2 : CValidationState state;
549 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
550 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
551 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
552 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
553 : }
554 : // Block with two ProReg txes using same ip address
555 1 : {
556 3 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
557 3 : auto tx2 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
558 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
559 2 : CBlockIndex indexFake(block);
560 1 : indexFake.nHeight = nHeight;
561 1 : indexFake.pprev = chainTip;
562 2 : CValidationState state;
563 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
564 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address");
565 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
566 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
567 : }
568 :
569 : // register multiple MNs per block
570 4 : for (size_t i = 0; i < 3; i++) {
571 6 : std::vector<CMutableTransaction> txns;
572 12 : for (size_t j = 0; j < 3; j++) {
573 18 : const CKey& ownerKey = GetRandomKey();
574 18 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
575 18 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey());
576 9 : const uint256& txid = tx.GetHash();
577 9 : dmnHashes.emplace_back(txid);
578 9 : ownerKeys.emplace(txid, ownerKey);
579 9 : operatorKeys.emplace(txid, operatorKey);
580 :
581 18 : CValidationState dummyState;
582 45 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainActive.Tip(), view, dummyState); ));
583 18 : BOOST_CHECK(CheckTransactionSignature(tx));
584 9 : txns.emplace_back(tx);
585 : }
586 3 : CreateAndProcessBlock(txns, coinbaseKey);
587 3 : chainTip = chainActive.Tip();
588 3 : BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);
589 3 : auto mnList = deterministicMNManager->GetListAtChainTip();
590 12 : for (size_t j = 0; j < 3; j++) {
591 18 : BOOST_CHECK(mnList.HasMN(txns[j].GetHash()));
592 : }
593 :
594 3 : nHeight++;
595 : }
596 :
597 : // Mine 30 blocks, checking MN reward payments
598 1 : mapPayments.clear();
599 31 : for (size_t i = 0; i < 30; i++) {
600 30 : auto mnList = deterministicMNManager->GetListAtChainTip();
601 60 : auto dmnExpectedPayee = mnList.GetMNPayee();
602 60 : CBlock block = CreateAndProcessBlock({}, coinbaseKey);
603 30 : chainTip = chainActive.Tip();
604 30 : BOOST_ASSERT(!block.vtx.empty());
605 60 : BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
606 30 : mapPayments[dmnExpectedPayee->proTxHash]++;
607 :
608 30 : nHeight++;
609 : }
610 : // 30 blocks, 15 masternodes. Must have been paid exactly 2 times each.
611 1 : CheckPayments(mapPayments, 15, 2);
612 :
613 : // Check that the prev DMN winner is different that the tip one
614 2 : std::vector<CTxOut> vecMnOutsPrev;
615 2 : BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip->pprev, vecMnOutsPrev));
616 2 : std::vector<CTxOut> vecMnOutsNow;
617 2 : BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip, vecMnOutsNow));
618 2 : BOOST_CHECK(vecMnOutsPrev != vecMnOutsNow);
619 :
620 : // Craft an invalid block paying to the previous block DMN again
621 1 : CBlock invalidBlock = CreateBlock({}, coinbaseKey);
622 2 : std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(invalidBlock);
623 2 : CMutableTransaction invalidCoinbaseTx = CreateCoinbaseTx(CScript(), chainTip);
624 1 : invalidCoinbaseTx.vout.clear();
625 2 : for (const CTxOut& mnOut: vecMnOutsPrev) {
626 1 : invalidCoinbaseTx.vout.emplace_back(mnOut);
627 : }
628 1 : invalidCoinbaseTx.vout.emplace_back(
629 2 : CTxOut(GetBlockValue(nHeight + 1) - GetMasternodePayment(nHeight + 1),
630 2 : GetScriptForDestination(coinbaseKey.GetPubKey().GetID())));
631 2 : pblock->vtx[0] = MakeTransactionRef(invalidCoinbaseTx);
632 1 : pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
633 1 : ProcessNewBlock(pblock, nullptr);
634 : // block not connected
635 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip());
636 2 : BOOST_CHECK(chainTip->nHeight == nHeight);
637 2 : BOOST_CHECK(chainTip->GetBlockHash() != pblock->GetHash());
638 :
639 : // ProUpServ: change masternode IP
640 1 : {
641 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
642 2 : auto tx = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), 1000, CScript(), coinbaseKey);
643 :
644 2 : CValidationState dummyState;
645 4 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, dummyState); ));
646 2 : BOOST_CHECK(CheckTransactionSignature(tx));
647 : // also verify that payloads are not malleable after they have been signed
648 1 : auto tx2 = MalleateProUpServTx(tx);
649 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, dummyState); ));
650 2 : BOOST_CHECK_EQUAL(dummyState.GetRejectReason(), "bad-protx-sig");
651 :
652 2 : CreateAndProcessBlock({tx}, coinbaseKey);
653 1 : chainTip = chainActive.Tip();
654 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);
655 :
656 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
657 1 : BOOST_ASSERT(dmn != nullptr);
658 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->addr.GetPort(), 1000);
659 :
660 1 : nHeight++;
661 : }
662 :
663 : // ProUpServ: Try to change the IP of a masternode to the one of another registered masternode
664 1 : {
665 1 : int randomIdx = InsecureRandRange(dmnHashes.size());
666 1 : int randomIdx2 = 0;
667 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx);
668 1 : const uint256& proTx = dmnHashes[randomIdx]; // mn to update
669 2 : int new_port = deterministicMNManager->GetListAtChainTip().GetMN(dmnHashes[randomIdx2])->pdmnState->addr.GetPort();
670 :
671 2 : auto tx = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), new_port, CScript(), coinbaseKey);
672 :
673 2 : CValidationState state;
674 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
675 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-addr");
676 : }
677 :
678 : // ProUpServ: Try to change the IP of a masternode that doesn't exist
679 1 : {
680 1 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
681 2 : auto tx = CreateProUpServTx(utxos, GetRandHash(), operatorKey, port, CScript(), coinbaseKey);
682 :
683 2 : CValidationState state;
684 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
685 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-hash");
686 : }
687 :
688 : // ProUpServ: Change masternode operator payout. (new masternode created here)
689 1 : {
690 : // first create a ProRegTx with 5% reward for the operator, and mine it
691 1 : const CKey& ownerKey = GetRandomKey();
692 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
693 2 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey(), 500);
694 1 : const uint256& txid = tx.GetHash();
695 2 : CreateAndProcessBlock({tx}, coinbaseKey);
696 1 : chainTip = chainActive.Tip();
697 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
698 1 : auto mnList = deterministicMNManager->GetListAtChainTip();
699 2 : BOOST_CHECK(mnList.HasMN(txid));
700 2 : auto dmn = mnList.GetMN(txid);
701 2 : BOOST_CHECK(dmn->pdmnState->scriptOperatorPayout.empty());
702 1 : BOOST_CHECK_EQUAL(dmn->nOperatorReward, 500);
703 :
704 : // then send the ProUpServTx and check the operator payee
705 2 : const CScript& operatorPayee = GenerateRandomAddress();
706 2 : auto tx2 = CreateProUpServTx(utxos, txid, operatorKey, (port-1), operatorPayee, coinbaseKey);
707 2 : CreateAndProcessBlock({tx2}, coinbaseKey);
708 1 : chainTip = chainActive.Tip();
709 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
710 2 : dmn = deterministicMNManager->GetListAtChainTip().GetMN(txid);
711 1 : BOOST_ASSERT(dmn != nullptr);
712 2 : BOOST_CHECK(dmn->pdmnState->scriptOperatorPayout == operatorPayee);
713 : }
714 :
715 : // ProUpServ: Try to change masternode operator payout when the operator reward is zero
716 1 : {
717 1 : const CScript& operatorPayee = GenerateRandomAddress();
718 2 : auto tx = CreateProUpServTx(utxos, dmnHashes[0], operatorKeys.at(dmnHashes[0]), 1, operatorPayee, coinbaseKey);
719 2 : CValidationState state;
720 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
721 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-operator-payee");
722 : }
723 :
724 : // Block including
725 : // - (1) ProRegTx registering a masternode
726 : // - (2) ProUpServTx changing the IP of another masternode, to the one used by (1)
727 1 : {
728 3 : auto tx1 = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
729 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
730 2 : auto tx2 = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), (port-1), CScript(), coinbaseKey);
731 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
732 2 : CBlockIndex indexFake(block);
733 1 : indexFake.nHeight = nHeight;
734 1 : indexFake.pprev = chainTip;
735 2 : CValidationState state;
736 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
737 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-addr");
738 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to ProcessBlockAndCheckRejectionReason.
739 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
740 : }
741 :
742 : // ProUpReg: change voting key, operator key and payout address
743 1 : {
744 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
745 1 : CBLSSecretKey new_operatorKey = GetRandomBLSKey();
746 2 : const CKey& new_votingKey = GetRandomKey();
747 2 : const CScript& new_payee = GenerateRandomAddress();
748 : // try first with wrong owner key
749 2 : CValidationState state;
750 2 : auto tx = CreateProUpRegTx(utxos, proTx, GetRandomKey(), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
751 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "ProUpReg verifies with wrong owner key");
752 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
753 : // then use the proper key
754 1 : state = CValidationState();
755 1 : tx = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
756 5 : BOOST_CHECK_MESSAGE(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), state.GetRejectReason());
757 2 : BOOST_CHECK_MESSAGE(CheckTransactionSignature(tx), "ProUpReg signature verification failed");
758 : // also verify that payloads are not malleable after they have been signed
759 1 : auto tx2 = MalleateProTxPayout<ProUpRegPL>(tx);
760 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, state); ), "Malleated ProUpReg accepted");
761 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
762 :
763 2 : CreateAndProcessBlock({tx}, coinbaseKey);
764 1 : chainTip = chainActive.Tip();
765 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
766 :
767 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
768 1 : BOOST_ASSERT(dmn != nullptr);
769 3 : BOOST_CHECK_MESSAGE(dmn->pdmnState->pubKeyOperator.Get() == new_operatorKey.GetPublicKey(), "mn operator key not changed");
770 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->keyIDVoting == new_votingKey.GetPubKey().GetID(), "mn voting key not changed");
771 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptPayout == new_payee, "mn script payout not changed");
772 :
773 1 : operatorKeys[proTx] = std::move(new_operatorKey);
774 :
775 : // check that changing the operator key puts the MN in PoSe banned state
776 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->addr == CService(), "IP address not cleared after changing operator");
777 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptOperatorPayout.empty(), "operator payee not empty after changing operator");
778 2 : BOOST_CHECK(dmn->IsPoSeBanned());
779 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, nHeight);
780 :
781 : // revive the MN
782 2 : auto tx3 = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), 2000, CScript(), coinbaseKey);
783 2 : CreateAndProcessBlock({tx3}, coinbaseKey);
784 1 : chainTip = chainActive.Tip();
785 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
786 2 : dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
787 :
788 : // check updated dmn state
789 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->addr.GetPort(), 2000);
790 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, -1);
791 2 : BOOST_CHECK(!dmn->IsPoSeBanned());
792 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeRevivedHeight, nHeight);
793 :
794 : // Mine 32 blocks, checking MN reward payments
795 1 : mapPayments.clear();
796 33 : for (size_t i = 0; i < 32; i++) {
797 64 : auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
798 64 : CBlock block = CreateAndProcessBlock({}, coinbaseKey);
799 32 : chainTip = chainActive.Tip();
800 32 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
801 32 : BOOST_ASSERT(!block.vtx.empty());
802 64 : BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
803 32 : mapPayments[dmnExpectedPayee->proTxHash]++;
804 : }
805 : // 16 masternodes: 2 rewards each
806 1 : CheckPayments(mapPayments, 16, 2);
807 : }
808 :
809 : // ProUpReg: Try to change the voting key of a masternode that doesn't exist
810 1 : {
811 1 : const CKey& votingKey = GetRandomKey();
812 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
813 3 : auto tx = CreateProUpRegTx(utxos, GetRandHash(), GetRandomKey(), operatorKey.GetPublicKey(), votingKey, GenerateRandomAddress(), coinbaseKey);
814 :
815 2 : CValidationState state;
816 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "Accepted ProUpReg with invalid protx hash");
817 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-hash");
818 : }
819 :
820 : // ProUpReg: Try to change the operator key of a masternode to the one of another registered masternode
821 1 : {
822 1 : int randomIdx = InsecureRandRange(dmnHashes.size());
823 1 : int randomIdx2 = 0;
824 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx);
825 1 : const uint256& proTx = dmnHashes[randomIdx]; // mn to update
826 1 : const CBLSSecretKey& new_operatorKey = operatorKeys.at(dmnHashes[randomIdx2]);
827 :
828 3 : auto tx = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), GetRandomKey(), GenerateRandomAddress(), coinbaseKey);
829 :
830 2 : CValidationState state;
831 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "Accepted ProUpReg with duplicate operator key");
832 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-key");
833 : }
834 :
835 : // Block with two ProUpReg txes using same operator key
836 1 : {
837 1 : int randomIdx1 = InsecureRandRange(dmnHashes.size());
838 1 : int randomIdx2 = 0;
839 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx1);
840 1 : const uint256& proTx1 = dmnHashes[randomIdx1];
841 1 : const uint256& proTx2 = dmnHashes[randomIdx2];
842 1 : BOOST_ASSERT(proTx1 != proTx2);
843 1 : const CBLSSecretKey& new_operatorKey = GetRandomBLSKey();
844 2 : const CKey& new_votingKey = GetRandomKey();
845 2 : const CScript& new_payee = GenerateRandomAddress();
846 2 : auto tx1 = CreateProUpRegTx(utxos, proTx1, ownerKeys.at(proTx1), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
847 2 : auto tx2 = CreateProUpRegTx(utxos, proTx2, ownerKeys.at(proTx2), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
848 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
849 2 : CBlockIndex indexFake(block);
850 1 : indexFake.nHeight = nHeight;
851 1 : indexFake.pprev = chainTip;
852 2 : CValidationState state;
853 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ),
854 : "Accepted block with duplicate operator key in ProUpReg txes");
855 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
856 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
857 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
858 : }
859 :
860 : // Block including
861 : // - (1) ProRegTx registering a masternode
862 : // - (2) ProUpRegTx changing the operator key of another masternode, to the one used by (1)
863 1 : {
864 1 : const CBLSSecretKey& new_operatorKey = GetRandomBLSKey();
865 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), new_operatorKey.GetPublicKey());
866 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
867 3 : auto tx2 = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), GetRandomKey(), GenerateRandomAddress(), coinbaseKey);
868 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
869 2 : CBlockIndex indexFake(block);
870 1 : indexFake.nHeight = nHeight;
871 1 : indexFake.pprev = chainTip;
872 2 : CValidationState state;
873 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ),
874 : "Accepted block with duplicate operator key in ProReg+ProUpReg txes");
875 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
876 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
877 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
878 : }
879 :
880 : // ProUpRev: revoke masternode service
881 1 : {
882 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
883 1 : ProUpRevPL::RevocationReason reason = ProUpRevPL::RevocationReason::REASON_TERMINATION_OF_SERVICE;
884 : // try first with wrong operator key
885 2 : CValidationState state;
886 2 : auto tx = CreateProUpRevTx(utxos, proTx, reason, GetRandomBLSKey(), coinbaseKey);
887 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "ProUpReg verifies with wrong owner key");
888 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
889 : // then use the proper key
890 1 : state = CValidationState();
891 1 : tx = CreateProUpRevTx(utxos, proTx, reason, operatorKeys.at(proTx), coinbaseKey);
892 4 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
893 2 : BOOST_CHECK_MESSAGE(CheckTransactionSignature(tx), "ProUpReg signature verification failed");
894 : // also verify that payloads are not malleable after they have been signed
895 1 : auto tx2 = MalleateProUpRevTx(tx);
896 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, state); ), "Malleated ProUpReg accepted");
897 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
898 :
899 2 : CreateAndProcessBlock({tx}, coinbaseKey);
900 1 : chainTip = chainActive.Tip();
901 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
902 :
903 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
904 1 : BOOST_ASSERT(dmn != nullptr);
905 2 : BOOST_CHECK_MESSAGE(!dmn->pdmnState->pubKeyOperator.Get().IsValid(), "mn operator key not removed");
906 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->addr == CService(), "mn IP address not removed");
907 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptOperatorPayout.empty(), "mn operator payout not removed");
908 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nRevocationReason, reason);
909 2 : BOOST_CHECK(dmn->IsPoSeBanned());
910 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, nHeight);
911 : }
912 :
913 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
914 1 : }
915 :
916 : // Dummy commitment where the DKG shares are replaced with the operator keys of each member.
917 : // members at index skeys.size(), ..., llmqType.size - 1 are invalid
918 5 : static llmq::CFinalCommitment CreateFinalCommitment(std::vector<CBLSPublicKey>& pkeys,
919 : std::vector<CBLSSecretKey>& skeys,
920 : const uint256& quorumHash)
921 : {
922 5 : size_t m = skeys.size();
923 5 : BOOST_ASSERT(pkeys.size() == m);
924 :
925 5 : llmq::CFinalCommitment qfl;
926 5 : qfl.llmqType = (uint8_t)Consensus::LLMQ_TEST;
927 5 : const auto& params = Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST);
928 5 : BOOST_ASSERT(m <= (size_t) params.size); // m-of-n
929 :
930 : // non-included members are marked invalid
931 5 : qfl.signers.resize(params.size);
932 5 : qfl.validMembers.resize(params.size);
933 20 : for (size_t i = 0; i < (size_t) params.size; i++) {
934 15 : qfl.signers[i] = i < m;
935 30 : qfl.validMembers[i] = i < m;
936 : }
937 :
938 5 : qfl.quorumHash = quorumHash;
939 :
940 : // create dummy quorum keys, just aggregating operator BLS keys
941 5 : qfl.quorumPublicKey = CBLSPublicKey::AggregateInsecure(pkeys);
942 :
943 : // use dummy non-null verification vector hash
944 5 : qfl.quorumVvecHash = UINT256_ONE;
945 :
946 : // add signatures
947 5 : const uint256& commitmentHash = llmq::utils::BuildCommitmentHash((Consensus::LLMQType)qfl.llmqType, quorumHash, qfl.validMembers, qfl.quorumPublicKey, qfl.quorumVvecHash);
948 5 : std::vector<CBLSSignature> sigs;
949 17 : for (size_t i = 0; i < m; i++) {
950 12 : sigs.emplace_back(skeys[i].Sign(commitmentHash));
951 : }
952 5 : qfl.membersSig = CBLSSignature::AggregateSecure(sigs, pkeys, commitmentHash);
953 5 : qfl.quorumSig = CBLSSecretKey::AggregateInsecure(skeys).Sign(commitmentHash);
954 :
955 10 : return qfl;
956 : }
957 :
958 12 : CMutableTransaction CreateQfcTx(const uint256& quorumHash, int nHeight, Optional<llmq::CFinalCommitment> opt_qfc)
959 : {
960 12 : llmq::LLMQCommPL pl;
961 12 : pl.commitment = opt_qfc ? *opt_qfc : llmq::CFinalCommitment(Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST), quorumHash);
962 12 : pl.nHeight = nHeight;
963 12 : CMutableTransaction tx;
964 12 : tx.nVersion = CTransaction::TxVersion::SAPLING;
965 12 : tx.nType = CTransaction::TxType::LLMQCOMM;
966 12 : SetTxPayload(tx, pl);
967 24 : return tx;
968 : }
969 :
970 11 : CMutableTransaction CreateNullQfcTx(const uint256& quorumHash, int nHeight)
971 : {
972 22 : return CreateQfcTx(quorumHash, nHeight, nullopt);
973 : }
974 :
975 1 : CService ip(uint32_t i)
976 : {
977 1 : struct in_addr s;
978 1 : s.s_addr = i;
979 2 : return CService(CNetAddr(s), Params().GetDefaultPort());
980 : }
981 :
982 6 : static void ProcessQuorum(llmq::CQuorumBlockProcessor* processor, const llmq::CFinalCommitment& qfc, CNode* node, int expected_banscore = 0)
983 : {
984 6 : CDataStream vRecv(SER_NETWORK, PROTOCOL_VERSION);
985 6 : vRecv << qfc;
986 6 : int banScore{0};
987 6 : processor->ProcessMessage(node, vRecv, banScore);
988 6 : BOOST_CHECK_EQUAL(banScore, expected_banscore);
989 6 : }
990 :
991 : static NodeId id = 0;
992 :
993 : // future: split dkg_pose from qfc_invalid_paths test coverage.
994 2 : BOOST_FIXTURE_TEST_CASE(dkg_pose_and_qfc_invalid_paths, TestChain400Setup)
995 : {
996 1 : auto utxos = BuildSimpleUtxoMap(coinbaseTxns);
997 :
998 1 : CBlockIndex* chainTip = chainActive.Tip();
999 1 : int nHeight = chainTip->nHeight;
1000 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight + 2);
1001 :
1002 : // load empty list (last block before enforcement)
1003 1 : CreateAndProcessBlock({}, coinbaseKey);
1004 1 : chainTip = chainActive.Tip();
1005 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1006 :
1007 : // force mnsync complete and enable spork 8
1008 1 : g_tiertwo_sync_state.SetCurrentSyncPhase(MASTERNODE_SYNC_FINISHED);
1009 1 : int64_t nTime = GetTime() - 10;
1010 2 : const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
1011 1 : sporkManager.AddOrUpdateSporkMessage(sporkMnPayment);
1012 2 : BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));
1013 :
1014 1 : int port = 1;
1015 :
1016 2 : std::vector<uint256> dmnHashes;
1017 2 : std::map<uint256, CKey> ownerKeys;
1018 1 : std::map<uint256, CBLSSecretKey> operatorKeys;
1019 :
1020 : // register one MN per block
1021 7 : for (size_t i = 0; i < 6; i++) {
1022 12 : const CKey& ownerKey = GetRandomKey();
1023 12 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
1024 12 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey());
1025 6 : const uint256& txid = tx.GetHash();
1026 6 : dmnHashes.emplace_back(txid);
1027 6 : ownerKeys.emplace(txid, ownerKey);
1028 6 : operatorKeys.emplace(txid, operatorKey);
1029 12 : CreateAndProcessBlock({tx}, coinbaseKey);
1030 6 : chainTip = chainActive.Tip();
1031 6 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1032 12 : BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid));
1033 : }
1034 :
1035 : // enable SPORK_21
1036 2 : const CSporkMessage& spork = CSporkMessage(SPORK_21_LEGACY_MNS_MAX_HEIGHT, nHeight, GetTime());
1037 1 : sporkManager.AddOrUpdateSporkMessage(spork);
1038 2 : BOOST_CHECK(deterministicMNManager->LegacyMNObsolete(nHeight + 1));
1039 :
1040 : // Mine 20 blocks
1041 21 : for (size_t i = 0; i < 20; i++) {
1042 20 : CreateAndProcessBlock({}, coinbaseKey);
1043 20 : chainTip = chainActive.Tip();
1044 20 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1045 : }
1046 :
1047 1 : BOOST_CHECK_EQUAL(nHeight, 427);
1048 : // dkg starts at 420
1049 1 : auto& params = Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST);
1050 2 : uint256 quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1051 1 : const CBlockIndex* quorumIndex = mapBlockIndex.at(quorumHash);
1052 :
1053 : // get quorum mns
1054 2 : auto members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1055 2 : std::vector<CBLSPublicKey> pkeys;
1056 1 : std::vector<CBLSSecretKey> skeys;
1057 3 : for (size_t i = 0; i < members.size()-1; i++) { // all, except the last one...
1058 2 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1059 2 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1060 : }
1061 1 : const uint256& invalidmn_proTx = members.back()->proTxHash; // ...which must be punished.
1062 :
1063 : // create final commitment
1064 2 : llmq::CFinalCommitment qfc = CreateFinalCommitment(pkeys, skeys, quorumHash);
1065 2 : BOOST_CHECK(!qfc.IsNull());
1066 1 : {
1067 1 : LOCK(cs_main);
1068 2 : CValidationState state;
1069 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc, chainTip, state));
1070 : }
1071 :
1072 : // verify that it fails changing the key of one of the signers
1073 2 : std::vector<CBLSPublicKey> allkeys(pkeys);
1074 1 : allkeys.emplace_back(members.back()->pdmnState->pubKeyOperator.Get());
1075 2 : BOOST_CHECK(qfc.Verify(allkeys, params)); // already checked with VerifyLLMQCommitment
1076 1 : allkeys[0] = GetRandomBLSKey().GetPublicKey();
1077 2 : BOOST_CHECK(!qfc.Verify(allkeys, params));
1078 :
1079 : // receive final commitment message
1080 4 : CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, CAddress(ip(0xa0b0c001), NODE_NONE), 0, 0, "", true);
1081 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc, &dummyNode);
1082 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc)));
1083 :
1084 : // Generate blocks up to be able to mine a null qfc at block 430
1085 1 : CreateAndProcessBlock({}, coinbaseKey);
1086 1 : chainTip = chainActive.Tip();
1087 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1088 1 : BOOST_CHECK_EQUAL(nHeight, 428);
1089 :
1090 : // Coverage for the following qfc paths:
1091 : // 1) Mine a qfc with an invalid height, which should end up being rejected.
1092 : // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1093 : // 3) Mine two qfc in the same block, which should end up being rejected.
1094 : // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1095 : // 5) Mine two blocks with a null qfc.
1096 : // 6) Try to relay the valid qfc to the mempool, which should end up being rejected.
1097 : // 7a) Mine a qfc with an invalid quorum hash (invalid height), which should end up being rejected.
1098 : // 7b) Mine a qfc with an invalid quorum hash (non-existent), which should end up being rejected.
1099 : // 7c) Mine a qfc with an invalid quorum hash (forked), which should end up being rejected.
1100 : // 7d) Mine a qfc with an old quorum hash, which should end up being rejected.
1101 : // 8) Mine the final valid qfc in a block.
1102 : // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1103 :
1104 : // 1) Mine a qfc with an invalid height, which should end up being rejected.
1105 1 : CMutableTransaction nullQfcTx = CreateNullQfcTx(quorumHash, nHeight);
1106 2 : CScript coinsbaseScript = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
1107 4 : auto pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1108 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-height", nHeight);
1109 :
1110 : // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1111 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1112 1 : pblock_invalid = std::make_shared<CBlock>(
1113 3 : CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1114 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-allowed", nHeight);
1115 :
1116 : // One more block, 429.
1117 1 : CreateAndProcessBlock({}, coinbaseKey);
1118 1 : chainTip = chainActive.Tip();
1119 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1120 :
1121 : // 3) Mine two qfc in the same block, which should end up on a rejection. (one null, one valid)
1122 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1123 1 : pblock_invalid = std::make_shared<CBlock>(
1124 3 : CreateBlock({nullQfcTx}, coinsbaseScript, true, false, true));
1125 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-dup", nHeight);
1126 :
1127 : // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1128 2 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({}, coinsbaseScript, true, false, false));
1129 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-missing", nHeight);
1130 :
1131 : // 5) Mine two blocks with a null qfc.
1132 3 : for (int i = 0; i < 2; i++) {
1133 4 : const auto& block = CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false);
1134 2 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
1135 2 : chainTip = chainActive.Tip();
1136 2 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1137 2 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1138 : }
1139 1 : BOOST_CHECK_EQUAL(nHeight, 431);
1140 :
1141 : // 6) Try to relay the valid qfc to the mempool, which should end up on a rejection.
1142 1 : CTransactionRef qcTx;
1143 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinableCommitmentTx(Consensus::LLMQ_TEST, nHeight + 1, qcTx));
1144 2 : CValidationState mempoolState;
1145 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return AcceptToMemoryPool(mempool, mempoolState, qcTx, true, nullptr); ));
1146 2 : BOOST_CHECK_EQUAL(mempoolState.GetRejectReason(), "llmqcomm");
1147 :
1148 : // 7a) Mine a qfc with an invalid quorum hash (invalid height), which should end up being rejected.
1149 1 : nullQfcTx = CreateNullQfcTx(chainTip->GetBlockHash(), nHeight + 1);
1150 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1151 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-height", nHeight);
1152 :
1153 : // 7b) Mine a qfc with an invalid quorum hash (non-existent), which should end up being rejected.
1154 1 : nullQfcTx = CreateNullQfcTx(UINT256_ONE, nHeight + 1);
1155 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1156 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-hash-not-found", nHeight);
1157 :
1158 : // 7c) Mine a qfc with an invalid quorum hash (forked), which should end up being rejected.
1159 : // -- first create a secondary chain at height 420
1160 4 : CBlockIndex* pblock_419 = WITH_LOCK(cs_main, return mapBlockIndex.at(chainActive[419]->GetBlockHash()); );
1161 3 : auto pblock_forked = std::make_shared<CBlock>(CreateBlock({}, coinsbaseScript, true, false, false, pblock_419));
1162 : // increment nonce and re-solve to get a different block
1163 1 : pblock_forked->nNonce++;
1164 2 : BOOST_CHECK(SolveBlock(pblock_forked, 420));
1165 3 : BOOST_CHECK(ProcessNewBlock(pblock_forked, nullptr));
1166 1 : {
1167 1 : LOCK(cs_main);
1168 1 : const auto it = mapBlockIndex.find(pblock_forked->GetHash());
1169 2 : BOOST_CHECK(it != mapBlockIndex.end());
1170 3 : BOOST_CHECK(!chainActive.Contains(it->second));
1171 : }
1172 :
1173 : // -- then mine a commitment referencing the quorum hash from the secondary chain
1174 1 : nullQfcTx = CreateNullQfcTx(pblock_forked->GetHash(), nHeight + 1);
1175 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1176 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-hash-not-active-chain", nHeight);
1177 :
1178 : // 7d) Mine a qfc with an old quorum hash, which should end up being rejected.
1179 1 : int old_quorum_hash_height = nHeight - (nHeight % params.dkgInterval) - params.cacheDkgInterval - params.dkgInterval;
1180 2 : uint256 old_quorum_hash = chainActive[old_quorum_hash_height]->GetBlockHash();
1181 1 : nullQfcTx = CreateNullQfcTx(old_quorum_hash, nHeight + 1);
1182 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1183 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-height-old", nHeight);
1184 :
1185 : // Now check the message over the wire. future: add error rejection code.
1186 2 : auto old_qfc = CreateFinalCommitment(pkeys, skeys, old_quorum_hash);
1187 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), old_qfc, &dummyNode, 100);
1188 :
1189 : // 8) Mine the final valid qfc in a block.
1190 1 : CreateAndProcessBlock({}, coinbaseKey);
1191 1 : chainTip = chainActive.Tip();
1192 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1193 :
1194 : // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1195 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1196 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1197 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-allowed", nHeight);
1198 :
1199 : // final commitment has been mined
1200 2 : llmq::CFinalCommitment ret;
1201 1 : uint256 retMinedBlockHash;
1202 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinedCommitment(Consensus::LLMQ_TEST, quorumHash, ret, retMinedBlockHash));
1203 2 : BOOST_CHECK(chainTip->GetBlockHash() == retMinedBlockHash);
1204 3 : BOOST_CHECK(qfc.quorumPublicKey == ret.quorumPublicKey);
1205 2 : BOOST_CHECK(qfc.quorumVvecHash == ret.quorumVvecHash);
1206 3 : BOOST_CHECK(qfc.quorumSig == ret.quorumSig);
1207 3 : BOOST_CHECK(qfc.membersSig == ret.membersSig);
1208 :
1209 : // non-participating mn has been punished
1210 2 : auto punished_mn = deterministicMNManager->GetListAtChainTip().GetMN(invalidmn_proTx);
1211 1 : BOOST_CHECK_EQUAL(punished_mn->pdmnState->nPoSePenalty, 66);
1212 :
1213 : // penalty is decreased each block
1214 1 : CreateAndProcessBlock({}, coinbaseKey);
1215 1 : chainTip = chainActive.Tip();
1216 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1217 2 : punished_mn = deterministicMNManager->GetListAtChainTip().GetMN(invalidmn_proTx);
1218 1 : BOOST_CHECK_EQUAL(punished_mn->pdmnState->nPoSePenalty, 65);
1219 :
1220 : // New DKG starts at block 440. Mine till block 441 and create another valid 2-of-3 commitment
1221 9 : for (size_t i = 0; i < 8; i++) {
1222 8 : CreateAndProcessBlock({}, coinbaseKey);
1223 8 : chainTip = chainActive.Tip();
1224 8 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1225 : }
1226 1 : BOOST_CHECK_EQUAL(nHeight, 441);
1227 2 : quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1228 1 : quorumIndex = mapBlockIndex.at(quorumHash);
1229 2 : members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1230 1 : pkeys.clear();
1231 1 : skeys.clear();
1232 4 : for (size_t i = 0; i < members.size(); i++) {
1233 3 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1234 3 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1235 : }
1236 2 : std::vector<CBLSPublicKey> pkeys2(pkeys.begin(), pkeys.end()-1); // remove the last one.
1237 2 : std::vector<CBLSSecretKey> skeys2(skeys.begin(), skeys.end()-1);
1238 2 : llmq::CFinalCommitment qfc2 = CreateFinalCommitment(pkeys2, skeys2, quorumHash);
1239 2 : BOOST_CHECK(!qfc2.IsNull());
1240 1 : {
1241 1 : LOCK(cs_main);
1242 2 : CValidationState state;
1243 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc2, chainTip, state));
1244 : }
1245 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc2, &dummyNode);
1246 : // final commitment received and accepted
1247 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc2)));
1248 :
1249 : // Now receive another commitment for the same quorum hash, but with all 3 signatures
1250 1 : qfc = CreateFinalCommitment(pkeys, skeys, quorumHash);
1251 2 : BOOST_CHECK(!qfc.IsNull());
1252 1 : {
1253 1 : LOCK(cs_main);
1254 2 : CValidationState state;
1255 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc, chainTip, state));
1256 : }
1257 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc, &dummyNode);
1258 2 : BOOST_CHECK(qfc.CountSigners() > qfc2.CountSigners());
1259 :
1260 : // final commitment received, accepted, and replaced the previous one (with less members)
1261 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc)));
1262 :
1263 : // activate spork 22 and try to mine a non-null commitment
1264 1 : nTime = GetTime() - 10;
1265 1 : sporkManager.AddOrUpdateSporkMessage(CSporkMessage(SPORK_22_LLMQ_DKG_MAINTENANCE, nTime + 1, nTime));
1266 2 : BOOST_CHECK(sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE));
1267 2 : auto qtx = CreateQfcTx(quorumHash, nHeight + 1, Optional<llmq::CFinalCommitment>(qfc2));
1268 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({qtx}, coinsbaseScript, true, false, false));
1269 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-null-spork22", nHeight);
1270 :
1271 : // mine a null commitment
1272 9 : for (size_t i = 0; i < 8; i++) {
1273 8 : CreateAndProcessBlock({}, coinbaseKey);
1274 : }
1275 2 : nHeight = WITH_LOCK(cs_main, return chainActive.Height(); );
1276 1 : BOOST_CHECK_EQUAL(nHeight, 449);
1277 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1278 4 : auto pblock = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1279 1 : ProcessNewBlock(pblock, nullptr);
1280 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1281 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1282 :
1283 20 : for (size_t i = 0; i < 19; i++) {
1284 19 : CreateAndProcessBlock({}, coinbaseKey);
1285 57 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1286 19 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1287 : }
1288 1 : BOOST_CHECK_EQUAL(nHeight, 469);
1289 :
1290 : // test rejection of non-null commitments over the wire
1291 1 : {
1292 1 : LOCK(cs_main);
1293 2 : quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1294 1 : quorumIndex = mapBlockIndex.at(quorumHash);
1295 : }
1296 2 : members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1297 1 : pkeys.clear();
1298 1 : skeys.clear();
1299 4 : for (size_t i = 0; i < members.size(); i++) {
1300 3 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1301 3 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1302 : }
1303 2 : llmq::CFinalCommitment qfc3 = CreateFinalCommitment(pkeys, skeys, quorumHash);
1304 2 : BOOST_CHECK(!qfc3.IsNull());
1305 1 : {
1306 1 : LOCK(cs_main);
1307 2 : CValidationState state;
1308 2 : BOOST_CHECK(!VerifyLLMQCommitment(qfc3, chainTip, state));
1309 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-qc-not-null-spork22");
1310 : }
1311 : // final commitment not accepted
1312 1 : uint256 qfc3_hash = ::SerializeHash(qfc3);
1313 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc3, &dummyNode, 50);
1314 2 : BOOST_CHECK(!llmq::quorumBlockProcessor->HasMinableCommitment(qfc3_hash));
1315 :
1316 : // disable spork 22 and accept it
1317 1 : sporkManager.AddOrUpdateSporkMessage(CSporkMessage(SPORK_22_LLMQ_DKG_MAINTENANCE, 4070908800ULL, GetTime()));
1318 2 : BOOST_CHECK(!sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE));
1319 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc3, &dummyNode);
1320 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(qfc3_hash));
1321 :
1322 : // and mine it
1323 1 : CreateAndProcessBlock({}, coinbaseKey);
1324 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1325 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1326 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinedCommitment(Consensus::LLMQ_TEST, quorumHash, ret, retMinedBlockHash));
1327 2 : BOOST_CHECK(chainTip->GetBlockHash() == retMinedBlockHash);
1328 3 : BOOST_CHECK(qfc3.quorumPublicKey == ret.quorumPublicKey);
1329 2 : BOOST_CHECK(qfc3.quorumVvecHash == ret.quorumVvecHash);
1330 3 : BOOST_CHECK(qfc3.quorumSig == ret.quorumSig);
1331 3 : BOOST_CHECK(qfc3.membersSig == ret.membersSig);
1332 :
1333 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
1334 1 : }
1335 :
1336 : BOOST_AUTO_TEST_SUITE_END()
|