From 9c7033f009aef0474e1589839c63c53139b7b729 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 24 Sep 2026 09:07:36 +0700 Subject: [PATCH] feat(platform)!: masternode votes cost 80% less (PV14) A masternode vote takes contested_document_single_vote_cost from the contest's prefunded balance. At protocol version 14 that cost drops from 10,000,000 credits (0.0001 Dash) to 2,000,000 (0.00002 Dash), so a DPNS contender's 0.1 Dash funds 5,000 votes instead of 1,000 and a busy contest is far less likely to run dry and refuse later votes. Amended in VOTE_RESOLUTION_FUND_FEES_VERSION2, which only protocol version 14 uses. The voting strategy test that pins a latest-version fee is re-pinned and gains a protocol version 13 twin that keeps the old 0.0001 Dash baseline. Co-Authored-By: Claude Opus 5.5 --- .../strategy_tests/test_cases/voting_tests.rs | 57 +++++++++++++++---- .../fee/vote_resolution_fund_fees/v2.rs | 2 + .../rs-platform-version/src/version/v14.rs | 14 ++++- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs index feba7ab5fe5..2672a7e624c 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs @@ -1748,6 +1748,38 @@ mod tests { #[stack_size(STACK_SIZE)] #[test] async fn run_chain_with_voting_after_won_by_identity_with_specialized_funds_distribution() { + // A vote costs 2_000_000 from protocol version 14. + // We did 5 votes in this epoch, and from protocol version 14 each contested document + // contributes 0.1 DASH (10_000_000_000) to the vote resolution fund instead of 0.2 + // DASH, so the two contenders funded 20_000_000_000, of which 19 votes cost 38_000_000 + // and 19_962_000_000 was left over when the vote finished. + // So we basically have 19_962_000_000 + 10_000_000 + run_chain_with_voting_after_won_by_identity_with_specialized_funds_distribution_at_protocol_version( + PlatformVersion::latest().protocol_version, + 19_972_000_000, + ) + .await; + } + + /// PROTOCOL_VERSION_13: a vote costs 10_000_000 and each contested document contributes + /// 0.2 DASH, so the two contenders funded 40_000_000_000, of which 19 votes cost + /// 190_000_000 and 39_810_000_000 was left over; with the 5 votes of the epoch that is + /// 39_810_000_000 + 50_000_000. + #[stack_size(STACK_SIZE)] + #[test] + async fn run_chain_with_voting_after_won_by_identity_with_specialized_funds_distribution_protocol_version_13( + ) { + run_chain_with_voting_after_won_by_identity_with_specialized_funds_distribution_at_protocol_version( + 13, + 39_860_000_000, + ) + .await; + } + + async fn run_chain_with_voting_after_won_by_identity_with_specialized_funds_distribution_at_protocol_version( + protocol_version: dpp::version::ProtocolVersion, + expected_processing_fees: u64, + ) { // In this test we try to insert two state transitions with the same unique index // We use the DPNS contract, and we insert two documents both with the same "name" // This is a common scenario we should see quite often @@ -1765,9 +1797,21 @@ mod tests { }; let mut platform = TestPlatformBuilder::new() .with_config(config.clone()) + .with_initial_protocol_version(protocol_version) .build_with_mock_rpc(); - let platform_version = PlatformVersion::latest(); + let platform_version = PlatformVersion::get(protocol_version) + .expect("expected platform version for the requested protocol_version"); + // An older protocol version is held by the proposers the first run sets up, whose + // versions the continued run carries on; the latest needs no holding + let upgrading_info = + (protocol_version != PlatformVersion::latest().protocol_version).then(|| { + UpgradingInfo { + current_protocol_version: protocol_version, + proposed_protocol_versions_with_weight: vec![(protocol_version, 1)], + upgrade_three_quarters_life: 0.2, + } + }); let mut rng = StdRng::seed_from_u64(567); @@ -1890,7 +1934,7 @@ mod tests { extra_normal_mns: 0, validator_quorum_count: 24, chain_lock_quorum_count: 24, - upgrading_info: None, + upgrading_info, proposer_strategy: Default::default(), rotate_quorums: false, failure_testing: None, @@ -2145,14 +2189,7 @@ mod tests { ) .expect("expected to get processing fees made in epoch"); - // A vote costs 10_000_000 - // We did 5 votes in this epoch, - // From protocol version 14 each contested document contributes 0.1 DASH - // (10_000_000_000) to the vote resolution fund instead of 0.2 DASH, so the two - // contenders funded 20_000_000_000, of which 19 votes cost 190_000_000 and - // 19_810_000_000 was left over when the vote finished. - // So we basically have 19_810_000_000 + 50_000_000 - assert_eq!(processing_fees, 19_860_000_000); + assert_eq!(processing_fees, expected_processing_fees); } #[stack_size(STACK_SIZE)] diff --git a/packages/rs-platform-version/src/version/fee/vote_resolution_fund_fees/v2.rs b/packages/rs-platform-version/src/version/fee/vote_resolution_fund_fees/v2.rs index c13e7f8c94e..e31728180fb 100644 --- a/packages/rs-platform-version/src/version/fee/vote_resolution_fund_fees/v2.rs +++ b/packages/rs-platform-version/src/version/fee/vote_resolution_fund_fees/v2.rs @@ -4,5 +4,7 @@ use crate::version::fee::vote_resolution_fund_fees::VoteResolutionFundFees; /// Introduced in protocol version 14 (4.2). pub const VOTE_RESOLUTION_FUND_FEES_VERSION2: VoteResolutionFundFees = VoteResolutionFundFees { contested_document_vote_resolution_fund_required_amount: 10_000_000_000, // 0.1 DASH + // A fifth of the 0.0001 DASH before: a contest's fund covers five times the votes + contested_document_single_vote_cost: 2_000_000, // 0.00002 DASH ..VOTE_RESOLUTION_FUND_FEES_VERSION1 }; diff --git a/packages/rs-platform-version/src/version/v14.rs b/packages/rs-platform-version/src/version/v14.rs index 2f9f81d3334..fa16226c5f6 100644 --- a/packages/rs-platform-version/src/version/v14.rs +++ b/packages/rs-platform-version/src/version/v14.rs @@ -1092,7 +1092,7 @@ pub const PLATFORM_V14: PlatformVersion = PlatformVersion { // The TTL ephemeral-bytes rate (270 credits/byte to processing) rides // the shared storage table; it is dead below v14 (the `ttl` grammar // does not parse), so no table fork is needed. - fee_version: FEE_VERSION3, // changed: contested document contribution reduced to 0.1 DASH; registration surcharge for once-per-identity token distributions + fee_version: FEE_VERSION3, // changed: contested document contribution reduced to 0.1 DASH; masternode vote cost reduced to 0.00002 DASH; registration surcharge for once-per-identity token distributions system_limits: SYSTEM_LIMITS_V4, // changed: daily withdrawal limit becomes 15% of the total credits a day ago + time-range overlap-factor cap (24) + time-range TTL cap (1 week) and per-write drop cap (32) + GroveDB proof envelope floor (V1); max_contract_moderators, max_contract_suspension_until, max_contract_moderation_reason_length, max_contract_warnings_per_identity, max_contract_moderation_reason_documents and contract_document_restore_window_ms (a week) consensus: ConsensusVersions { tenderdash_consensus_version: 1, @@ -1116,12 +1116,24 @@ mod tests { 20_000_000_000, "protocol {protocol_version} must preserve the 0.2 DASH contribution" ); + assert_eq!( + version + .fee_version + .vote_resolution_fund_fees + .contested_document_single_vote_cost, + 10_000_000, + "protocol {protocol_version} must preserve the 0.0001 DASH vote" + ); } let mut expected_fees = PLATFORM_V13.fee_version.clone(); expected_fees .vote_resolution_fund_fees .contested_document_vote_resolution_fund_required_amount = 10_000_000_000; + // A masternode vote costs a fifth of what it did: 0.00002 DASH from the contest's fund + expected_fees + .vote_resolution_fund_fees + .contested_document_single_vote_cost = 2_000_000; // The once-per-identity token distribution exists from protocol version 14 on, and a // token that uses it pays the surcharge of the other distribution kinds. assert_eq!(