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

          Line data    Source code
       1             : // Copyright (c) 2026 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 <chain.h>
       6             : #include <chainparams.h>
       7             : #include <consensus/amount.h>
       8             : #include <governance/superblock.h>
       9             : #include <key.h>
      10             : #include <primitives/transaction.h>
      11             : #include <pubkey.h>
      12             : #include <script/script.h>
      13             : #include <script/standard.h>
      14             : #include <uint256.h>
      15             : 
      16             : #include <test/util/setup_common.h>
      17             : 
      18             : #include <boost/test/unit_test.hpp>
      19             : 
      20             : #include <vector>
      21             : 
      22             : struct SuperblockRegtestSetup : public BasicTestingSetup {
      23           1 :     SuperblockRegtestSetup() : BasicTestingSetup(CBaseChainParams::REGTEST) {}
      24             : };
      25             : 
      26         146 : BOOST_FIXTURE_TEST_SUITE(governance_superblock_tests, SuperblockRegtestSetup)
      27             : 
      28             : // Regression test for: CSuperblock::IsValid was matching expected payments
      29             : // against coinbase outputs using a forward scan that re-started at the
      30             : // previously matched index, which allowed two adjacent expected payments
      31             : // with identical scriptPubKey and amount to both match the same coinbase
      32             : // output. Each expected payment must consume a distinct output.
      33         148 : BOOST_AUTO_TEST_CASE(isvalid_duplicate_payments_require_distinct_outputs)
      34             : {
      35           1 :     const auto& consensus = Params().GetConsensus();
      36           1 :     const int nBlockHeight = consensus.nSuperblockStartBlock + consensus.nSuperblockCycle;
      37           1 :     BOOST_REQUIRE(CSuperblock::IsValidBlockHeight(nBlockHeight));
      38             : 
      39           1 :     CKey key;
      40           1 :     key.MakeNewKey(/*fCompressed=*/true);
      41           1 :     const CTxDestination dest{PKHash(key.GetPubKey())};
      42           1 :     const CScript scriptPayee = GetScriptForDestination(dest);
      43           1 :     const CAmount nPayAmount = 1 * COIN;
      44             : 
      45             :     // Two identical expected payments (same script, same amount).
      46           1 :     std::vector<CGovernancePayment> payments;
      47           1 :     payments.emplace_back(dest, nPayAmount, /*proposalHash=*/uint256());
      48           1 :     payments.emplace_back(dest, nPayAmount, /*proposalHash=*/uint256::ONE);
      49           1 :     BOOST_REQUIRE(payments[0].IsValid());
      50           1 :     BOOST_REQUIRE(payments[1].IsValid());
      51             : 
      52           1 :     CSuperblock sb(nBlockHeight, payments);
      53           1 :     BOOST_REQUIRE_EQUAL(sb.CountPayments(), 2);
      54             : 
      55           1 :     const CScript scriptMinerOrMN = CScript() << OP_RETURN;
      56           1 :     const CAmount blockReward = 500 * COIN;
      57           1 :     CChain dummy_chain;
      58             : 
      59             :     // Case 1 (regression, V24): coinbase carries only ONE output matching the
      60             :     // duplicate expected payment. With the buggy forward scan that restarted
      61             :     // at the previously matched index, both expected payments would match the
      62             :     // single matching vout and IsValid would (incorrectly) return true.
      63             :     // From V24 on, the second expected payment must find a distinct output
      64             :     // and validation must fail.
      65             :     {
      66           1 :         CMutableTransaction txNew;
      67           1 :         txNew.vout.emplace_back(blockReward - nPayAmount, scriptMinerOrMN);
      68           1 :         txNew.vout.emplace_back(nPayAmount, scriptPayee); // single matching output
      69           1 :         BOOST_CHECK(!sb.IsValid(dummy_chain, CTransaction(txNew), nBlockHeight, blockReward, /*is_v24=*/true));
      70           1 :     }
      71             : 
      72             :     // Case 2 (V24): coinbase carries TWO outputs matching the duplicate expected
      73             :     // payments. The fix must still accept this legitimate case.
      74             :     {
      75           1 :         CMutableTransaction txNew;
      76           1 :         txNew.vout.emplace_back(blockReward - 2 * nPayAmount, scriptMinerOrMN);
      77           1 :         txNew.vout.emplace_back(nPayAmount, scriptPayee);
      78           1 :         txNew.vout.emplace_back(nPayAmount, scriptPayee);
      79           1 :         BOOST_CHECK(sb.IsValid(dummy_chain, CTransaction(txNew), nBlockHeight, blockReward, /*is_v24=*/true));
      80           1 :     }
      81             : 
      82             :     // Case 3 (pre-V24): the stricter distinct-output rule is gated behind V24.
      83             :     // Before activation the legacy scan is preserved, so the single-output
      84             :     // coinbase from Case 1 must still be accepted to avoid changing consensus
      85             :     // for already-validated history.
      86             :     {
      87           1 :         CMutableTransaction txNew;
      88           1 :         txNew.vout.emplace_back(blockReward - nPayAmount, scriptMinerOrMN);
      89           1 :         txNew.vout.emplace_back(nPayAmount, scriptPayee); // single matching output
      90           1 :         BOOST_CHECK(sb.IsValid(dummy_chain, CTransaction(txNew), nBlockHeight, blockReward, /*is_v24=*/false));
      91           1 :     }
      92           1 : }
      93             : 
      94         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16