LCOV - code coverage report
Current view: top level - src/test - block_reward_reallocation_tests.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 207 208 99.5 %
Date: 2026-06-25 07:23:51 Functions: 20 20 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2021-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 <bls/bls.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <evo/providertx.h>
      10             : #include <evo/specialtx.h>
      11             : #include <masternode/payments.h>
      12             : #include <util/helpers.h>
      13             : #include <util/std23.h>
      14             : 
      15             : #include <chainparams.h>
      16             : #include <deploymentstatus.h>
      17             : #include <node/miner.h>
      18             : #include <node/transaction.h>
      19             : #include <script/interpreter.h>
      20             : #include <script/sign.h>
      21             : #include <script/signingprovider.h>
      22             : #include <script/standard.h>
      23             : #include <validation.h>
      24             : 
      25             : #include <boost/test/unit_test.hpp>
      26             : 
      27             : #include <map>
      28             : #include <vector>
      29             : 
      30             : using node::BlockAssembler;
      31             : using node::GetTransaction;
      32             : 
      33             : using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
      34             : 
      35             : struct TestChainBRRBeforeActivationSetup : public TestChainSetup
      36             : {
      37             :     // Force fast DIP3 activation
      38           1 :     TestChainBRRBeforeActivationSetup() :
      39           1 :         TestChainSetup(497, CBaseChainParams::REGTEST,
      40           1 :                        {"-dip3params=30:50", "-testactivationheight=brr@1000", "-testactivationheight=v20@1200", "-testactivationheight=mn_rr@2200"})
      41             :     {
      42           1 :     }
      43             : };
      44             : 
      45           1 : static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransactionRef>& txs)
      46             : {
      47           1 :     SimpleUTXOMap utxos;
      48        1492 :     for (auto [i, tx] : std23::views::enumerate(txs)) {
      49         994 :         for (auto [j, output] : std23::views::enumerate(tx->vout)) {
      50         497 :             utxos.try_emplace(COutPoint(tx->GetHash(), j), std::make_pair((int)i + 1, output.nValue));
      51             :         }
      52             :     }
      53           1 :     return utxos;
      54           1 : }
      55             : 
      56           1 : static std::vector<COutPoint> SelectUTXOs(const CChain& active_chain, SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet)
      57             : {
      58           1 :     changeRet = 0;
      59             : 
      60           1 :     std::vector<COutPoint> selectedUtxos;
      61           1 :     CAmount selectedAmount = 0;
      62           3 :     while (!utoxs.empty()) {
      63           3 :         bool found = false;
      64           3 :         for (auto it = utoxs.begin(); it != utoxs.end(); ++it) {
      65           3 :             if (active_chain.Height() - it->second.first < 101) {
      66           0 :                 continue;
      67             :             }
      68             : 
      69           3 :             found = true;
      70           3 :             selectedAmount += it->second.second;
      71           3 :             selectedUtxos.emplace_back(it->first);
      72           3 :             utoxs.erase(it);
      73           3 :             break;
      74             :         }
      75           3 :         BOOST_REQUIRE(found);
      76           3 :         if (selectedAmount >= amount) {
      77           1 :             changeRet = selectedAmount - amount;
      78           1 :             break;
      79             :         }
      80             :     }
      81             : 
      82           1 :     return selectedUtxos;
      83           1 : }
      84             : 
      85           1 : static void FundTransaction(const CChain& active_chain, CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, CAmount amount)
      86             : {
      87             :     CAmount change;
      88           1 :     auto inputs = SelectUTXOs(active_chain, utoxs, amount, change);
      89           4 :     for (const auto& input : inputs) {
      90           3 :         tx.vin.emplace_back(input);
      91             :     }
      92           1 :     tx.vout.emplace_back(amount, scriptPayout);
      93           1 :     if (change != 0) {
      94           1 :         tx.vout.emplace_back(change, scriptPayout);
      95           1 :     }
      96           1 : }
      97             : 
      98           1 : static void SignTransaction(const CTxMemPool& mempool, CMutableTransaction& tx, const CKey& coinbaseKey)
      99             : {
     100           1 :     FillableSigningProvider tempKeystore;
     101           1 :     tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
     102             : 
     103           7 :     for (auto [i, input] : std23::views::enumerate(tx.vin)) {
     104           3 :         uint256 hashBlock;
     105           6 :         CTransactionRef txFrom = GetTransaction(/*block_index=*/nullptr, &mempool, input.prevout.hash,
     106           3 :                                                 Params().GetConsensus(), hashBlock);
     107           3 :         BOOST_REQUIRE(txFrom);
     108           3 :         BOOST_REQUIRE(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL));
     109           3 :     }
     110           1 : }
     111             : 
     112           1 : static CMutableTransaction CreateProRegTx(const CChain& active_chain, const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet)
     113             : {
     114           1 :     ownerKeyRet.MakeNewKey(true);
     115           1 :     operatorKeyRet.MakeNewKey();
     116             : 
     117           1 :     CProRegTx proTx;
     118           1 :     proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false);
     119           1 :     proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion);
     120           1 :     proTx.collateralOutpoint.n = 0;
     121           1 :     BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)),
     122             :                       NetInfoStatus::Success);
     123           1 :     proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID();
     124           1 :     proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load());
     125           1 :     proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID();
     126           1 :     proTx.scriptPayout = scriptPayout;
     127             : 
     128           1 :     CMutableTransaction tx;
     129           1 :     tx.nVersion = 3;
     130           1 :     tx.nType = TRANSACTION_PROVIDER_REGISTER;
     131           1 :     FundTransaction(active_chain, tx, utxos, scriptPayout, dmn_types::Regular.collat_amount);
     132           1 :     proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
     133           1 :     SetTxPayload(tx, proTx);
     134           1 :     SignTransaction(mempool, tx, coinbaseKey);
     135             : 
     136           1 :     return tx;
     137           1 : }
     138             : 
     139           1 : static CScript GenerateRandomAddress()
     140             : {
     141           1 :     CKey key;
     142           1 :     key.MakeNewKey(false);
     143           1 :     return GetScriptForDestination(PKHash(key.GetPubKey()));
     144           1 : }
     145             : 
     146         146 : BOOST_AUTO_TEST_SUITE(block_reward_reallocation_tests)
     147             : 
     148         148 : BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationSetup)
     149             : {
     150           1 :     auto& dmnman = *Assert(m_node.dmnman);
     151           1 :     const auto& consensus_params = Params().GetConsensus();
     152             : 
     153           1 :     CScript coinbasePubKey = GetScriptForRawPubKey(coinbaseKey.GetPubKey());
     154             : 
     155           2 :     BOOST_REQUIRE(DeploymentDIP0003Enforced(WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Height()),
     156             :                                             consensus_params));
     157             : 
     158             :     // Register one MN
     159           1 :     CKey ownerKey;
     160           1 :     CBLSSecretKey operatorKey;
     161           1 :     auto utxos = BuildSimpleUtxoMap(m_coinbase_txns);
     162           1 :     auto tx = CreateProRegTx(m_node.chainman->ActiveChain(), *m_node.mempool, utxos, 1, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey);
     163             : 
     164           1 :     CreateAndProcessBlock({tx}, coinbasePubKey);
     165             : 
     166             :     {
     167           1 :         LOCK(cs_main);
     168           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     169           1 :         dmnman.UpdatedBlockTip(tip);
     170             : 
     171           1 :         BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     172             : 
     173           1 :         BOOST_CHECK_EQUAL(tip->nHeight, 498);
     174           1 :         BOOST_CHECK(tip->nHeight < Params().GetConsensus().BRRHeight);
     175           1 :     }
     176             : 
     177           1 :     CreateAndProcessBlock({}, coinbasePubKey);
     178             : 
     179             :     {
     180           1 :         LOCK(cs_main);
     181           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     182           1 :         BOOST_CHECK_EQUAL(tip->nHeight, 499);
     183           1 :         dmnman.UpdatedBlockTip(tip);
     184           1 :         BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     185           1 :         BOOST_CHECK(tip->nHeight < Params().GetConsensus().BRRHeight);
     186             :         // Creating blocks by different ways
     187           1 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     188           1 :     }
     189         500 :     for ([[maybe_unused]] auto _ : util::irange(499)) {
     190         499 :         CreateAndProcessBlock({}, coinbasePubKey);
     191         499 :         LOCK(cs_main);
     192         499 :         dmnman.UpdatedBlockTip(m_node.chainman->ActiveChain().Tip());
     193         499 :     }
     194           1 :     BOOST_CHECK(m_node.chainman->ActiveChain().Height() < Params().GetConsensus().BRRHeight);
     195           1 :     CreateAndProcessBlock({}, coinbasePubKey);
     196             : 
     197             :     {
     198             :         // Advance to ACTIVE at height = (BRRHeight - 1)
     199           1 :         LOCK(cs_main);
     200           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     201           1 :         BOOST_CHECK_EQUAL(tip->nHeight, Params().GetConsensus().BRRHeight - 1);
     202           1 :         dmnman.UpdatedBlockTip(tip);
     203           1 :         BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     204           1 :     }
     205             : 
     206             :     {
     207             :         // Reward split should stay ~50/50 before the first superblock after activation.
     208             :         // This applies even if reallocation was activated right at superblock height like it does here.
     209             :         // next block should be signaling by default
     210           1 :         LOCK(cs_main);
     211           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     212           1 :         const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     213           1 :         dmnman.UpdatedBlockTip(tip);
     214           1 :         BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash()));
     215           1 :         const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     216           1 :         const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     217           1 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     218           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
     219           1 :     }
     220             : 
     221          20 :     for ([[maybe_unused]] auto _ : util::irange(consensus_params.nSuperblockCycle - 1)) {
     222          19 :         CreateAndProcessBlock({}, coinbasePubKey);
     223             :     }
     224             : 
     225             :     {
     226           1 :         LOCK(cs_main);
     227           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     228           1 :         const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     229           1 :         const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     230           1 :         const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     231           1 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     232           1 :         BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 28847249686);
     233           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
     234           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, 14423624841); // 0.4999999999
     235           1 :     }
     236             : 
     237             :     // Reallocation should kick-in with the superblock after 19 adjustments, 3 superblocks long each
     238          20 :     for ([[maybe_unused]] auto i : util::irange(19)) {
     239          76 :         for ([[maybe_unused]] auto j : util::irange(3)) {
     240        1197 :             for ([[maybe_unused]] auto k : util::irange(consensus_params.nSuperblockCycle)) {
     241        1140 :                 CreateAndProcessBlock({}, coinbasePubKey);
     242             :             }
     243          57 :             LOCK(cs_main);
     244          57 :             const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     245          57 :             const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     246          57 :             const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     247          57 :             const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     248          57 :             const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     249          57 :             BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
     250          57 :         }
     251             :     }
     252           1 :     BOOST_CHECK(DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_V20));
     253             :     // Allocation of block subsidy is 60% MN, 20% miners and 20% treasury
     254             :     {
     255             :         // Reward split should reach ~75/25 after reallocation is done
     256           1 :         LOCK(cs_main);
     257           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     258           1 :         const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     259           1 :         const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     260           1 :         const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     261           1 :         CAmount block_subsidy_potential = block_subsidy + block_subsidy_sb;
     262           1 :         BOOST_CHECK_EQUAL(block_subsidy_potential, 177167660);
     263           1 :         CAmount expected_block_reward = block_subsidy_potential - block_subsidy_potential / 5;
     264             : 
     265           1 :         const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     266           1 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     267           1 :         BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), expected_block_reward);
     268           1 :         BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 141734128);
     269           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment);
     270           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, 106300596); // 0.75
     271           1 :     }
     272           1 :     BOOST_CHECK(!DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_MN_RR));
     273             : 
     274             :     // Reward split should stay ~75/25 after reallocation is done,
     275             :     // check 10 next superblocks
     276          11 :     for ([[maybe_unused]] auto i : util::irange(10)) {
     277         210 :         for ([[maybe_unused]] auto k : util::irange(consensus_params.nSuperblockCycle)) {
     278         200 :             CreateAndProcessBlock({}, coinbasePubKey);
     279             :         }
     280          10 :         LOCK(cs_main);
     281          10 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     282          10 :         const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     283          10 :         const bool isMNRewardReallocated{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_MN_RR)};
     284          10 :         const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     285          10 :         CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     286          10 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     287             : 
     288          10 :         if (isMNRewardReallocated) {
     289           8 :             const CAmount platform_payment = PlatformShare(masternode_payment);
     290           8 :             masternode_payment -= platform_payment;
     291           8 :         }
     292          10 :         size_t payment_index = isMNRewardReallocated ? 1 : 0;
     293             : 
     294          10 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[payment_index].nValue, masternode_payment);
     295          10 :     }
     296             : 
     297           1 :     BOOST_CHECK(DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), consensus_params, Consensus::DEPLOYMENT_MN_RR));
     298             :     { // At this moment Masternode reward should be reallocated to platform
     299             :         // Allocation of block subsidy is 60% MN, 20% miners and 20% treasury
     300           1 :         LOCK(cs_main);
     301           1 :         const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()};
     302           1 :         const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)};
     303           1 :         const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     304           1 :         const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active);
     305           1 :         CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active);
     306           1 :         const CAmount platform_payment = PlatformShare(masternode_payment);
     307           1 :         masternode_payment -= platform_payment;
     308           1 :         const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey);
     309             : 
     310           1 :         CAmount block_subsidy_potential = block_subsidy + block_subsidy_sb;
     311           1 :         BOOST_CHECK_EQUAL(tip->nHeight, 2358);
     312           1 :         BOOST_CHECK_EQUAL(block_subsidy_potential, 164512828);
     313             :         // Treasury is 20% since MNRewardReallocation
     314           1 :         CAmount expected_block_reward = block_subsidy_potential - block_subsidy_potential / 5;
     315             :         // Since MNRewardReallocation, MN reward share is 75% of the block reward
     316           1 :         CAmount expected_masternode_reward = expected_block_reward * 3 / 4;
     317           1 :         CAmount expected_mn_platform_payment = PlatformShare(expected_masternode_reward);
     318           1 :         CAmount expected_mn_core_payment = expected_masternode_reward - expected_mn_platform_payment;
     319             : 
     320           1 :         BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), expected_block_reward);
     321           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[1].nValue, masternode_payment);
     322           1 :         BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[1].nValue, expected_mn_core_payment);
     323           1 :     }
     324           1 : }
     325             : 
     326         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16