LCOV - code coverage report
Current view: top level - src/test - evo_deterministicmns_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 880 883 99.7 %
Date: 2025-02-23 09:33:43 Functions: 72 72 100.0 %

          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()

Generated by: LCOV version 1.14