diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs index b0154172624..f16198bc266 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs @@ -81,3 +81,131 @@ where } } } + +#[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(height: u64, block_time_ms: u64) -> 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, + round: 0, + block_time_ms, + 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: platform_state, + proposer_results: None, + }) + } + + #[test] + fn test_first_block_should_always_checkpoint() { + let platform_version = PlatformVersion::latest(); + let platform_config = crate::config::PlatformConfig { + testing_configs: crate::config::PlatformTestConfig { + disable_checkpoints: false, + ..Default::default() + }, + ..Default::default() + }; + let platform = TestPlatformBuilder::new() + .with_config(platform_config) + .build_with_mock_rpc() + .set_genesis_state(); + + // With no existing checkpoints and checkpoint feature enabled (v7+), + // the first block should trigger a checkpoint + if platform_version + .drive_abci + .methods + .block_end + .should_checkpoint + .is_none() + { + // If checkpoints are disabled in this version, skip + return; + } + + let block_execution_context = make_block_execution_context(1, 1_000_000); + let result = platform + .should_checkpoint_v0(&block_execution_context, platform_version) + .expect("expected Ok"); + + // When there are no checkpoints, it should return Some (checkpoint needed) + assert!(result.is_some(), "first block should trigger checkpoint"); + } + + #[test] + fn test_checkpoint_interval_zero_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.checkpoints.frequency_seconds = 0; + + let block_execution_context = make_block_execution_context(1, 1_000_000); + let result = platform + .should_checkpoint_v0(&block_execution_context, &modified_version) + .expect("expected Ok"); + + assert!( + result.is_none(), + "should return None when frequency is zero" + ); + } + + #[test] + fn test_num_checkpoints_zero_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.checkpoints.num_checkpoints = 0; + + let block_execution_context = make_block_execution_context(1, 1_000_000); + let result = platform + .should_checkpoint_v0(&block_execution_context, &modified_version) + .expect("expected Ok"); + + assert!( + result.is_none(), + "should return None when num_checkpoints is zero" + ); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs index f60633bfff4..7c8ffe3ffaf 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs @@ -103,6 +103,120 @@ mod tests { use rand::SeedableRng; use std::collections::BTreeMap; + #[test] + fn test_choose_quorum_v0_empty_quorums_returns_none() { + let quorums = BTreeMap::new(); + let request_id = [0u8; 32]; + + let result = Platform::::choose_quorum_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ); + + assert!(result.is_none()); + } + + #[test] + fn test_choose_quorum_thread_safe_v0_empty_quorums_returns_none() { + let quorums: BTreeMap = BTreeMap::new(); + let request_id = [0u8; 32]; + + let result = Platform::::choose_quorum_thread_safe_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ); + + assert!(result.is_none()); + } + + #[test] + fn test_choose_quorum_thread_safe_v0_single_quorum() { + let quorum_hash = QuorumHash::from_slice( + hex::decode("000000dc07d722238a994116c3395c334211d9864ff5b37c3be51d5fdda66223") + .expect("valid hex") + .as_slice(), + ) + .expect("valid quorum hash"); + + let key = [42u8; 48]; + let quorums = BTreeMap::from([(quorum_hash, key)]); + let request_id = [1u8; 32]; + + let result = Platform::::choose_quorum_thread_safe_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ); + + let (_, returned_key) = result.expect("expected quorum selection for single quorum"); + assert_eq!(*returned_key, key); + } + + #[test] + fn test_choose_quorum_thread_safe_v0_deterministic() { + let quorum_hash1 = QuorumHash::from_slice( + hex::decode("000000dc07d722238a994116c3395c334211d9864ff5b37c3be51d5fdda66223") + .expect("valid hex") + .as_slice(), + ) + .expect("valid quorum hash"); + let quorum_hash2 = QuorumHash::from_slice( + hex::decode("000000bd5639c21dd8abf60253c3fe0343d87a9762b5b8f57e2b4ea1523fd071") + .expect("valid hex") + .as_slice(), + ) + .expect("valid quorum hash"); + + let key1 = [1u8; 48]; + let key2 = [2u8; 48]; + let quorums = BTreeMap::from([(quorum_hash1, key1), (quorum_hash2, key2)]); + let request_id = [42u8; 32]; + + // Call twice with same inputs - should be deterministic + let result1 = Platform::::choose_quorum_thread_safe_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ) + .expect("expected quorum selection on first call"); + let result2 = Platform::::choose_quorum_thread_safe_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ) + .expect("expected quorum selection on second call"); + + assert_eq!(result1.0, result2.0); + } + + #[test] + fn test_choose_quorum_v0_single_quorum() { + let quorum_hash = QuorumHash::from_slice( + hex::decode("000000dc07d722238a994116c3395c334211d9864ff5b37c3be51d5fdda66223") + .expect("valid hex") + .as_slice(), + ) + .expect("valid quorum hash"); + + let mut rng = StdRng::seed_from_u64(42); + let key = SecretKey::random(&mut rng).public_key(); + let quorums = BTreeMap::from([(quorum_hash, key)]); + let request_id = [1u8; 32]; + + let result = Platform::::choose_quorum_v0( + QuorumType::Llmq50_60, + &quorums, + &request_id, + ); + + assert!( + result.is_some(), + "expected quorum selection for single quorum" + ); + } + #[test] fn test_choose_quorum() { // Active quorums: diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/make_sure_core_is_synced_to_chain_lock/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/make_sure_core_is_synced_to_chain_lock/v0/mod.rs index bb86d5928aa..9b8d3ad3d86 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/make_sure_core_is_synced_to_chain_lock/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/make_sure_core_is_synced_to_chain_lock/v0/mod.rs @@ -36,3 +36,8 @@ where }) } } + +// Tests removed: the production method `make_sure_core_is_synced_to_chain_lock_v0` requires +// a Platform whose `core_rpc` has mock expectations configured before construction. +// The previous tests created mocks but never called the production method, only testing +// inline if/else arithmetic. Real integration coverage belongs in higher-level tests. diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_through_core/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_through_core/v0/mod.rs index 1aa5d4db57b..20bc0f54ba8 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_through_core/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_through_core/v0/mod.rs @@ -41,3 +41,9 @@ where } } } + +// Tests removed: all 4 tests only exercised mock wiring by calling methods directly on +// MockCoreRPCLike, never invoking the production method `verify_chain_lock_through_core_v0`. +// The production method requires a Platform whose `core_rpc` has mock expectations +// configured before construction, which is impractical for isolated unit tests. +// Real integration coverage belongs in higher-level tests. diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs index 345fa4d963f..dff4cc3eb28 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs @@ -671,3 +671,271 @@ impl Platform { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + #[test] + fn test_same_version_transition_is_noop() { + 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"), + }; + + // When previous == current, no transition_to_version_* should be triggered + let result = platform.perform_events_on_first_block_of_protocol_change_v0( + &platform_state, + &block_info, + &transaction, + platform_version.protocol_version, + platform_version, + ); + + assert!(result.is_ok()); + } + + #[test] + fn test_transition_to_version_6_inserts_wallet_utils_contract() { + 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"), + }; + + // Transition from version 5 to current (which is >= 6) + // should insert the wallet utils contract + let result = platform.transition_to_version_6(&block_info, &transaction, platform_version); + + assert!(result.is_ok()); + + // Verify the contract was inserted by loading it + let wallet_utils_contract = dpp::system_data_contracts::load_system_data_contract( + dpp::data_contracts::SystemDataContract::WalletUtils, + platform_version, + ) + .expect("expected to load wallet utils contract"); + + use dpp::data_contract::accessors::v0::DataContractV0Getters; + let (_fee_result, contract_fetch_info) = platform + .drive + .get_contract_with_fetch_info_and_fee( + *wallet_utils_contract.id().as_bytes(), + None, + false, + Some(&transaction), + platform_version, + ) + .expect("expected to fetch contract"); + assert!( + contract_fetch_info.is_some(), + "WalletUtils contract should exist after transition_to_version_6" + ); + } + + // test_transition_to_version_9 removed: requires prior state from versions 4-8 + + #[test] + fn test_transition_to_version_11_creates_address_trees() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let result = platform.transition_to_version_11(&transaction, platform_version); + + assert!(result.is_ok()); + + // Verify that the AddressBalances root tree was created + use drive::drive::RootTree; + use drive::grovedb::Element; + use drive::grovedb_path::SubtreePath; + let element = platform.drive.grove.get( + SubtreePath::empty(), + &[RootTree::AddressBalances as u8], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + element.value.is_ok(), + "AddressBalances root tree should exist" + ); + } + + #[test] + fn test_transition_to_version_12_creates_shielded_pool_trees() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + // Version 12 depends on version 11 trees existing + platform + .transition_to_version_11(&transaction, platform_version) + .expect("expected version 11 transition to succeed"); + + platform + .transition_to_version_12(&transaction, platform_version) + .expect("expected version 12 transition to succeed"); + + // Verify shielded credit pool tree was created under AddressBalances + let shielded_pool_element = platform.drive.grove.get( + SubtreePath::from(&[&[RootTree::AddressBalances as u8] as &[u8]]), + &[SHIELDED_CREDIT_POOL_KEY_U8], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + shielded_pool_element.value.is_ok(), + "Shielded credit pool tree should exist after transition_to_version_12" + ); + + // Verify notes tree was created inside the shielded pool + let shielded_pool_path = shielded_credit_pool_path(); + let notes_element = platform.drive.grove.get( + SubtreePath::from(shielded_pool_path.as_ref()), + &[SHIELDED_NOTES_KEY], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + notes_element.value.is_ok(), + "Shielded notes tree should exist after transition_to_version_12" + ); + + // Verify nullifiers tree was created + let nullifiers_element = platform.drive.grove.get( + SubtreePath::from(shielded_pool_path.as_ref()), + &[SHIELDED_NULLIFIERS_KEY], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + nullifiers_element.value.is_ok(), + "Shielded nullifiers tree should exist after transition_to_version_12" + ); + + // Verify anchors tree was created + let anchors_element = platform.drive.grove.get( + SubtreePath::from(shielded_pool_path.as_ref()), + &[SHIELDED_ANCHORS_IN_POOL_KEY], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + anchors_element.value.is_ok(), + "Shielded anchors tree should exist after transition_to_version_12" + ); + } + + // test_full_transition_from_version_3_to_latest and test_transition_from_version_5 + // removed: multi-version transitions require cumulative state from each prior version + + #[test] + fn test_transition_from_version_10_triggers_11_and_12() { + 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"), + }; + + // From version 10, versions 11 and 12 should trigger + platform + .perform_events_on_first_block_of_protocol_change_v0( + &platform_state, + &block_info, + &transaction, + 10, + platform_version, + ) + .expect("expected transition from version 10 to succeed"); + + // Verify version 11 artifacts: AddressBalances root tree should exist + use drive::drive::RootTree; + use drive::grovedb_path::SubtreePath; + let address_balances = platform.drive.grove.get( + SubtreePath::empty(), + &[RootTree::AddressBalances as u8], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + address_balances.value.is_ok(), + "AddressBalances root tree should exist (from version 11 transition)" + ); + + // Verify version 12 artifacts: shielded credit pool tree should exist + let shielded_pool = platform.drive.grove.get( + SubtreePath::from(&[&[RootTree::AddressBalances as u8] as &[u8]]), + &[SHIELDED_CREDIT_POOL_KEY_U8], + Some(&transaction), + &platform_version.drive.grove_version, + ); + assert!( + shielded_pool.value.is_ok(), + "Shielded credit pool tree should exist (from version 12 transition)" + ); + } + + #[test] + fn test_idempotent_transition_to_version_6() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 100, + core_height: 100, + epoch: Epoch::new(1).expect("expected epoch"), + }; + + // First call should succeed + platform + .transition_to_version_6(&block_info, &transaction, platform_version) + .expect("first transition should succeed"); + + // Second call should also succeed (idempotent due to insert_contract) + let result = platform.transition_to_version_6(&block_info, &transaction, platform_version); + assert!(result.is_ok()); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_address_balances/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_address_balances/v0/mod.rs index 6840bb5ab47..7a5f2a69552 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_address_balances/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_address_balances/v0/mod.rs @@ -27,3 +27,37 @@ where Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + #[test] + fn test_cleanup_with_no_expired_entries() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 1, + core_height: 1, + epoch: Epoch::default(), + }; + + // Should succeed even when there are no expired entries to clean up + let result = platform.cleanup_recent_block_storage_address_balances_v0( + &block_info, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_nullifiers/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_nullifiers/v0/mod.rs index aaadf5adefd..12ca4827422 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_nullifiers/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/cleanup_recent_block_storage_nullifiers/v0/mod.rs @@ -25,3 +25,36 @@ where Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + #[test] + fn test_cleanup_nullifiers_with_no_expired_entries() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let transaction = platform.drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 1, + core_height: 1, + epoch: Epoch::default(), + }; + + let result = platform.cleanup_recent_block_storage_nullifiers_v0( + &block_info, + &transaction, + platform_version, + ); + + assert!(result.is_ok()); + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v0/mod.rs index 9b7e7bce54c..28104de3cfc 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v0/mod.rs @@ -121,3 +121,153 @@ where StateTransitionContainerV0::new(decoded_state_transitions) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::version::PlatformVersion; + + #[test] + fn test_decode_empty_state_transitions() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let raw_state_transitions: Vec> = vec![]; + let container = + platform.decode_raw_state_transitions_v0(&raw_state_transitions, platform_version); + + assert_eq!(container.into_iter().count(), 0); + } + + #[test] + fn test_decode_oversized_state_transition() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // Create a state transition that exceeds the max size + let max_size = platform_version.system_limits.max_state_transition_size as usize; + let oversized = vec![0u8; max_size + 1]; + + let raw_state_transitions = vec![oversized]; + let container = + platform.decode_raw_state_transitions_v0(&raw_state_transitions, platform_version); + + let decoded: Vec<_> = container.into_iter().collect(); + assert_eq!(decoded.len(), 1); + + match &decoded[0] { + DecodedStateTransition::InvalidEncoding(invalid) => { + assert!( + matches!( + &invalid.error, + dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::StateTransitionMaxSizeExceededError( + _ + ) + ) + ), + "expected StateTransitionMaxSizeExceededError" + ); + } + _ => panic!("expected InvalidEncoding for oversized state transition"), + } + } + + #[test] + fn test_decode_invalid_bytes_state_transition() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // Random garbage bytes that won't deserialize as a valid state transition + let garbage = vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB]; + + let raw_state_transitions = vec![garbage]; + let container = + platform.decode_raw_state_transitions_v0(&raw_state_transitions, platform_version); + + let decoded: Vec<_> = container.into_iter().collect(); + assert_eq!(decoded.len(), 1); + + // Should be either InvalidEncoding (PlatformDeserializationError) or FailedToDecode + match &decoded[0] { + DecodedStateTransition::InvalidEncoding(_) => {} + DecodedStateTransition::FailedToDecode(_) => {} + DecodedStateTransition::SuccessfullyDecoded(_) => { + panic!("garbage bytes should not decode successfully") + } + } + } + + #[test] + fn test_decode_multiple_mixed_state_transitions() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let max_size = platform_version.system_limits.max_state_transition_size as usize; + let oversized = vec![0u8; max_size + 1]; + let garbage = vec![0xFF, 0xFE, 0xFD]; + + let raw_state_transitions = vec![oversized, garbage]; + let container = + platform.decode_raw_state_transitions_v0(&raw_state_transitions, platform_version); + + let decoded: Vec<_> = container.into_iter().collect(); + assert_eq!(decoded.len(), 2); + + // First should be oversized error + match &decoded[0] { + DecodedStateTransition::InvalidEncoding(_) => {} + _ => panic!("first should be InvalidEncoding for oversized"), + } + + // Second should be invalid encoding or failed to decode + match &decoded[1] { + DecodedStateTransition::InvalidEncoding(_) => {} + DecodedStateTransition::FailedToDecode(_) => {} + DecodedStateTransition::SuccessfullyDecoded(_) => { + panic!("garbage should not decode successfully") + } + } + } + + #[test] + fn test_decode_state_transition_at_exact_max_size_is_not_rejected_as_oversized() { + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // Create a state transition that is exactly at the max size limit. + // It should attempt to decode (not be rejected as oversized). + let max_size = platform_version.system_limits.max_state_transition_size as usize; + let at_limit = vec![0u8; max_size]; + + let raw_state_transitions = vec![at_limit]; + let container = + platform.decode_raw_state_transitions_v0(&raw_state_transitions, platform_version); + + let decoded: Vec<_> = container.into_iter().collect(); + assert_eq!(decoded.len(), 1); + + // Should NOT be rejected as oversized - it should pass the size check + // and attempt deserialization. Any result other than the size error is acceptable. + if let DecodedStateTransition::InvalidEncoding(ref inv) = decoded[0] { + assert!( + !matches!( + &inv.error, + ConsensusError::BasicError(BasicError::StateTransitionMaxSizeExceededError(_)) + ), + "buffer at exactly max size should not be rejected by the size check" + ); + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/store_address_balances_to_recent_block_storage/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/store_address_balances_to_recent_block_storage/v0/mod.rs index b2449693c35..5b366c7984d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/store_address_balances_to_recent_block_storage/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/store_address_balances_to_recent_block_storage/v0/mod.rs @@ -46,3 +46,6 @@ where Ok(()) } } + +// Tests for store_address_balances_to_recent_block_storage_v0 are covered by +// higher-level integration tests that exercise realistic address balance data.