diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/mod.rs index a12f1c79c1f..c253ce42ec7 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/mod.rs @@ -38,3 +38,127 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + + #[test] + fn test_dispatcher_none_version_returns_none() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .block_end + .should_checkpoint = None; + + use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; + use crate::execution::types::block_state_info::v0::BlockStateInfoV0; + use crate::execution::types::block_state_info::BlockStateInfo; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; + use std::collections::BTreeMap; + + let platform_state = platform.state.load(); + let block_platform_state = platform_state.as_ref().clone(); + + let block_execution_context = BlockExecutionContext::V0(BlockExecutionContextV0 { + block_state_info: BlockStateInfo::V0(BlockStateInfoV0 { + height: 1, + round: 0, + block_time_ms: 1_000_000, + previous_block_time_ms: None, + proposer_pro_tx_hash: [0u8; 32], + core_chain_locked_height: 1, + block_hash: None, + app_hash: None, + }), + epoch_info: EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }), + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs::default(), + block_address_balance_changes: BTreeMap::new(), + block_platform_state, + proposer_results: None, + }); + + // When should_checkpoint version is None, the dispatcher returns Ok(None) directly + let result = platform + .should_checkpoint(&block_execution_context, &modified_version) + .expect("expected Ok"); + assert!(result.is_none(), "expected None when version is None"); + } + + #[test] + fn test_dispatcher_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .block_end + .should_checkpoint = Some(255); + + use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; + use crate::execution::types::block_state_info::v0::BlockStateInfoV0; + use crate::execution::types::block_state_info::BlockStateInfo; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; + use std::collections::BTreeMap; + + let platform_state = platform.state.load(); + let block_platform_state = platform_state.as_ref().clone(); + + let block_execution_context = BlockExecutionContext::V0(BlockExecutionContextV0 { + block_state_info: BlockStateInfo::V0(BlockStateInfoV0 { + height: 1, + round: 0, + block_time_ms: 1_000_000, + previous_block_time_ms: None, + proposer_pro_tx_hash: [0u8; 32], + core_chain_locked_height: 1, + block_hash: None, + app_hash: None, + }), + epoch_info: EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }), + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs::default(), + block_address_balance_changes: BTreeMap::new(), + block_platform_state, + proposer_results: None, + }); + + let result = platform.should_checkpoint(&block_execution_context, &modified_version); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "should_checkpoint"); + assert_eq!(known_versions, vec![0]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/update_drive_cache/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/update_drive_cache/v0/mod.rs index 35852a05d92..cc49b678424 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/update_drive_cache/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/update_drive_cache/v0/mod.rs @@ -35,3 +35,187 @@ where protocol_versions_counter.merge_block_cache() } } + +#[cfg(test)] +mod tests { + use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; + use crate::execution::types::block_execution_context::BlockExecutionContext; + use crate::execution::types::block_state_info::v0::BlockStateInfoV0; + use crate::execution::types::block_state_info::BlockStateInfo; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + fn make_block_execution_context(epoch_info: EpochInfo) -> BlockExecutionContext { + let platform_version = PlatformVersion::latest(); + let platform_state = + crate::platform_types::platform_state::PlatformState::default_with_protocol_versions( + platform_version.protocol_version, + platform_version.protocol_version, + &crate::config::PlatformConfig::default_for_network( + dpp::dashcore::Network::Testnet, + ), + ) + .expect("expected platform state"); + + BlockExecutionContext::V0(BlockExecutionContextV0 { + block_state_info: BlockStateInfo::V0(BlockStateInfoV0 { + height: 1, + round: 0, + block_time_ms: 1_000_000, + previous_block_time_ms: None, + proposer_pro_tx_hash: [0u8; 32], + core_chain_locked_height: 1, + block_hash: None, + app_hash: None, + }), + epoch_info, + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs::default(), + block_address_balance_changes: BTreeMap::new(), + block_platform_state: platform_state, + proposer_results: None, + }) + } + + #[test] + fn test_update_drive_cache_merges_block_cache_without_epoch_change() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Put some data in the block cache + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.set_block_cache_version_count(next_version, 5); + } + + // No epoch change + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_execution_context = make_block_execution_context(epoch_info); + + platform.update_drive_cache_v0(&block_execution_context); + + // After merging, the block cache data should now be in the global cache + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + let value = counter.get(&next_version).expect("expected to get value"); + assert_eq!(value, Some(&5u64)); + } + } + + #[test] + fn test_update_drive_cache_clears_and_unblocks_global_cache_on_epoch_change() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Put some data in both caches and block the global cache + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 50); + counter.set_block_cache_version_count(next_version, 3); + counter.block_global_cache(); + } + + // Verify global cache is blocked + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + assert!(counter.get(&next_version).is_err()); + } + + // Epoch change (not genesis) + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 5, + previous_epoch_index: Some(4), + is_epoch_change: true, + }); + + let block_execution_context = make_block_execution_context(epoch_info); + + platform.update_drive_cache_v0(&block_execution_context); + + // After epoch change, global cache should be cleared and unblocked + // Then block cache (with version 3) should be merged into the now-empty global cache + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + // Global cache was cleared, then block cache merged in + let value = counter.get(&next_version).expect("expected to get value"); + assert_eq!(value, Some(&3u64)); + } + } + + #[test] + fn test_update_drive_cache_does_not_clear_global_cache_without_epoch_change() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + let another_version = platform_version.protocol_version + 2; + + // Put data in global cache and block cache + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 50); + counter.set_block_cache_version_count(another_version, 10); + } + + // Not an epoch change + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 5, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_execution_context = make_block_execution_context(epoch_info); + + platform.update_drive_cache_v0(&block_execution_context); + + // Global cache should NOT be cleared - should still have the old data + // And block cache should be merged in + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + let v1 = counter.get(&next_version).expect("expected to get value"); + assert_eq!(v1, Some(&50u64)); + let v2 = counter + .get(&another_version) + .expect("expected to get value"); + assert_eq!(v2, Some(&10u64)); + } + } + + #[test] + fn test_update_drive_cache_contracts_cache_merge() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // Not an epoch change + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_execution_context = make_block_execution_context(epoch_info); + + // This should not panic and should successfully merge the data contracts cache + platform.update_drive_cache_v0(&block_execution_context); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs index 25922cdf19d..b0c33116f0c 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs @@ -72,3 +72,81 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; + use crate::execution::types::block_state_info::v0::BlockStateInfoV0; + use crate::execution::types::block_state_info::BlockStateInfo; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; + use crate::test::helpers::setup::TestPlatformBuilder; + use std::collections::BTreeMap; + + fn make_block_execution_context(block_platform_state: PlatformState) -> BlockExecutionContext { + BlockExecutionContext::V0(BlockExecutionContextV0 { + block_state_info: BlockStateInfo::V0(BlockStateInfoV0 { + height: 1, + round: 0, + block_time_ms: 1_000_000, + previous_block_time_ms: None, + proposer_pro_tx_hash: [0u8; 32], + core_chain_locked_height: 1, + block_hash: None, + app_hash: None, + }), + epoch_info: EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }), + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs::default(), + block_address_balance_changes: BTreeMap::new(), + block_platform_state, + proposer_results: None, + }) + } + + #[test] + fn test_dispatcher_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .block_end + .validator_set_update = 255; + + let platform_state = platform.state.load(); + let block_platform_state = platform_state.as_ref().clone(); + + let mut block_execution_context = make_block_execution_context(block_platform_state); + + let result = platform.validator_set_update( + [0u8; 32], + &platform_state, + &mut block_execution_context, + &modified_version, + ); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "validator_set_update"); + assert_eq!(known_versions, vec![0, 1, 2]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/mod.rs index 49b14661e7c..164c0446c0b 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/mod.rs @@ -49,3 +49,82 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + + #[test] + fn test_dispatcher_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .protocol_upgrade + .check_for_desired_protocol_upgrade = 255; + + let result = platform.check_for_desired_protocol_upgrade(100, &modified_version); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "check_for_desired_protocol_upgrade"); + assert_eq!(known_versions, vec![0, 1]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } + + #[test] + fn test_dispatcher_routes_to_v0() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let mut v0_version = platform_version.clone(); + v0_version + .drive_abci + .methods + .protocol_upgrade + .check_for_desired_protocol_upgrade = 0; + + // No votes, so should return None + let result = platform + .check_for_desired_protocol_upgrade(100, &v0_version) + .expect("expected no error"); + assert_eq!(result, None); + } + + #[test] + fn test_dispatcher_routes_to_v1() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let mut v1_version = platform_version.clone(); + v1_version + .drive_abci + .methods + .protocol_upgrade + .check_for_desired_protocol_upgrade = 1; + + // No votes, so should return None + let result = platform + .check_for_desired_protocol_upgrade(100, &v1_version) + .expect("expected no error"); + assert_eq!(result, None); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v0/mod.rs index 304b832ca99..8e34c4c0a77 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v0/mod.rs @@ -79,3 +79,201 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::version::PlatformVersion; + + #[test] + fn test_no_upgrade_when_no_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // No votes have been cast, so no version should pass threshold + let result = platform + .check_for_desired_protocol_upgrade_v0(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_upgrade_when_sufficient_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Simulate votes: put enough votes for the next version in the global cache + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + // With 100 active hpmns and 75% threshold, we need 76 votes (1 + 100*75/100 = 76) + counter.global_cache.insert(next_version, 80); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_no_upgrade_when_insufficient_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Simulate votes below threshold + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + // With 100 active hpmns and 75% threshold, we need 76 votes + // Only supply 50 + counter.global_cache.insert(next_version, 50); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_error_when_multiple_versions_pass_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + let another_version = platform_version.protocol_version + 2; + + // Simulate two versions both passing the threshold (an incoherence situation) + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 80); + counter.global_cache.insert(another_version, 80); + } + + let result = platform.check_for_desired_protocol_upgrade_v0(100, platform_version); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::ProtocolUpgradeIncoherence(_))) => {} + _ => panic!("expected ProtocolUpgradeIncoherence error"), + } + } + + #[test] + fn test_upgrade_with_votes_exactly_at_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Calculate the exact threshold + let pct = platform_version + .drive_abci + .methods + .protocol_upgrade + .protocol_version_upgrade_percentage_needed; + let required = 1 + (100u64 * pct / 100); + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, required); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_no_upgrade_with_one_below_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // Calculate the exact threshold and supply one less + let pct = platform_version + .drive_abci + .methods + .protocol_upgrade + .protocol_version_upgrade_percentage_needed; + let required = 1 + (100u64 * pct / 100); + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, required - 1); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_upgrade_with_zero_active_hpmns() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // With 0 active hpmns, required = 1 + 0 = 1 + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 1); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(0, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_upgrade_with_single_active_hpmn() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // With 1 active hpmn and 75% threshold: required = 1 + (1 * 75 / 100) = 1 + // Since 1*75/100 = 0 in integer division, required = 1 + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 1); + } + + let result = platform + .check_for_desired_protocol_upgrade_v0(1, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v1/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v1/mod.rs index fd66cc62cd8..f6a9156c0c1 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v1/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/check_for_desired_protocol_upgrade/v1/mod.rs @@ -62,3 +62,193 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::version::PlatformVersion; + + #[test] + fn test_v1_no_upgrade_when_no_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_v1_upgrade_when_sufficient_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 80); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_v1_no_upgrade_when_insufficient_votes() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 50); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_v1_error_when_multiple_versions_pass_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + let another_version = platform_version.protocol_version + 2; + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 80); + counter.global_cache.insert(another_version, 80); + } + + let result = platform.check_for_desired_protocol_upgrade_v1(100, platform_version); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::ProtocolUpgradeIncoherence(_))) => {} + _ => panic!("expected ProtocolUpgradeIncoherence error"), + } + } + + #[test] + fn test_v1_upgrade_with_votes_exactly_at_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + let pct = platform_version + .drive_abci + .methods + .protocol_upgrade + .protocol_version_upgrade_percentage_needed; + let required = 1 + (100u64 * pct / 100); + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, required); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_v1_no_upgrade_with_one_below_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + let pct = platform_version + .drive_abci + .methods + .protocol_upgrade + .protocol_version_upgrade_percentage_needed; + let required = 1 + (100u64 * pct / 100); + + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, required - 1); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, None); + } + + #[test] + fn test_v1_block_cache_votes_count_towards_threshold() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // versions_passing_threshold looks at both global and block caches + // Put enough votes only in block_cache to pass threshold + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.set_block_cache_version_count(next_version, 80); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(100, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } + + #[test] + fn test_v1_upgrade_with_large_number_of_hpmns() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let next_version = platform_version.protocol_version + 1; + + // With 10000 active hpmns and 75% threshold: + // required = 1 + (10000 * 75 / 100) = 7501 + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(next_version, 7501); + } + + let result = platform + .check_for_desired_protocol_upgrade_v1(10000, platform_version) + .expect("expected no error"); + + assert_eq!(result, Some(next_version)); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs index 7a101b4e342..0fc4352ecc5 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs @@ -70,3 +70,95 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform_types::platform_state::PlatformStateV0Methods; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + + #[test] + fn test_perform_events_when_version_method_is_none() { + // When the perform_events_on_first_block_of_protocol_change method is None, + // the dispatcher should return Ok(()) without doing anything. + // We can test this indirectly: the latest platform version has Some(0), + // but we can verify the dispatch for a same-version transition (no-op case). + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let platform_state = platform.state.load(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 100, + core_height: 100, + epoch: Epoch::new(1).expect("expected epoch"), + }; + + // Calling with previous_protocol_version == current version should be fine + // since all transition conditions (previous < X && current >= X) will be false + let result = platform.perform_events_on_first_block_of_protocol_change( + &platform_state, + &block_info, + &transaction, + platform_version.protocol_version, + platform_version, + ); + + assert!(result.is_ok()); + } + + #[test] + fn test_perform_events_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + let platform_state = platform.state.load(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 100, + core_height: 100, + epoch: Epoch::new(1).expect("expected epoch"), + }; + + // Create a modified platform version with unknown method version + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .protocol_upgrade + .perform_events_on_first_block_of_protocol_change = Some(255); + + let result = platform.perform_events_on_first_block_of_protocol_change( + &platform_state, + &block_info, + &transaction, + platform_version.protocol_version, + &modified_version, + ); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "perform_events_on_first_block_of_protocol_change"); + assert_eq!(known_versions, vec![0]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/mod.rs index 18fff0fb658..892a6ff7a3d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/mod.rs @@ -56,3 +56,68 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::epoch::Epoch; + + #[test] + fn test_dispatcher_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .protocol_upgrade + .upgrade_protocol_version_on_epoch_change = 255; + + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 2, + core_height: 100, + epoch: Epoch::default(), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + let result = platform.upgrade_protocol_version_on_epoch_change( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + &modified_version, + ); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "upgrade_protocol_version_on_epoch_change"); + assert_eq!(known_versions, vec![0]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/v0/mod.rs index 1e79fb7279a..f87f67705b5 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/upgrade_protocol_version/v0/mod.rs @@ -167,3 +167,301 @@ impl Platform { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform_types::epoch_info::v0::EpochInfoV0; + use crate::platform_types::epoch_info::EpochInfo; + use crate::platform_types::platform_state::PlatformStateV0Methods; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + #[test] + fn test_no_epoch_change_same_protocol_version_succeeds() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 2, + core_height: 100, + epoch: Epoch::default(), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + } + + #[test] + fn test_not_epoch_change_with_different_protocol_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // Not an epoch change + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: false, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 2, + core_height: 100, + epoch: Epoch::default(), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + // Use a different (lower) protocol version so previous != current + // The platform was initialized with latest, now pass an older version + let older_version = PlatformVersion::first(); + assert_ne!( + older_version.protocol_version, platform_version.protocol_version, + "this test requires at least two protocol versions to exist" + ); + + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + older_version, + ); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::CorruptedCodeExecution(msg))) => { + assert!(msg.contains("unexpected protocol upgrade")); + } + _ => panic!("expected CorruptedCodeExecution error"), + } + } + + #[test] + fn test_epoch_change_blocks_global_cache() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // Simulate epoch change (not genesis) + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 5, + previous_epoch_index: Some(4), + is_epoch_change: true, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 500, + core_height: 100, + epoch: Epoch::new(5).expect("expected epoch"), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + // Ensure the global cache starts unblocked + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + assert!(counter.get(&platform_version.protocol_version).is_ok()); + } + + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + + // After epoch change, the global cache should be blocked + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + assert!(counter.get(&platform_version.protocol_version).is_err()); + } + } + + #[test] + fn test_epoch_change_inserts_fee_version_when_empty() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 1, + previous_epoch_index: Some(0), + is_epoch_change: true, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 100, + core_height: 100, + epoch: Epoch::new(1).expect("expected epoch"), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + // Clear fee versions to test the "empty" path + block_platform_state.previous_fee_versions_mut().clear(); + + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + + // Verify that a fee version was inserted for the current epoch + let fee_versions = block_platform_state.previous_fee_versions(); + assert!( + fee_versions.get(&1u16).is_some(), + "expected fee version to be inserted for epoch 1" + ); + } + + #[test] + fn test_epoch_change_with_upgrade_vote_sets_next_version() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // Use the current version as the "next" vote target, so it doesn't + // exceed the latest supported version (which would trigger a warning + // path that requires genesis_time to be stored). + let voted_version = platform_version.protocol_version; + + // Insert enough votes for the current version + { + let mut counter = platform.drive.cache.protocol_versions_counter.write(); + counter.global_cache.insert(voted_version, 100); + } + + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 2, + previous_epoch_index: Some(1), + is_epoch_change: true, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 200, + core_height: 100, + epoch: Epoch::new(2).expect("expected epoch"), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + platform_version, + ); + + assert!(result.is_ok(), "expected ok, got error: {:?}", result.err()); + + // The next epoch protocol version should now be set to the voted version + assert_eq!( + block_platform_state.next_epoch_protocol_version(), + voted_version + ); + } + + #[test] + fn test_genesis_epoch_change_does_not_trigger_upgrade_logic() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // Genesis epoch change (epoch 0, is_epoch_change = true) + // is_epoch_change_but_not_genesis() returns false for epoch 0 + let epoch_info = EpochInfo::V0(EpochInfoV0 { + current_epoch_index: 0, + previous_epoch_index: None, + is_epoch_change: true, + }); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 1, + core_height: 100, + epoch: Epoch::default(), + }; + + let last_committed_state = platform.state.load(); + let mut block_platform_state = last_committed_state.as_ref().clone(); + + // Global cache should still be accessible after genesis epoch (no blocking) + let result = platform.upgrade_protocol_version_on_epoch_change_v0( + &block_info, + &epoch_info, + &last_committed_state, + &mut block_platform_state, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + + // The global cache should NOT be blocked (genesis epoch doesn't trigger upgrade) + { + let counter = platform.drive.cache.protocol_versions_counter.read(); + assert!(counter.get(&platform_version.protocol_version).is_ok()); + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/mod.rs index 68718a29b63..8d43556798d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/mod.rs @@ -52,3 +52,42 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + + #[test] + fn test_dispatcher_unknown_version_returns_error() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let mut modified_version = platform_version.clone(); + modified_version + .drive_abci + .methods + .tokens_processing + .validate_token_aggregated_balance = 255; + + let result = platform.validate_token_aggregated_balance(&transaction, &modified_version); + + assert!(result.is_err()); + match result { + Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method, + known_versions, + received, + })) => { + assert_eq!(method, "validate_token_aggregated_balance"); + assert_eq!(known_versions, vec![0]); + assert_eq!(received, 255); + } + _ => panic!("expected UnknownVersionMismatch error"), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs index 9903296dba6..0463df8551d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs @@ -35,3 +35,49 @@ impl Platform { Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::version::PlatformVersion; + + #[test] + fn test_validate_token_aggregated_balance_with_verification_enabled() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + // The default config should have verify_token_sum_trees enabled + // On a fresh genesis state with no token operations, balances should be balanced + let transaction = platform.drive.grove.start_transaction(); + + let result = platform.validate_token_aggregated_balance_v0(&transaction, platform_version); + + assert!( + result.is_ok(), + "expected token balance validation to pass on fresh genesis state: {:?}", + result.err() + ); + } + + #[test] + fn test_validate_token_aggregated_balance_with_verification_disabled() { + let platform_version = PlatformVersion::latest(); + let mut config = + crate::config::PlatformConfig::default_for_network(dpp::dashcore::Network::Regtest); + config.execution.verify_token_sum_trees = false; + + let platform = TestPlatformBuilder::new() + .with_config(config) + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // With verification disabled, this should always succeed + let result = platform.validate_token_aggregated_balance_v0(&transaction, platform_version); + + assert!(result.is_ok()); + } +}