LCOV - code coverage report
Current view: top level - src/test - evo_deterministicmns_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 789 793 99.5 %
Date: 2026-06-25 07:23:43 Functions: 85 85 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <test/util/setup_common.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <consensus/validation.h>
       9             : #include <deploymentstatus.h>
      10             : #include <evo/deterministicmns.h>
      11             : #include <evo/providertx.h>
      12             : #include <evo/simplifiedmns.h>
      13             : #include <evo/specialtx.h>
      14             : #include <evo/specialtxman.h>
      15             : #include <llmq/context.h>
      16             : #include <messagesigner.h>
      17             : #include <node/transaction.h>
      18             : #include <policy/policy.h>
      19             : #include <script/interpreter.h>
      20             : #include <script/sign.h>
      21             : #include <script/signingprovider.h>
      22             : #include <script/standard.h>
      23             : #include <test/util/txmempool.h>
      24             : #include <txmempool.h>
      25             : #include <validation.h>
      26             : 
      27             : #include <boost/test/unit_test.hpp>
      28             : 
      29             : #include <vector>
      30             : 
      31             : using node::GetTransaction;
      32             : 
      33             : using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
      34             : 
      35           9 : static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransactionRef>& txs)
      36             : {
      37           9 :     SimpleUTXOMap utxos;
      38        4135 :     for (size_t i = 0; i < txs.size(); i++) {
      39        4126 :         auto& tx = txs[i];
      40        8252 :         for (size_t j = 0; j < tx->vout.size(); j++) {
      41        4126 :             utxos.emplace(COutPoint(tx->GetHash(), j), std::make_pair((int)i + 1, tx->vout[j].nValue));
      42        4126 :         }
      43        4126 :     }
      44           9 :     return utxos;
      45           9 : }
      46             : 
      47          54 : static std::vector<COutPoint> SelectUTXOs(const CChain& active_chain, SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet)
      48             : {
      49          54 :     changeRet = 0;
      50             : 
      51          54 :     std::vector<COutPoint> selectedUtxos;
      52          54 :     CAmount selectedAmount = 0;
      53         125 :     while (!utoxs.empty()) {
      54         124 :         bool found = false;
      55         685 :         for (auto it = utoxs.begin(); it != utoxs.end(); ++it) {
      56         685 :             if (active_chain.Height() - it->second.first < 101) {
      57         561 :                 continue;
      58             :             }
      59             : 
      60         124 :             found = true;
      61         124 :             selectedAmount += it->second.second;
      62         124 :             selectedUtxos.emplace_back(it->first);
      63         124 :             utoxs.erase(it);
      64         124 :             break;
      65             :         }
      66         124 :         BOOST_REQUIRE(found);
      67         124 :         if (selectedAmount >= amount) {
      68          53 :             changeRet = selectedAmount - amount;
      69          53 :             break;
      70             :         }
      71             :     }
      72             : 
      73          54 :     return selectedUtxos;
      74          54 : }
      75             : 
      76          54 : static void FundTransaction(const CChain& active_chain, CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, CAmount amount, const CKey& coinbaseKey)
      77             : {
      78             :     CAmount change;
      79          54 :     auto inputs = SelectUTXOs(active_chain, utoxs, amount, change);
      80         178 :     for (size_t i = 0; i < inputs.size(); i++) {
      81         124 :         tx.vin.emplace_back(CTxIn(inputs[i]));
      82         124 :     }
      83          54 :     tx.vout.emplace_back(CTxOut(amount, scriptPayout));
      84          54 :     if (change != 0) {
      85          39 :         tx.vout.emplace_back(CTxOut(change, scriptPayout));
      86          39 :     }
      87          54 : }
      88             : 
      89          56 : static void SignTransaction(const CTxMemPool& mempool, CMutableTransaction& tx, const CKey& coinbaseKey)
      90             : {
      91          56 :     FillableSigningProvider tempKeystore;
      92          56 :     tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
      93             : 
      94         186 :     for (size_t i = 0; i < tx.vin.size(); i++) {
      95         130 :         uint256 hashBlock;
      96         130 :         CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, tx.vin[i].prevout.hash,
      97         130 :                                                 Params().GetConsensus(), hashBlock);
      98         130 :         BOOST_REQUIRE(txFrom);
      99         130 :         BOOST_REQUIRE(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
     100         130 :     }
     101          56 : }
     102             : 
     103          34 : static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet)
     104             : {
     105          34 :     ownerKeyRet.MakeNewKey(true);
     106          34 :     operatorKeyRet.MakeNewKey();
     107             : 
     108          34 :     CProRegTx proTx;
     109          34 :     proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     110          34 :     proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
     111          34 :     proTx.collateralOutpoint.n = 0;
     112          34 :     BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
     113             :                       NetInfoStatus::Success);
     114          34 :     proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID();
     115          34 :     proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load());
     116          34 :     proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID();
     117          34 :     proTx.scriptPayout = scriptPayout;
     118             : 
     119          34 :     CMutableTransaction tx;
     120          34 :     tx.nVersion = 3;
     121          34 :     tx.nType = TRANSACTION_PROVIDER_REGISTER;
     122          34 :     FundTransaction(active_chain, tx, utxos, scriptPayout, dmn_types::Regular.collat_amount, coinbaseKey);
     123          34 :     proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
     124          34 :     SetTxPayload(tx, proTx);
     125          34 :     SignTransaction(mempool, tx, coinbaseKey);
     126             : 
     127          34 :     return tx;
     128          34 : }
     129             : 
     130           6 : static CMutableTransaction CreateProUpServTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, int port, const CScript& scriptOperatorPayout, const CKey& coinbaseKey)
     131             : {
     132           6 :     CProUpServTx proTx;
     133           6 :     proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     134           6 :     proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
     135           6 :     proTx.proTxHash = proTxHash;
     136           6 :     BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
     137             :                       NetInfoStatus::Success);
     138           6 :     proTx.scriptOperatorPayout = scriptOperatorPayout;
     139             : 
     140           6 :     CMutableTransaction tx;
     141           6 :     tx.nVersion = 3;
     142           6 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
     143           6 :     FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
     144           6 :     proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
     145           6 :     proTx.sig = operatorKey.Sign(::SerializeHash(proTx), bls::bls_legacy_scheme);
     146           6 :     SetTxPayload(tx, proTx);
     147           6 :     SignTransaction(mempool, tx, coinbaseKey);
     148             : 
     149           6 :     return tx;
     150           6 : }
     151             : 
     152           3 : static CMutableTransaction CreateProUpRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CKey& mnKey, const CBLSPublicKey& pubKeyOperator, const CKeyID& keyIDVoting, const CScript& scriptPayout, const CKey& coinbaseKey)
     153             : {
     154           3 :     CProUpRegTx proTx;
     155           3 :     proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     156           3 :     proTx.proTxHash = proTxHash;
     157           3 :     proTx.pubKeyOperator.Set(pubKeyOperator, bls::bls_legacy_scheme.load());
     158           3 :     proTx.keyIDVoting = keyIDVoting;
     159           3 :     proTx.scriptPayout = scriptPayout;
     160             : 
     161           3 :     CMutableTransaction tx;
     162           3 :     tx.nVersion = 3;
     163           3 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
     164           3 :     FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
     165           3 :     proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
     166           3 :     CHashSigner::SignHash(::SerializeHash(proTx), mnKey, proTx.vchSig);
     167           3 :     SetTxPayload(tx, proTx);
     168           3 :     SignTransaction(mempool, tx, coinbaseKey);
     169             : 
     170           3 :     return tx;
     171           3 : }
     172             : 
     173           3 : static CMutableTransaction CreateProUpRevTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, const CKey& coinbaseKey)
     174             : {
     175           3 :     CProUpRevTx proTx;
     176           3 :     proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     177           3 :     proTx.proTxHash = proTxHash;
     178             : 
     179           3 :     CMutableTransaction tx;
     180           3 :     tx.nVersion = 3;
     181           3 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
     182           3 :     FundTransaction(active_chain, tx, utxos, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())), 1 * COIN, coinbaseKey);
     183           3 :     proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
     184           3 :     proTx.sig = operatorKey.Sign(::SerializeHash(proTx), bls::bls_legacy_scheme);
     185           3 :     SetTxPayload(tx, proTx);
     186           3 :     SignTransaction(mempool, tx, coinbaseKey);
     187             : 
     188           3 :     return tx;
     189           3 : }
     190             : 
     191             : template<typename ProTx>
     192          14 : static CMutableTransaction MalleateProTxPayout(const CMutableTransaction& tx)
     193             : {
     194          14 :     auto opt_protx = GetTxPayload<ProTx>(tx);
     195          14 :     BOOST_REQUIRE(opt_protx.has_value());
     196          14 :     auto& protx = *opt_protx;
     197             : 
     198          14 :     CKey key;
     199          14 :     key.MakeNewKey(false);
     200          14 :     protx.scriptPayout = GetScriptForDestination(PKHash(key.GetPubKey()));
     201             : 
     202          14 :     CMutableTransaction tx2 = tx;
     203          14 :     SetTxPayload(tx2, protx);
     204             : 
     205          14 :     return tx2;
     206          14 : }
     207             : 
     208          32 : static CScript GenerateRandomAddress()
     209             : {
     210          32 :     CKey key;
     211          32 :     key.MakeNewKey(false);
     212          32 :     return GetScriptForDestination(PKHash(key.GetPubKey()));
     213          32 : }
     214             : 
     215         120 : static CDeterministicMNCPtr FindPayoutDmn(CDeterministicMNManager& dmnman, const CBlock& block)
     216             : {
     217         120 :     auto dmnList = dmnman.GetListAtChainTip();
     218             : 
     219         360 :     for (const auto& txout : block.vtx[0]->vout) {
     220         360 :         CDeterministicMNCPtr found;
     221        4560 :         dmnList.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     222        4200 :             if (found == nullptr && txout.scriptPubKey == dmn->pdmnState->scriptPayout) {
     223         120 :                 found = dmn;
     224         120 :             }
     225        4200 :         });
     226         360 :         if (found != nullptr) {
     227         120 :             return found;
     228             :         }
     229         360 :     }
     230           0 :     return nullptr;
     231         120 : }
     232             : 
     233          28 : static bool CheckTransactionSignature(const CTxMemPool& mempool, const CMutableTransaction& tx)
     234             : {
     235          61 :     for (unsigned int i = 0; i < tx.vin.size(); i++) {
     236          47 :         const auto& txin = tx.vin[i];
     237          47 :         uint256 hashBlock;
     238          94 :         CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, txin.prevout.hash,
     239          47 :                                                 Params().GetConsensus(), hashBlock);
     240          47 :         BOOST_REQUIRE(txFrom);
     241             : 
     242          47 :         CAmount amount = txFrom->vout[txin.prevout.n].nValue;
     243          47 :         if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount, MissingDataBehavior::ASSERT_FAIL))) {
     244          14 :             return false;
     245             :         }
     246          47 :     }
     247          14 :     return true;
     248          28 : }
     249             : 
     250           1 : void FuncDIP3Activation(TestChainSetup& setup)
     251             : {
     252           1 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     253           1 :     auto& dmnman = *Assert(setup.m_node.dmnman);
     254             : 
     255           1 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     256           1 :     CKey ownerKey;
     257           1 :     CBLSSecretKey operatorKey;
     258           1 :     CTxDestination payoutDest = DecodeDestination("yRq1Ky1AfFmf597rnotj7QRxsDUKePVWNF");
     259           1 :     auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, GetScriptForDestination(payoutDest), setup.coinbaseKey, ownerKey, operatorKey);
     260           1 :     std::vector<CMutableTransaction> txns = {tx};
     261             : 
     262           1 :     const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
     263           1 :     int nHeight = chainman.ActiveChain().Height();
     264             : 
     265             :     // We start one block before DIP3 activation, so mining a block with a DIP3 transaction should fail
     266           1 :     auto block = std::make_shared<CBlock>(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate()));
     267           1 :     chainman.ProcessNewBlock(block, true, nullptr);
     268           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     269           1 :     BOOST_REQUIRE(block->GetHash() != chainman.ActiveChain().Tip()->GetBlockHash());
     270           1 :     BOOST_REQUIRE(!dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     271             : 
     272             :     // This block should activate DIP3
     273           1 :     setup.CreateAndProcessBlock({}, coinbase_pk);
     274           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     275             :     // Mining a block with a DIP3 transaction should succeed now
     276           1 :     block = std::make_shared<CBlock>(setup.CreateBlock(txns, coinbase_pk, chainman.ActiveChainstate()));
     277           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     278           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     279           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2);
     280           1 :     BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
     281           1 :     BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     282           1 : };
     283             : 
     284           1 : void FuncV19Activation(TestChainSetup& setup)
     285             : {
     286           1 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     287           1 :     auto& dmnman = *Assert(setup.m_node.dmnman);
     288             : 
     289           1 :     BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     290             : 
     291             :     // create
     292           1 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     293           1 :     CKey owner_key;
     294           1 :     CBLSSecretKey operator_key;
     295           1 :     CKey collateral_key;
     296           1 :     collateral_key.MakeNewKey(false);
     297           1 :     auto collateralScript = GetScriptForDestination(PKHash(collateral_key.GetPubKey()));
     298           1 :     auto tx_reg = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, collateralScript, setup.coinbaseKey, owner_key, operator_key);
     299           1 :     auto tx_reg_hash = tx_reg.GetHash();
     300             : 
     301           1 :     const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
     302           1 :     int nHeight = chainman.ActiveChain().Height();
     303             : 
     304           1 :     auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate()));
     305           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     306           1 :     BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     307           1 :     ++nHeight;
     308           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     309           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     310           1 :     dmnman.DoMaintenance();
     311           1 :     auto tip_list = dmnman.GetListAtChainTip();
     312           1 :     BOOST_REQUIRE(tip_list.HasMN(tx_reg_hash));
     313           1 :     auto pindex_create = chainman.ActiveChain().Tip();
     314           1 :     auto base_list = dmnman.GetListForBlock(pindex_create);
     315           1 :     std::vector<CDeterministicMNListDiff> diffs;
     316             : 
     317             :     // update
     318           1 :     CBLSSecretKey operator_key_new;
     319           1 :     operator_key_new.MakeNewKey();
     320           1 :     auto tx_upreg = CreateProUpRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, tx_reg_hash, owner_key, operator_key_new.GetPublicKey(), owner_key.GetPubKey().GetID(), collateralScript, setup.coinbaseKey);
     321             : 
     322           1 :     block = std::make_shared<CBlock>(setup.CreateBlock({tx_upreg}, coinbase_pk, chainman.ActiveChainstate()));
     323           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     324           1 :     BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     325           1 :     ++nHeight;
     326           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     327           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     328           1 :     dmnman.DoMaintenance();
     329           1 :     tip_list = dmnman.GetListAtChainTip();
     330           1 :     BOOST_REQUIRE(tip_list.HasMN(tx_reg_hash));
     331           1 :     diffs.push_back(base_list.BuildDiff(tip_list));
     332             : 
     333             :     // spend
     334           1 :     CMutableTransaction tx_spend;
     335           1 :     COutPoint collateralOutpoint(tx_reg_hash, 0);
     336           1 :     tx_spend.vin.emplace_back(collateralOutpoint);
     337           1 :     tx_spend.vout.emplace_back(999.99 * COIN, collateralScript);
     338             : 
     339           1 :     FillableSigningProvider signing_provider;
     340           1 :     signing_provider.AddKeyPubKey(collateral_key, collateral_key.GetPubKey());
     341           1 :     BOOST_REQUIRE(SignSignature(signing_provider, CTransaction(tx_reg), tx_spend, 0, SIGHASH_ALL));
     342           1 :     block = std::make_shared<CBlock>(setup.CreateBlock({tx_spend}, coinbase_pk, chainman.ActiveChainstate()));
     343           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     344           1 :     BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     345           1 :     ++nHeight;
     346           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     347           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     348           1 :     dmnman.DoMaintenance();
     349           1 :     diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
     350           1 :     tip_list = dmnman.GetListAtChainTip();
     351           1 :     BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
     352           1 :     BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
     353             : 
     354             :     // mine another block so that it's not the last one before V19
     355           1 :     setup.CreateAndProcessBlock({}, coinbase_pk);
     356           1 :     BOOST_REQUIRE(!DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     357           1 :     ++nHeight;
     358           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     359           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     360           1 :     dmnman.DoMaintenance();
     361           1 :     diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
     362           1 :     tip_list = dmnman.GetListAtChainTip();
     363           1 :     BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
     364           1 :     BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
     365             : 
     366             :     // this block should activate V19
     367           1 :     setup.CreateAndProcessBlock({}, coinbase_pk);
     368           1 :     BOOST_REQUIRE(DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     369           1 :     ++nHeight;
     370           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight);
     371           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     372           1 :     dmnman.DoMaintenance();
     373           1 :     diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
     374           1 :     tip_list = dmnman.GetListAtChainTip();
     375           1 :     BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
     376           1 :     BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
     377             : 
     378             :     // check mn list/diff
     379           1 :     CDeterministicMNListDiff dummy_diff = base_list.BuildDiff(tip_list);
     380           1 :     CDeterministicMNList dummy_list{base_list};
     381           1 :     dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
     382             :     // Lists should match
     383           1 :     BOOST_REQUIRE(dummy_list == tip_list);
     384             : 
     385             :     // mine 10 more blocks
     386          11 :     for (int i = 0; i < 10; ++i)
     387             :     {
     388          10 :         setup.CreateAndProcessBlock({}, coinbase_pk);
     389          10 :         BOOST_REQUIRE(
     390             :             DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19));
     391          10 :         BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1 + i);
     392          10 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     393          10 :         dmnman.DoMaintenance();
     394          10 :         diffs.push_back(tip_list.BuildDiff(dmnman.GetListAtChainTip()));
     395          10 :         tip_list = dmnman.GetListAtChainTip();
     396          10 :         BOOST_REQUIRE(!tip_list.HasMN(tx_reg_hash));
     397          10 :         BOOST_REQUIRE(dmnman.GetListForBlock(pindex_create).HasMN(tx_reg_hash));
     398          10 :     }
     399             : 
     400             :     // check mn list/diff
     401           1 :     const CBlockIndex* v19_index = chainman.ActiveChain().Tip()->GetAncestor(Params().GetConsensus().V19Height);
     402           1 :     auto v19_list = dmnman.GetListForBlock(v19_index);
     403           1 :     dummy_diff = v19_list.BuildDiff(tip_list);
     404           1 :     dummy_list = v19_list;
     405           1 :     dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
     406           1 :     BOOST_REQUIRE(dummy_list == tip_list);
     407             : 
     408             :     // NOTE: this fails on v19/v19.1 with errors like:
     409             :     // "RemoveMN: Can't delete a masternode ... with a pubKeyOperator=..."
     410           1 :     dummy_diff = base_list.BuildDiff(tip_list);
     411           1 :     dummy_list = base_list;
     412           1 :     dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), dummy_diff);
     413           1 :     BOOST_REQUIRE(dummy_list == tip_list);
     414             : 
     415           1 :     dummy_list = base_list;
     416          15 :     for (const auto& diff : diffs) {
     417          14 :         dummy_list.ApplyDiff(chainman.ActiveChain().Tip(), diff);
     418             :     }
     419           1 :     BOOST_REQUIRE(dummy_list == tip_list);
     420           1 : };
     421             : 
     422           2 : void FuncDIP3Protx(TestChainSetup& setup)
     423             : {
     424           2 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     425           2 :     auto& dmnman = *Assert(setup.m_node.dmnman);
     426             : 
     427           2 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     428             : 
     429           2 :     const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
     430           2 :     int nHeight = chainman.ActiveChain().Height();
     431           2 :     int port = 1;
     432             : 
     433           2 :     std::vector<uint256> dmnHashes;
     434           2 :     std::map<uint256, CKey> ownerKeys;
     435           2 :     std::map<uint256, CBLSSecretKey> operatorKeys;
     436             : 
     437             :     // register one MN per block
     438          14 :     for (size_t i = 0; i < 6; i++) {
     439          12 :         CKey ownerKey;
     440          12 :         CBLSSecretKey operatorKey;
     441          12 :         auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey);
     442          12 :         dmnHashes.emplace_back(tx.GetHash());
     443          12 :         ownerKeys.emplace(tx.GetHash(), ownerKey);
     444          12 :         operatorKeys.emplace(tx.GetHash(), operatorKey);
     445             : 
     446             :         // also verify that payloads are not malleable after they have been signed
     447             :         // the form of ProRegTx we use here is one with a collateral included, so there is no signature inside the
     448             :         // payload itself. This means, we need to rely on script verification, which takes the hash of the extra payload
     449             :         // into account
     450          12 :         auto tx2 = MalleateProTxPayout<CProRegTx>(tx);
     451          12 :         TxValidationState dummy_state;
     452             :         // Technically, the payload is still valid...
     453             :         {
     454          12 :             LOCK(cs_main);
     455          12 :             BOOST_REQUIRE(CheckProRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman,
     456             :                                         chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
     457          12 :             BOOST_REQUIRE(CheckProRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman,
     458             :                                         chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
     459          12 :         }
     460             :         // But the signature should not verify anymore
     461          12 :         BOOST_REQUIRE(CheckTransactionSignature(*(setup.m_node.mempool), tx));
     462          12 :         BOOST_REQUIRE(!CheckTransactionSignature(*(setup.m_node.mempool), tx2));
     463             : 
     464          12 :         setup.CreateAndProcessBlock({tx}, coinbase_pk);
     465          12 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     466             : 
     467          12 :         BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     468          12 :         BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     469             : 
     470          12 :         nHeight++;
     471          12 :     }
     472             : 
     473           2 :     int DIP0003EnforcementHeightBackup = Params().GetConsensus().DIP0003EnforcementHeight;
     474           2 :     const_cast<Consensus::Params&>(Params().GetConsensus()).DIP0003EnforcementHeight = chainman.ActiveChain().Height() + 1;
     475           2 :     setup.CreateAndProcessBlock({}, coinbase_pk);
     476           2 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     477           2 :     nHeight++;
     478             : 
     479             :     // check MN reward payments
     480          42 :     for (size_t i = 0; i < 20; i++) {
     481          40 :         auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
     482          40 :         BOOST_ASSERT(dmnExpectedPayee);
     483             : 
     484          40 :         CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
     485          40 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     486          40 :         BOOST_REQUIRE(!block.vtx.empty());
     487             : 
     488          40 :         auto dmnPayout = FindPayoutDmn(dmnman, block);
     489          40 :         BOOST_REQUIRE(dmnPayout != nullptr);
     490          40 :         BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
     491             : 
     492          40 :         nHeight++;
     493          40 :     }
     494             : 
     495             :     // register multiple MNs per block
     496           8 :     for (size_t i = 0; i < 3; i++) {
     497           6 :         std::vector<CMutableTransaction> txns;
     498          24 :         for (size_t j = 0; j < 3; j++) {
     499          18 :             CKey ownerKey;
     500          18 :             CBLSSecretKey operatorKey;
     501          18 :             auto tx = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey);
     502          18 :             dmnHashes.emplace_back(tx.GetHash());
     503          18 :             ownerKeys.emplace(tx.GetHash(), ownerKey);
     504          18 :             operatorKeys.emplace(tx.GetHash(), operatorKey);
     505          18 :             txns.emplace_back(tx);
     506          18 :         }
     507           6 :         setup.CreateAndProcessBlock(txns, coinbase_pk);
     508           6 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     509           6 :         BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     510             : 
     511          24 :         for (size_t j = 0; j < 3; j++) {
     512          18 :             BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(txns[j].GetHash()));
     513          18 :         }
     514             : 
     515           6 :         nHeight++;
     516           6 :     }
     517             : 
     518             :     // test ProUpServTx
     519           2 :     auto tx = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], operatorKeys[dmnHashes[0]], 1000, CScript(), setup.coinbaseKey);
     520           2 :     setup.CreateAndProcessBlock({tx}, coinbase_pk);
     521           2 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     522           2 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     523           2 :     nHeight++;
     524             : 
     525           2 :     auto dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
     526           2 :     BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo->GetPrimary().GetPort() == 1000);
     527             : 
     528             :     // test ProUpRevTx
     529           2 :     tx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], operatorKeys[dmnHashes[0]], setup.coinbaseKey);
     530           2 :     setup.CreateAndProcessBlock({tx}, coinbase_pk);
     531           2 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     532           2 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     533           2 :     nHeight++;
     534             : 
     535           2 :     dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
     536           2 :     BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->GetBannedHeight() == nHeight);
     537             : 
     538             :     // test that the revoked MN does not get paid anymore
     539          42 :     for (size_t i = 0; i < 20; i++) {
     540          40 :         auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
     541          80 :         BOOST_REQUIRE(dmnExpectedPayee && dmnExpectedPayee->proTxHash != dmnHashes[0]);
     542             : 
     543          40 :         CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
     544          40 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     545          40 :         BOOST_REQUIRE(!block.vtx.empty());
     546             : 
     547          40 :         auto dmnPayout = FindPayoutDmn(dmnman, block);
     548          40 :         BOOST_REQUIRE(dmnPayout != nullptr);
     549          40 :         BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
     550             : 
     551          40 :         nHeight++;
     552          40 :     }
     553             : 
     554             :     // test reviving the MN
     555           2 :     CBLSSecretKey newOperatorKey;
     556           2 :     newOperatorKey.MakeNewKey();
     557           2 :     dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
     558           2 :     tx = CreateProUpRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], ownerKeys[dmnHashes[0]], newOperatorKey.GetPublicKey(), ownerKeys[dmnHashes[0]].GetPubKey().GetID(), dmn->pdmnState->scriptPayout, setup.coinbaseKey);
     559             :     // check malleability protection again, but this time by also relying on the signature inside the ProUpRegTx
     560           2 :     auto tx2 = MalleateProTxPayout<CProUpRegTx>(tx);
     561           2 :     TxValidationState dummy_state;
     562             :     {
     563           2 :         LOCK(cs_main);
     564           2 :         BOOST_REQUIRE(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman,
     565             :                                       chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
     566           2 :         BOOST_REQUIRE(!CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman,
     567             :                                        chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true));
     568           2 :     }
     569           2 :     BOOST_REQUIRE(CheckTransactionSignature(*(setup.m_node.mempool), tx));
     570           2 :     BOOST_REQUIRE(!CheckTransactionSignature(*(setup.m_node.mempool), tx2));
     571             :     // now process the block
     572           2 :     setup.CreateAndProcessBlock({tx}, coinbase_pk);
     573           2 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     574           2 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     575           2 :     nHeight++;
     576             : 
     577           2 :     tx = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, dmnHashes[0], newOperatorKey, 100, CScript(), setup.coinbaseKey);
     578           2 :     setup.CreateAndProcessBlock({tx}, coinbase_pk);
     579           2 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     580           2 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     581           2 :     nHeight++;
     582             : 
     583           2 :     dmn = dmnman.GetListAtChainTip().GetMN(dmnHashes[0]);
     584           2 :     BOOST_REQUIRE(dmn != nullptr && dmn->pdmnState->netInfo->GetPrimary().GetPort() == 100);
     585           2 :     BOOST_REQUIRE(dmn != nullptr && !dmn->pdmnState->IsBanned());
     586             : 
     587             :     // test that the revived MN gets payments again
     588           2 :     bool foundRevived = false;
     589          42 :     for (size_t i = 0; i < 20; i++) {
     590          40 :         auto dmnExpectedPayee = dmnman.GetListAtChainTip().GetMNPayee(chainman.ActiveChain().Tip());
     591          40 :         BOOST_ASSERT(dmnExpectedPayee);
     592          40 :         if (dmnExpectedPayee->proTxHash == dmnHashes[0]) {
     593           2 :             foundRevived = true;
     594           2 :         }
     595             : 
     596          40 :         CBlock block = setup.CreateAndProcessBlock({}, coinbase_pk);
     597          40 :         dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     598          40 :         BOOST_REQUIRE(!block.vtx.empty());
     599             : 
     600          40 :         auto dmnPayout = FindPayoutDmn(dmnman, block);
     601          40 :         BOOST_REQUIRE(dmnPayout != nullptr);
     602          40 :         BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString());
     603             : 
     604          40 :         nHeight++;
     605          40 :     }
     606           2 :     BOOST_REQUIRE(foundRevived);
     607             : 
     608           2 :     const_cast<Consensus::Params&>(Params().GetConsensus()).DIP0003EnforcementHeight = DIP0003EnforcementHeightBackup;
     609           2 : }
     610             : 
     611           2 : void FuncTestMempoolReorg(TestChainSetup& setup)
     612             : {
     613           2 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     614             : 
     615           2 :     const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
     616           2 :     int nHeight = chainman.ActiveChain().Height();
     617           2 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     618             : 
     619           2 :     CKey ownerKey;
     620           2 :     CKey payoutKey;
     621           2 :     CKey collateralKey;
     622           2 :     CBLSSecretKey operatorKey;
     623             : 
     624           2 :     ownerKey.MakeNewKey(true);
     625           2 :     payoutKey.MakeNewKey(true);
     626           2 :     collateralKey.MakeNewKey(true);
     627           2 :     operatorKey.MakeNewKey();
     628             : 
     629           2 :     auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
     630           2 :     auto scriptCollateral = GetScriptForDestination(PKHash(collateralKey.GetPubKey()));
     631             : 
     632             :     // Create a MN with an external collateral
     633           2 :     CMutableTransaction tx_collateral;
     634           2 :     FundTransaction(chainman.ActiveChain(), tx_collateral, utxos, scriptCollateral, dmn_types::Regular.collat_amount, setup.coinbaseKey);
     635           2 :     SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
     636             : 
     637           2 :     auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate()));
     638           2 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     639           2 :     setup.m_node.dmnman->UpdatedBlockTip(chainman.ActiveChain().Tip());
     640           2 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     641           2 :     BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
     642             : 
     643           2 :     CProRegTx payload;
     644           2 :     payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     645           2 :     payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
     646           2 :     BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
     647           2 :     payload.keyIDOwner = ownerKey.GetPubKey().GetID();
     648           2 :     payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
     649           2 :     payload.keyIDVoting = ownerKey.GetPubKey().GetID();
     650           2 :     payload.scriptPayout = scriptPayout;
     651             : 
     652           2 :     for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
     653           2 :         if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) {
     654           2 :             payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
     655           2 :             break;
     656             :         }
     657           0 :     }
     658             : 
     659           2 :     CMutableTransaction tx_reg;
     660           2 :     tx_reg.nVersion = 3;
     661           2 :     tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
     662           2 :     FundTransaction(chainman.ActiveChain(), tx_reg, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
     663           2 :     payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
     664           2 :     CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
     665           2 :     SetTxPayload(tx_reg, payload);
     666           2 :     SignTransaction(*(setup.m_node.mempool), tx_reg, setup.coinbaseKey);
     667             : 
     668           2 :     CTxMemPool testPool;
     669           2 :     if (setup.m_node.dmnman) {
     670           2 :         testPool.ConnectManagers(setup.m_node.dmnman.get(), setup.m_node.llmq_ctx->isman.get());
     671           2 :     }
     672           2 :     TestMemPoolEntryHelper entry;
     673           2 :     LOCK2(cs_main, testPool.cs);
     674             : 
     675             :     // Create ProUpServ and test block reorg which double-spend ProRegTx
     676           2 :     auto tx_up_serv = CreateProUpServTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, tx_reg.GetHash(), operatorKey, 2, CScript(), setup.coinbaseKey);
     677           2 :     testPool.addUnchecked(entry.FromTx(tx_up_serv));
     678             :     // A disconnected block would insert ProRegTx back into mempool
     679           2 :     testPool.addUnchecked(entry.FromTx(tx_reg));
     680           2 :     BOOST_CHECK_EQUAL(testPool.size(), 2U);
     681             : 
     682             :     // Create a tx that will double-spend ProRegTx
     683           2 :     CMutableTransaction tx_reg_ds;
     684           2 :     tx_reg_ds.vin = tx_reg.vin;
     685           2 :     tx_reg_ds.vout.emplace_back(0, CScript() << OP_RETURN);
     686           2 :     SignTransaction(*(setup.m_node.mempool), tx_reg_ds, setup.coinbaseKey);
     687             : 
     688             :     // Check mempool as if a new block with tx_reg_ds was connected instead of the old one with tx_reg
     689           2 :     std::vector<CTransactionRef> block_reorg;
     690           2 :     block_reorg.emplace_back(std::make_shared<CTransaction>(tx_reg_ds));
     691           2 :     testPool.removeForBlock(block_reorg, nHeight + 2);
     692           2 :     BOOST_CHECK_EQUAL(testPool.size(), 0U);
     693           2 : }
     694             : 
     695           2 : void FuncTestMempoolDualProregtx(TestChainSetup& setup)
     696             : {
     697           2 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     698             : 
     699           2 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     700             : 
     701             :     // Create a MN
     702           2 :     CKey ownerKey1;
     703           2 :     CBLSSecretKey operatorKey1;
     704           2 :     auto tx_reg1 = CreateProRegTx(chainman.ActiveChain(), *(setup.m_node.mempool), utxos, 1, GenerateRandomAddress(), setup.coinbaseKey, ownerKey1, operatorKey1);
     705             : 
     706             :     // Create a MN with an external collateral that references tx_reg1
     707           2 :     CKey ownerKey;
     708           2 :     CKey payoutKey;
     709           2 :     CKey collateralKey;
     710           2 :     CBLSSecretKey operatorKey;
     711             : 
     712           2 :     ownerKey.MakeNewKey(true);
     713           2 :     payoutKey.MakeNewKey(true);
     714           2 :     collateralKey.MakeNewKey(true);
     715           2 :     operatorKey.MakeNewKey();
     716             : 
     717           2 :     auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
     718             : 
     719           2 :     CProRegTx payload;
     720           2 :     payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     721           2 :     payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
     722           2 :     BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:2"), NetInfoStatus::Success);
     723           2 :     payload.keyIDOwner = ownerKey.GetPubKey().GetID();
     724           2 :     payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
     725           2 :     payload.keyIDVoting = ownerKey.GetPubKey().GetID();
     726           2 :     payload.scriptPayout = scriptPayout;
     727             : 
     728           2 :     for (size_t i = 0; i < tx_reg1.vout.size(); ++i) {
     729           2 :         if (tx_reg1.vout[i].nValue == dmn_types::Regular.collat_amount) {
     730           2 :             payload.collateralOutpoint = COutPoint(tx_reg1.GetHash(), i);
     731           2 :             break;
     732             :         }
     733           0 :     }
     734             : 
     735           2 :     CMutableTransaction tx_reg2;
     736           2 :     tx_reg2.nVersion = 3;
     737           2 :     tx_reg2.nType = TRANSACTION_PROVIDER_REGISTER;
     738           2 :     FundTransaction(chainman.ActiveChain(), tx_reg2, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
     739           2 :     payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg2));
     740           2 :     CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
     741           2 :     SetTxPayload(tx_reg2, payload);
     742           2 :     SignTransaction(*(setup.m_node.mempool), tx_reg2, setup.coinbaseKey);
     743             : 
     744           2 :     CTxMemPool testPool;
     745           2 :     if (setup.m_node.dmnman) {
     746           2 :         testPool.ConnectManagers(setup.m_node.dmnman.get(), setup.m_node.llmq_ctx->isman.get());
     747           2 :     }
     748           2 :     TestMemPoolEntryHelper entry;
     749           2 :     LOCK2(cs_main, testPool.cs);
     750             : 
     751           2 :     testPool.addUnchecked(entry.FromTx(tx_reg1));
     752           2 :     BOOST_CHECK_EQUAL(testPool.size(), 1U);
     753           2 :     BOOST_CHECK(testPool.existsProviderTxConflict(CTransaction(tx_reg2)));
     754           2 : }
     755             : 
     756           1 : void FuncVerifyDB(TestChainSetup& setup)
     757             : {
     758           1 :     auto& chainman = *Assert(setup.m_node.chainman.get());
     759           1 :     auto& dmnman = *Assert(setup.m_node.dmnman);
     760             : 
     761           1 :     const CScript coinbase_pk = GetScriptForRawPubKey(setup.coinbaseKey.GetPubKey());
     762           1 :     int nHeight = chainman.ActiveChain().Height();
     763           1 :     auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns);
     764             : 
     765           1 :     CKey ownerKey;
     766           1 :     CKey payoutKey;
     767           1 :     CKey collateralKey;
     768           1 :     CBLSSecretKey operatorKey;
     769             : 
     770           1 :     ownerKey.MakeNewKey(true);
     771           1 :     payoutKey.MakeNewKey(true);
     772           1 :     collateralKey.MakeNewKey(true);
     773           1 :     operatorKey.MakeNewKey();
     774             : 
     775           1 :     auto scriptPayout = GetScriptForDestination(PKHash(payoutKey.GetPubKey()));
     776           1 :     auto scriptCollateral = GetScriptForDestination(PKHash(collateralKey.GetPubKey()));
     777             : 
     778             :     // Create a MN with an external collateral
     779           1 :     CMutableTransaction tx_collateral;
     780           1 :     FundTransaction(chainman.ActiveChain(), tx_collateral, utxos, scriptCollateral, dmn_types::Regular.collat_amount, setup.coinbaseKey);
     781           1 :     SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
     782             : 
     783           1 :     auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, coinbase_pk, chainman.ActiveChainstate()));
     784           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     785           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     786           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 1);
     787           1 :     BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
     788             : 
     789           1 :     CProRegTx payload;
     790           1 :     payload.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     791           1 :     payload.netInfo = NetInfoInterface::MakeNetInfo(payload.nVersion);
     792           1 :     BOOST_CHECK_EQUAL(payload.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
     793           1 :     payload.keyIDOwner = ownerKey.GetPubKey().GetID();
     794           1 :     payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
     795           1 :     payload.keyIDVoting = ownerKey.GetPubKey().GetID();
     796           1 :     payload.scriptPayout = scriptPayout;
     797             : 
     798           1 :     for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
     799           1 :         if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) {
     800           1 :             payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
     801           1 :             break;
     802             :         }
     803           0 :     }
     804             : 
     805           1 :     CMutableTransaction tx_reg;
     806           1 :     tx_reg.nVersion = 3;
     807           1 :     tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
     808           1 :     FundTransaction(chainman.ActiveChain(), tx_reg, utxos, scriptPayout, dmn_types::Regular.collat_amount, setup.coinbaseKey);
     809           1 :     payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
     810           1 :     CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
     811           1 :     SetTxPayload(tx_reg, payload);
     812           1 :     SignTransaction(*(setup.m_node.mempool), tx_reg, setup.coinbaseKey);
     813             : 
     814           1 :     auto tx_reg_hash = tx_reg.GetHash();
     815             : 
     816           1 :     block = std::make_shared<CBlock>(setup.CreateBlock({tx_reg}, coinbase_pk, chainman.ActiveChainstate()));
     817           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     818           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     819           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 2);
     820           1 :     BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
     821           1 :     BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx_reg_hash));
     822             : 
     823             :     // Now spend the collateral while updating the same MN
     824           1 :     SimpleUTXOMap collateral_utxos;
     825           1 :     collateral_utxos.emplace(payload.collateralOutpoint, std::make_pair(1, 1000));
     826           1 :     auto proUpRevTx = CreateProUpRevTx(chainman.ActiveChain(), *(setup.m_node.mempool), collateral_utxos, tx_reg_hash, operatorKey, collateralKey);
     827             : 
     828           1 :     block = std::make_shared<CBlock>(setup.CreateBlock({proUpRevTx}, coinbase_pk, chainman.ActiveChainstate()));
     829           1 :     BOOST_REQUIRE(chainman.ProcessNewBlock(block, true, nullptr));
     830           1 :     dmnman.UpdatedBlockTip(chainman.ActiveChain().Tip());
     831           1 :     BOOST_CHECK_EQUAL(chainman.ActiveChain().Height(), nHeight + 3);
     832           1 :     BOOST_CHECK_EQUAL(block->GetHash(), chainman.ActiveChain().Tip()->GetBlockHash());
     833           1 :     BOOST_REQUIRE(!dmnman.GetListAtChainTip().HasMN(tx_reg_hash));
     834             : 
     835             :     // Verify db consistency
     836           1 :     LOCK(cs_main);
     837           1 :     BOOST_REQUIRE(CVerifyDB().VerifyDB(chainman.ActiveChainstate(), Params().GetConsensus(),
     838             :                                        chainman.ActiveChainstate().CoinsTip(), *(setup.m_node.evodb), 4, 2));
     839           1 : }
     840             : 
     841           2 : static CDeterministicMNCPtr create_mock_mn(uint64_t internal_id)
     842             : {
     843             :     // Create a mock MN
     844           2 :     CKey ownerKey;
     845           2 :     ownerKey.MakeNewKey(true);
     846           2 :     CBLSSecretKey operatorKey;
     847           2 :     operatorKey.MakeNewKey();
     848             : 
     849           2 :     auto dmnState = std::make_shared<CDeterministicMNState>();
     850           2 :     dmnState->confirmedHash = GetRandHash();
     851           2 :     dmnState->keyIDOwner = ownerKey.GetPubKey().GetID();
     852           2 :     dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
     853           2 :     dmnState->keyIDVoting = ownerKey.GetPubKey().GetID();
     854           2 :     dmnState->netInfo = NetInfoInterface::MakeNetInfo(
     855           2 :         ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false));
     856           2 :     BOOST_CHECK_EQUAL(dmnState->netInfo->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:1"), NetInfoStatus::Success);
     857             : 
     858           2 :     auto dmn = std::make_shared<CDeterministicMN>(internal_id, MnType::Regular);
     859           2 :     dmn->proTxHash = GetRandHash();
     860           2 :     dmn->collateralOutpoint = COutPoint(GetRandHash(), 0);
     861           2 :     dmn->nOperatorReward = 0;
     862           2 :     dmn->pdmnState = dmnState;
     863             : 
     864           2 :     return dmn;
     865           2 : }
     866             : 
     867           2 : static void SmlCache(TestChainSetup& setup)
     868             : {
     869           2 :     BOOST_CHECK(setup.m_node.dmnman != nullptr);
     870             : 
     871             :     // Create empty list and verify SML cache
     872           2 :     CDeterministicMNList emptyList(uint256(), 0, 0);
     873           2 :     auto sml_empty = emptyList.to_sml();
     874             : 
     875             :     // Should return the same cached object
     876           2 :     BOOST_CHECK(sml_empty == emptyList.to_sml());
     877             : 
     878             :     // Should contain empty list
     879           2 :     BOOST_CHECK_EQUAL(sml_empty->mnList.size(), 0);
     880             : 
     881             :     // Copy list should return the same cached object
     882           2 :     CDeterministicMNList mn_list_1(emptyList);
     883           2 :     BOOST_CHECK(sml_empty == mn_list_1.to_sml());
     884             : 
     885           2 :     CDeterministicMNList mn_list_2;
     886             :     // Assigning list should return the same cached object
     887           2 :     mn_list_2 = emptyList;
     888           2 :     BOOST_CHECK(sml_empty == mn_list_2.to_sml());
     889             : 
     890           2 :     auto dmn = create_mock_mn(1);
     891             : 
     892             :     // Add MN - should invalidate cache
     893           2 :     mn_list_1.AddMN(dmn, true);
     894           2 :     auto sml_add = mn_list_1.to_sml();
     895             : 
     896             :     // Cache should be invalidated, so different pointer but equal content after regeneration
     897           2 :     BOOST_CHECK(sml_empty != sml_add); // Different pointer (cache invalidated)
     898             : 
     899           2 :     BOOST_CHECK_EQUAL(sml_add->mnList.size(), 1); // Should contain the added MN
     900             : 
     901             :     {
     902             :         // Remove MN - should invalidate cache
     903           2 :         CDeterministicMNList mn_list(mn_list_1);
     904           2 :         BOOST_CHECK(mn_list_1.to_sml() == mn_list.to_sml());
     905             : 
     906           2 :         mn_list.RemoveMN(dmn->proTxHash);
     907           2 :         auto sml_remove = mn_list.to_sml();
     908             : 
     909             :         // Cache should be invalidated
     910           2 :         BOOST_CHECK(sml_remove != sml_add);
     911           2 :         BOOST_CHECK(sml_remove != sml_empty);
     912           2 :         BOOST_CHECK_EQUAL(sml_remove->mnList.size(), 0); // Should be empty after removal
     913           2 :     }
     914             : 
     915             :     // Start with a list containing one MN mn_list_1
     916             :     // Test 1: Update with same SML entry data - cache should NOT be invalidated
     917           2 :     auto unchangedState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     918           2 :     unchangedState->nPoSePenalty += 10;
     919           2 :     mn_list_1.UpdateMN(*dmn, unchangedState);
     920             : 
     921             :     // Cache should NOT be invalidated since SML entry didn't change
     922           2 :     BOOST_CHECK(sml_add == mn_list_1.to_sml()); // Same pointer (cache preserved)
     923             : 
     924             :     // Test 2: Update with different SML entry data - cache SHOULD be invalidated
     925           2 :     auto changedState = std::make_shared<CDeterministicMNState>(*unchangedState);
     926           2 :     changedState->pubKeyOperator.Set(CBLSPublicKey{}, bls::bls_legacy_scheme.load());
     927           2 :     mn_list_1.UpdateMN(*dmn, changedState);
     928             : 
     929             :     // Cache should be invalidated since SML entry changed
     930           2 :     BOOST_CHECK(sml_add != mn_list_1.to_sml());
     931           2 :     BOOST_CHECK_EQUAL(mn_list_1.to_sml()->mnList.size(), 1); // Still one MN but with updated data
     932           2 : }
     933             : 
     934         146 : BOOST_AUTO_TEST_SUITE(evo_dip3_activation_tests)
     935             : 
     936             : struct TestChainDIP3BeforeActivationSetup : public TestChainSetup {
     937           7 :     TestChainDIP3BeforeActivationSetup() :
     938           6 :         TestChainSetup(430)
     939           1 :     {
     940           7 :     }
     941             : };
     942             : 
     943             : struct TestChainDIP3Setup : public TestChainDIP3BeforeActivationSetup {
     944          10 :     TestChainDIP3Setup()
     945           5 :     {
     946             :         // Activate DIP3 here
     947           5 :         CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     948          10 :     }
     949             : };
     950             : 
     951             : struct TestChainV19BeforeActivationSetup : public TestChainSetup {
     952             :     TestChainV19BeforeActivationSetup();
     953             : };
     954             : 
     955             : struct TestChainV19Setup : public TestChainV19BeforeActivationSetup {
     956           8 :     TestChainV19Setup()
     957           4 :     {
     958           4 :         const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
     959             :         // Activate V19
     960          24 :         for (int i = 0; i < 5; ++i) {
     961          20 :             CreateAndProcessBlock({}, coinbase_pk);
     962          20 :         }
     963           4 :         bool v19_just_activated{
     964           4 :             DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19) &&
     965           4 :             !DeploymentActiveAt(*m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19)};
     966           4 :         assert(v19_just_activated);
     967           8 :     }
     968             : };
     969             : 
     970             : // 5 blocks earlier
     971           6 : TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() :
     972           5 :     TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"})
     973           1 : {
     974           5 :     bool v19_active{DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(),
     975             :                                           Consensus::DEPLOYMENT_V19)};
     976           5 :     assert(!v19_active);
     977           6 : }
     978             : 
     979             : // DIP3 can only be activated with legacy scheme (v19 is activated later)
     980         148 : BOOST_AUTO_TEST_CASE(dip3_activation_legacy)
     981             : {
     982           1 :     TestChainDIP3BeforeActivationSetup setup;
     983           1 :     FuncDIP3Activation(setup);
     984           1 : }
     985             : 
     986             : // V19 can only be activated with legacy scheme
     987         148 : BOOST_AUTO_TEST_CASE(v19_activation_legacy)
     988             : {
     989           1 :     TestChainV19BeforeActivationSetup setup;
     990           1 :     FuncV19Activation(setup);
     991           1 : }
     992             : 
     993         148 : BOOST_AUTO_TEST_CASE(dip3_protx_legacy)
     994             : {
     995           1 :     TestChainDIP3Setup setup;
     996           1 :     FuncDIP3Protx(setup);
     997           1 : }
     998             : 
     999         148 : BOOST_AUTO_TEST_CASE(dip3_protx_basic)
    1000             : {
    1001           1 :     TestChainV19Setup setup;
    1002           1 :     FuncDIP3Protx(setup);
    1003           1 : }
    1004             : 
    1005         148 : BOOST_AUTO_TEST_CASE(test_mempool_reorg_legacy)
    1006             : {
    1007           1 :     TestChainDIP3Setup setup;
    1008           1 :     FuncTestMempoolReorg(setup);
    1009           1 : }
    1010             : 
    1011         148 : BOOST_AUTO_TEST_CASE(test_mempool_reorg_basic)
    1012             : {
    1013           1 :     TestChainV19Setup setup;
    1014           1 :     FuncTestMempoolReorg(setup);
    1015           1 : }
    1016             : 
    1017         148 : BOOST_AUTO_TEST_CASE(test_mempool_dual_proregtx_legacy)
    1018             : {
    1019           1 :     TestChainDIP3Setup setup;
    1020           1 :     FuncTestMempoolDualProregtx(setup);
    1021           1 : }
    1022             : 
    1023         148 : BOOST_AUTO_TEST_CASE(test_mempool_dual_proregtx_basic)
    1024             : {
    1025           1 :     TestChainV19Setup setup;
    1026           1 :     FuncTestMempoolDualProregtx(setup);
    1027           1 : }
    1028             : 
    1029             : //This one can be started only with legacy scheme, since inside undo block will switch it back to legacy resulting into an inconsistency
    1030         148 : BOOST_AUTO_TEST_CASE(verify_db_legacy)
    1031             : {
    1032           1 :     TestChainDIP3Setup setup;
    1033           1 :     FuncVerifyDB(setup);
    1034           1 : }
    1035             : 
    1036         148 : BOOST_AUTO_TEST_CASE(test_sml_cache_legacy)
    1037             : {
    1038           1 :     TestChainDIP3Setup setup;
    1039           1 :     SmlCache(setup);
    1040           1 : }
    1041             : 
    1042         148 : BOOST_AUTO_TEST_CASE(test_sml_cache_basic)
    1043             : {
    1044           1 :     TestChainV19Setup setup;
    1045           1 :     SmlCache(setup);
    1046           1 : }
    1047             : 
    1048         148 : BOOST_AUTO_TEST_CASE(field_bit_migration_validation)
    1049             : {
    1050             :     // Test individual field mappings for ALL 19 fields
    1051             :     struct FieldMapping {
    1052             :         uint32_t legacyBit;
    1053             :         uint32_t newBit;
    1054             :         std::string name;
    1055             :     };
    1056             : 
    1057           1 :     std::vector<FieldMapping> mappings = {
    1058           1 :         {0x0001, CDeterministicMNStateDiff::Field_nRegisteredHeight, "nRegisteredHeight"},
    1059           1 :         {0x0002, CDeterministicMNStateDiff::Field_nLastPaidHeight, "nLastPaidHeight"},
    1060           1 :         {0x0004, CDeterministicMNStateDiff::Field_nPoSePenalty, "nPoSePenalty"},
    1061           1 :         {0x0008, CDeterministicMNStateDiff::Field_nPoSeRevivedHeight, "nPoSeRevivedHeight"},
    1062           1 :         {0x0010, CDeterministicMNStateDiff::Field_nPoSeBanHeight, "nPoSeBanHeight"},
    1063           1 :         {0x0020, CDeterministicMNStateDiff::Field_nRevocationReason, "nRevocationReason"},
    1064           1 :         {0x0040, CDeterministicMNStateDiff::Field_confirmedHash, "confirmedHash"},
    1065           1 :         {0x0080, CDeterministicMNStateDiff::Field_confirmedHashWithProRegTxHash, "confirmedHashWithProRegTxHash"},
    1066           1 :         {0x0100, CDeterministicMNStateDiff::Field_keyIDOwner, "keyIDOwner"},
    1067           1 :         {0x0200, CDeterministicMNStateDiff::Field_pubKeyOperator, "pubKeyOperator"},
    1068           1 :         {0x0400, CDeterministicMNStateDiff::Field_keyIDVoting, "keyIDVoting"},
    1069           1 :         {0x0800, CDeterministicMNStateDiff::Field_netInfo, "netInfo"},
    1070           1 :         {0x1000, CDeterministicMNStateDiff::Field_scriptPayout, "scriptPayout"},
    1071           1 :         {0x2000, CDeterministicMNStateDiff::Field_scriptOperatorPayout, "scriptOperatorPayout"},
    1072           1 :         {0x4000, CDeterministicMNStateDiff::Field_nConsecutivePayments, "nConsecutivePayments"},
    1073           1 :         {0x8000, CDeterministicMNStateDiff::Field_platformNodeID, "platformNodeID"},
    1074           1 :         {0x10000, CDeterministicMNStateDiff::Field_platformP2PPort, "platformP2PPort"},
    1075           1 :         {0x20000, CDeterministicMNStateDiff::Field_platformHTTPPort, "platformHTTPPort"},
    1076           1 :         {0x40000, CDeterministicMNStateDiff::Field_nVersion, "nVersion"},
    1077             :     };
    1078             : 
    1079             :     // Verify each field mapping is correct
    1080          20 :     for (const auto& mapping : mappings) {
    1081             :         // Test individual field conversion
    1082          19 :         CDeterministicMNStateDiffLegacy legacyDiff;
    1083          19 :         legacyDiff.fields |= mapping.legacyBit;
    1084             :         // Convert to new format
    1085          19 :         auto newDiff = legacyDiff.ToNewFormat();
    1086          19 :         BOOST_CHECK_MESSAGE(newDiff.fields == mapping.newBit, strprintf("Field %s: legacy 0x%x should convert to 0x%x",
    1087             :                                                                         mapping.name, mapping.legacyBit, mapping.newBit));
    1088          19 :     }
    1089             : 
    1090             :     // Test complex multi-field scenarios
    1091           1 :     uint32_t complexLegacyFields = 0x0200 | // Legacy Field_pubKeyOperator
    1092             :                                    0x0800 | // Legacy Field_netInfo
    1093             :                                    0x1000 | // Legacy Field_scriptPayout
    1094             :                                    0x40000; // Legacy Field_nVersion
    1095             : 
    1096           1 :     uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion |       // 0x0001
    1097             :                                  CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400 (was 0x0200)
    1098             :                                  CDeterministicMNStateDiff::Field_netInfo |        // 0x1000 (was 0x0800)
    1099             :                                  CDeterministicMNStateDiff::Field_scriptPayout;    // 0x2000 (was 0x1000)
    1100             : 
    1101           1 :     CDeterministicMNStateDiffLegacy legacyDiff;
    1102           1 :     legacyDiff.fields |= complexLegacyFields;
    1103             :     // Convert to new format
    1104           1 :     auto newDiff = legacyDiff.ToNewFormat();
    1105           1 :     BOOST_CHECK_EQUAL(newDiff.fields, expectedNewFields);
    1106             : 
    1107             :     // Verify no bit conflicts exist in new field layout
    1108           1 :     std::set<uint32_t> usedBits;
    1109          20 :     for (const auto& mapping : mappings) {
    1110          19 :         BOOST_CHECK_MESSAGE(usedBits.find(mapping.newBit) == usedBits.end(),
    1111             :                             strprintf("Duplicate bit 0x%x found for field %s", mapping.newBit, mapping.name));
    1112          19 :         usedBits.insert(mapping.newBit);
    1113             :     }
    1114             : 
    1115             :     // Verify all 19 fields have unique bit assignments
    1116           1 :     BOOST_CHECK_EQUAL(usedBits.size(), 19);
    1117           1 : }
    1118             : 
    1119         148 : BOOST_AUTO_TEST_CASE(migration_logic_validation)
    1120             : {
    1121             :     // Test the database migration logic for nVersion-first format conversion.
    1122             :     // Migration logic is handled at CDeterministicMNListDiff level
    1123             :     // using CDeterministicMNStateDiffLegacy for legacy format deserialization.
    1124             : 
    1125             :     // Create sample legacy format state diff
    1126           1 :     CDeterministicMNStateDiffLegacy legacyDiff;
    1127           1 :     legacyDiff.fields = 0x40000 | 0x0200 | 0x0800 | 0x0010; // Legacy: nVersion, pubKeyOperator, netInfo, nPoSeBanHeight
    1128           1 :     legacyDiff.state.nVersion = ProTxVersion::BasicBLS;
    1129           1 :     CBLSSecretKey sk;
    1130           1 :     sk.MakeNewKey();
    1131           1 :     legacyDiff.state.pubKeyOperator.Set(sk.GetPublicKey(), false);
    1132           1 :     BOOST_CHECK(!legacyDiff.state.pubKeyOperator.IsLegacy());
    1133           1 :     legacyDiff.state.netInfo = NetInfoInterface::MakeNetInfo(ProTxVersion::BasicBLS);
    1134           1 :     BOOST_CHECK(!legacyDiff.state.IsBanned());
    1135           1 :     legacyDiff.state.BanIfNotBanned(2367316);
    1136           1 :     BOOST_CHECK(legacyDiff.state.IsBanned());
    1137           1 :     BOOST_CHECK_EQUAL(legacyDiff.state.GetBannedHeight(), 2367316);
    1138             : 
    1139             : 
    1140             :     // Test legacy class conversion (this would normally be done by CDeterministicMNListDiff)
    1141           1 :     CDataStream ss(SER_DISK, CLIENT_VERSION);
    1142           1 :     ss << legacyDiff;
    1143             : 
    1144           1 :     CDeterministicMNStateDiffLegacy legacyDeserializer(deserialize, ss);
    1145           1 :     CDeterministicMNStateDiff convertedDiff = legacyDeserializer.ToNewFormat();
    1146           1 :     BOOST_CHECK(!legacyDiff.state.pubKeyOperator.IsLegacy());
    1147           1 :     BOOST_CHECK(convertedDiff.state.pubKeyOperator.IsLegacy());
    1148           1 :     convertedDiff.state.pubKeyOperator.SetLegacy(false);
    1149           1 :     BOOST_CHECK(!convertedDiff.state.pubKeyOperator.IsLegacy());
    1150             : 
    1151             :     // Verify conversion worked correctly
    1152           1 :     uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion |       // 0x0001
    1153             :                                  CDeterministicMNStateDiff::Field_nPoSeBanHeight | // 0x0020
    1154             :                                  CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400
    1155             :                                  CDeterministicMNStateDiff::Field_netInfo;         // 0x1000
    1156             : 
    1157           1 :     BOOST_CHECK_EQUAL(convertedDiff.fields, expectedNewFields);
    1158           1 :     BOOST_CHECK_EQUAL(convertedDiff.state.nVersion, legacyDiff.state.nVersion);
    1159           1 :     BOOST_CHECK_EQUAL(convertedDiff.state.GetBannedHeight(), legacyDiff.state.GetBannedHeight());
    1160           1 :     BOOST_CHECK(convertedDiff.state.pubKeyOperator.Get() == legacyDiff.state.pubKeyOperator.Get());
    1161           1 :     BOOST_CHECK_EQUAL(convertedDiff.state.pubKeyOperator.ToString(), legacyDiff.state.pubKeyOperator.ToString());
    1162           1 : }
    1163             : 
    1164         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16