From d76e4b7ee0c7d035cd6ecf567047aa76cf662b17 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 01:53:43 -0500 Subject: [PATCH 1/5] test: share v19 activation chain fixture --- src/test/bls_tests.cpp | 17 ---------------- src/test/evo_deterministicmns_tests.cpp | 27 ------------------------- src/test/util/setup_common.cpp | 20 ++++++++++++++++++ src/test/util/setup_common.h | 10 +++++++++ 4 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/test/bls_tests.cpp b/src/test/bls_tests.cpp index 4ebfeb931586..ff0452e2eb89 100644 --- a/src/test/bls_tests.cpp +++ b/src/test/bls_tests.cpp @@ -636,23 +636,6 @@ BOOST_AUTO_TEST_CASE(test_get_hash_consistency) BOOST_CHECK(hash1 == hash2); } -namespace { -struct TestChainV19Setup : public TestChainSetup { - TestChainV19Setup() : - TestChainSetup(494, CBaseChainParams::REGTEST, - {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"}) - { - const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); - // Mine up to the block just before V19 activation - for (int i = 0; i < 5; ++i) { - CreateAndProcessBlock({}, coinbase_pk); - } - assert(DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19) && - !DeploymentActiveAt(*m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19)); - } -}; -} // namespace - BOOST_AUTO_TEST_CASE(v19_boundary_validation_failure_restores_bls_scheme) { TestChainV19Setup setup; diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 4241faa7fa8f..73a735abaa1d 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -1521,33 +1521,6 @@ struct TestChainDIP3Setup : public TestChainDIP3BeforeActivationSetup { } }; -struct TestChainV19BeforeActivationSetup : public TestChainSetup { - TestChainV19BeforeActivationSetup(); -}; - -struct TestChainV19Setup : public TestChainV19BeforeActivationSetup { - TestChainV19Setup() - { - const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); - // Activate V19 - for (int i = 0; i < 5; ++i) { - CreateAndProcessBlock({}, coinbase_pk); - } - bool v19_just_activated{WITH_LOCK(::cs_main, return DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19) && - !DeploymentActiveAt(*m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19))}; - assert(v19_just_activated); - } -}; - -// 5 blocks earlier -TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() : - TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"}) -{ - bool v19_active{WITH_LOCK(::cs_main, return DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), - Consensus::DEPLOYMENT_V19))}; - assert(!v19_active); -} - struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { TestChainV24SignalBeforeV19Setup() : TestChainSetup(494, CBaseChainParams::REGTEST, diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 957c494dfcc1..e21f5cc2908d 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -498,6 +498,26 @@ TestChainSetup::TestChainSetup( } } +TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() + : TestChainSetup{494, CBaseChainParams::REGTEST, + {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"}} +{ + assert(WITH_LOCK(::cs_main, return !DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), + Consensus::DEPLOYMENT_V19))); +} + +TestChainV19Setup::TestChainV19Setup() +{ + const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); + for (int i = 0; i < 5; ++i) { + CreateAndProcessBlock({}, coinbase_pk); + } + assert(WITH_LOCK(::cs_main, return DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), + Consensus::DEPLOYMENT_V19) && + !DeploymentActiveAt(*m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), + Consensus::DEPLOYMENT_V19))); +} + void TestChainSetup::mineBlocks(int num_blocks) { CScript scriptPubKey = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 8001b84248df..2cfe238872db 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -217,6 +217,16 @@ struct TestChainSetup : public TestingSetup CKey coinbaseKey; // private/public key needed to spend coinbase transactions }; +/** Test chain stopped immediately before v19 activation at height 500. */ +struct TestChainV19BeforeActivationSetup : public TestChainSetup { + TestChainV19BeforeActivationSetup(); +}; + +/** Test chain whose next block activates v19 at height 500. */ +struct TestChainV19Setup : public TestChainV19BeforeActivationSetup { + TestChainV19Setup(); +}; + /** * Testing fixture that pre-creates a 100-block REGTEST-mode block chain */ From 0a9eb2d8cb5334625d81e82e971c7db0a08d4e2f Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 02:04:25 -0500 Subject: [PATCH 2/5] test: speed up v24 evo activation --- src/test/evo_deterministicmns_tests.cpp | 88 +++++++++++++++++-------- src/test/util/setup_common.cpp | 18 +++-- src/test/util/setup_common.h | 11 ++-- 3 files changed, 74 insertions(+), 43 deletions(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 73a735abaa1d..c1f13e38826a 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -15,9 +16,11 @@ #include #include #include -#include +#include #include +#include #include +#include #include #include #include @@ -811,20 +814,53 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) return std::nullopt; }; - // Mine until a block's (pre-operator) masternode reward is even: only then does the 50% - // operator share equal the single owner share, so the owner and operator outputs -- both - // paying shared_script -- collide into two identical coinbase outputs. The reward is constant - // within a subsidy/reallocation epoch, so this can take up to a full epoch; intermediate - // blocks are processed to advance the chain. - auto mine_until_duplicate = [&]() -> std::pair { - for (int i = 0; i < 2000; ++i) { - CBlock good = setup.CreateBlock({}, coinbase_pk, chainman.ActiveChainstate()); - if (auto dup = find_duplicate(good)) return {good, *dup}; - BOOST_REQUIRE(chainman.ProcessNewBlock(std::make_shared(good), /*force_processing=*/true, nullptr)); - sync_dmn_tip(); + // At this height the subsidy-only Evo masternode reward is odd. Derive the smallest fee that + // makes it even, then let BlockAssembler distribute that fee so the 50/50 owner and operator + // outputs collide immediately. A fresh fee transaction is needed for each sibling pair. + auto create_duplicate_block = [&]() -> std::pair { + const CBlockIndex* tip = tip_index(); + const MnRewardEra era = GetMnRewardEraAfter(tip, chainman); + BOOST_REQUIRE(era == MnRewardEra::EvoReward); + const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus, /*fV20Active=*/true); + const CAmount platform_reward = PlatformShare(GetMasternodePayment(tip->nHeight + 1, block_subsidy, consensus, era)); + auto payee_reward = [&](CAmount fee) { + return GetMasternodePayment(tip->nHeight + 1, block_subsidy + fee, consensus, era) - platform_reward; + }; + BOOST_REQUIRE((payee_reward(/*fee=*/0) & 1) != 0); + CAmount fee{1}; + while ((payee_reward(fee) & 1) != 0) + ++fee; + BOOST_REQUIRE_EQUAL(fee, 1); + + CMutableTransaction fee_tx; + WITH_LOCK(::cs_main, + FundTransaction(chainman.ActiveChain(), fee_tx, utxos, coinbase_pk, 1 * COIN, setup.coinbaseKey)); + BOOST_REQUIRE(fee_tx.vout.back().nValue > fee); + fee_tx.vout.back().nValue -= fee; + SignTransaction(*setup.m_node.mempool, fee_tx, setup.coinbaseKey); + + auto& mempool = *setup.m_node.mempool; + // Bypass fee policy while preserving the miner's InstantSend safety check. + constexpr int64_t ISLOCK_SAFETY_DELAY{601}; + setup.m_node.clhandler->UpdateTxFirstSeenMap({fee_tx.GetHash()}, tip->GetMedianTimePast() - ISLOCK_SAFETY_DELAY); + { + LOCK2(::cs_main, mempool.cs); + mempool.addUnchecked(TestMemPoolEntryHelper{}.Fee(fee).SpendsCoinbase(true).FromTx(fee_tx)); } - BOOST_REQUIRE_MESSAGE(false, "expected owner/operator collision to yield a duplicate coinbase output"); - return {}; // unreachable, BOOST_REQUIRE above aborts the test + + node::BlockAssembler::Options options; + options.blockMinFeeRate = CFeeRate{0}; + CBlock good = node::BlockAssembler(chainman.ActiveChainstate(), setup.m_node, &mempool, options) + .CreateNewBlock(coinbase_pk) + ->block; + BOOST_REQUIRE( + std::ranges::any_of(good.vtx, [&](const CTransactionRef& tx) { return tx->GetHash() == fee_tx.GetHash(); })); + const auto dup = find_duplicate(good); + BOOST_REQUIRE(dup.has_value()); + good.hashMerkleRoot = BlockMerkleRoot(good); + while (!CheckProofOfWork(good.GetHash(), good.nBits, consensus)) + ++good.nNonce; + return {good, *dup}; }; // Build the "merge cheat" sibling of `good`: drop one of the two identical outputs and fold @@ -860,7 +896,7 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) // ---- Pre-v24: legacy existence-only matching ACCEPTS the merge cheat (masternode underpaid). { - const auto [good, dup] = mine_until_duplicate(); + const auto [good, dup] = create_duplicate_block(); // The collision must be reached while v24 is still pending, otherwise this phase would be // testing post-v24 behaviour by accident. BOOST_REQUIRE(!DeploymentActiveAfter(tip_index(), chainman, Consensus::DEPLOYMENT_V24)); @@ -880,7 +916,7 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) // ---- Post-v24: strict multiplicity matching REJECTS the same merge (two expected outputs, one // distinct actual). Rejection is observed as the tip not advancing. { - const auto [good, dup] = mine_until_duplicate(); + const auto [good, dup] = create_duplicate_block(); const CBlock merged = build_merge_cheat(good, dup); const uint256 tip_before = tip_hash(); chainman.ProcessNewBlock(std::make_shared(merged), /*force_processing=*/true, nullptr); @@ -1525,8 +1561,9 @@ struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { TestChainV24SignalBeforeV19Setup() : TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", - "-testactivationheight=mn_rr@500", - "-vbparams=v24:0:9999999999:0:500:400:300:5:0"}) + "-testactivationheight=mn_rr@500", "-vbparams=v24:0:9999999999:510:1:1:1:5:0"}, + /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true, + uint256S("0x083fa179797ea7e5893198ff1b6eab632526c2eeb6f0ca6c42fcac9cb9bad366")) { assert(WITH_LOCK(::cs_main, return !DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19))); @@ -1535,16 +1572,11 @@ struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { } }; -// v19/v20/mn_rr active at height 500, with v24 signalled but still PENDING (not yet locked in). -// The 500-block window means v24 locks in well after v19, so this fixture stops in the window -// where basic-BLS (v2) registrations are valid but strict multiplicity matching is off, letting -// a single test cross the v24 activation boundary. -struct TestChainV24PendingSetup : public TestChainSetup { - TestChainV24PendingSetup() : - TestChainSetup(494, CBaseChainParams::REGTEST, - {"-testactivationheight=v19@500", "-testactivationheight=v20@500", - "-testactivationheight=mn_rr@500", - "-vbparams=v24:0:9999999999:0:500:400:300:5:0"}) +// Advance the shared v24 chain just far enough that v19/v20/mn_rr are active while v24 remains +// held in LOCKED_IN by its minimum activation height. The boundary test needs this short state: +// basic-BLS registrations are valid, but strict payment multiplicity is not active yet. +struct TestChainV24PendingSetup : public TestChainV24SignalBeforeV19Setup { + TestChainV24PendingSetup() { const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); auto& chainman = *Assert(m_node.chainman); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index e21f5cc2908d..9ae21e0ab5e6 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -450,13 +450,10 @@ TestChain100Setup::TestChain100Setup( { } -TestChainSetup::TestChainSetup( - int num_blocks, - const std::string& chain_name, - const std::vector& extra_args, - const bool coins_db_in_memory, - const bool block_tree_db_in_memory) - : TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} +TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, + const std::vector& extra_args, const bool coins_db_in_memory, + const bool block_tree_db_in_memory, const std::optional& expected_tip_hash) : + TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} { SetMockTime(1598887952); constexpr std::array vchKey = { @@ -492,9 +489,10 @@ TestChainSetup::TestChainSetup( { LOCK(::cs_main); auto hash = checkpoints.mapCheckpoints.find(num_blocks); - assert( - hash != checkpoints.mapCheckpoints.end() && - m_node.chainman->ActiveChain().Tip()->GetBlockHash() == hash->second); + const uint256 actual_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash(); + assert(expected_tip_hash.has_value() || hash != checkpoints.mapCheckpoints.end()); + const uint256& expected_hash = expected_tip_hash.has_value() ? *expected_tip_hash : hash->second; + assert(actual_hash == expected_hash); } } diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 2cfe238872db..9aed11f7fb08 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -143,11 +144,11 @@ class CScript; struct TestChainSetup : public TestingSetup { - TestChainSetup(int num_blocks, - const std::string& chain_name = CBaseChainParams::REGTEST, - const std::vector& extra_args = {}, - const bool coins_db_in_memory = true, - const bool block_tree_db_in_memory = true); + /** @param expected_tip_hash Configuration-specific checkpoint overriding the height default. */ + TestChainSetup(int num_blocks, const std::string& chain_name = CBaseChainParams::REGTEST, + const std::vector& extra_args = {}, const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true, + const std::optional& expected_tip_hash = std::nullopt); ~TestChainSetup(); /** From bcbf35e873540c1c67b141e7ca46362d21f7e51a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 02:45:57 -0500 Subject: [PATCH 3/5] test: lower evo activation fixture heights --- src/test/evo_deterministicmns_tests.cpp | 9 ++++++++- src/test/util/setup_common.cpp | 17 ++++++++++++++--- src/test/util/setup_common.h | 4 ++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index c1f13e38826a..8f16c0b45f58 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -1542,9 +1542,16 @@ static void SmlCache(TestChainSetup& setup) BOOST_AUTO_TEST_SUITE(evo_dip3_activation_tests) +// FuncDIP3Protx registers six masternodes in successive blocks. Height 109 is the lowest boundary +// that keeps two mature coinbases available for every 1000 DASH collateral; height 108 runs out on +// the sixth registration. +constexpr int DIP3_ACTIVATION_HEIGHT{109}; + struct TestChainDIP3BeforeActivationSetup : public TestChainSetup { TestChainDIP3BeforeActivationSetup() : - TestChainSetup(430) + TestChainSetup(DIP3_ACTIVATION_HEIGHT - 2, CBaseChainParams::REGTEST, {"-dip3params=109:500"}, + /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true, + uint256S("0x40233e79ab24bc7c3e5686ac2b63915e15e1b1deecc3d0919f7ec32a9433fdfb")) { } }; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 9ae21e0ab5e6..14594bb97920 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -496,9 +496,20 @@ TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, } } -TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() - : TestChainSetup{494, CBaseChainParams::REGTEST, - {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@500"}} +namespace { +// This is the lowest activation height that leaves enough mature pre-mined coinbases for all +// consumers of the shared fixture. The DIP3 prerequisite is active before the v19 boundary work. +constexpr int V19_ACTIVATION_HEIGHT{109}; +} // namespace + +TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() : + TestChainSetup{V19_ACTIVATION_HEIGHT - 6, + CBaseChainParams::REGTEST, + {"-dip3params=100:500", "-testactivationheight=v19@109", "-testactivationheight=v20@109", + "-testactivationheight=mn_rr@109"}, + /*coins_db_in_memory=*/true, + /*block_tree_db_in_memory=*/true, + uint256S("0x13adad9565d0ca558f5675c50e3828f4354d26b64de044ebc88686056f30faab")} { assert(WITH_LOCK(::cs_main, return !DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19))); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 9aed11f7fb08..4477c8e5a16c 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -218,12 +218,12 @@ struct TestChainSetup : public TestingSetup CKey coinbaseKey; // private/public key needed to spend coinbase transactions }; -/** Test chain stopped immediately before v19 activation at height 500. */ +/** Test chain stopped five blocks before v19 activation at height 109. */ struct TestChainV19BeforeActivationSetup : public TestChainSetup { TestChainV19BeforeActivationSetup(); }; -/** Test chain whose next block activates v19 at height 500. */ +/** Test chain whose next block activates v19 at height 109. */ struct TestChainV19Setup : public TestChainV19BeforeActivationSetup { TestChainV19Setup(); }; From a88c7017eba74f768ffcb6c28306ebbd9a91bae0 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 08:31:36 -0500 Subject: [PATCH 4/5] test: restore v24 boundary block helper --- src/test/evo_deterministicmns_tests.cpp | 79 +++++++------------------ 1 file changed, 23 insertions(+), 56 deletions(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 8f16c0b45f58..7128914cb098 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -16,11 +15,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -715,6 +712,7 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) // Start in the pre-v24 window: v19 active (basic BLS scheme) so we can register a v2 MN, but // v24 -- and thus strict multiplicity matching -- not yet active. BOOST_REQUIRE(DeploymentActiveAfter(tip_index(), consensus, Consensus::DEPLOYMENT_V19)); + BOOST_REQUIRE(!DeploymentActiveAfter(tip_index(), consensus, Consensus::DEPLOYMENT_MN_RR)); BOOST_REQUIRE(!DeploymentActiveAfter(tip_index(), chainman, Consensus::DEPLOYMENT_V24)); BOOST_REQUIRE(!bls::bls_legacy_scheme.load()); @@ -814,53 +812,20 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) return std::nullopt; }; - // At this height the subsidy-only Evo masternode reward is odd. Derive the smallest fee that - // makes it even, then let BlockAssembler distribute that fee so the 50/50 owner and operator - // outputs collide immediately. A fresh fee transaction is needed for each sibling pair. - auto create_duplicate_block = [&]() -> std::pair { - const CBlockIndex* tip = tip_index(); - const MnRewardEra era = GetMnRewardEraAfter(tip, chainman); - BOOST_REQUIRE(era == MnRewardEra::EvoReward); - const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus, /*fV20Active=*/true); - const CAmount platform_reward = PlatformShare(GetMasternodePayment(tip->nHeight + 1, block_subsidy, consensus, era)); - auto payee_reward = [&](CAmount fee) { - return GetMasternodePayment(tip->nHeight + 1, block_subsidy + fee, consensus, era) - platform_reward; - }; - BOOST_REQUIRE((payee_reward(/*fee=*/0) & 1) != 0); - CAmount fee{1}; - while ((payee_reward(fee) & 1) != 0) - ++fee; - BOOST_REQUIRE_EQUAL(fee, 1); - - CMutableTransaction fee_tx; - WITH_LOCK(::cs_main, - FundTransaction(chainman.ActiveChain(), fee_tx, utxos, coinbase_pk, 1 * COIN, setup.coinbaseKey)); - BOOST_REQUIRE(fee_tx.vout.back().nValue > fee); - fee_tx.vout.back().nValue -= fee; - SignTransaction(*setup.m_node.mempool, fee_tx, setup.coinbaseKey); - - auto& mempool = *setup.m_node.mempool; - // Bypass fee policy while preserving the miner's InstantSend safety check. - constexpr int64_t ISLOCK_SAFETY_DELAY{601}; - setup.m_node.clhandler->UpdateTxFirstSeenMap({fee_tx.GetHash()}, tip->GetMedianTimePast() - ISLOCK_SAFETY_DELAY); - { - LOCK2(::cs_main, mempool.cs); - mempool.addUnchecked(TestMemPoolEntryHelper{}.Fee(fee).SpendsCoinbase(true).FromTx(fee_tx)); + // Mine until a block's (pre-operator) masternode reward is even: only then does the 50% + // operator share equal the single owner share, so the owner and operator outputs -- both + // paying shared_script -- collide into two identical coinbase outputs. The reward is constant + // within a subsidy/reallocation epoch, so this can take up to a full epoch; intermediate + // blocks are processed to advance the chain. + auto mine_until_duplicate = [&]() -> std::pair { + for (int i = 0; i < 2000; ++i) { + CBlock good = setup.CreateBlock({}, coinbase_pk, chainman.ActiveChainstate()); + if (auto dup = find_duplicate(good)) return {good, *dup}; + BOOST_REQUIRE(chainman.ProcessNewBlock(std::make_shared(good), /*force_processing=*/true, nullptr)); + sync_dmn_tip(); } - - node::BlockAssembler::Options options; - options.blockMinFeeRate = CFeeRate{0}; - CBlock good = node::BlockAssembler(chainman.ActiveChainstate(), setup.m_node, &mempool, options) - .CreateNewBlock(coinbase_pk) - ->block; - BOOST_REQUIRE( - std::ranges::any_of(good.vtx, [&](const CTransactionRef& tx) { return tx->GetHash() == fee_tx.GetHash(); })); - const auto dup = find_duplicate(good); - BOOST_REQUIRE(dup.has_value()); - good.hashMerkleRoot = BlockMerkleRoot(good); - while (!CheckProofOfWork(good.GetHash(), good.nBits, consensus)) - ++good.nNonce; - return {good, *dup}; + BOOST_REQUIRE_MESSAGE(false, "expected owner/operator collision to yield a duplicate coinbase output"); + return {}; // unreachable, BOOST_REQUIRE above aborts the test }; // Build the "merge cheat" sibling of `good`: drop one of the two identical outputs and fold @@ -896,7 +861,7 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) // ---- Pre-v24: legacy existence-only matching ACCEPTS the merge cheat (masternode underpaid). { - const auto [good, dup] = create_duplicate_block(); + const auto [good, dup] = mine_until_duplicate(); // The collision must be reached while v24 is still pending, otherwise this phase would be // testing post-v24 behaviour by accident. BOOST_REQUIRE(!DeploymentActiveAfter(tip_index(), chainman, Consensus::DEPLOYMENT_V24)); @@ -912,11 +877,12 @@ void FuncMNPaymentMultiplicityV24Boundary(TestChainSetup& setup) sync_dmn_tip(); } BOOST_REQUIRE(DeploymentActiveAfter(tip_index(), chainman, Consensus::DEPLOYMENT_V24)); + BOOST_REQUIRE(!DeploymentActiveAfter(tip_index(), consensus, Consensus::DEPLOYMENT_MN_RR)); // ---- Post-v24: strict multiplicity matching REJECTS the same merge (two expected outputs, one // distinct actual). Rejection is observed as the tip not advancing. { - const auto [good, dup] = create_duplicate_block(); + const auto [good, dup] = mine_until_duplicate(); const CBlock merged = build_merge_cheat(good, dup); const uint256 tip_before = tip_hash(); chainman.ProcessNewBlock(std::make_shared(merged), /*force_processing=*/true, nullptr); @@ -1568,7 +1534,7 @@ struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { TestChainV24SignalBeforeV19Setup() : TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", - "-testactivationheight=mn_rr@500", "-vbparams=v24:0:9999999999:510:1:1:1:5:0"}, + "-testactivationheight=mn_rr@511", "-vbparams=v24:0:9999999999:510:1:1:1:5:0"}, /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true, uint256S("0x083fa179797ea7e5893198ff1b6eab632526c2eeb6f0ca6c42fcac9cb9bad366")) { @@ -1579,16 +1545,17 @@ struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { } }; -// Advance the shared v24 chain just far enough that v19/v20/mn_rr are active while v24 remains -// held in LOCKED_IN by its minimum activation height. The boundary test needs this short state: -// basic-BLS registrations are valid, but strict payment multiplicity is not active yet. +// Advance the shared v24 chain just far enough that v19/v20 are active while v24 remains held in +// LOCKED_IN by its minimum activation height. Delaying mn_rr until the block after v24 keeps the +// subsidy-only masternode reward even on both sides of the boundary, so the duplicate-payment +// helper does not need to mine to the next subsidy epoch. struct TestChainV24PendingSetup : public TestChainV24SignalBeforeV19Setup { TestChainV24PendingSetup() { const CScript coinbase_pk = GetScriptForRawPubKey(coinbaseKey.GetPubKey()); auto& chainman = *Assert(m_node.chainman); auto& dmnman = *Assert(m_node.dmnman); - // Mine just enough to activate v19/v20/mn_rr (height 500) while keeping v24 pending. + // Mine just enough to activate v19/v20 (height 500) while keeping v24 and mn_rr pending. for (int i = 0; i < 20 && WITH_LOCK(::cs_main, return !DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman.GetConsensus(), Consensus::DEPLOYMENT_V19)); ++i) { CreateAndProcessBlock({}, coinbase_pk); dmnman.UpdatedBlockTip(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip())); From b57f55f00c9a5d1053028c22ef31cc1953f36061 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 12:36:55 -0500 Subject: [PATCH 5/5] test: use shared fixture checkpoints --- src/test/bls_tests.cpp | 1 - src/test/evo_deterministicmns_tests.cpp | 6 ++--- src/test/util/setup_common.cpp | 29 +++++++++++++++---------- src/test/util/setup_common.h | 11 +++++----- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/test/bls_tests.cpp b/src/test/bls_tests.cpp index ff0452e2eb89..40f244e3e818 100644 --- a/src/test/bls_tests.cpp +++ b/src/test/bls_tests.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 7128914cb098..b3bc63a27a97 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -1516,8 +1516,7 @@ constexpr int DIP3_ACTIVATION_HEIGHT{109}; struct TestChainDIP3BeforeActivationSetup : public TestChainSetup { TestChainDIP3BeforeActivationSetup() : TestChainSetup(DIP3_ACTIVATION_HEIGHT - 2, CBaseChainParams::REGTEST, {"-dip3params=109:500"}, - /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true, - uint256S("0x40233e79ab24bc7c3e5686ac2b63915e15e1b1deecc3d0919f7ec32a9433fdfb")) + /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true) { } }; @@ -1535,8 +1534,7 @@ struct TestChainV24SignalBeforeV19Setup : public TestChainSetup { TestChainSetup(494, CBaseChainParams::REGTEST, {"-testactivationheight=v19@500", "-testactivationheight=v20@500", "-testactivationheight=mn_rr@511", "-vbparams=v24:0:9999999999:510:1:1:1:5:0"}, - /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true, - uint256S("0x083fa179797ea7e5893198ff1b6eab632526c2eeb6f0ca6c42fcac9cb9bad366")) + /*coins_db_in_memory=*/true, /*block_tree_db_in_memory=*/true) { assert(WITH_LOCK(::cs_main, return !DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19))); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 14594bb97920..57a4efe0e819 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -450,10 +450,13 @@ TestChain100Setup::TestChain100Setup( { } -TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, - const std::vector& extra_args, const bool coins_db_in_memory, - const bool block_tree_db_in_memory, const std::optional& expected_tip_hash) : - TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} +TestChainSetup::TestChainSetup( + int num_blocks, + const std::string& chain_name, + const std::vector& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} { SetMockTime(1598887952); constexpr std::array vchKey = { @@ -477,22 +480,25 @@ TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, { 98, uint256S("0x150e127929d578d8129b77a6cb7e2e343a1379aa3feaaa9cce59e0a645756a81") }, /*TestChain100Setup=*/ { 100, uint256S("0x6ffb83129c19ebdf1ae3771be6a67fe34b35f4c956326b9ba152fac1649f65ae") }, + /*TestChainV19BeforeActivationSetup=*/ + { 103, uint256S("0x13adad9565d0ca558f5675c50e3828f4354d26b64de044ebc88686056f30faab") }, + /*TestChainDIP3BeforeActivationSetup=*/ + { 107, uint256S("0x40233e79ab24bc7c3e5686ac2b63915e15e1b1deecc3d0919f7ec32a9433fdfb") }, /*TestChainDIP3BeforeActivationSetup=*/ { 430, uint256S("0x0bcefaa33fec56cd84d05d0e76cd6a78badcc20f627d91903646de6a07930a14") }, + /*TestChainV24SignalBeforeV19Setup=*/ + { 494, uint256S("0x083fa179797ea7e5893198ff1b6eab632526c2eeb6f0ca6c42fcac9cb9bad366") }, /*TestChainBRRBeforeActivationSetup=*/ { 497, uint256S("0x0857a9b5db51835b1c828f019f4c664b5fe6c28ac44a6d868436930f832d31e5") }, - /*TestChainV19BeforeActivationSetup=*/ - { 494, uint256S("0x44ee5c8a5e5cbd4437d63c54ddc1d40329be811b25c492fa901e11cdf408f905") }, } }; { LOCK(::cs_main); auto hash = checkpoints.mapCheckpoints.find(num_blocks); - const uint256 actual_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash(); - assert(expected_tip_hash.has_value() || hash != checkpoints.mapCheckpoints.end()); - const uint256& expected_hash = expected_tip_hash.has_value() ? *expected_tip_hash : hash->second; - assert(actual_hash == expected_hash); + assert( + hash != checkpoints.mapCheckpoints.end() && + m_node.chainman->ActiveChain().Tip()->GetBlockHash() == hash->second); } } @@ -508,8 +514,7 @@ TestChainV19BeforeActivationSetup::TestChainV19BeforeActivationSetup() : {"-dip3params=100:500", "-testactivationheight=v19@109", "-testactivationheight=v20@109", "-testactivationheight=mn_rr@109"}, /*coins_db_in_memory=*/true, - /*block_tree_db_in_memory=*/true, - uint256S("0x13adad9565d0ca558f5675c50e3828f4354d26b64de044ebc88686056f30faab")} + /*block_tree_db_in_memory=*/true} { assert(WITH_LOCK(::cs_main, return !DeploymentActiveAfter(m_node.chainman->ActiveChain().Tip(), m_node.chainman->GetConsensus(), Consensus::DEPLOYMENT_V19))); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 4477c8e5a16c..63f2a9d52a16 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -144,11 +143,11 @@ class CScript; struct TestChainSetup : public TestingSetup { - /** @param expected_tip_hash Configuration-specific checkpoint overriding the height default. */ - TestChainSetup(int num_blocks, const std::string& chain_name = CBaseChainParams::REGTEST, - const std::vector& extra_args = {}, const bool coins_db_in_memory = true, - const bool block_tree_db_in_memory = true, - const std::optional& expected_tip_hash = std::nullopt); + TestChainSetup(int num_blocks, + const std::string& chain_name = CBaseChainParams::REGTEST, + const std::vector& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); ~TestChainSetup(); /**