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 75 : static std::vector<COutPoint> SelectUTXOs(SimpleUTXOMap& utxos, CAmount amount, CAmount& changeRet)
50 : {
51 75 : changeRet = 0;
52 75 : amount += fee;
53 :
54 75 : std::vector<COutPoint> selectedUtxos;
55 75 : CAmount selectedAmount = 0;
56 150 : int chainHeight = WITH_LOCK(cs_main, return chainActive.Height(); );
57 75 : while (!utxos.empty()) {
58 75 : bool found = false;
59 412 : for (auto it = utxos.begin(); it != utxos.end(); ++it) {
60 412 : if (chainHeight - it->second.first < 100) {
61 337 : continue;
62 : }
63 :
64 75 : found = true;
65 75 : selectedAmount += it->second.second;
66 75 : selectedUtxos.emplace_back(it->first);
67 75 : utxos.erase(it);
68 : break;
69 : }
70 0 : BOOST_ASSERT(found);
71 75 : if (selectedAmount >= amount) {
72 75 : changeRet = selectedAmount - amount;
73 75 : break;
74 : }
75 : }
76 :
77 75 : return selectedUtxos;
78 : }
79 :
80 59 : static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utxos, const CScript& scriptPayout, const CScript& scriptChange, CAmount amount)
81 : {
82 59 : CAmount change;
83 59 : auto inputs = SelectUTXOs(utxos, amount, change);
84 118 : for (size_t i = 0; i < inputs.size(); i++) {
85 59 : tx.vin.emplace_back(inputs[i]);
86 : }
87 118 : tx.vout.emplace_back(CTxOut(amount, scriptPayout));
88 59 : if (change != 0) {
89 59 : tx.vout.emplace_back(change, scriptChange);
90 : }
91 59 : }
92 :
93 61 : static void SignTransaction(CMutableTransaction& tx, const CKey& coinbaseKey)
94 : {
95 122 : CBasicKeyStore tempKeystore;
96 61 : tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
97 :
98 123 : for (size_t i = 0; i < tx.vin.size(); i++) {
99 62 : CTransactionRef txFrom;
100 62 : uint256 hashBlock;
101 62 : BOOST_ASSERT(GetTransaction(tx.vin[i].prevout.hash, txFrom, hashBlock));
102 62 : BOOST_ASSERT(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
103 : }
104 61 : }
105 :
106 : // Makes a new tx with a single out of given amount to given destination (or coinbase address, if nullopt)
107 4 : static COutPoint CreateNewUTXO(SimpleUTXOMap& utxos,
108 : CMutableTransaction& mtx,
109 : const CKey& coinbaseKey, CAmount amount,
110 : Optional<CScript> scriptDest = nullopt)
111 : {
112 5 : const CScript& s = (scriptDest != nullopt ? *scriptDest
113 5 : : GetScriptForDestination(coinbaseKey.GetPubKey().GetID()));
114 4 : FundTransaction(mtx, utxos, s, s, amount);
115 4 : SignTransaction(mtx, coinbaseKey);
116 : int idx = -1;
117 8 : for (size_t i = 0; i < mtx.vout.size() && idx < 0; i++) {
118 4 : if (mtx.vout[i].nValue == amount) idx = i;
119 : }
120 8 : BOOST_CHECK(idx >= 0);
121 4 : return COutPoint(mtx.GetHash(), idx);
122 : }
123 :
124 44 : static CKey GetRandomKey()
125 : {
126 44 : CKey keyRet;
127 44 : keyRet.MakeNewKey(true);
128 44 : return keyRet;
129 : }
130 :
131 43 : static CBLSSecretKey GetRandomBLSKey()
132 : {
133 43 : CBLSSecretKey sk;
134 43 : sk.MakeNewKey();
135 43 : 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 39 : 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 39 : ProRegPL pl;
149 39 : pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0));
150 39 : pl.addr = LookupNumeric("1.1.1.1", port);
151 39 : pl.keyIDOwner = ownerKey.GetPubKey().GetID();
152 39 : pl.pubKeyOperator = operatorPubKey;
153 39 : pl.keyIDVoting = ownerKey.GetPubKey().GetID();
154 39 : pl.scriptPayout = scriptPayout;
155 39 : pl.nOperatorReward = operatorReward;
156 :
157 39 : CMutableTransaction tx;
158 39 : tx.nVersion = CTransaction::TxVersion::SAPLING;
159 39 : tx.nType = CTransaction::TxType::PROREG;
160 112 : FundTransaction(tx, utxos, scriptPayout,
161 78 : GetScriptForDestination(coinbaseKey.GetPubKey().GetID()),
162 39 : (optCollateralOut ? 0 : Params().GetConsensus().nMNCollateralAmt - (fInvalidCollateral ? 1 : 0)));
163 :
164 39 : pl.inputsHash = CalcTxInputsHash(tx);
165 39 : SetTxPayload(tx, pl);
166 39 : SignTransaction(tx, coinbaseKey);
167 :
168 39 : 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 53 : static CScript GenerateRandomAddress()
243 : {
244 53 : CKey key;
245 53 : key.MakeNewKey(false);
246 159 : 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 spending the declared external collateral in the same tx.
471 : // Without the guard this reaches the mempool, because AcceptToMemoryPool validates
472 : // before applying inputs to the view, and then halts block production: ConnectBlock
473 : // runs UpdateCoins first, so the collateral is spent by the time CheckSpecialTx runs
474 : // and BlockAssembler throws rather than dropping the tx.
475 1 : {
476 1 : CMutableTransaction mtx;
477 1 : const COutPoint& coll_out = CreateNewUTXO(utxos, mtx, coinbaseKey, Params().GetConsensus().nMNCollateralAmt);
478 2 : CreateAndProcessBlock({mtx}, coinbaseKey);
479 1 : chainTip = chainActive.Tip();
480 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
481 2 : Coin coll_coin;
482 2 : BOOST_CHECK(view->GetUTXOCoin(coll_out, coll_coin));
483 :
484 3 : auto tx = CreateProRegTx(Optional<COutPoint>(coll_out), utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
485 : // Spend the collateral, then make the tx valid in every other respect: refresh the
486 : // inputs hash and prove collateral ownership. Without a fully valid tx this would
487 : // be rejected by CheckStringSig and never exercise the collateral rule at all.
488 1 : tx.vin.emplace_back(coll_out);
489 2 : ProRegPL pl;
490 2 : BOOST_CHECK(GetTxPayload(tx, pl));
491 1 : pl.inputsHash = CalcTxInputsHash(CTransaction(tx));
492 1 : pl.vchSig.clear();
493 3 : BOOST_CHECK(CMessageSigner::SignMessage(pl.MakeSignString(), pl.vchSig, coinbaseKey));
494 1 : SetTxPayload(tx, pl);
495 1 : SignTransaction(tx, coinbaseKey);
496 :
497 1 : CValidationState state;
498 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
499 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-used-as-input");
500 :
501 1 : utxos.emplace(coll_out, std::make_pair(coll_coin.nHeight, coll_coin.out.nValue));
502 : }
503 : // Try to register with invalid internal collateral
504 1 : {
505 3 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, true);
506 2 : CValidationState state;
507 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
508 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-amount");
509 : }
510 : // Try to register reusing the collateral key as owner/voting key
511 1 : {
512 1 : const CKey& coll_key = GetRandomKey();
513 : // create a valid collateral
514 2 : CMutableTransaction mtx;
515 1 : const COutPoint& coll_out = CreateNewUTXO(utxos, mtx, coinbaseKey, Params().GetConsensus().nMNCollateralAmt,
516 2 : GetScriptForDestination(coll_key.GetPubKey().GetID()));
517 2 : CreateAndProcessBlock({mtx}, coinbaseKey);
518 1 : chainTip = chainActive.Tip();
519 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
520 2 : Coin coll_coin;
521 2 : BOOST_CHECK(view->GetUTXOCoin(coll_out, coll_coin));
522 1 : BOOST_CHECK_EQUAL(coll_coin.out.nValue, Params().GetConsensus().nMNCollateralAmt);
523 :
524 : // create the ProReg tx reusing the collateral key
525 2 : auto tx = CreateProRegTx(Optional<COutPoint>(coll_out), utxos, port, GenerateRandomAddress(), coinbaseKey, coll_key, GetRandomBLSKey().GetPublicKey());
526 2 : CValidationState state;
527 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
528 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collateral-reuse");
529 : }
530 : // Try to register used owner key
531 1 : {
532 1 : const CKey& ownerKey = ownerKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]);
533 2 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, GetRandomBLSKey().GetPublicKey());
534 2 : CValidationState state;
535 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
536 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key");
537 : }
538 : // Try to register used operator key
539 1 : {
540 1 : const CBLSSecretKey& operatorKey = operatorKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]);
541 2 : auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKey.GetPublicKey());
542 2 : CValidationState state;
543 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
544 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
545 : }
546 : // Try to register used IP address
547 1 : {
548 3 : auto tx = CreateProRegTx(nullopt, utxos, 1 + InsecureRandRange(port-1), GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
549 2 : CValidationState state;
550 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
551 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address");
552 : }
553 : // Block with two ProReg txes using same owner key
554 1 : {
555 1 : const CKey& ownerKey = GetRandomKey();
556 2 : const CBLSSecretKey& operatorKey1 = GetRandomBLSKey();
557 2 : const CBLSSecretKey& operatorKey2 = GetRandomBLSKey();
558 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey1.GetPublicKey());
559 2 : auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey2.GetPublicKey());
560 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
561 2 : CBlockIndex indexFake(block);
562 1 : indexFake.nHeight = nHeight;
563 1 : indexFake.pprev = chainTip;
564 2 : CValidationState state;
565 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
566 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key");
567 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
568 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
569 : }
570 : // Block with two ProReg txes using same operator key
571 1 : {
572 1 : const CKey& ownerKey1 = GetRandomKey();
573 2 : const CKey& ownerKey2 = GetRandomKey();
574 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
575 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey.GetPublicKey());
576 2 : auto tx2 = CreateProRegTx(nullopt, utxos, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey2, operatorKey.GetPublicKey());
577 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
578 2 : CBlockIndex indexFake(block);
579 1 : indexFake.nHeight = nHeight;
580 1 : indexFake.pprev = chainTip;
581 2 : CValidationState state;
582 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
583 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
584 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
585 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
586 : }
587 : // Block with two ProReg txes using same ip address
588 1 : {
589 3 : auto tx1 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
590 3 : auto tx2 = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
591 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
592 2 : CBlockIndex indexFake(block);
593 1 : indexFake.nHeight = nHeight;
594 1 : indexFake.pprev = chainTip;
595 2 : CValidationState state;
596 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
597 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address");
598 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to check reject reason
599 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
600 : }
601 :
602 : // register multiple MNs per block
603 4 : for (size_t i = 0; i < 3; i++) {
604 6 : std::vector<CMutableTransaction> txns;
605 12 : for (size_t j = 0; j < 3; j++) {
606 18 : const CKey& ownerKey = GetRandomKey();
607 18 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
608 18 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey());
609 9 : const uint256& txid = tx.GetHash();
610 9 : dmnHashes.emplace_back(txid);
611 9 : ownerKeys.emplace(txid, ownerKey);
612 9 : operatorKeys.emplace(txid, operatorKey);
613 :
614 18 : CValidationState dummyState;
615 45 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainActive.Tip(), view, dummyState); ));
616 18 : BOOST_CHECK(CheckTransactionSignature(tx));
617 9 : txns.emplace_back(tx);
618 : }
619 3 : CreateAndProcessBlock(txns, coinbaseKey);
620 3 : chainTip = chainActive.Tip();
621 3 : BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);
622 3 : auto mnList = deterministicMNManager->GetListAtChainTip();
623 12 : for (size_t j = 0; j < 3; j++) {
624 18 : BOOST_CHECK(mnList.HasMN(txns[j].GetHash()));
625 : }
626 :
627 3 : nHeight++;
628 : }
629 :
630 : // Mine 30 blocks, checking MN reward payments
631 1 : mapPayments.clear();
632 31 : for (size_t i = 0; i < 30; i++) {
633 30 : auto mnList = deterministicMNManager->GetListAtChainTip();
634 60 : auto dmnExpectedPayee = mnList.GetMNPayee();
635 60 : CBlock block = CreateAndProcessBlock({}, coinbaseKey);
636 30 : chainTip = chainActive.Tip();
637 30 : BOOST_ASSERT(!block.vtx.empty());
638 60 : BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
639 30 : mapPayments[dmnExpectedPayee->proTxHash]++;
640 :
641 30 : nHeight++;
642 : }
643 : // 30 blocks, 15 masternodes. Must have been paid exactly 2 times each.
644 1 : CheckPayments(mapPayments, 15, 2);
645 :
646 : // Check that the prev DMN winner is different that the tip one
647 2 : std::vector<CTxOut> vecMnOutsPrev;
648 2 : BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip->pprev, vecMnOutsPrev));
649 2 : std::vector<CTxOut> vecMnOutsNow;
650 2 : BOOST_CHECK(masternodePayments.GetMasternodeTxOuts(chainTip, vecMnOutsNow));
651 2 : BOOST_CHECK(vecMnOutsPrev != vecMnOutsNow);
652 :
653 : // Craft an invalid block paying to the previous block DMN again
654 1 : CBlock invalidBlock = CreateBlock({}, coinbaseKey);
655 2 : std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(invalidBlock);
656 2 : CMutableTransaction invalidCoinbaseTx = CreateCoinbaseTx(CScript(), chainTip);
657 1 : invalidCoinbaseTx.vout.clear();
658 2 : for (const CTxOut& mnOut: vecMnOutsPrev) {
659 1 : invalidCoinbaseTx.vout.emplace_back(mnOut);
660 : }
661 1 : invalidCoinbaseTx.vout.emplace_back(
662 2 : CTxOut(GetBlockValue(nHeight + 1) - GetMasternodePayment(nHeight + 1),
663 2 : GetScriptForDestination(coinbaseKey.GetPubKey().GetID())));
664 2 : pblock->vtx[0] = MakeTransactionRef(invalidCoinbaseTx);
665 1 : pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
666 1 : ProcessNewBlock(pblock, nullptr);
667 : // block not connected
668 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip());
669 2 : BOOST_CHECK(chainTip->nHeight == nHeight);
670 2 : BOOST_CHECK(chainTip->GetBlockHash() != pblock->GetHash());
671 :
672 : // ProUpServ: change masternode IP
673 1 : {
674 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
675 2 : auto tx = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), 1000, CScript(), coinbaseKey);
676 :
677 2 : CValidationState dummyState;
678 4 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, dummyState); ));
679 2 : BOOST_CHECK(CheckTransactionSignature(tx));
680 : // also verify that payloads are not malleable after they have been signed
681 1 : auto tx2 = MalleateProUpServTx(tx);
682 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, dummyState); ));
683 2 : BOOST_CHECK_EQUAL(dummyState.GetRejectReason(), "bad-protx-sig");
684 :
685 2 : CreateAndProcessBlock({tx}, coinbaseKey);
686 1 : chainTip = chainActive.Tip();
687 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1);
688 :
689 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
690 1 : BOOST_ASSERT(dmn != nullptr);
691 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->addr.GetPort(), 1000);
692 :
693 1 : nHeight++;
694 : }
695 :
696 : // ProUpServ: Try to change the IP of a masternode to the one of another registered masternode
697 1 : {
698 1 : int randomIdx = InsecureRandRange(dmnHashes.size());
699 1 : int randomIdx2 = 0;
700 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx);
701 1 : const uint256& proTx = dmnHashes[randomIdx]; // mn to update
702 2 : int new_port = deterministicMNManager->GetListAtChainTip().GetMN(dmnHashes[randomIdx2])->pdmnState->addr.GetPort();
703 :
704 2 : auto tx = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), new_port, CScript(), coinbaseKey);
705 :
706 2 : CValidationState state;
707 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
708 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-addr");
709 : }
710 :
711 : // ProUpServ: Try to change the IP of a masternode that doesn't exist
712 1 : {
713 1 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
714 2 : auto tx = CreateProUpServTx(utxos, GetRandHash(), operatorKey, port, CScript(), coinbaseKey);
715 :
716 2 : CValidationState state;
717 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
718 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-hash");
719 : }
720 :
721 : // ProUpServ: Change masternode operator payout. (new masternode created here)
722 1 : {
723 : // first create a ProRegTx with 5% reward for the operator, and mine it
724 1 : const CKey& ownerKey = GetRandomKey();
725 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
726 2 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey(), 500);
727 1 : const uint256& txid = tx.GetHash();
728 2 : CreateAndProcessBlock({tx}, coinbaseKey);
729 1 : chainTip = chainActive.Tip();
730 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
731 1 : auto mnList = deterministicMNManager->GetListAtChainTip();
732 2 : BOOST_CHECK(mnList.HasMN(txid));
733 2 : auto dmn = mnList.GetMN(txid);
734 2 : BOOST_CHECK(dmn->pdmnState->scriptOperatorPayout.empty());
735 1 : BOOST_CHECK_EQUAL(dmn->nOperatorReward, 500);
736 :
737 : // then send the ProUpServTx and check the operator payee
738 2 : const CScript& operatorPayee = GenerateRandomAddress();
739 2 : auto tx2 = CreateProUpServTx(utxos, txid, operatorKey, (port-1), operatorPayee, coinbaseKey);
740 2 : CreateAndProcessBlock({tx2}, coinbaseKey);
741 1 : chainTip = chainActive.Tip();
742 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
743 2 : dmn = deterministicMNManager->GetListAtChainTip().GetMN(txid);
744 1 : BOOST_ASSERT(dmn != nullptr);
745 2 : BOOST_CHECK(dmn->pdmnState->scriptOperatorPayout == operatorPayee);
746 : }
747 :
748 : // ProUpServ: Try to change masternode operator payout when the operator reward is zero
749 1 : {
750 1 : const CScript& operatorPayee = GenerateRandomAddress();
751 2 : auto tx = CreateProUpServTx(utxos, dmnHashes[0], operatorKeys.at(dmnHashes[0]), 1, operatorPayee, coinbaseKey);
752 2 : CValidationState state;
753 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
754 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-operator-payee");
755 : }
756 :
757 : // Block including
758 : // - (1) ProRegTx registering a masternode
759 : // - (2) ProUpServTx changing the IP of another masternode, to the one used by (1)
760 1 : {
761 3 : auto tx1 = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey());
762 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
763 2 : auto tx2 = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), (port-1), CScript(), coinbaseKey);
764 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
765 2 : CBlockIndex indexFake(block);
766 1 : indexFake.nHeight = nHeight;
767 1 : indexFake.pprev = chainTip;
768 2 : CValidationState state;
769 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ));
770 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-addr");
771 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr); // todo: move to ProcessBlockAndCheckRejectionReason.
772 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
773 : }
774 :
775 : // ProUpReg: change voting key, operator key and payout address
776 1 : {
777 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
778 1 : CBLSSecretKey new_operatorKey = GetRandomBLSKey();
779 2 : const CKey& new_votingKey = GetRandomKey();
780 2 : const CScript& new_payee = GenerateRandomAddress();
781 : // try first with wrong owner key
782 2 : CValidationState state;
783 2 : auto tx = CreateProUpRegTx(utxos, proTx, GetRandomKey(), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
784 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "ProUpReg verifies with wrong owner key");
785 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
786 : // then use the proper key
787 1 : state = CValidationState();
788 1 : tx = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
789 5 : BOOST_CHECK_MESSAGE(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), state.GetRejectReason());
790 2 : BOOST_CHECK_MESSAGE(CheckTransactionSignature(tx), "ProUpReg signature verification failed");
791 : // also verify that payloads are not malleable after they have been signed
792 1 : auto tx2 = MalleateProTxPayout<ProUpRegPL>(tx);
793 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, state); ), "Malleated ProUpReg accepted");
794 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
795 :
796 2 : CreateAndProcessBlock({tx}, coinbaseKey);
797 1 : chainTip = chainActive.Tip();
798 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
799 :
800 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
801 1 : BOOST_ASSERT(dmn != nullptr);
802 3 : BOOST_CHECK_MESSAGE(dmn->pdmnState->pubKeyOperator.Get() == new_operatorKey.GetPublicKey(), "mn operator key not changed");
803 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->keyIDVoting == new_votingKey.GetPubKey().GetID(), "mn voting key not changed");
804 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptPayout == new_payee, "mn script payout not changed");
805 :
806 1 : operatorKeys[proTx] = std::move(new_operatorKey);
807 :
808 : // check that changing the operator key puts the MN in PoSe banned state
809 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->addr == CService(), "IP address not cleared after changing operator");
810 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptOperatorPayout.empty(), "operator payee not empty after changing operator");
811 2 : BOOST_CHECK(dmn->IsPoSeBanned());
812 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, nHeight);
813 :
814 : // revive the MN
815 2 : auto tx3 = CreateProUpServTx(utxos, proTx, operatorKeys.at(proTx), 2000, CScript(), coinbaseKey);
816 2 : CreateAndProcessBlock({tx3}, coinbaseKey);
817 1 : chainTip = chainActive.Tip();
818 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
819 2 : dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
820 :
821 : // check updated dmn state
822 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->addr.GetPort(), 2000);
823 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, -1);
824 2 : BOOST_CHECK(!dmn->IsPoSeBanned());
825 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeRevivedHeight, nHeight);
826 :
827 : // Mine 32 blocks, checking MN reward payments
828 1 : mapPayments.clear();
829 33 : for (size_t i = 0; i < 32; i++) {
830 64 : auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
831 64 : CBlock block = CreateAndProcessBlock({}, coinbaseKey);
832 32 : chainTip = chainActive.Tip();
833 32 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
834 32 : BOOST_ASSERT(!block.vtx.empty());
835 64 : BOOST_CHECK(IsMNPayeeInBlock(block, dmnExpectedPayee->pdmnState->scriptPayout));
836 32 : mapPayments[dmnExpectedPayee->proTxHash]++;
837 : }
838 : // 16 masternodes: 2 rewards each
839 1 : CheckPayments(mapPayments, 16, 2);
840 : }
841 :
842 : // ProUpReg: Try to change the voting key of a masternode that doesn't exist
843 1 : {
844 1 : const CKey& votingKey = GetRandomKey();
845 2 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
846 3 : auto tx = CreateProUpRegTx(utxos, GetRandHash(), GetRandomKey(), operatorKey.GetPublicKey(), votingKey, GenerateRandomAddress(), coinbaseKey);
847 :
848 2 : CValidationState state;
849 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "Accepted ProUpReg with invalid protx hash");
850 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-hash");
851 : }
852 :
853 : // ProUpReg: Try to change the operator key of a masternode to the one of another registered masternode
854 1 : {
855 1 : int randomIdx = InsecureRandRange(dmnHashes.size());
856 1 : int randomIdx2 = 0;
857 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx);
858 1 : const uint256& proTx = dmnHashes[randomIdx]; // mn to update
859 1 : const CBLSSecretKey& new_operatorKey = operatorKeys.at(dmnHashes[randomIdx2]);
860 :
861 3 : auto tx = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), GetRandomKey(), GenerateRandomAddress(), coinbaseKey);
862 :
863 2 : CValidationState state;
864 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "Accepted ProUpReg with duplicate operator key");
865 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-key");
866 : }
867 :
868 : // Block with two ProUpReg txes using same operator key
869 1 : {
870 1 : int randomIdx1 = InsecureRandRange(dmnHashes.size());
871 1 : int randomIdx2 = 0;
872 1 : do { randomIdx2 = InsecureRandRange(dmnHashes.size()); } while (randomIdx2 == randomIdx1);
873 1 : const uint256& proTx1 = dmnHashes[randomIdx1];
874 1 : const uint256& proTx2 = dmnHashes[randomIdx2];
875 1 : BOOST_ASSERT(proTx1 != proTx2);
876 1 : const CBLSSecretKey& new_operatorKey = GetRandomBLSKey();
877 2 : const CKey& new_votingKey = GetRandomKey();
878 2 : const CScript& new_payee = GenerateRandomAddress();
879 2 : auto tx1 = CreateProUpRegTx(utxos, proTx1, ownerKeys.at(proTx1), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
880 2 : auto tx2 = CreateProUpRegTx(utxos, proTx2, ownerKeys.at(proTx2), new_operatorKey.GetPublicKey(), new_votingKey, new_payee, coinbaseKey);
881 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
882 2 : CBlockIndex indexFake(block);
883 1 : indexFake.nHeight = nHeight;
884 1 : indexFake.pprev = chainTip;
885 2 : CValidationState state;
886 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ),
887 : "Accepted block with duplicate operator key in ProUpReg txes");
888 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
889 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
890 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
891 : }
892 :
893 : // Block including
894 : // - (1) ProRegTx registering a masternode
895 : // - (2) ProUpRegTx changing the operator key of another masternode, to the one used by (1)
896 1 : {
897 1 : const CBLSSecretKey& new_operatorKey = GetRandomBLSKey();
898 2 : auto tx1 = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), new_operatorKey.GetPublicKey());
899 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
900 3 : auto tx2 = CreateProUpRegTx(utxos, proTx, ownerKeys.at(proTx), new_operatorKey.GetPublicKey(), GetRandomKey(), GenerateRandomAddress(), coinbaseKey);
901 4 : CBlock block = CreateBlock({tx1, tx2}, coinbaseKey);
902 2 : CBlockIndex indexFake(block);
903 1 : indexFake.nHeight = nHeight;
904 1 : indexFake.pprev = chainTip;
905 2 : CValidationState state;
906 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return ProcessSpecialTxsInBlock(block, &indexFake, view, state, true); ),
907 : "Accepted block with duplicate operator key in ProReg+ProUpReg txes");
908 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key");
909 1 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
910 2 : BOOST_CHECK_EQUAL(WITH_LOCK(cs_main, return chainActive.Height(); ), nHeight); // bad block not connected
911 : }
912 :
913 : // ProUpRev: revoke masternode service
914 1 : {
915 1 : const uint256& proTx = dmnHashes[InsecureRandRange(dmnHashes.size())]; // pick one at random
916 1 : ProUpRevPL::RevocationReason reason = ProUpRevPL::RevocationReason::REASON_TERMINATION_OF_SERVICE;
917 : // try first with wrong operator key
918 2 : CValidationState state;
919 2 : auto tx = CreateProUpRevTx(utxos, proTx, reason, GetRandomBLSKey(), coinbaseKey);
920 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ), "ProUpReg verifies with wrong owner key");
921 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
922 : // then use the proper key
923 1 : state = CValidationState();
924 1 : tx = CreateProUpRevTx(utxos, proTx, reason, operatorKeys.at(proTx), coinbaseKey);
925 4 : BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state); ));
926 2 : BOOST_CHECK_MESSAGE(CheckTransactionSignature(tx), "ProUpReg signature verification failed");
927 : // also verify that payloads are not malleable after they have been signed
928 1 : auto tx2 = MalleateProUpRevTx(tx);
929 4 : BOOST_CHECK_MESSAGE(!WITH_LOCK(cs_main, return CheckSpecialTx(tx2, chainTip, view, state); ), "Malleated ProUpReg accepted");
930 2 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-sig");
931 :
932 2 : CreateAndProcessBlock({tx}, coinbaseKey);
933 1 : chainTip = chainActive.Tip();
934 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
935 :
936 2 : auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(proTx);
937 1 : BOOST_ASSERT(dmn != nullptr);
938 2 : BOOST_CHECK_MESSAGE(!dmn->pdmnState->pubKeyOperator.Get().IsValid(), "mn operator key not removed");
939 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->addr == CService(), "mn IP address not removed");
940 2 : BOOST_CHECK_MESSAGE(dmn->pdmnState->scriptOperatorPayout.empty(), "mn operator payout not removed");
941 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nRevocationReason, reason);
942 2 : BOOST_CHECK(dmn->IsPoSeBanned());
943 1 : BOOST_CHECK_EQUAL(dmn->pdmnState->nPoSeBanHeight, nHeight);
944 : }
945 :
946 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
947 1 : }
948 :
949 : // Dummy commitment where the DKG shares are replaced with the operator keys of each member.
950 : // members at index skeys.size(), ..., llmqType.size - 1 are invalid
951 5 : static llmq::CFinalCommitment CreateFinalCommitment(std::vector<CBLSPublicKey>& pkeys,
952 : std::vector<CBLSSecretKey>& skeys,
953 : const uint256& quorumHash)
954 : {
955 5 : size_t m = skeys.size();
956 5 : BOOST_ASSERT(pkeys.size() == m);
957 :
958 5 : llmq::CFinalCommitment qfl;
959 5 : qfl.llmqType = (uint8_t)Consensus::LLMQ_TEST;
960 5 : const auto& params = Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST);
961 5 : BOOST_ASSERT(m <= (size_t) params.size); // m-of-n
962 :
963 : // non-included members are marked invalid
964 5 : qfl.signers.resize(params.size);
965 5 : qfl.validMembers.resize(params.size);
966 20 : for (size_t i = 0; i < (size_t) params.size; i++) {
967 15 : qfl.signers[i] = i < m;
968 30 : qfl.validMembers[i] = i < m;
969 : }
970 :
971 5 : qfl.quorumHash = quorumHash;
972 :
973 : // create dummy quorum keys, just aggregating operator BLS keys
974 5 : qfl.quorumPublicKey = CBLSPublicKey::AggregateInsecure(pkeys);
975 :
976 : // use dummy non-null verification vector hash
977 5 : qfl.quorumVvecHash = UINT256_ONE;
978 :
979 : // add signatures
980 5 : const uint256& commitmentHash = llmq::utils::BuildCommitmentHash((Consensus::LLMQType)qfl.llmqType, quorumHash, qfl.validMembers, qfl.quorumPublicKey, qfl.quorumVvecHash);
981 5 : std::vector<CBLSSignature> sigs;
982 17 : for (size_t i = 0; i < m; i++) {
983 12 : sigs.emplace_back(skeys[i].Sign(commitmentHash));
984 : }
985 5 : qfl.membersSig = CBLSSignature::AggregateSecure(sigs, pkeys, commitmentHash);
986 5 : qfl.quorumSig = CBLSSecretKey::AggregateInsecure(skeys).Sign(commitmentHash);
987 :
988 10 : return qfl;
989 : }
990 :
991 12 : CMutableTransaction CreateQfcTx(const uint256& quorumHash, int nHeight, Optional<llmq::CFinalCommitment> opt_qfc)
992 : {
993 12 : llmq::LLMQCommPL pl;
994 12 : pl.commitment = opt_qfc ? *opt_qfc : llmq::CFinalCommitment(Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST), quorumHash);
995 12 : pl.nHeight = nHeight;
996 12 : CMutableTransaction tx;
997 12 : tx.nVersion = CTransaction::TxVersion::SAPLING;
998 12 : tx.nType = CTransaction::TxType::LLMQCOMM;
999 12 : SetTxPayload(tx, pl);
1000 24 : return tx;
1001 : }
1002 :
1003 11 : CMutableTransaction CreateNullQfcTx(const uint256& quorumHash, int nHeight)
1004 : {
1005 22 : return CreateQfcTx(quorumHash, nHeight, nullopt);
1006 : }
1007 :
1008 1 : CService ip(uint32_t i)
1009 : {
1010 1 : struct in_addr s;
1011 1 : s.s_addr = i;
1012 2 : return CService(CNetAddr(s), Params().GetDefaultPort());
1013 : }
1014 :
1015 6 : static void ProcessQuorum(llmq::CQuorumBlockProcessor* processor, const llmq::CFinalCommitment& qfc, CNode* node, int expected_banscore = 0)
1016 : {
1017 6 : CDataStream vRecv(SER_NETWORK, PROTOCOL_VERSION);
1018 6 : vRecv << qfc;
1019 6 : int banScore{0};
1020 6 : processor->ProcessMessage(node, vRecv, banScore);
1021 6 : BOOST_CHECK_EQUAL(banScore, expected_banscore);
1022 6 : }
1023 :
1024 : static NodeId id = 0;
1025 :
1026 : // future: split dkg_pose from qfc_invalid_paths test coverage.
1027 2 : BOOST_FIXTURE_TEST_CASE(dkg_pose_and_qfc_invalid_paths, TestChain400Setup)
1028 : {
1029 1 : auto utxos = BuildSimpleUtxoMap(coinbaseTxns);
1030 :
1031 1 : CBlockIndex* chainTip = chainActive.Tip();
1032 1 : int nHeight = chainTip->nHeight;
1033 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight + 2);
1034 :
1035 : // load empty list (last block before enforcement)
1036 1 : CreateAndProcessBlock({}, coinbaseKey);
1037 1 : chainTip = chainActive.Tip();
1038 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1039 :
1040 : // force mnsync complete and enable spork 8
1041 1 : g_tiertwo_sync_state.SetCurrentSyncPhase(MASTERNODE_SYNC_FINISHED);
1042 1 : int64_t nTime = GetTime() - 10;
1043 2 : const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
1044 1 : sporkManager.AddOrUpdateSporkMessage(sporkMnPayment);
1045 2 : BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));
1046 :
1047 1 : int port = 1;
1048 :
1049 2 : std::vector<uint256> dmnHashes;
1050 2 : std::map<uint256, CKey> ownerKeys;
1051 1 : std::map<uint256, CBLSSecretKey> operatorKeys;
1052 :
1053 : // register one MN per block
1054 7 : for (size_t i = 0; i < 6; i++) {
1055 12 : const CKey& ownerKey = GetRandomKey();
1056 12 : const CBLSSecretKey& operatorKey = GetRandomBLSKey();
1057 12 : auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey.GetPublicKey());
1058 6 : const uint256& txid = tx.GetHash();
1059 6 : dmnHashes.emplace_back(txid);
1060 6 : ownerKeys.emplace(txid, ownerKey);
1061 6 : operatorKeys.emplace(txid, operatorKey);
1062 12 : CreateAndProcessBlock({tx}, coinbaseKey);
1063 6 : chainTip = chainActive.Tip();
1064 6 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1065 12 : BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid));
1066 : }
1067 :
1068 : // enable SPORK_21
1069 2 : const CSporkMessage& spork = CSporkMessage(SPORK_21_LEGACY_MNS_MAX_HEIGHT, nHeight, GetTime());
1070 1 : sporkManager.AddOrUpdateSporkMessage(spork);
1071 2 : BOOST_CHECK(deterministicMNManager->LegacyMNObsolete(nHeight + 1));
1072 :
1073 : // Mine 20 blocks
1074 21 : for (size_t i = 0; i < 20; i++) {
1075 20 : CreateAndProcessBlock({}, coinbaseKey);
1076 20 : chainTip = chainActive.Tip();
1077 20 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1078 : }
1079 :
1080 1 : BOOST_CHECK_EQUAL(nHeight, 427);
1081 : // dkg starts at 420
1082 1 : auto& params = Params().GetConsensus().llmqs.at(Consensus::LLMQ_TEST);
1083 2 : uint256 quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1084 1 : const CBlockIndex* quorumIndex = mapBlockIndex.at(quorumHash);
1085 :
1086 : // get quorum mns
1087 2 : auto members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1088 2 : std::vector<CBLSPublicKey> pkeys;
1089 1 : std::vector<CBLSSecretKey> skeys;
1090 3 : for (size_t i = 0; i < members.size()-1; i++) { // all, except the last one...
1091 2 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1092 2 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1093 : }
1094 1 : const uint256& invalidmn_proTx = members.back()->proTxHash; // ...which must be punished.
1095 :
1096 : // create final commitment
1097 2 : llmq::CFinalCommitment qfc = CreateFinalCommitment(pkeys, skeys, quorumHash);
1098 2 : BOOST_CHECK(!qfc.IsNull());
1099 1 : {
1100 1 : LOCK(cs_main);
1101 2 : CValidationState state;
1102 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc, chainTip, state));
1103 : }
1104 :
1105 : // verify that it fails changing the key of one of the signers
1106 2 : std::vector<CBLSPublicKey> allkeys(pkeys);
1107 1 : allkeys.emplace_back(members.back()->pdmnState->pubKeyOperator.Get());
1108 2 : BOOST_CHECK(qfc.Verify(allkeys, params)); // already checked with VerifyLLMQCommitment
1109 1 : allkeys[0] = GetRandomBLSKey().GetPublicKey();
1110 2 : BOOST_CHECK(!qfc.Verify(allkeys, params));
1111 :
1112 : // receive final commitment message
1113 4 : CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, CAddress(ip(0xa0b0c001), NODE_NONE), 0, 0, "", true);
1114 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc, &dummyNode);
1115 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc)));
1116 :
1117 : // Generate blocks up to be able to mine a null qfc at block 430
1118 1 : CreateAndProcessBlock({}, coinbaseKey);
1119 1 : chainTip = chainActive.Tip();
1120 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1121 1 : BOOST_CHECK_EQUAL(nHeight, 428);
1122 :
1123 : // Coverage for the following qfc paths:
1124 : // 1) Mine a qfc with an invalid height, which should end up being rejected.
1125 : // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1126 : // 3) Mine two qfc in the same block, which should end up being rejected.
1127 : // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1128 : // 5) Mine two blocks with a null qfc.
1129 : // 6) Try to relay the valid qfc to the mempool, which should end up being rejected.
1130 : // 7a) Mine a qfc with an invalid quorum hash (invalid height), which should end up being rejected.
1131 : // 7b) Mine a qfc with an invalid quorum hash (non-existent), which should end up being rejected.
1132 : // 7c) Mine a qfc with an invalid quorum hash (forked), which should end up being rejected.
1133 : // 7d) Mine a qfc with an old quorum hash, which should end up being rejected.
1134 : // 8) Mine the final valid qfc in a block.
1135 : // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1136 :
1137 : // 1) Mine a qfc with an invalid height, which should end up being rejected.
1138 1 : CMutableTransaction nullQfcTx = CreateNullQfcTx(quorumHash, nHeight);
1139 2 : CScript coinsbaseScript = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
1140 4 : auto pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1141 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-height", nHeight);
1142 :
1143 : // 2) Mine a null qfc before the mining phase, which should end up being rejected.
1144 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1145 1 : pblock_invalid = std::make_shared<CBlock>(
1146 3 : CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1147 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-allowed", nHeight);
1148 :
1149 : // One more block, 429.
1150 1 : CreateAndProcessBlock({}, coinbaseKey);
1151 1 : chainTip = chainActive.Tip();
1152 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1153 :
1154 : // 3) Mine two qfc in the same block, which should end up on a rejection. (one null, one valid)
1155 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1156 1 : pblock_invalid = std::make_shared<CBlock>(
1157 3 : CreateBlock({nullQfcTx}, coinsbaseScript, true, false, true));
1158 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-dup", nHeight);
1159 :
1160 : // 4) Mine block without qfc during the mining phase, which should end up being rejected.
1161 2 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({}, coinsbaseScript, true, false, false));
1162 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-missing", nHeight);
1163 :
1164 : // 5) Mine two blocks with a null qfc.
1165 3 : for (int i = 0; i < 2; i++) {
1166 4 : const auto& block = CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false);
1167 2 : ProcessNewBlock(std::make_shared<const CBlock>(block), nullptr);
1168 2 : chainTip = chainActive.Tip();
1169 2 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1170 2 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1171 : }
1172 1 : BOOST_CHECK_EQUAL(nHeight, 431);
1173 :
1174 : // 6) Try to relay the valid qfc to the mempool, which should end up on a rejection.
1175 1 : CTransactionRef qcTx;
1176 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinableCommitmentTx(Consensus::LLMQ_TEST, nHeight + 1, qcTx));
1177 2 : CValidationState mempoolState;
1178 4 : BOOST_CHECK(!WITH_LOCK(cs_main, return AcceptToMemoryPool(mempool, mempoolState, qcTx, true, nullptr); ));
1179 2 : BOOST_CHECK_EQUAL(mempoolState.GetRejectReason(), "llmqcomm");
1180 :
1181 : // 7a) Mine a qfc with an invalid quorum hash (invalid height), which should end up being rejected.
1182 1 : nullQfcTx = CreateNullQfcTx(chainTip->GetBlockHash(), nHeight + 1);
1183 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1184 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-height", nHeight);
1185 :
1186 : // 7b) Mine a qfc with an invalid quorum hash (non-existent), which should end up being rejected.
1187 1 : nullQfcTx = CreateNullQfcTx(UINT256_ONE, nHeight + 1);
1188 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1189 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-hash-not-found", nHeight);
1190 :
1191 : // 7c) Mine a qfc with an invalid quorum hash (forked), which should end up being rejected.
1192 : // -- first create a secondary chain at height 420
1193 4 : CBlockIndex* pblock_419 = WITH_LOCK(cs_main, return mapBlockIndex.at(chainActive[419]->GetBlockHash()); );
1194 3 : auto pblock_forked = std::make_shared<CBlock>(CreateBlock({}, coinsbaseScript, true, false, false, pblock_419));
1195 : // increment nonce and re-solve to get a different block
1196 1 : pblock_forked->nNonce++;
1197 2 : BOOST_CHECK(SolveBlock(pblock_forked, 420));
1198 3 : BOOST_CHECK(ProcessNewBlock(pblock_forked, nullptr));
1199 1 : {
1200 1 : LOCK(cs_main);
1201 1 : const auto it = mapBlockIndex.find(pblock_forked->GetHash());
1202 2 : BOOST_CHECK(it != mapBlockIndex.end());
1203 3 : BOOST_CHECK(!chainActive.Contains(it->second));
1204 : }
1205 :
1206 : // -- then mine a commitment referencing the quorum hash from the secondary chain
1207 1 : nullQfcTx = CreateNullQfcTx(pblock_forked->GetHash(), nHeight + 1);
1208 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1209 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-hash-not-active-chain", nHeight);
1210 :
1211 : // 7d) Mine a qfc with an old quorum hash, which should end up being rejected.
1212 1 : int old_quorum_hash_height = nHeight - (nHeight % params.dkgInterval) - params.cacheDkgInterval - params.dkgInterval;
1213 2 : uint256 old_quorum_hash = chainActive[old_quorum_hash_height]->GetBlockHash();
1214 1 : nullQfcTx = CreateNullQfcTx(old_quorum_hash, nHeight + 1);
1215 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1216 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-quorum-height-old", nHeight);
1217 :
1218 : // Now check the message over the wire. future: add error rejection code.
1219 2 : auto old_qfc = CreateFinalCommitment(pkeys, skeys, old_quorum_hash);
1220 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), old_qfc, &dummyNode, 100);
1221 :
1222 : // 8) Mine the final valid qfc in a block.
1223 1 : CreateAndProcessBlock({}, coinbaseKey);
1224 1 : chainTip = chainActive.Tip();
1225 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1226 :
1227 : // 9) Mine a null qfc after mining a valid qfc, which should end up being rejected.
1228 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1229 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1230 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-allowed", nHeight);
1231 :
1232 : // final commitment has been mined
1233 2 : llmq::CFinalCommitment ret;
1234 1 : uint256 retMinedBlockHash;
1235 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinedCommitment(Consensus::LLMQ_TEST, quorumHash, ret, retMinedBlockHash));
1236 2 : BOOST_CHECK(chainTip->GetBlockHash() == retMinedBlockHash);
1237 3 : BOOST_CHECK(qfc.quorumPublicKey == ret.quorumPublicKey);
1238 2 : BOOST_CHECK(qfc.quorumVvecHash == ret.quorumVvecHash);
1239 3 : BOOST_CHECK(qfc.quorumSig == ret.quorumSig);
1240 3 : BOOST_CHECK(qfc.membersSig == ret.membersSig);
1241 :
1242 : // non-participating mn has been punished
1243 2 : auto punished_mn = deterministicMNManager->GetListAtChainTip().GetMN(invalidmn_proTx);
1244 1 : BOOST_CHECK_EQUAL(punished_mn->pdmnState->nPoSePenalty, 66);
1245 :
1246 : // penalty is decreased each block
1247 1 : CreateAndProcessBlock({}, coinbaseKey);
1248 1 : chainTip = chainActive.Tip();
1249 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1250 2 : punished_mn = deterministicMNManager->GetListAtChainTip().GetMN(invalidmn_proTx);
1251 1 : BOOST_CHECK_EQUAL(punished_mn->pdmnState->nPoSePenalty, 65);
1252 :
1253 : // New DKG starts at block 440. Mine till block 441 and create another valid 2-of-3 commitment
1254 9 : for (size_t i = 0; i < 8; i++) {
1255 8 : CreateAndProcessBlock({}, coinbaseKey);
1256 8 : chainTip = chainActive.Tip();
1257 8 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1258 : }
1259 1 : BOOST_CHECK_EQUAL(nHeight, 441);
1260 2 : quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1261 1 : quorumIndex = mapBlockIndex.at(quorumHash);
1262 2 : members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1263 1 : pkeys.clear();
1264 1 : skeys.clear();
1265 4 : for (size_t i = 0; i < members.size(); i++) {
1266 3 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1267 3 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1268 : }
1269 2 : std::vector<CBLSPublicKey> pkeys2(pkeys.begin(), pkeys.end()-1); // remove the last one.
1270 2 : std::vector<CBLSSecretKey> skeys2(skeys.begin(), skeys.end()-1);
1271 2 : llmq::CFinalCommitment qfc2 = CreateFinalCommitment(pkeys2, skeys2, quorumHash);
1272 2 : BOOST_CHECK(!qfc2.IsNull());
1273 1 : {
1274 1 : LOCK(cs_main);
1275 2 : CValidationState state;
1276 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc2, chainTip, state));
1277 : }
1278 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc2, &dummyNode);
1279 : // final commitment received and accepted
1280 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc2)));
1281 :
1282 : // Now receive another commitment for the same quorum hash, but with all 3 signatures
1283 1 : qfc = CreateFinalCommitment(pkeys, skeys, quorumHash);
1284 2 : BOOST_CHECK(!qfc.IsNull());
1285 1 : {
1286 1 : LOCK(cs_main);
1287 2 : CValidationState state;
1288 2 : BOOST_CHECK(VerifyLLMQCommitment(qfc, chainTip, state));
1289 : }
1290 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc, &dummyNode);
1291 2 : BOOST_CHECK(qfc.CountSigners() > qfc2.CountSigners());
1292 :
1293 : // final commitment received, accepted, and replaced the previous one (with less members)
1294 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(::SerializeHash(qfc)));
1295 :
1296 : // activate spork 22 and try to mine a non-null commitment
1297 1 : nTime = GetTime() - 10;
1298 1 : sporkManager.AddOrUpdateSporkMessage(CSporkMessage(SPORK_22_LLMQ_DKG_MAINTENANCE, nTime + 1, nTime));
1299 2 : BOOST_CHECK(sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE));
1300 2 : auto qtx = CreateQfcTx(quorumHash, nHeight + 1, Optional<llmq::CFinalCommitment>(qfc2));
1301 3 : pblock_invalid = std::make_shared<CBlock>(CreateBlock({qtx}, coinsbaseScript, true, false, false));
1302 1 : ProcessBlockAndCheckRejectionReason(pblock_invalid, "bad-qc-not-null-spork22", nHeight);
1303 :
1304 : // mine a null commitment
1305 9 : for (size_t i = 0; i < 8; i++) {
1306 8 : CreateAndProcessBlock({}, coinbaseKey);
1307 : }
1308 2 : nHeight = WITH_LOCK(cs_main, return chainActive.Height(); );
1309 1 : BOOST_CHECK_EQUAL(nHeight, 449);
1310 1 : nullQfcTx = CreateNullQfcTx(quorumHash, nHeight + 1);
1311 4 : auto pblock = std::make_shared<CBlock>(CreateBlock({nullQfcTx}, coinsbaseScript, true, false, false));
1312 1 : ProcessNewBlock(pblock, nullptr);
1313 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1314 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1315 :
1316 20 : for (size_t i = 0; i < 19; i++) {
1317 19 : CreateAndProcessBlock({}, coinbaseKey);
1318 57 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1319 19 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1320 : }
1321 1 : BOOST_CHECK_EQUAL(nHeight, 469);
1322 :
1323 : // test rejection of non-null commitments over the wire
1324 1 : {
1325 1 : LOCK(cs_main);
1326 2 : quorumHash = chainActive[nHeight - (nHeight % params.dkgInterval)]->GetBlockHash();
1327 1 : quorumIndex = mapBlockIndex.at(quorumHash);
1328 : }
1329 2 : members = deterministicMNManager->GetAllQuorumMembers(Consensus::LLMQ_TEST, quorumIndex);
1330 1 : pkeys.clear();
1331 1 : skeys.clear();
1332 4 : for (size_t i = 0; i < members.size(); i++) {
1333 3 : pkeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
1334 3 : skeys.emplace_back(operatorKeys.at(members[i]->proTxHash));
1335 : }
1336 2 : llmq::CFinalCommitment qfc3 = CreateFinalCommitment(pkeys, skeys, quorumHash);
1337 2 : BOOST_CHECK(!qfc3.IsNull());
1338 1 : {
1339 1 : LOCK(cs_main);
1340 2 : CValidationState state;
1341 2 : BOOST_CHECK(!VerifyLLMQCommitment(qfc3, chainTip, state));
1342 3 : BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-qc-not-null-spork22");
1343 : }
1344 : // final commitment not accepted
1345 1 : uint256 qfc3_hash = ::SerializeHash(qfc3);
1346 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc3, &dummyNode, 50);
1347 2 : BOOST_CHECK(!llmq::quorumBlockProcessor->HasMinableCommitment(qfc3_hash));
1348 :
1349 : // disable spork 22 and accept it
1350 1 : sporkManager.AddOrUpdateSporkMessage(CSporkMessage(SPORK_22_LLMQ_DKG_MAINTENANCE, 4070908800ULL, GetTime()));
1351 2 : BOOST_CHECK(!sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE));
1352 1 : ProcessQuorum(llmq::quorumBlockProcessor.get(), qfc3, &dummyNode);
1353 2 : BOOST_CHECK(llmq::quorumBlockProcessor->HasMinableCommitment(qfc3_hash));
1354 :
1355 : // and mine it
1356 1 : CreateAndProcessBlock({}, coinbaseKey);
1357 3 : chainTip = WITH_LOCK(cs_main, return chainActive.Tip(); );
1358 1 : BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight);
1359 2 : BOOST_CHECK(llmq::quorumBlockProcessor->GetMinedCommitment(Consensus::LLMQ_TEST, quorumHash, ret, retMinedBlockHash));
1360 2 : BOOST_CHECK(chainTip->GetBlockHash() == retMinedBlockHash);
1361 3 : BOOST_CHECK(qfc3.quorumPublicKey == ret.quorumPublicKey);
1362 2 : BOOST_CHECK(qfc3.quorumVvecHash == ret.quorumVvecHash);
1363 3 : BOOST_CHECK(qfc3.quorumSig == ret.quorumSig);
1364 3 : BOOST_CHECK(qfc3.membersSig == ret.membersSig);
1365 :
1366 1 : UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
1367 1 : }
1368 :
1369 : BOOST_AUTO_TEST_SUITE_END()
|