diff --git a/Cargo.lock b/Cargo.lock index acf00c616b..7f534c4316 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3334,6 +3334,7 @@ version = "1.0.0-dev.16" dependencies = [ "bincode", "grovedb-version", + "once_cell", "thiserror", "versioned-feature-core", ] diff --git a/packages/rs-drive-abci/src/abci/handler/check_tx.rs b/packages/rs-drive-abci/src/abci/handler/check_tx.rs index 1de59518ca..bcad2ba2b4 100644 --- a/packages/rs-drive-abci/src/abci/handler/check_tx.rs +++ b/packages/rs-drive-abci/src/abci/handler/check_tx.rs @@ -4,6 +4,7 @@ use crate::error::execution::ExecutionError; use crate::error::Error; use crate::metrics::{LABEL_ABCI_RESPONSE_CODE, LABEL_CHECK_TX_MODE, LABEL_STATE_TRANSITION_NAME}; use crate::platform_types::platform::{Platform, PlatformRef}; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::rpc::core::CoreRPCLike; use dpp::consensus::codes::ErrorWithCode; use dpp::fee::SignedCredits; diff --git a/packages/rs-drive-abci/src/abci/handler/finalize_block.rs b/packages/rs-drive-abci/src/abci/handler/finalize_block.rs index 177a97f0c0..9653391c7d 100644 --- a/packages/rs-drive-abci/src/abci/handler/finalize_block.rs +++ b/packages/rs-drive-abci/src/abci/handler/finalize_block.rs @@ -3,6 +3,7 @@ use crate::error::execution::ExecutionError; use crate::error::Error; use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; use crate::platform_types::cleaned_abci_messages::finalized_block_cleaned_request::v0::FinalizeBlockCleanedRequest; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::rpc::core::CoreRPCLike; use std::sync::atomic::Ordering; use tenderdash_abci::proto::abci as proto; diff --git a/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs b/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs index 29930663f9..6d79969e59 100644 --- a/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs +++ b/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs @@ -8,7 +8,6 @@ use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; use crate::rpc::core::CoreRPCLike; use dpp::dashcore::hashes::Hash; -use dpp::version::PlatformVersion; use dpp::version::TryIntoPlatformVersioned; use tenderdash_abci::proto::abci as proto; use tenderdash_abci::proto::abci::tx_record::TxAction; @@ -117,13 +116,10 @@ where app_hash, state_transitions_result, validator_set_update, - protocol_version, + platform_version, mut block_execution_context, } = run_result.into_data().map_err(Error::Protocol)?; - let platform_version = PlatformVersion::get(protocol_version) - .expect("must be set in run block proposal from existing protocol version"); - // We need to let Tenderdash know about the transactions we should remove from execution let valid_tx_count = state_transitions_result.valid_count(); let failed_tx_count = state_transitions_result.failed_count(); @@ -192,7 +188,7 @@ where validator_set_update, // TODO: implement consensus param updates consensus_param_updates: None, - app_version: protocol_version as u64, + app_version: platform_version.protocol_version as u64, }; block_execution_context.set_proposer_results(Some(response.clone())); diff --git a/packages/rs-drive-abci/src/abci/handler/process_proposal.rs b/packages/rs-drive-abci/src/abci/handler/process_proposal.rs index b6fff75644..9e3c6e5e3e 100644 --- a/packages/rs-drive-abci/src/abci/handler/process_proposal.rs +++ b/packages/rs-drive-abci/src/abci/handler/process_proposal.rs @@ -202,13 +202,10 @@ where app_hash, state_transitions_result: state_transition_results, validator_set_update, - protocol_version, + platform_version, block_execution_context, } = run_result.into_data().map_err(Error::Protocol)?; - let platform_version = PlatformVersion::get(protocol_version) - .expect("must be set in run block proposer from existing platform version"); - app.block_execution_context() .write() .unwrap() diff --git a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs index 2f6c46e88a..5f2de53c3d 100644 --- a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs @@ -157,6 +157,7 @@ where &platform_ref, state_transition, check_tx_level, + platform_version, )?; // If there are any validation errors happen we return diff --git a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs index 7a766b14f6..86a8b2f6f3 100644 --- a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs +++ b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs @@ -61,10 +61,19 @@ where last_committed_platform_version, )?; - // Determine a protocol version for this block - let platform_version = if epoch_info.is_epoch_change_but_not_genesis() { + // Create a bock state from previous committed state + let mut block_platform_state = platform_state.clone(); + + // Determine a platform version for this block + let block_platform_version = if epoch_info.is_epoch_change_but_not_genesis() + && platform_state.next_epoch_protocol_version() + != platform_state.current_protocol_version_in_consensus() + { // Switch to next proposed platform version if we are on the first block of the new epoch - // This version must be set to the state as current one during block processing + // and the next protocol version (locked in the previous epoch) is different from the + // current protocol version. + // This version will be set to the block state, and we decide on next version for next epoch + // during block processing let next_protocol_version = platform_state.next_epoch_protocol_version(); // We should panic if this node is not supported a new protocol version @@ -80,13 +89,31 @@ Your software version: {}, latest supported protocol version: {}."#, ); }; + // Set current protocol version to the block platform state + block_platform_state.set_current_protocol_version_in_consensus(next_protocol_version); + next_platform_version } else { // Stay on the last committed platform version last_committed_platform_version }; - match platform_version + // Patch platform version and run migrations if we have patches and/or + // migrations defined for this height. + // It modifies the protocol version to function version mapping to apply hotfixes + // Also it performs migrations to fix corrupted state or prepare it for new features + let block_platform_version = if let Some(patched_platform_version) = self + .apply_platform_version_patch_and_migrate_state_for_height( + block_proposal.height, + &mut block_platform_state, + transaction, + )? { + patched_platform_version + } else { + block_platform_version + }; + + match block_platform_version .drive_abci .methods .engine @@ -98,7 +125,8 @@ Your software version: {}, latest supported protocol version: {}."#, epoch_info, transaction, platform_state, - platform_version, + block_platform_state, + block_platform_version, ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { method: "run_block_proposal".to_string(), diff --git a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs index d7deec1350..f7ec515448 100644 --- a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs @@ -65,7 +65,8 @@ where epoch_info: EpochInfo, transaction: &Transaction, last_committed_platform_state: &PlatformState, - platform_version: &PlatformVersion, + mut block_platform_state: PlatformState, + platform_version: &'static PlatformVersion, ) -> Result, Error> { tracing::trace!( @@ -102,9 +103,6 @@ where let last_block_core_height = last_committed_platform_state .last_committed_known_core_height_or(self.config.abci.genesis_core_height); - // Create a bock state from previous committed state - let mut block_platform_state = last_committed_platform_state.clone(); - // Init block execution context let block_state_info = block_state_info::v0::BlockStateInfoV0::from_block_proposal( &block_proposal, @@ -388,7 +386,7 @@ where app_hash: root_hash, state_transitions_result, validator_set_update, - protocol_version: platform_version.protocol_version, + platform_version, block_execution_context, }, )) diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_start/migrate_state/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_start/migrate_state/mod.rs new file mode 100644 index 0000000000..96d91d938c --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/block_start/migrate_state/mod.rs @@ -0,0 +1,30 @@ +use crate::error::Error; + +use dpp::prelude::BlockHeight; +use drive::grovedb::Transaction; + +use crate::platform_types::platform::Platform; + +use crate::platform_types::platform_state::PlatformState; + +impl Platform { + /// Perform state migration based on block height + pub fn migrate_state_for_height( + &self, + height: BlockHeight, + _block_platform_state: &mut PlatformState, + _transaction: &Transaction, + ) -> Result<(), Error> { + #[allow(clippy::match_single_binding)] + let is_migrated = match height { + // 30 => self.migration_30_test(block_platform_state, transaction)?, + _ => false, + }; + + if is_migrated { + tracing::debug!("Successfully migrated state for height {}", height); + } + + Ok(()) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_start/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_start/mod.rs index cd4e1a74dc..7557122603 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_start/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_start/mod.rs @@ -1,2 +1,6 @@ /// Clearing the drive cache should happen when a new block is going to be run pub(in crate::execution) mod clear_drive_block_cache; +/// State migration +mod migrate_state; +/// Patch the platform version function mapping and migrate state based on the block height +pub(in crate::execution) mod patch_platform; diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_start/patch_platform.rs b/packages/rs-drive-abci/src/execution/platform_events/block_start/patch_platform.rs new file mode 100644 index 0000000000..953ec4f217 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/block_start/patch_platform.rs @@ -0,0 +1,28 @@ +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use dpp::prelude::BlockHeight; +use dpp::version::PlatformVersion; +use drive::grovedb::Transaction; + +impl Platform { + /// This function patches platform version and run migrations + /// It modifies protocol version to function version mapping to apply hotfixes + /// Also it performs migrations to fix corrupted state or prepare it for new features + /// + /// This function appends the patch to PlatformState, potentially alter Drive and Platform execution state + /// and returns patched version + pub fn apply_platform_version_patch_and_migrate_state_for_height( + &self, + height: BlockHeight, + platform_state: &mut PlatformState, + transaction: &Transaction, + ) -> Result, Error> { + let patched_platform_version = + platform_state.apply_platform_version_patch_for_height(height)?; + + self.migrate_state_for_height(height, platform_state, transaction)?; + + Ok(patched_platform_version) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/add_epoch_pool_to_proposers_payout_operations/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/add_epoch_pool_to_proposers_payout_operations/v0/mod.rs index 5a522d8e17..bd24d6e7f7 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/add_epoch_pool_to_proposers_payout_operations/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/add_epoch_pool_to_proposers_payout_operations/v0/mod.rs @@ -175,6 +175,7 @@ impl Platform { #[cfg(test)] mod tests { use super::*; + use crate::platform_types::platform_state::v0::PlatformStateV0Methods; mod add_epoch_pool_to_proposers_payout_operations { use super::*; 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 98fe4cc90a..8c44e9981a 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 @@ -52,9 +52,6 @@ impl Platform { previous_block_protocol_version, current_block_protocol_version, ); - - block_platform_state - .set_current_protocol_version_in_consensus(current_block_protocol_version); }; // Determine a new protocol version for the next epoch if enough proposers voted diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/mod.rs index bd7eb14d37..c389fbd3e6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/mod.rs @@ -7,6 +7,7 @@ use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::StateTransition; +use dpp::version::PlatformVersion; use crate::execution::check_tx::CheckTxLevel; @@ -25,8 +26,8 @@ pub(in crate::execution) fn state_transition_to_execution_event_for_check_tx<'a, platform: &'a PlatformRef, state_transition: StateTransition, check_tx_level: CheckTxLevel, + platform_version: &PlatformVersion, ) -> Result>>, Error> { - let platform_version = platform.state.current_platform_version()?; match platform_version .drive_abci .validation_and_processing diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/mod.rs index 9771c5e5d6..4a3a7a1324 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/mod.rs @@ -9,6 +9,7 @@ use dpp::block::block_info::BlockInfo; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::StateTransition; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use drive::grovedb::TransactionArg; /// There are multiple stages in a state transition processing: diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index 5d028bbd88..0aa45d57e4 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -25,6 +25,7 @@ use crate::execution::validation::state_transition::processor::v0::{ }; use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; impl ValidationMode { /// Returns if we should validate the contract when we transform it from its serialized form diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs index 1f788e575e..a30f47c652 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs @@ -18,6 +18,7 @@ use crate::execution::validation::state_transition::data_contract_update::state: use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; use crate::platform_types::platform::PlatformRef; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::rpc::core::CoreRPCLike; impl StateTransitionActionTransformerV0 for DataContractUpdateTransition { @@ -78,6 +79,7 @@ mod tests { DataContractUpdateTransition, DataContractUpdateTransitionV0, }; + use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; use dpp::tests::fixtures::get_data_contract_fixture; use dpp::tests::json_document::json_document_to_contract; @@ -577,9 +579,8 @@ mod tests { let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-owner.json"; - let platform_version = platform - .state - .load() + let platform_state = platform.state.load(); + let platform_version = platform_state .current_platform_version() .expect("expected to get current platform version"); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/mod.rs index 23ec6cf3e8..3d32ae7529 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/mod.rs @@ -5,6 +5,7 @@ use crate::execution::validation::state_transition::data_contract_update::state: use crate::execution::validation::state_transition::processor::v0::StateTransitionStateValidationV0; use crate::execution::validation::state_transition::ValidationMode; use crate::platform_types::platform::PlatformRef; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::rpc::core::CoreRPCLike; use dpp::block::block_info::BlockInfo; use dpp::state_transition::data_contract_update_transition::DataContractUpdateTransition; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs index b890535a29..e0960ea715 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs @@ -153,6 +153,7 @@ mod tests { use dpp::withdrawal::Pooling; use drive::drive::contract::DataContractFetchInfo; use crate::execution::types::state_transition_execution_context::v0::StateTransitionExecutionContextV0; + use crate::platform_types::platform_state::v0::PlatformStateV0Methods; #[test] fn should_throw_error_if_withdrawal_not_found() { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/mod.rs index 3283a5b7c7..9373c0b56e 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/mod.rs @@ -33,6 +33,7 @@ use crate::execution::validation::state_transition::processor::v0::{ }; use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; impl ValidationMode { /// Returns a bool on whether we should validate that documents are valid against the state @@ -235,6 +236,7 @@ impl StateTransitionStateValidationV0 for DocumentsBatchTransition { #[cfg(test)] mod tests { use crate::execution::validation::state_transition::state_transitions::tests::setup_identity; + use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; use crate::test::helpers::setup::TestPlatformBuilder; use dpp::block::block_info::BlockInfo; @@ -1076,9 +1078,8 @@ mod tests { let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-creation-restricted-to-owner.json"; - let platform_version = platform - .state - .load() + let platform_state = platform.state.load(); + let platform_version = platform_state .current_platform_version() .expect("expected to get current platform version"); @@ -1951,9 +1952,8 @@ mod tests { let contract_path = "tests/supporting_files/contract/dashpay/dashpay-contract-contact-request-mutable-and-can-not-be-deleted.json"; - let platform_version = platform - .state - .load() + let platform_state = platform.state.load(); + let platform_version = platform_state .current_platform_version() .expect("expected to get current platform version"); @@ -2114,9 +2114,8 @@ mod tests { let contract_path = "tests/supporting_files/contract/dashpay/dashpay-contract-contact-request-not-mutable-and-can-be-deleted.json"; - let platform_version = platform - .state - .load() + let platform_state = platform.state.load(); + let platform_version = platform_state .current_platform_version() .expect("expected to get current platform version"); @@ -2516,9 +2515,8 @@ mod tests { let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-no-owner-indexes.json"; - let platform_version = platform - .state - .load() + let platform_state = platform.state.load(); + let platform_version = platform_state .current_platform_version() .expect("expected to get current platform version"); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/state/v0/mod.rs index 9c4d778aec..5ee90a17ab 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/state/v0/mod.rs @@ -23,6 +23,7 @@ use crate::execution::validation::state_transition::documents_batch::data_trigge use crate::platform_types::platform::{PlatformStateRef}; use crate::execution::validation::state_transition::state_transitions::documents_batch::transformer::v0::DocumentsBatchTransitionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; mod data_triggers; pub mod fetch_contender; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/transformer/v0/mod.rs index 4113953476..009d055f83 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/transformer/v0/mod.rs @@ -53,6 +53,7 @@ use drive::state_transition_action::document::documents_batch::document_transiti use drive::state_transition_action::system::bump_identity_data_contract_nonce_action::BumpIdentityDataContractNonceAction; use crate::execution::types::execution_operation::ValidationOperation; use crate::execution::types::state_transition_execution_context::{StateTransitionExecutionContext, StateTransitionExecutionContextMethodsV0}; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; pub(in crate::execution::validation::state_transition::state_transitions::documents_batch) trait DocumentsBatchTransitionTransformerV0 { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/mod.rs index 32ad5e8de8..8a55b8bfdd 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/mod.rs @@ -23,6 +23,7 @@ use dpp::version::PlatformVersion; use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; use crate::execution::validation::state_transition::identity_create::advanced_structure::v0::IdentityCreateStateTransitionAdvancedStructureValidationV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use drive::grovedb::TransactionArg; use drive::state_transition_action::identity::identity_create::IdentityCreateTransitionAction; use drive::state_transition_action::StateTransitionAction; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer/mod.rs index 914f0befe1..a21e404965 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer/mod.rs @@ -24,6 +24,7 @@ use crate::execution::validation::state_transition::processor::v0::{ }; use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; impl StateTransitionActionTransformerV0 for IdentityCreditTransferTransition { fn transform_into_action( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs index 6ea4eafed4..c3021ff8b7 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs @@ -25,6 +25,7 @@ use crate::execution::validation::state_transition::processor::v0::{ }; use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; impl StateTransitionActionTransformerV0 for IdentityCreditWithdrawalTransition { fn transform_into_action( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/mod.rs index 0efe7ffd38..cc7f18d24a 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/mod.rs @@ -20,6 +20,7 @@ use crate::execution::validation::state_transition::identity_top_up::transform_i use crate::execution::validation::state_transition::processor::v0::StateTransitionBasicStructureValidationV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; /// A trait to transform into a top up action pub trait StateTransitionIdentityTopUpTransitionActionTransformer { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs index a8c09846bb..9b8cd341e8 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs @@ -26,6 +26,7 @@ use crate::execution::validation::state_transition::processor::v0::{ use crate::execution::validation::state_transition::transformer::StateTransitionActionTransformerV0; use crate::execution::validation::state_transition::ValidationMode; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; impl StateTransitionActionTransformerV0 for IdentityUpdateTransition { fn transform_into_action( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/mod.rs index a851df8ef4..fe7bcb7e72 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/mod.rs @@ -34,8 +34,7 @@ impl StateTransitionActionTransformerV0 for MasternodeVoteTransition { _execution_context: &mut StateTransitionExecutionContext, tx: TransactionArg, ) -> Result, Error> { - let platform_version = - PlatformVersion::get(platform.state.current_protocol_version_in_consensus())?; + let platform_version = platform.state.current_platform_version()?; match platform_version .drive_abci .validation_and_processing @@ -65,8 +64,7 @@ impl StateTransitionStateValidationV0 for MasternodeVoteTransition { _execution_context: &mut StateTransitionExecutionContext, tx: TransactionArg, ) -> Result, Error> { - let platform_version = - PlatformVersion::get(platform.state.current_protocol_version_in_consensus())?; + let platform_version = platform.state.current_platform_version()?; match platform_version .drive_abci .validation_and_processing diff --git a/packages/rs-drive-abci/src/platform_types/block_execution_outcome/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/block_execution_outcome/v0/mod.rs index e863244a03..8fca892850 100644 --- a/packages/rs-drive-abci/src/platform_types/block_execution_outcome/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/block_execution_outcome/v0/mod.rs @@ -1,8 +1,8 @@ use crate::abci::AbciError; use crate::execution::types::block_execution_context::BlockExecutionContext; use crate::platform_types::state_transitions_processing_result::StateTransitionsProcessingResult; -use dpp::util::deserializer::ProtocolVersion; use dpp::validation::SimpleValidationResult; +use dpp::version::PlatformVersion; use tenderdash_abci::proto::abci::ValidatorSetUpdate; /// The outcome of the block execution, either by prepare proposal, or process proposal @@ -16,8 +16,8 @@ pub struct BlockExecutionOutcome { /// The changes to the validator set // TODO We should use another DTO, only abci module should deal with Tenderdash proto structures pub validator_set_update: Option, - /// Current block protocol version - pub protocol_version: ProtocolVersion, + /// Current block platform version + pub platform_version: &'static PlatformVersion, /// Block execution context pub block_execution_context: BlockExecutionContext, } diff --git a/packages/rs-drive-abci/src/platform_types/platform/mod.rs b/packages/rs-drive-abci/src/platform_types/platform/mod.rs index d0e42b8e65..20c13b0b5c 100644 --- a/packages/rs-drive-abci/src/platform_types/platform/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform/mod.rs @@ -213,17 +213,27 @@ impl Platform { drive: Drive, core_rpc: C, config: PlatformConfig, - platform_state: PlatformState, + mut platform_state: PlatformState, ) -> Result, Error> where C: CoreRPCLike, { - PlatformVersion::set_current(PlatformVersion::get( - platform_state.current_protocol_version_in_consensus(), - )?); - let height = platform_state.last_committed_block_height(); + // Set patched or original platform version as current + let platform_version = platform_state + .apply_all_patches_to_platform_version_up_to_height(height) + .transpose() + .unwrap_or_else(|| { + let platform_version = + PlatformVersion::get(platform_state.current_protocol_version_in_consensus()) + .map_err(Error::from); + + platform_version + })?; + + PlatformVersion::set_current(platform_version); + let platform: Platform = Platform { drive, state: ArcSwap::new(Arc::new(platform_state)), diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs index 6b54b6bfeb..ab731b6984 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs @@ -1,9 +1,11 @@ +mod patch_platform_version; /// Version 0 pub mod v0; use crate::error::Error; use crate::platform_types::platform_state::v0::{ MasternodeListChanges, PlatformStateForSavingV0, PlatformStateV0, PlatformStateV0Methods, + PlatformStateV0PrivateMethods, }; use crate::platform_types::validator_set::ValidatorSet; @@ -63,7 +65,7 @@ impl PlatformSerializable for PlatformState { type Error = Error; fn serialize_to_bytes(&self) -> Result, Self::Error> { - let platform_version = PlatformVersion::get(self.current_protocol_version_in_consensus())?; + let platform_version = self.current_platform_version()?; let config = config::standard().with_big_endian().with_no_limit(); let platform_state_for_saving: PlatformStateForSaving = self.clone().try_into_platform_versioned(platform_version)?; @@ -107,12 +109,6 @@ impl PlatformState { pub fn fingerprint(&self) -> Result<[u8; 32], Error> { Ok(hash_double(self.serialize_to_bytes()?)) } - /// Get the current platform version - pub fn current_platform_version(&self) -> Result<&'static PlatformVersion, Error> { - Ok(PlatformVersion::get( - self.current_protocol_version_in_consensus(), - )?) - } /// The default state at platform start pub fn default_with_protocol_versions( @@ -204,6 +200,24 @@ impl TryFromPlatformVersioned for PlatformState { } } +impl PlatformStateV0PrivateMethods for PlatformState { + /// Patched platform version. Used to fix urgent bugs as not part of normal upgrade process. + /// The patched version returns from the public current_platform_version getter in case if present. + fn patched_platform_version(&self) -> Option<&'static PlatformVersion> { + match self { + PlatformState::V0(v0) => v0.patched_platform_version, + } + } + + /// Set patched platform version. It's using to fix urgent bugs as not a part of normal upgrade process + /// The patched version returns from the public current_platform_version getter in case if present. + fn set_patched_platform_version(&mut self, version: Option<&'static PlatformVersion>) { + match self { + PlatformState::V0(v0) => v0.patched_platform_version = version, + } + } +} + impl PlatformStateV0Methods for PlatformState { fn last_committed_block_height(&self) -> u64 { match self { diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/patch_platform_version.rs b/packages/rs-drive-abci/src/platform_types/platform_state/patch_platform_version.rs new file mode 100644 index 0000000000..ea85c30256 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/platform_state/patch_platform_version.rs @@ -0,0 +1,144 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dpp::prelude::BlockHeight; +use dpp::version::patches::PATCHES; +use dpp::version::PlatformVersion; +use dpp::version::INITIAL_PROTOCOL_VERSION; +use std::sync::atomic::{AtomicU32, Ordering}; + +use crate::platform_types::platform_state::v0::{ + PlatformStateV0Methods, PlatformStateV0PrivateMethods, +}; +use crate::platform_types::platform_state::PlatformState; + +static PATCHED_PROTOCOL_VERSION: AtomicU32 = AtomicU32::new(INITIAL_PROTOCOL_VERSION); + +impl PlatformState { + /// Apply all patches to platform version up to specified height + /// It changes protocol version to function version mapping to apply hotfixes + /// PlatformVersion can be already patched, so a patch will be applied on the top + /// + /// This function appends the patch to PlatformState and returns patched version + pub fn apply_all_patches_to_platform_version_up_to_height( + &mut self, + height: BlockHeight, + ) -> Result, Error> { + if self.patched_platform_version().is_some() { + return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "platform version already patched", + ))); + } + + let protocol_version = self.current_protocol_version_in_consensus(); + + let patches = PATCHES.read().unwrap(); + + // Find a patch that matches protocol version first + let Some(patches_per_heights) = patches.get(&protocol_version) else { + return Ok(None); + }; + + if patches_per_heights.is_empty() { + return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "patches per height can't be empty", + ))); + } + + let platform_version_to_patch = self.current_platform_version()?; + + let mut patched_version = platform_version_to_patch.clone(); + + // Apply all patches up to specified height + for (height, patch_fn) in patches_per_heights.range(..=height) { + patched_version = patch_fn(patched_version); + + tracing::debug!( + protocol_version, + height, + "Applied patch for platform version {} and height {:?}", + protocol_version, + height + ); + } + + // Make patch version as static ref to transparently replace original version + let boxed_version = Box::new(patched_version); + let static_patched_version: &'static PlatformVersion = Box::leak(boxed_version); + + // Set patched version to the Platform (execution) state that will be used + // instead of the current version + self.set_patched_platform_version(Some(static_patched_version)); + + Ok(Some(static_patched_version)) + } + + /// Apply a patch to platform version based on specified height + /// It changes protocol version to function version mapping to apply hotfixes + /// PlatformVersion can be already patched, so a patch will be applied on the top + /// + /// This function appends the patch to PlatformState and returns patched version + pub fn apply_platform_version_patch_for_height( + &mut self, + height: BlockHeight, + ) -> Result, Error> { + let protocol_version = self.current_protocol_version_in_consensus(); + + // If we switched protocol version we need to + // drop patched version from PlatformState + if self.patched_platform_version().is_some() { + let previous_protocol_version = PATCHED_PROTOCOL_VERSION.load(Ordering::Relaxed); + if previous_protocol_version != protocol_version { + tracing::debug!( + protocol_version, + height, + "Disable patches for platform version {} because we switched to version {}", + previous_protocol_version, + protocol_version, + ); + + self.set_patched_platform_version(None); + } + } + + let patches = PATCHES.read().unwrap(); + + // Find a patch that matches protocol version first + let Some(patches_per_heights) = patches.get(&protocol_version) else { + return Ok(None); + }; + + // Find a patch that matches block height + let Some(patch_fn) = patches_per_heights.get(&height) else { + return Ok(None); + }; + + // Potentially already patched version + let platform_version_to_patch = self.current_platform_version()?; + + // Apply the patch + let patched_version = patch_fn(platform_version_to_patch.clone()); + + // Make patch version as static ref to transparently replace original version + let boxed_version = Box::new(patched_version); + let static_patched_version: &'static PlatformVersion = Box::leak(boxed_version); + + // Set current protocol version if not set yet + if self.patched_platform_version().is_none() { + PATCHED_PROTOCOL_VERSION.store(protocol_version, Ordering::Relaxed); + } + + // Set patched version to the Platform (execution) state that will be used + // instead of the current version + self.set_patched_platform_version(Some(static_patched_version)); + + tracing::debug!( + protocol_version, + height, + "Applied patch for platform version {} and height {:?}", + protocol_version, + height + ); + + Ok(Some(static_patched_version)) + } +} diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/v0/mod.rs index 555bcbe96f..2ebba43151 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/v0/mod.rs @@ -43,6 +43,10 @@ pub struct PlatformStateV0 { pub current_validator_set_quorum_hash: QuorumHash, /// next quorum pub next_validator_set_quorum_hash: Option, + /// This is a modified current platform version based on + /// `current_protocol_version_in_consensus` with some function versions + /// changed to fix an urgent bug that is not a part of normal upgrade process + pub patched_platform_version: Option<&'static PlatformVersion>, /// current validator set quorums /// The validator set quorums are a subset of the quorums, but they also contain the list of /// all members @@ -156,7 +160,7 @@ impl TryFrom for PlatformStateForSavingV0 { type Error = Error; fn try_from(value: PlatformStateV0) -> Result { - let platform_version = PlatformVersion::get(value.current_protocol_version_in_consensus)?; + let platform_version = value.current_platform_version()?; Ok(PlatformStateForSavingV0 { genesis_block_info: value.genesis_block_info, last_committed_block_info: value.last_committed_block_info, @@ -214,6 +218,7 @@ impl From for PlatformStateV0 { next_validator_set_quorum_hash: value .next_validator_set_quorum_hash .map(|bytes| QuorumHash::from_byte_array(bytes.to_buffer())), + patched_platform_version: None, validator_sets: value .validator_sets .into_iter() @@ -251,6 +256,7 @@ impl PlatformStateV0 { next_epoch_protocol_version, current_validator_set_quorum_hash: QuorumHash::all_zeros(), next_validator_set_quorum_hash: None, + patched_platform_version: None, validator_sets: Default::default(), chain_lock_validating_quorums: SignatureVerificationQuorumSet::new( &config.chain_lock, @@ -285,8 +291,18 @@ pub struct MasternodeListChanges { pub new_banned_masternodes: Vec, } +pub(super) trait PlatformStateV0PrivateMethods { + /// Patched platform version. Used to fix urgent bugs as not part of normal upgrade process. + /// The patched version returns from the public current_platform_version getter in case if present. + fn patched_platform_version(&self) -> Option<&'static PlatformVersion>; + + /// Set patched platform version. It's using to fix urgent bugs as not a part of normal upgrade process + /// The patched version returns from the public current_platform_version getter in case if present. + fn set_patched_platform_version(&mut self, version: Option<&'static PlatformVersion>); +} + /// Platform state methods introduced in version 0 of Platform State Struct -pub trait PlatformStateV0Methods { +pub trait PlatformStateV0Methods: PlatformStateV0PrivateMethods { /// The last block height or 0 for genesis fn last_committed_block_height(&self) -> u64; /// The height of the platform, only committed blocks increase height @@ -317,7 +333,15 @@ pub trait PlatformStateV0Methods { fn last_committed_block_info(&self) -> &Option; /// Returns the current protocol version that is in consensus. fn current_protocol_version_in_consensus(&self) -> ProtocolVersion; - + /// Get the current platform version or patched if present + fn current_platform_version(&self) -> Result<&'static PlatformVersion, Error> { + self.patched_platform_version() + .map(|version| Ok(version)) + .unwrap_or_else(|| { + PlatformVersion::get(self.current_protocol_version_in_consensus()) + .map_err(Error::from) + }) + } /// Returns the upcoming protocol version for the next epoch. fn next_epoch_protocol_version(&self) -> ProtocolVersion; @@ -435,6 +459,20 @@ pub trait PlatformStateV0Methods { Self: Sized; } +impl PlatformStateV0PrivateMethods for PlatformStateV0 { + /// Patched platform version. Used to fix urgent bugs as not part of normal upgrade process. + /// The patched version returns from the public current_platform_version getter in case if present. + fn patched_platform_version(&self) -> Option<&'static PlatformVersion> { + self.patched_platform_version + } + + /// Set patched platform version. It's using to fix urgent bugs as not a part of normal upgrade process + /// The patched version returns from the public current_platform_version getter in case if present. + fn set_patched_platform_version(&mut self, version: Option<&'static PlatformVersion>) { + self.patched_platform_version = version; + } +} + impl PlatformStateV0Methods for PlatformStateV0 { /// The last block height or 0 for genesis fn last_committed_block_height(&self) -> u64 { diff --git a/packages/rs-drive-abci/src/query/mod.rs b/packages/rs-drive-abci/src/query/mod.rs index 86cbd6043f..19e2ce1097 100644 --- a/packages/rs-drive-abci/src/query/mod.rs +++ b/packages/rs-drive-abci/src/query/mod.rs @@ -21,6 +21,7 @@ pub type QueryValidationResult = ValidationResult; mod tests { use crate::error::query::QueryError; use crate::platform_types::platform::Platform; + use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::platform_types::platform_state::PlatformState; use crate::query::QueryValidationResult; use crate::rpc::core::MockCoreRPCLike; diff --git a/packages/rs-drive-abci/src/test/helpers/setup.rs b/packages/rs-drive-abci/src/test/helpers/setup.rs index f42942c6d6..6c7f6e3fef 100644 --- a/packages/rs-drive-abci/src/test/helpers/setup.rs +++ b/packages/rs-drive-abci/src/test/helpers/setup.rs @@ -35,6 +35,7 @@ use std::ops::{Deref, DerefMut}; use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; #[cfg(any(feature = "mocks", test))] use crate::rpc::core::MockCoreRPCLike; use crate::test::fixture::abci::static_system_identity_public_keys_v0; diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 6050a08032..cab148423a 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -24,6 +24,7 @@ mod execution; mod failures; mod masternode_list_item_helpers; mod masternodes; +mod patch_platform_tests; mod query; mod strategy; mod upgrade_fork_tests; diff --git a/packages/rs-drive-abci/tests/strategy_tests/patch_platform_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/patch_platform_tests.rs new file mode 100644 index 0000000000..ee2e087e45 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/patch_platform_tests.rs @@ -0,0 +1,428 @@ +#[cfg(test)] +mod tests { + use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; + use drive::config::DriveConfig; + use std::collections::{BTreeMap, HashMap}; + + use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy}; + use crate::strategy::{ + ChainExecutionOutcome, ChainExecutionParameters, NetworkStrategy, StrategyRandomness, + UpgradingInfo, + }; + use drive_abci::config::{ + ChainLockConfig, ExecutionConfig, InstantLockConfig, PlatformConfig, PlatformTestConfig, + ValidatorSetConfig, + }; + use drive_abci::platform_types::platform_state::v0::PlatformStateV0Methods; + use drive_abci::test::helpers::setup::TestPlatformBuilder; + use platform_version::version; + use platform_version::version::mocks::v2_test::TEST_PROTOCOL_VERSION_2; + use platform_version::version::patches::PatchFn; + use platform_version::version::PlatformVersion; + + #[test] + fn test_patch_version() { + // Define the desired stack size + let stack_size = 4 * 1024 * 1024; // Let's set the stack size to be higher than the default 2MB + + let builder = std::thread::Builder::new() + .stack_size(stack_size) + .name("custom_stack_size_thread".into()); + + pub fn patch_1_5_test(mut platform_version: PlatformVersion) -> PlatformVersion { + platform_version + .drive_abci + .query + .document_query + .default_current_version = 5; + + platform_version + } + + pub fn patch_1_10_test(mut platform_version: PlatformVersion) -> PlatformVersion { + platform_version.drive_abci.query.document_query.max_version = 10; + + platform_version + } + + pub fn patch_2_30_test(mut platform_version: PlatformVersion) -> PlatformVersion { + platform_version.drive_abci.query.document_query.min_version = 30; + + platform_version + } + + let mut patches = version::patches::PATCHES.write().unwrap(); + + *patches = HashMap::from_iter(vec![ + { + ( + 1, + BTreeMap::from_iter(vec![ + (5, patch_1_5_test as PatchFn), + (10, patch_1_10_test as PatchFn), + ]), + ) + }, + { + ( + TEST_PROTOCOL_VERSION_2, + BTreeMap::from_iter(vec![(30, patch_2_30_test as PatchFn)]), + ) + }, + ]); + + drop(patches); + + let handler = builder + .spawn(|| { + let strategy = NetworkStrategy { + total_hpmns: 4, + upgrading_info: Some(UpgradingInfo { + current_protocol_version: 1, + proposed_protocol_versions_with_weight: vec![(TEST_PROTOCOL_VERSION_2, 1)], + upgrade_three_quarters_life: 0.0, + }), + ..Default::default() + }; + + let config = PlatformConfig { + validator_set: ValidatorSetConfig { + quorum_size: 4, + ..Default::default() + }, + chain_lock: ChainLockConfig::default_100_67(), + instant_lock: InstantLockConfig::default_100_67(), + execution: ExecutionConfig { + epoch_time_length_s: 60 * 60, + ..Default::default() + }, + drive: DriveConfig::default(), + block_spacing_ms: 1000 * 60 * 5, + testing_configs: PlatformTestConfig::default_minimal_verifications(), + + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + // Run chain before the first patch + + let ChainExecutionOutcome { + abci_app, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions, + end_time_ms, + identity_nonce_counter, + identity_contract_nonce_counter, + instant_lock_quorums, + .. + } = run_chain_for_strategy( + &mut platform, + 4, + strategy.clone(), + config.clone(), + 13, + &mut None, + ); + + let platform = abci_app.platform; + + // Make sure patch 1 5 is not applied yet + let state = platform.state.load(); + let platform_version = state + .current_platform_version() + .expect("getting patched version shouldn't fail"); + + assert_eq!(state.last_committed_block_epoch().index, 0); + assert_eq!(state.current_protocol_version_in_consensus(), 1); + assert_eq!( + platform_version + .drive_abci + .query + .document_query + .default_current_version, + 0 + ); + + // Run for 2 more blocks to make sure patch 1 5 is applied, + // and it persists for the further blocks + + let block_start = state + .last_committed_block_info() + .as_ref() + .unwrap() + .basic_info() + .height + + 1; + + let ChainExecutionOutcome { + abci_app, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions, + end_time_ms, + identity_nonce_counter, + identity_contract_nonce_counter, + instant_lock_quorums, + .. + } = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start, + core_height_start: 1, + block_count: 2, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions.clone()), + current_identity_nonce_counter: identity_nonce_counter, + current_identity_contract_nonce_counter: identity_contract_nonce_counter, + current_votes: BTreeMap::default(), + start_time_ms: 1681094380000, + current_time_ms: end_time_ms, + instant_lock_quorums, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(7), + ); + + // Make sure patch 1 5 is applied + let state = platform.state.load(); + let platform_version = state + .current_platform_version() + .expect("getting patched version shouldn't fail"); + + assert_eq!(state.last_committed_block_epoch().index, 0); + assert_eq!(state.current_protocol_version_in_consensus(), 1); + assert_eq!( + platform_version + .drive_abci + .query + .document_query + .default_current_version, + 5 + ); + + // Run chain for 9 more blocks to apply patch 1 15 + + let block_start = state + .last_committed_block_info() + .as_ref() + .unwrap() + .basic_info() + .height + + 1; + + let ChainExecutionOutcome { + abci_app, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions, + end_time_ms, + identity_nonce_counter, + identity_contract_nonce_counter, + instant_lock_quorums, + .. + } = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start, + core_height_start: 1, + block_count: 4, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions.clone()), + current_identity_nonce_counter: identity_nonce_counter, + current_identity_contract_nonce_counter: identity_contract_nonce_counter, + current_votes: BTreeMap::default(), + start_time_ms: 1681094380000, + current_time_ms: end_time_ms, + instant_lock_quorums, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(7), + ); + + // Make sure patch 1 5 and 10 is applied + let state = platform.state.load(); + let platform_version = state + .current_platform_version() + .expect("getting patched version shouldn't fail"); + + assert_eq!(state.last_committed_block_epoch().index, 0); + assert_eq!(state.current_protocol_version_in_consensus(), 1); + assert_eq!( + platform_version.drive_abci.query.document_query.max_version, + 10 + ); + assert_eq!( + platform_version + .drive_abci + .query + .document_query + .default_current_version, + 5 + ); + + // Run chain for 10 more blocks to upgrade to version 2 + + let block_start = state + .last_committed_block_info() + .as_ref() + .unwrap() + .basic_info() + .height + + 1; + + let ChainExecutionOutcome { + abci_app, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions, + end_time_ms, + identity_nonce_counter, + identity_contract_nonce_counter, + instant_lock_quorums, + .. + } = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start, + core_height_start: 1, + block_count: 15, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + current_identity_nonce_counter: identity_nonce_counter, + current_identity_contract_nonce_counter: identity_contract_nonce_counter, + current_votes: BTreeMap::default(), + start_time_ms: 1681094380000, + current_time_ms: end_time_ms, + instant_lock_quorums, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(18), + ); + + // Make sure we switched version and drop all patches + let state = platform.state.load(); + let platform_version = state + .current_platform_version() + .expect("getting patched version shouldn't fail"); + + assert_eq!(state.last_committed_block_epoch().index, 2); + assert_eq!( + state.current_protocol_version_in_consensus(), + TEST_PROTOCOL_VERSION_2 + ); + assert_eq!( + platform_version + .drive_abci + .query + .document_query + .default_current_version, + 0 + ); + assert_eq!( + platform_version.drive_abci.query.document_query.min_version, + 0 + ); + assert_eq!( + platform_version.drive_abci.query.document_query.max_version, + 0 + ); + + // Run chain for 10 more blocks to apply 2 45 patch + + let block_start = state + .last_committed_block_info() + .as_ref() + .unwrap() + .basic_info() + .height + + 1; + + let ChainExecutionOutcome { .. } = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start, + core_height_start: 1, + block_count: 10, + proposers, + validator_quorums: quorums, + current_validator_quorum_hash: current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + current_identity_nonce_counter: identity_nonce_counter, + current_identity_contract_nonce_counter: identity_contract_nonce_counter, + current_votes: BTreeMap::default(), + start_time_ms: 1681094380000, + current_time_ms: end_time_ms, + instant_lock_quorums, + }, + strategy, + config, + StrategyRandomness::SeedEntropy(18), + ); + + // Make sure we applied 2 30 and patches for version 1 is ignored + let state = platform.state.load(); + let platform_version = state + .current_platform_version() + .expect("getting patched version shouldn't fail"); + + assert_eq!( + state.current_protocol_version_in_consensus(), + TEST_PROTOCOL_VERSION_2 + ); + assert_eq!( + platform_version + .drive_abci + .query + .document_query + .default_current_version, + 0 + ); + assert_eq!( + platform_version.drive_abci.query.document_query.min_version, + 30 + ); + assert_eq!( + platform_version.drive_abci.query.document_query.max_version, + 0 + ); + }) + .expect("Failed to create thread with custom stack size"); + + fn cleanup_version_patches() { + let mut patches = version::patches::PATCHES.write().unwrap(); + patches.clear(); + } + + // Wait for the thread to finish and assert that it didn't panic. + handler + .join() + .map(|result| { + cleanup_version_patches(); + + result + }) + .map_err(|e| { + cleanup_version_patches(); + + e + }) + .expect("Thread has panicked"); + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs index 249e181794..b1fc8e7be8 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs @@ -7,7 +7,7 @@ mod tests { use dpp::dashcore::{BlockHash, ChainLock}; use dpp::version::PlatformVersion; use drive::config::DriveConfig; - use std::collections::BTreeMap; + use std::collections::{BTreeMap, HashMap}; use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy}; use crate::strategy::{ @@ -18,13 +18,15 @@ mod tests { ChainLockConfig, ExecutionConfig, InstantLockConfig, PlatformConfig, PlatformTestConfig, ValidatorSetConfig, }; + use drive_abci::logging::LogLevel; use drive_abci::mimic::MimicExecuteBlockOptions; use drive_abci::platform_types::platform_state::v0::PlatformStateV0Methods; use drive_abci::test::helpers::setup::TestPlatformBuilder; - use platform_version::version::mocks::v2_test::{TEST_PLATFORM_V2, TEST_PROTOCOL_VERSION_2}; - use platform_version::version::mocks::v3_test::{TEST_PLATFORM_V3, TEST_PROTOCOL_VERSION_3}; - use platform_version::version::mocks::TEST_PROTOCOL_VERSION_SHIFT_BYTES; - use platform_version::version::v1::PLATFORM_V1; + use platform_version::version; + use platform_version::version::mocks::v2_test::TEST_PROTOCOL_VERSION_2; + use platform_version::version::mocks::v3_test::TEST_PROTOCOL_VERSION_3; + use platform_version::version::patches::PatchFn; + use platform_version::version::v1::PROTOCOL_VERSION_1; use strategy_tests::frequency::Frequency; use strategy_tests::{IdentityInsertInfo, StartIdentities, Strategy}; @@ -523,27 +525,31 @@ mod tests { #[test] fn run_chain_on_epoch_change_with_new_version_and_removing_votes() { - // Add a new version to upgrade to new protocol version only with one votes - const TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE: u32 = - (1 << TEST_PROTOCOL_VERSION_SHIFT_BYTES) + 4; - - let mut test_platform_v4 = PLATFORM_V1.clone(); - test_platform_v4 - .drive_abci - .methods - .protocol_upgrade - .protocol_version_upgrade_percentage_needed = 1; - - PlatformVersion::replace_test_versions(vec![ - TEST_PLATFORM_V2, - TEST_PLATFORM_V3, - test_platform_v4, - ]); + fn patch_upgrade_percentage(mut platform_version: PlatformVersion) -> PlatformVersion { + platform_version + .drive_abci + .methods + .protocol_upgrade + .protocol_version_upgrade_percentage_needed = 1; + + platform_version + } + + let mut patches = version::patches::PATCHES.write().unwrap(); + + *patches = HashMap::from_iter(vec![{ + ( + 1, + BTreeMap::from_iter(vec![(1, patch_upgrade_percentage as PatchFn)]), + ) + }]); + + drop(patches); let strategy = NetworkStrategy { total_hpmns: 50, upgrading_info: Some(UpgradingInfo { - current_protocol_version: TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE, + current_protocol_version: PROTOCOL_VERSION_1, proposed_protocol_versions_with_weight: vec![(TEST_PROTOCOL_VERSION_2, 1)], upgrade_three_quarters_life: 0.0, }), @@ -573,7 +579,7 @@ mod tests { epoch_time_length_s, ..Default::default() }, - initial_protocol_version: TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE, + initial_protocol_version: PROTOCOL_VERSION_1, block_spacing_ms: epoch_time_length_s * 1000, testing_configs: PlatformTestConfig { block_signing: false, @@ -612,22 +618,14 @@ mod tests { assert_eq!(state.last_committed_block_epoch().index, 0); assert_eq!( state.current_protocol_version_in_consensus(), - TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE - ); - assert_eq!( - state.next_epoch_protocol_version(), - TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE + PROTOCOL_VERSION_1 ); + assert_eq!(state.next_epoch_protocol_version(), PROTOCOL_VERSION_1); assert_eq!(state.last_committed_core_height(), 2); assert_eq!(counter.get(&1).unwrap(), None); assert_eq!(counter.get(&TEST_PROTOCOL_VERSION_2).unwrap(), Some(&1)); assert_eq!(counter.get(&TEST_PROTOCOL_VERSION_3).unwrap(), None); - assert_eq!( - counter - .get(&TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE) - .unwrap(), - None - ); + assert_eq!(counter.get(&PROTOCOL_VERSION_1).unwrap(), None); drop(counter); @@ -689,13 +687,16 @@ mod tests { assert_eq!(state.last_committed_block_epoch().index, 1); assert_eq!( state.current_protocol_version_in_consensus(), - TEST_PROTOCOL_VERSION_4_WITH_1_HPMN_UPGRADE + PROTOCOL_VERSION_1 ); assert_eq!(state.next_epoch_protocol_version(), TEST_PROTOCOL_VERSION_2); assert_eq!(counter.get(&1).unwrap(), None); assert_eq!(counter.get(&TEST_PROTOCOL_VERSION_2).unwrap(), None); assert_eq!(counter.get(&TEST_PROTOCOL_VERSION_3).unwrap(), Some(&1)); assert_eq!(state.last_committed_core_height(), 3); + + let mut patches = version::patches::PATCHES.write().unwrap(); + patches.clear(); } #[test] @@ -935,6 +936,8 @@ mod tests { #[test] fn run_chain_version_upgrade_slow_upgrade_quick_reversion_after_lock_in() { + drive_abci::logging::init_for_tests(LogLevel::Silent); + // Define the desired stack size let stack_size = 4 * 1024 * 1024; //Let's set the stack size to be higher than the default 2MB diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 22c1d1b8e6..221183d1bb 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -12,6 +12,7 @@ thiserror = { version = "1.0.59" } bincode = { version = "2.0.0-rc.3"} versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } grovedb-version = { git = "https://github.com/dashpay/grovedb", version = "1.0.0-rc.2"} +once_cell = "1.19.0" [features] mock-versions = [] diff --git a/packages/rs-platform-version/src/version/mod.rs b/packages/rs-platform-version/src/version/mod.rs index 809c467fea..928d5ea35a 100644 --- a/packages/rs-platform-version/src/version/mod.rs +++ b/packages/rs-platform-version/src/version/mod.rs @@ -9,7 +9,10 @@ pub mod drive_versions; pub mod fee; #[cfg(feature = "mock-versions")] pub mod mocks; +pub mod patches; pub mod v1; -pub const LATEST_VERSION: u32 = PROTOCOL_VERSION_1; -pub const INITIAL_PROTOCOL_VERSION: u32 = 1; +pub type ProtocolVersion = u32; + +pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_1; +pub const INITIAL_PROTOCOL_VERSION: ProtocolVersion = 1; diff --git a/packages/rs-platform-version/src/version/patches/mod.rs b/packages/rs-platform-version/src/version/patches/mod.rs new file mode 100644 index 0000000000..ef079268ac --- /dev/null +++ b/packages/rs-platform-version/src/version/patches/mod.rs @@ -0,0 +1,13 @@ +use crate::version::{PlatformVersion, ProtocolVersion}; +use once_cell::sync::Lazy; +use std::collections::{BTreeMap, HashMap}; +use std::sync::RwLock; + +/// Patch function signature. It uses to dynamically modify platform version +pub type PatchFn = fn(PlatformVersion) -> PlatformVersion; + +type HeightToPatchRanges = BTreeMap; + +/// Patch function per height, per protocol version +pub static PATCHES: Lazy>> = + Lazy::new(|| RwLock::new(HashMap::new())); diff --git a/packages/rs-platform-version/src/version/protocol_version.rs b/packages/rs-platform-version/src/version/protocol_version.rs index a17d420d43..487b27513a 100644 --- a/packages/rs-platform-version/src/version/protocol_version.rs +++ b/packages/rs-platform-version/src/version/protocol_version.rs @@ -14,6 +14,7 @@ use crate::version::v1::PLATFORM_V1; #[cfg(feature = "mock-versions")] use std::sync::OnceLock; +use crate::version::ProtocolVersion; pub use versioned_feature_core::*; #[derive(Clone, Debug, Default)] @@ -29,7 +30,7 @@ pub struct PlatformArchitectureVersion { #[derive(Clone, Debug)] pub struct PlatformVersion { - pub protocol_version: u32, + pub protocol_version: ProtocolVersion, pub identity: FeatureVersionBounds, pub proofs: FeatureVersionBounds, pub dpp: DPPVersion, @@ -52,7 +53,7 @@ const DEFAULT_PLATFORM_TEST_VERSIONS: &[PlatformVersion] = &[TEST_PLATFORM_V2, T pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V1; impl PlatformVersion { - pub fn get<'a>(version: u32) -> Result<&'a Self, PlatformVersionError> { + pub fn get<'a>(version: ProtocolVersion) -> Result<&'a Self, PlatformVersionError> { if version > 0 { #[cfg(feature = "mock-versions")] { @@ -81,7 +82,7 @@ impl PlatformVersion { } pub fn get_version_or_latest<'a>( - version: Option, + version: Option, ) -> Result<&'a Self, PlatformVersionError> { if let Some(version) = version { if version > 0 { diff --git a/packages/rs-platform-version/src/version/v1.rs b/packages/rs-platform-version/src/version/v1.rs index 2ad92fa06c..e26c7a567e 100644 --- a/packages/rs-platform-version/src/version/v1.rs +++ b/packages/rs-platform-version/src/version/v1.rs @@ -77,10 +77,10 @@ use crate::version::drive_versions::{ }; use crate::version::fee::v1::FEE_VERSION1; use crate::version::protocol_version::{FeatureVersionBounds, PlatformVersion}; -use crate::version::{AbciStructureVersion, PlatformArchitectureVersion}; +use crate::version::{AbciStructureVersion, PlatformArchitectureVersion, ProtocolVersion}; use grovedb_version::version::v1::GROVE_V1; -pub const PROTOCOL_VERSION_1: u32 = 1; +pub const PROTOCOL_VERSION_1: ProtocolVersion = 1; pub const PLATFORM_V1: PlatformVersion = PlatformVersion { protocol_version: 1,