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

Generated by: LCOV version 1.14