diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs index af0e181b66..25922cdf19 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs @@ -1,5 +1,6 @@ mod v0; mod v1; +mod v2; use crate::error::execution::ExecutionError; use crate::error::Error; @@ -58,9 +59,14 @@ where platform_state, block_execution_context, ), + 2 => self.validator_set_update_v2( + proposer_pro_tx_hash, + platform_state, + block_execution_context, + ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { method: "validator_set_update".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, })), } diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/v2/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/v2/mod.rs new file mode 100644 index 0000000000..6fc9f86b7d --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/v2/mod.rs @@ -0,0 +1,221 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::types::block_execution_context::v0::{ + BlockExecutionContextV0Getters, BlockExecutionContextV0MutableGetters, +}; +use crate::execution::types::block_execution_context::BlockExecutionContext; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::v0::PlatformStateV0Methods; +use crate::platform_types::platform_state::PlatformState; +use crate::platform_types::validator_set::v0::ValidatorSetV0Getters; +use crate::rpc::core::CoreRPCLike; +use itertools::Itertools; + +use crate::platform_types::validator_set::ValidatorSetExt; +use dpp::dashcore::hashes::Hash; +use tenderdash_abci::proto::abci::ValidatorSetUpdate; + +impl Platform +where + C: CoreRPCLike, +{ + /// We need to validate against the platform state for rotation and not the block execution + /// context state + /// We introduced v1 because the end quorums could be rotating out, giving a small advantage + /// to proposers with a smaller pro_tx_hash + /// To understand this imagine we have proposers + /// `a b c d e f g h i j k` in quorum 24 + /// c is the current proposer + /// Quorum 24 no longer is valid + /// We jump to quorum 0. + /// a b and c just got paid, but the rest did not. + #[inline(always)] + pub(super) fn validator_set_update_v2( + &self, + proposer_pro_tx_hash: [u8; 32], + platform_state: &PlatformState, + block_execution_context: &mut BlockExecutionContext, + ) -> Result, Error> { + let mut perform_rotation = false; + + if let Some(validator_set) = block_execution_context + .block_platform_state() + .validator_sets() + .get(&platform_state.current_validator_set_quorum_hash()) + { + if let Some((last_member_pro_tx_hash, _)) = validator_set.members().last_key_value() { + // we should also perform a rotation if the validator set went through all quorum members + // this means we are at the last member of the quorum + if last_member_pro_tx_hash.as_byte_array() == &proposer_pro_tx_hash { + tracing::debug!( + method = "validator_set_update_v2", + "rotation: quorum finished as we hit last member {} of quorum {}. All known quorums are: [{}]. quorum rotation expected", + hex::encode(proposer_pro_tx_hash), + hex::encode(platform_state.current_validator_set_quorum_hash().as_byte_array()), + block_execution_context + .block_platform_state() + .validator_sets() + .keys() + .map(hex::encode).collect::>().join(" | "), + ); + perform_rotation = true; + } + } else { + // the validator set has no members, very weird, but let's just perform a rotation + tracing::debug!( + method = "validator_set_update_v2", + "rotation: validator set has no members", + ); + perform_rotation = true; + } + + // We should also perform a rotation if there are more than one quorum in the system + // and that the new proposer is on the same quorum and the last proposer but is before + // them in the list of proposers. + // This only works if Tenderdash goes through proposers properly + if &platform_state.last_committed_quorum_hash() + == platform_state + .current_validator_set_quorum_hash() + .as_byte_array() + && platform_state.last_committed_block_proposer_pro_tx_hash() > proposer_pro_tx_hash + && platform_state.validator_sets().len() > 1 + { + // 1 - We haven't changed quorums + // 2 - The new proposer is before the old proposer + // 3 - There are more than one quorum in the system + tracing::debug!( + method = "validator_set_update_v2", + "rotation: quorum finished as we hit last an earlier member {} than last block proposer {} for quorum {}. All known quorums are: [{}]. quorum rotation expected", + hex::encode(proposer_pro_tx_hash), + hex::encode(block_execution_context.block_platform_state().last_committed_block_proposer_pro_tx_hash()), + hex::encode(platform_state.current_validator_set_quorum_hash().as_byte_array()), + block_execution_context + .block_platform_state() + .validator_sets() + .keys() + .map(hex::encode).collect::>().join(" | "), + ); + perform_rotation = true; + } + } else { + // we also need to perform a rotation if the validator set is being removed + tracing::debug!( + method = "validator_set_update_v2", + "rotation: new quorums not containing current quorum current {:?}, {}. quorum rotation expected", + block_execution_context + .block_platform_state() + .validator_sets() + .keys() + .map(|quorum_hash| format!("{}", quorum_hash)), + &platform_state.current_validator_set_quorum_hash() + ); + perform_rotation = true; + } + + //todo: (maybe) perform a rotation if quorum health is low + + if perform_rotation { + // get the index of the previous quorum + let mut index = platform_state + .validator_sets() + .get_index_of(&platform_state.current_validator_set_quorum_hash()) + .ok_or(Error::Execution(ExecutionError::CorruptedCachedState( + format!("perform_rotation: current validator set quorum hash {} not in current known validator sets [{}] processing block {}", platform_state.current_validator_set_quorum_hash(), platform_state + .validator_sets().keys().map(|quorum_hash| quorum_hash.to_string()).join(" | "), + platform_state.last_committed_block_height() + 1, + ))))?; + // we should rotate the quorum + let quorum_count = platform_state.validator_sets().len(); + match quorum_count { + 0 => Err(Error::Execution(ExecutionError::CorruptedCachedState( + "no current quorums".to_string(), + ))), + 1 => Ok(None), // no rotation as we are the only quorum + count => { + let start_index = index; + let oldest_quorum_index_we_can_go_to = if count > 10 { + // if we have a lot of quorums (like on testnet and mainnet) + // we shouldn't start using the last ones as they could cycle out + count - 2 + } else { + count + }; + index = if index + 1 >= oldest_quorum_index_we_can_go_to { + 0 + } else { + index + 1 + }; + // We can't just take the next item because it might no longer be in the state + for _i in 0..oldest_quorum_index_we_can_go_to { + let (quorum_hash, _) = platform_state + .validator_sets() + .get_index(index) + .expect("expected next validator set"); + + // We still have it in the state + if let Some(new_validator_set) = block_execution_context + .block_platform_state() + .validator_sets() + .get(quorum_hash) + { + tracing::debug!( + method = "validator_set_update_v2", + "rotation: to new quorum: {} with {} members", + &quorum_hash, + new_validator_set.members().len() + ); + let validator_set_update = new_validator_set.to_update(); + block_execution_context + .block_platform_state_mut() + .set_next_validator_set_quorum_hash(Some(*quorum_hash)); + return Ok(Some(validator_set_update)); + } + index = (index + 1) % oldest_quorum_index_we_can_go_to; + if index == start_index { + break; + } + } + // All quorums changed + if let Some((quorum_hash, new_validator_set)) = block_execution_context + .block_platform_state() + .validator_sets() + .first() + { + tracing::debug!( + method = "validator_set_update_v2", + "rotation: all quorums changed, rotation to new quorum: {}", + &quorum_hash + ); + let validator_set_update = new_validator_set.to_update(); + let new_quorum_hash = *quorum_hash; + block_execution_context + .block_platform_state_mut() + .set_next_validator_set_quorum_hash(Some(new_quorum_hash)); + return Ok(Some(validator_set_update)); + } + tracing::debug!("no new quorums to choose from"); + Ok(None) + } + } + } else { + let current_validator_set = block_execution_context + .block_platform_state() + .current_validator_set()?; + if current_validator_set != platform_state.current_validator_set()? { + // Something changed, for example the IP of a validator changed, or someone's ban status + + tracing::debug!( + method = "validator_set_update_v2", + "validator set update without rotation" + ); + Ok(Some(current_validator_set.to_update())) + } else { + tracing::debug!( + method = "validator_set_update_v2", + "no validator set update", + ); + Ok(None) + } + } + } +} diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs index 5f6dacf380..48caab4b3c 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs @@ -3,6 +3,7 @@ use versioned_feature_core::{FeatureVersion, OptionalFeatureVersion}; pub mod v1; pub mod v2; pub mod v3; +pub mod v4; #[derive(Clone, Debug, Default)] pub struct DriveAbciMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs new file mode 100644 index 0000000000..bedfb591c3 --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs @@ -0,0 +1,121 @@ +use crate::version::drive_abci_versions::drive_abci_method_versions::{ + DriveAbciBlockEndMethodVersions, DriveAbciBlockFeeProcessingMethodVersions, + DriveAbciBlockStartMethodVersions, DriveAbciCoreBasedUpdatesMethodVersions, + DriveAbciCoreChainLockMethodVersionsAndConstants, DriveAbciCoreInstantSendLockMethodVersions, + DriveAbciEngineMethodVersions, DriveAbciEpochMethodVersions, + DriveAbciFeePoolInwardsDistributionMethodVersions, + DriveAbciFeePoolOutwardsDistributionMethodVersions, + DriveAbciIdentityCreditWithdrawalMethodVersions, DriveAbciInitializationMethodVersions, + DriveAbciMasternodeIdentitiesUpdatesMethodVersions, DriveAbciMethodVersions, + DriveAbciPlatformStateStorageMethodVersions, DriveAbciProtocolUpgradeMethodVersions, + DriveAbciStateTransitionProcessingMethodVersions, DriveAbciVotingMethodVersions, +}; + +pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMethodVersions { + engine: DriveAbciEngineMethodVersions { + init_chain: 0, + check_tx: 0, + run_block_proposal: 0, + finalize_block_proposal: 0, + consensus_params_update: 1, + }, + initialization: DriveAbciInitializationMethodVersions { + initial_core_height_and_time: 0, + create_genesis_state: 0, + }, + core_based_updates: DriveAbciCoreBasedUpdatesMethodVersions { + update_core_info: 0, + update_masternode_list: 0, + update_quorum_info: 0, + masternode_updates: DriveAbciMasternodeIdentitiesUpdatesMethodVersions { + get_voter_identity_key: 0, + get_operator_identity_keys: 0, + get_owner_identity_withdrawal_key: 0, + get_owner_identity_owner_key: 0, + get_voter_identifier_from_masternode_list_item: 0, + get_operator_identifier_from_masternode_list_item: 0, + create_operator_identity: 0, + create_owner_identity: 1, + create_voter_identity: 0, + disable_identity_keys: 0, + update_masternode_identities: 0, + update_operator_identity: 0, + update_owner_withdrawal_address: 1, + update_voter_identity: 0, + }, + }, + protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions { + check_for_desired_protocol_upgrade: 1, + upgrade_protocol_version_on_epoch_change: 0, + perform_events_on_first_block_of_protocol_change: Some(0), + protocol_version_upgrade_percentage_needed: 67, + }, + block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions { + add_process_epoch_change_operations: 0, + process_block_fees: 0, + }, + core_chain_lock: DriveAbciCoreChainLockMethodVersionsAndConstants { + choose_quorum: 0, + verify_chain_lock: 0, + verify_chain_lock_locally: 0, + verify_chain_lock_through_core: 0, + make_sure_core_is_synced_to_chain_lock: 0, + recent_block_count_amount: 2, + }, + core_instant_send_lock: DriveAbciCoreInstantSendLockMethodVersions { + verify_recent_signature_locally: 0, + }, + fee_pool_inwards_distribution: DriveAbciFeePoolInwardsDistributionMethodVersions { + add_distribute_block_fees_into_pools_operations: 0, + add_distribute_storage_fee_to_epochs_operations: 0, + }, + fee_pool_outwards_distribution: DriveAbciFeePoolOutwardsDistributionMethodVersions { + add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations: 0, + add_epoch_pool_to_proposers_payout_operations: 0, + find_oldest_epoch_needing_payment: 0, + fetch_reward_shares_list_for_masternode: 0, + }, + withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions { + build_untied_withdrawal_transactions_from_documents: 0, + dequeue_and_build_unsigned_withdrawal_transactions: 0, + fetch_transactions_block_inclusion_status: 0, + pool_withdrawals_into_transactions_queue: 0, + update_broadcasted_withdrawal_statuses: 0, + rebroadcast_expired_withdrawal_documents: 0, + append_signatures_and_broadcast_withdrawal_transactions: 0, + cleanup_expired_locks_of_withdrawal_amounts: 0, + }, + voting: DriveAbciVotingMethodVersions { + keep_record_of_finished_contested_resource_vote_poll: 0, + clean_up_after_vote_poll_end: 0, + clean_up_after_contested_resources_vote_poll_end: 0, + check_for_ended_vote_polls: 0, + tally_votes_for_contested_document_resource_vote_poll: 0, + award_document_to_winner: 0, + delay_vote_poll: 0, + run_dao_platform_events: 0, + remove_votes_for_removed_masternodes: 0, + }, + state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions { + execute_event: 0, + process_raw_state_transitions: 0, + decode_raw_state_transitions: 0, + validate_fees_of_event: 0, + }, + epoch: DriveAbciEpochMethodVersions { + gather_epoch_info: 0, + get_genesis_time: 0, + }, + block_start: DriveAbciBlockStartMethodVersions { + clear_drive_block_cache: 0, + }, + block_end: DriveAbciBlockEndMethodVersions { + update_state_cache: 0, + update_drive_cache: 0, + validator_set_update: 2, + }, + platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { + fetch_platform_state: 0, + store_platform_state: 0, + }, +}; diff --git a/packages/rs-platform-version/src/version/mod.rs b/packages/rs-platform-version/src/version/mod.rs index 489aa21cc4..5c9f2d2942 100644 --- a/packages/rs-platform-version/src/version/mod.rs +++ b/packages/rs-platform-version/src/version/mod.rs @@ -1,5 +1,5 @@ mod protocol_version; -use crate::version::v4::PROTOCOL_VERSION_4; +use crate::version::v5::PROTOCOL_VERSION_5; pub use protocol_version::*; mod consensus_versions; @@ -16,8 +16,9 @@ pub mod v1; pub mod v2; pub mod v3; pub mod v4; +pub mod v5; pub type ProtocolVersion = u32; -pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_4; +pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_5; pub const INITIAL_PROTOCOL_VERSION: ProtocolVersion = 1; diff --git a/packages/rs-platform-version/src/version/protocol_version.rs b/packages/rs-platform-version/src/version/protocol_version.rs index dc408cc613..67f1b96754 100644 --- a/packages/rs-platform-version/src/version/protocol_version.rs +++ b/packages/rs-platform-version/src/version/protocol_version.rs @@ -19,6 +19,7 @@ use crate::version::system_limits::SystemLimits; use crate::version::v2::PLATFORM_V2; use crate::version::v3::PLATFORM_V3; use crate::version::v4::PLATFORM_V4; +use crate::version::v5::PLATFORM_V5; use crate::version::ProtocolVersion; pub use versioned_feature_core::*; @@ -34,8 +35,13 @@ pub struct PlatformVersion { pub system_limits: SystemLimits, } -pub const PLATFORM_VERSIONS: &[PlatformVersion] = - &[PLATFORM_V1, PLATFORM_V2, PLATFORM_V3, PLATFORM_V4]; +pub const PLATFORM_VERSIONS: &[PlatformVersion] = &[ + PLATFORM_V1, + PLATFORM_V2, + PLATFORM_V3, + PLATFORM_V4, + PLATFORM_V5, +]; #[cfg(feature = "mock-versions")] // We use OnceLock to be able to modify the version mocks @@ -43,7 +49,7 @@ pub static PLATFORM_TEST_VERSIONS: OnceLock> = OnceLock::ne #[cfg(feature = "mock-versions")] const DEFAULT_PLATFORM_TEST_VERSIONS: &[PlatformVersion] = &[TEST_PLATFORM_V2, TEST_PLATFORM_V3]; -pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V4; +pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V5; pub const DESIRED_PLATFORM_VERSION: &PlatformVersion = LATEST_PLATFORM_VERSION; diff --git a/packages/rs-platform-version/src/version/v5.rs b/packages/rs-platform-version/src/version/v5.rs new file mode 100644 index 0000000000..988531b3d0 --- /dev/null +++ b/packages/rs-platform-version/src/version/v5.rs @@ -0,0 +1,64 @@ +use crate::version::consensus_versions::ConsensusVersions; +use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1; +use crate::version::dpp_versions::dpp_contract_versions::v1::CONTRACT_VERSIONS_V1; +use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1; +use crate::version::dpp_versions::dpp_document_versions::v1::DOCUMENT_VERSIONS_V1; +use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1; +use crate::version::dpp_versions::dpp_identity_versions::v1::IDENTITY_VERSIONS_V1; +use crate::version::dpp_versions::dpp_method_versions::v1::DPP_METHOD_VERSIONS_V1; +use crate::version::dpp_versions::dpp_state_transition_conversion_versions::v2::STATE_TRANSITION_CONVERSION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_state_transition_method_versions::v1::STATE_TRANSITION_METHOD_VERSIONS_V1; +use crate::version::dpp_versions::dpp_state_transition_serialization_versions::v1::STATE_TRANSITION_SERIALIZATION_VERSIONS_V1; +use crate::version::dpp_versions::dpp_state_transition_versions::v2::STATE_TRANSITION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_validation_versions::v2::DPP_VALIDATION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_voting_versions::v2::VOTING_VERSION_V2; +use crate::version::dpp_versions::DPPVersion; +use crate::version::drive_abci_versions::drive_abci_method_versions::v4::DRIVE_ABCI_METHOD_VERSIONS_V4; +use crate::version::drive_abci_versions::drive_abci_query_versions::v1::DRIVE_ABCI_QUERY_VERSIONS_V1; +use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIVE_ABCI_STRUCTURE_VERSIONS_V1; +use crate::version::drive_abci_versions::drive_abci_validation_versions::v3::DRIVE_ABCI_VALIDATION_VERSIONS_V3; +use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v2::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2; +use crate::version::drive_abci_versions::DriveAbciVersion; +use crate::version::drive_versions::v2::DRIVE_VERSION_V2; +use crate::version::fee::v1::FEE_VERSION1; +use crate::version::protocol_version::PlatformVersion; +use crate::version::system_data_contract_versions::v1::SYSTEM_DATA_CONTRACT_VERSIONS_V1; +use crate::version::system_limits::v1::SYSTEM_LIMITS_V1; +use crate::version::ProtocolVersion; + +pub const PROTOCOL_VERSION_5: ProtocolVersion = 5; + +/// This version added a fix to withdrawals so we would rotate to first quorum always. + +pub const PLATFORM_V5: PlatformVersion = PlatformVersion { + protocol_version: PROTOCOL_VERSION_5, + drive: DRIVE_VERSION_V2, + drive_abci: DriveAbciVersion { + structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, + methods: DRIVE_ABCI_METHOD_VERSIONS_V4, // changed to v4 + validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V3, + withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2, + query: DRIVE_ABCI_QUERY_VERSIONS_V1, + }, + dpp: DPPVersion { + costs: DPP_COSTS_VERSIONS_V1, + validation: DPP_VALIDATION_VERSIONS_V2, + state_transition_serialization_versions: STATE_TRANSITION_SERIALIZATION_VERSIONS_V1, + state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2, + state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, + state_transitions: STATE_TRANSITION_VERSIONS_V2, + contract_versions: CONTRACT_VERSIONS_V1, + document_versions: DOCUMENT_VERSIONS_V1, + identity_versions: IDENTITY_VERSIONS_V1, + voting_versions: VOTING_VERSION_V2, + asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1, + methods: DPP_METHOD_VERSIONS_V1, + factory_versions: DPP_FACTORY_VERSIONS_V1, + }, + system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V1, + fee_version: FEE_VERSION1, + system_limits: SYSTEM_LIMITS_V1, + consensus: ConsensusVersions { + tenderdash_consensus_version: 1, + }, +};