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