diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 76b2cce1054..5a0d8842a61 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -63,8 +63,8 @@ service Platform { rpc getTokenTotalSupply(GetTokenTotalSupplyRequest) returns (GetTokenTotalSupplyResponse); rpc getGroupInfo(GetGroupInfoRequest) returns (GetGroupInfoResponse); rpc getGroupInfos(GetGroupInfosRequest) returns (GetGroupInfosResponse); -// rpc getActiveGroupActions(GetActiveGroupActionsRequest) returns (GetActiveGroupActionsResponse); -// rpc getClosedGroupActions(GetClosedGroupActionsRequest) returns (GetClosedGroupActionsResponse); + rpc getGroupActions(GetGroupActionsRequest) returns (GetGroupActionsResponse); + rpc getGroupActionSigners(GetGroupActionSignersRequest) returns (GetGroupActionSignersResponse); } // Proof message includes cryptographic proofs for validating responses @@ -1512,70 +1512,125 @@ message GetGroupInfosResponse { } } -message GetActiveGroupActionsRequest { - message GetActiveGroupActionsRequestV0 { +message GetGroupActionsRequest { + enum ActionStatus { + ACTIVE = 0; // Request the active actions + CLOSED = 1; // Request the closed actions + } + + message StartAtActionId { + bytes start_action_id = 1; + bool start_action_id_included = 2; + } + + message GetGroupActionsRequestV0 { bytes contract_id = 1; uint32 group_contract_position = 2; - bool prove = 3; + ActionStatus status = 3; + optional StartAtActionId start_at_action_id = 4; + optional uint32 count = 5; + bool prove = 6; } oneof version { - GetActiveGroupActionsRequestV0 v0 = 1; + GetGroupActionsRequestV0 v0 = 1; } } -message GetActiveGroupActionsResponse { - message GetActiveGroupActionsResponseV0 { +message GetGroupActionsResponse { + message GetGroupActionsResponseV0 { // Mint event message MintEvent { uint64 amount = 1; // Amount to mint bytes recipient_id = 2; // Recipient identifier - string public_note = 3; // Public note + optional string public_note = 3; // Public note } // Burn event message BurnEvent { uint64 amount = 1; // Amount to burn - string public_note = 2; // Public note + optional string public_note = 2; // Public note } // Freeze event message FreezeEvent { bytes frozen_id = 1; // Identifier of the frozen entity - string public_note = 2; // Public note + optional string public_note = 2; // Public note } // Unfreeze event message UnfreezeEvent { bytes frozen_id = 1; // Identifier of the unfrozen entity - string public_note = 2; // Public note + optional string public_note = 2; // Public note } // Destroy frozen funds event message DestroyFrozenFundsEvent { bytes frozen_id = 1; // Identifier of the frozen entity uint64 amount = 2; // Amount to destroy - string public_note = 3; // Public note + optional string public_note = 3; // Public note + } + + // Shared encrypted note + message SharedEncryptedNote { + uint32 sender_key_index = 1; // Sender key index + uint32 recipient_key_index = 2; // Recipient key index + bytes encrypted_data = 3; // Encrypted data + } + + // Personal encrypted note + message PersonalEncryptedNote { + uint32 root_encryption_key_index = 1; // Root encryption key index + uint32 derivation_encryption_key_index = 2; // Derivation encryption key index + bytes encrypted_data = 3; // Encrypted data } // Transfer event message TransferEvent { bytes recipient_id = 1; // Recipient identifier - string public_note = 2; // Public note - bytes shared_encrypted_note = 3; // Shared encrypted note - bytes personal_encrypted_note = 4; // Personal encrypted note + optional string public_note = 2; // Public note + optional SharedEncryptedNote shared_encrypted_note = 3; // Shared encrypted note + optional PersonalEncryptedNote personal_encrypted_note = 4; // Personal encrypted note uint64 amount = 5; // Amount transferred } // Emergency action event message EmergencyActionEvent { - string action_type = 1; // Emergency action type - string public_note = 2; // Public note + // Enum for emergency action types + enum ActionType { + PAUSE = 0; // Pause action + RESUME = 1; // Resume action + } + + ActionType action_type = 1; // Emergency action type + optional string public_note = 2; // Public note } // Event associated with this action message GroupActionEvent { oneof event_type { TokenEvent token_event = 1; // Token event details + DocumentEvent document_event = 2; + ContractEvent contract_event = 3; + } + } + + message DocumentEvent { + oneof type { + DocumentCreateEvent create = 1; // Create event details + } + } + + message DocumentCreateEvent { + bytes created_document = 1; + } + + message ContractUpdateEvent { + bytes updated_contract = 1; + } + + message ContractEvent { + oneof type { + ContractUpdateEvent update = 1; // Contract update event } } @@ -1608,6 +1663,47 @@ message GetActiveGroupActionsResponse { ResponseMetadata metadata = 3; } oneof version { - GetActiveGroupActionsResponseV0 v0 = 1; + GetGroupActionsResponseV0 v0 = 1; + } +} + + +message GetGroupActionSignersRequest { + enum ActionStatus { + ACTIVE = 0; // Request the active actions + CLOSED = 1; // Request the closed actions + } + + message GetGroupActionSignersRequestV0 { + bytes contract_id = 1; + uint32 group_contract_position = 2; + ActionStatus status = 3; + bytes action_id = 4; + bool prove = 5; + } + oneof version { + GetGroupActionSignersRequestV0 v0 = 1; + } +} + +message GetGroupActionSignersResponse { + message GetGroupActionSignersResponseV0 { + message GroupActionSigner { + bytes signer_id = 1; + uint32 power = 2; + } + message GroupActionSigners { + repeated GroupActionSigner signers = 1; + } + + oneof result { + GroupActionSigners group_action_signers = 1; + Proof proof = 2; + } + ResponseMetadata metadata = 3; + } + + oneof version { + GetGroupActionSignersResponseV0 v0 = 1; } } \ No newline at end of file diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs index 83fde97a8e3..cc1de8cb817 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs @@ -1,3 +1,4 @@ +use crate::balances::credits::TokenAmount; use crate::data_contract::associated_token::token_configuration::v0::TokenConfigurationConventionV0; use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use crate::data_contract::change_control_rules::ChangeControlRules; @@ -13,13 +14,13 @@ pub trait TokenConfigurationV0Getters { fn conventions_mut(&mut self) -> &mut TokenConfigurationConventionV0; /// Returns the base supply. - fn base_supply(&self) -> u64; + fn base_supply(&self) -> TokenAmount; /// Returns the base supply. fn keeps_history(&self) -> bool; fn start_as_paused(&self) -> bool; /// Returns the maximum supply. - fn max_supply(&self) -> Option; + fn max_supply(&self) -> Option; /// Returns the max supply change rules. fn max_supply_change_rules(&self) -> &ChangeControlRules; @@ -63,10 +64,10 @@ pub trait TokenConfigurationV0Setters { fn set_conventions(&mut self, conventions: TokenConfigurationConventionV0); /// Sets the base supply. - fn set_base_supply(&mut self, base_supply: u64); + fn set_base_supply(&mut self, base_supply: TokenAmount); /// Sets the maximum supply. - fn set_max_supply(&mut self, max_supply: Option); + fn set_max_supply(&mut self, max_supply: Option); /// Sets the max supply change rules. fn set_max_supply_change_rules(&mut self, rules: ChangeControlRules); diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs index d0115ead551..60312bd445a 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs @@ -1,3 +1,4 @@ +use crate::balances::credits::TokenAmount; use crate::data_contract::associated_token::token_configuration::accessors::v0::{ TokenConfigurationV0Getters, TokenConfigurationV0Setters, }; @@ -22,7 +23,7 @@ impl TokenConfigurationV0Getters for TokenConfigurationV0 { } /// Returns the base supply. - fn base_supply(&self) -> u64 { + fn base_supply(&self) -> TokenAmount { self.base_supply } @@ -37,7 +38,7 @@ impl TokenConfigurationV0Getters for TokenConfigurationV0 { } /// Returns the maximum supply. - fn max_supply(&self) -> Option { + fn max_supply(&self) -> Option { self.max_supply } @@ -115,12 +116,12 @@ impl TokenConfigurationV0Setters for TokenConfigurationV0 { } /// Sets the base supply. - fn set_base_supply(&mut self, base_supply: u64) { + fn set_base_supply(&mut self, base_supply: TokenAmount) { self.base_supply = base_supply; } /// Sets the maximum supply. - fn set_max_supply(&mut self, max_supply: Option) { + fn set_max_supply(&mut self, max_supply: Option) { self.max_supply = max_supply; } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs index 4aed4bdc11f..a192c4ab431 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs @@ -1,5 +1,6 @@ mod accessors; +use crate::balances::credits::TokenAmount; use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use crate::data_contract::change_control_rules::v0::ChangeControlRulesV0; use crate::data_contract::change_control_rules::ChangeControlRules; @@ -37,10 +38,10 @@ fn default_decimals() -> u16 { pub struct TokenConfigurationV0 { pub conventions: TokenConfigurationConventionV0, /// The supply at the creation of the token - pub base_supply: u64, + pub base_supply: TokenAmount, /// The maximum supply the token can ever have #[serde(default)] - pub max_supply: Option, + pub max_supply: Option, /// Do we keep history, default is true. #[serde(default = "default_keeps_history")] pub keeps_history: bool, @@ -217,4 +218,9 @@ impl TokenConfigurationV0 { main_control_group_can_be_modified: AuthorizedActionTakers::NoOne, } } + + pub fn with_base_supply(mut self, base_supply: TokenAmount) -> Self { + self.base_supply = base_supply; + self + } } diff --git a/packages/rs-dpp/src/group/group_action_status.rs b/packages/rs-dpp/src/group/group_action_status.rs new file mode 100644 index 00000000000..95a7e54dc11 --- /dev/null +++ b/packages/rs-dpp/src/group/group_action_status.rs @@ -0,0 +1,29 @@ +use anyhow::bail; + +#[derive(Debug, PartialEq, PartialOrd, Clone, Eq)] +pub enum GroupActionStatus { + ActionActive, + ActionClosed, +} + +impl TryFrom for GroupActionStatus { + type Error = anyhow::Error; + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::ActionActive), + 1 => Ok(Self::ActionClosed), + value => bail!("unrecognized action status: {}", value), + } + } +} + +impl TryFrom for GroupActionStatus { + type Error = anyhow::Error; + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(Self::ActionActive), + 1 => Ok(Self::ActionClosed), + value => bail!("unrecognized action status: {}", value), + } + } +} diff --git a/packages/rs-dpp/src/group/mod.rs b/packages/rs-dpp/src/group/mod.rs index 1915d41c212..aba7606668e 100644 --- a/packages/rs-dpp/src/group/mod.rs +++ b/packages/rs-dpp/src/group/mod.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; pub mod action_event; pub mod group_action; +pub mod group_action_status; + #[derive(Debug, Clone, Copy, Encode, Decode, PartialEq)] pub enum GroupStateTransitionInfoStatus { GroupStateTransitionInfoProposer(GroupContractPosition), 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 4f65b489782..a0537bbd1bd 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 @@ -21,10 +21,7 @@ use drive::drive::identity::withdrawals::paths::{ get_withdrawal_root_path, WITHDRAWAL_TRANSACTIONS_BROADCASTED_KEY, WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY, }; -use drive::drive::prefunded_specialized_balances::{ - prefunded_specialized_balances_for_voting_path, - prefunded_specialized_balances_for_voting_path_vec, -}; +use drive::drive::prefunded_specialized_balances::prefunded_specialized_balances_for_voting_path_vec; use drive::drive::system::misc_path; use drive::drive::tokens::paths::{ tokens_root_path, TOKEN_BALANCES_KEY, TOKEN_IDENTITY_INFO_KEY, TOKEN_STATUS_INFO_KEY, diff --git a/packages/rs-drive-abci/src/query/group_queries/group_action_signers/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_action_signers/mod.rs new file mode 100644 index 00000000000..00d22106981 --- /dev/null +++ b/packages/rs-drive-abci/src/query/group_queries/group_action_signers/mod.rs @@ -0,0 +1,62 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_group_action_signers_request::Version as RequestVersion; +use dapi_grpc::platform::v0::get_group_action_signers_response::Version as ResponseVersion; +use dapi_grpc::platform::v0::{GetGroupActionSignersRequest, GetGroupActionSignersResponse}; +use dpp::version::PlatformVersion; +mod v0; + +impl Platform { + /// Querying of group action signers + pub fn query_group_action_signers( + &self, + GetGroupActionSignersRequest { version }: GetGroupActionSignersRequest, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let Some(version) = version else { + return Ok(QueryValidationResult::new_with_error( + QueryError::DecodingError( + "could not decode active group action signers query".to_string(), + ), + )); + }; + + let feature_version_bounds = &platform_version + .drive_abci + .query + .group_queries + .group_action_signers; + + let feature_version = match &version { + RequestVersion::V0(_) => 0, + }; + if !feature_version_bounds.check_version(feature_version) { + return Ok(QueryValidationResult::new_with_error( + QueryError::UnsupportedQueryVersion( + "group_action_signers".to_string(), + feature_version_bounds.min_version, + feature_version_bounds.max_version, + platform_version.protocol_version, + feature_version, + ), + )); + } + + match version { + RequestVersion::V0(request_v0) => { + let result = self.query_group_action_signers_v0( + request_v0, + platform_state, + platform_version, + )?; + Ok(result.map(|response_v0| GetGroupActionSignersResponse { + version: Some(ResponseVersion::V0(response_v0)), + })) + } + } + } +} diff --git a/packages/rs-drive-abci/src/query/group_queries/group_action_signers/v0/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_action_signers/v0/mod.rs new file mode 100644 index 00000000000..ed1ca8c424b --- /dev/null +++ b/packages/rs-drive-abci/src/query/group_queries/group_action_signers/v0/mod.rs @@ -0,0 +1,110 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_group_action_signers_request::GetGroupActionSignersRequestV0; +use dapi_grpc::platform::v0::get_group_action_signers_response::{ + get_group_action_signers_response_v0, GetGroupActionSignersResponseV0, +}; +use dapi_grpc::platform::v0::get_group_action_signers_response::get_group_action_signers_response_v0::{GroupActionSigner, GroupActionSigners}; +use dpp::check_validation_result_with_data; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::validation::ValidationResult; +use dpp::version::PlatformVersion; +use drive::error::query::QuerySyntaxError; + +impl Platform { + pub(super) fn query_group_action_signers_v0( + &self, + GetGroupActionSignersRequestV0 { + contract_id, + group_contract_position, + status, + action_id, + prove, + }: GetGroupActionSignersRequestV0, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let config = &self.config.drive; + let contract_id: Identifier = + check_validation_result_with_data!(contract_id.try_into().map_err(|_| { + QueryError::InvalidArgument( + "contract id must be a valid identifier (32 bytes long)".to_string(), + ) + })); + + let action_id: Identifier = + check_validation_result_with_data!(action_id.try_into().map_err(|_| { + QueryError::InvalidArgument( + "action id must be a valid identifier (32 bytes long)".to_string(), + ) + })); + + if group_contract_position > u16::MAX as u32 { + return Ok(QueryValidationResult::new_with_error(QueryError::Query( + QuerySyntaxError::InvalidParameter(format!( + "group contract position {} can not be over u16::MAX", + group_contract_position + )), + ))); + } + + let group_status: GroupActionStatus = + check_validation_result_with_data!(status.try_into().map_err(|_| { + QueryError::InvalidArgument( + "group action status must be Active or Closed".to_string(), + ) + })); + + let response = if prove { + let proof = check_validation_result_with_data!(self.drive.prove_action_signers( + contract_id, + group_contract_position as GroupContractPosition, + group_status, + action_id, + None, + platform_version, + )); + + GetGroupActionSignersResponseV0 { + result: Some(get_group_action_signers_response_v0::Result::Proof( + self.response_proof_v0(platform_state, proof), + )), + metadata: Some(self.response_metadata_v0(platform_state)), + } + } else { + let group_action_signers = self + .drive + .fetch_action_signers( + contract_id, + group_contract_position as GroupContractPosition, + group_status, + action_id, + None, + platform_version, + )? + .into_iter() + .map(|(signer_id, power)| GroupActionSigner { + signer_id: signer_id.to_vec(), + power, + }) + .collect(); + GetGroupActionSignersResponseV0 { + result: Some( + get_group_action_signers_response_v0::Result::GroupActionSigners( + GroupActionSigners { + signers: group_action_signers, + }, + ), + ), + metadata: Some(self.response_metadata_v0(platform_state)), + } + }; + + Ok(QueryValidationResult::new_with_data(response)) + } +} diff --git a/packages/rs-drive-abci/src/query/group_queries/group_actions/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_actions/mod.rs new file mode 100644 index 00000000000..51425507b78 --- /dev/null +++ b/packages/rs-drive-abci/src/query/group_queries/group_actions/mod.rs @@ -0,0 +1,59 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_group_actions_request::Version as RequestVersion; +use dapi_grpc::platform::v0::get_group_actions_response::Version as ResponseVersion; +use dapi_grpc::platform::v0::{GetGroupActionsRequest, GetGroupActionsResponse}; +use dpp::version::PlatformVersion; +mod v0; + +impl Platform { + /// Querying of active group actions + pub fn query_group_actions( + &self, + GetGroupActionsRequest { version }: GetGroupActionsRequest, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let Some(version) = version else { + return Ok(QueryValidationResult::new_with_error( + QueryError::DecodingError( + "could not decode active group actions query".to_string(), + ), + )); + }; + + let feature_version_bounds = &platform_version + .drive_abci + .query + .group_queries + .group_actions; + + let feature_version = match &version { + RequestVersion::V0(_) => 0, + }; + if !feature_version_bounds.check_version(feature_version) { + return Ok(QueryValidationResult::new_with_error( + QueryError::UnsupportedQueryVersion( + "group_actions".to_string(), + feature_version_bounds.min_version, + feature_version_bounds.max_version, + platform_version.protocol_version, + feature_version, + ), + )); + } + + match version { + RequestVersion::V0(request_v0) => { + let result = + self.query_group_actions_v0(request_v0, platform_state, platform_version)?; + Ok(result.map(|response_v0| GetGroupActionsResponse { + version: Some(ResponseVersion::V0(response_v0)), + })) + } + } + } +} diff --git a/packages/rs-drive-abci/src/query/group_queries/group_actions/v0/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_actions/v0/mod.rs new file mode 100644 index 00000000000..ef664f87f6d --- /dev/null +++ b/packages/rs-drive-abci/src/query/group_queries/group_actions/v0/mod.rs @@ -0,0 +1,242 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_group_actions_request::GetGroupActionsRequestV0; +use dapi_grpc::platform::v0::get_group_actions_response::get_group_actions_response_v0::{ + emergency_action_event, group_action_event, token_event, BurnEvent, DestroyFrozenFundsEvent, + EmergencyActionEvent, FreezeEvent, GroupActionEntry, GroupActionEvent, GroupActions, MintEvent, + PersonalEncryptedNote, SharedEncryptedNote, TokenEvent as TokenEventResponse, TransferEvent, + UnfreezeEvent, +}; +use dapi_grpc::platform::v0::get_group_actions_response::{ + get_group_actions_response_v0, GetGroupActionsResponseV0, +}; +use dpp::check_validation_result_with_data; +use dpp::data_contract::GroupContractPosition; +use dpp::group::action_event; +use dpp::group::group_action::GroupAction; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::tokens::emergency_action::TokenEmergencyAction; +use dpp::tokens::token_event::TokenEvent; +use dpp::validation::ValidationResult; +use dpp::version::PlatformVersion; +use drive::error::query::QuerySyntaxError; + +impl Platform { + pub(super) fn query_group_actions_v0( + &self, + GetGroupActionsRequestV0 { + contract_id, + group_contract_position, + status, + start_at_action_id, + count, + prove, + }: GetGroupActionsRequestV0, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let config = &self.config.drive; + let contract_id: Identifier = + check_validation_result_with_data!(contract_id.try_into().map_err(|_| { + QueryError::InvalidArgument( + "contract id must be a valid identifier (32 bytes long)".to_string(), + ) + })); + + if group_contract_position > u16::MAX as u32 { + return Ok(QueryValidationResult::new_with_error(QueryError::Query( + QuerySyntaxError::InvalidParameter(format!( + "group contract position {} can not be over u16::MAX", + group_contract_position + )), + ))); + } + + let limit = count + .map_or(Some(config.default_query_limit), |limit_value| { + if limit_value == 0 + || limit_value > u16::MAX as u32 + || limit_value as u16 > config.default_query_limit + { + None + } else { + Some(limit_value as u16) + } + }) + .ok_or(drive::error::Error::Query(QuerySyntaxError::InvalidLimit( + format!("limit greater than max limit {}", config.max_query_limit), + )))?; + + let maybe_start_at_action_id = match start_at_action_id { + None => None, + Some(start_at_action_id) => { + let start_at_action_id_identifier: Identifier = + check_validation_result_with_data!(start_at_action_id + .start_action_id + .try_into() + .map_err(|_| { + QueryError::InvalidArgument( + "start at action id must be a valid identifier (32 bytes long)" + .to_string(), + ) + })); + Some(( + start_at_action_id_identifier, + start_at_action_id.start_action_id_included, + )) + } + }; + + let group_status: GroupActionStatus = + check_validation_result_with_data!(status.try_into().map_err(|_| { + QueryError::InvalidArgument( + "group action status must be Active or Closed".to_string(), + ) + })); + + let response = if prove { + let proof = check_validation_result_with_data!(self.drive.prove_action_infos( + contract_id, + group_contract_position as GroupContractPosition, + group_status, + maybe_start_at_action_id, + Some(limit), + None, + platform_version, + )); + + GetGroupActionsResponseV0 { + result: Some(get_group_actions_response_v0::Result::Proof( + self.response_proof_v0(platform_state, proof), + )), + metadata: Some(self.response_metadata_v0(platform_state)), + } + } else { + let group_actions = self + .drive + .fetch_action_infos( + contract_id, + group_contract_position as GroupContractPosition, + group_status, + maybe_start_at_action_id, + Some(limit), + None, + platform_version, + )? + .into_iter() + .map(|(action_id, group_action)| { + // Convert the fetched GroupAction into a GroupActionEntry + GroupActionEntry { + action_id: action_id.to_vec(), + event: Some(GroupActionEvent { + event_type: Some(match group_action { + GroupAction::V0(group_action_v0) => match group_action_v0.event { + action_event::GroupActionEvent::TokenEvent(token_event) => match token_event { + TokenEvent::Mint(amount, recipient_id, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::Mint(MintEvent { + amount: amount as u64, + recipient_id: recipient_id.to_vec(), + public_note, + })), + }) + } + TokenEvent::Burn(amount, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::Burn(BurnEvent { + amount: amount as u64, + public_note, + })), + }) + } + TokenEvent::Freeze(frozen_id, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::Freeze(FreezeEvent { + frozen_id: frozen_id.to_vec(), + public_note, + })), + }) + } + TokenEvent::Unfreeze(frozen_id, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::Unfreeze(UnfreezeEvent { + frozen_id: frozen_id.to_vec(), + public_note, + })), + }) + } + TokenEvent::DestroyFrozenFunds(frozen_id, amount, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::DestroyFrozenFunds( + DestroyFrozenFundsEvent { + frozen_id: frozen_id.to_vec(), + amount, + public_note, + }, + )), + }) + } + TokenEvent::Transfer( + recipient_id, + public_note, + shared_encrypted_note, + personal_encrypted_note, + amount, + ) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::Transfer(TransferEvent { + recipient_id: recipient_id.to_vec(), + public_note, + shared_encrypted_note: shared_encrypted_note + .map(|(sender_key_index, recipient_key_index, note)| { + SharedEncryptedNote { + sender_key_index, + recipient_key_index, + encrypted_data: note, + } + }), + personal_encrypted_note: personal_encrypted_note + .map(|(root_encryption_key_index, derivation_encryption_key_index, note)| { + PersonalEncryptedNote { + root_encryption_key_index, + derivation_encryption_key_index, + encrypted_data: note, + } + }), + amount: amount as u64, + })), + }) + }, + TokenEvent::EmergencyAction(action, public_note) => { + group_action_event::EventType::TokenEvent(TokenEventResponse { + r#type: Some(token_event::Type::EmergencyAction(EmergencyActionEvent { + action_type: match action { + TokenEmergencyAction::Pause => emergency_action_event::ActionType::Pause.into(), + TokenEmergencyAction::Resume => emergency_action_event::ActionType::Resume.into(), + }, + public_note, + })), + }) + } + }, + }, + }), + }), + } + }) + .collect(); + GetGroupActionsResponseV0 { + result: Some(get_group_actions_response_v0::Result::GroupActions( + GroupActions { group_actions }, + )), + metadata: Some(self.response_metadata_v0(platform_state)), + } + }; + + Ok(QueryValidationResult::new_with_data(response)) + } +} diff --git a/packages/rs-drive-abci/src/query/group_queries/group_info/v0/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_info/v0/mod.rs index a5175ac3cff..84c55bfc609 100644 --- a/packages/rs-drive-abci/src/query/group_queries/group_info/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/group_queries/group_info/v0/mod.rs @@ -31,7 +31,7 @@ impl Platform { let contract_id: Identifier = check_validation_result_with_data!(contract_id.try_into().map_err(|_| { QueryError::InvalidArgument( - "token_id must be a valid identifier (32 bytes long)".to_string(), + "contract id must be a valid identifier (32 bytes long)".to_string(), ) })); diff --git a/packages/rs-drive-abci/src/query/group_queries/group_infos/v0/mod.rs b/packages/rs-drive-abci/src/query/group_queries/group_infos/v0/mod.rs index 88787fef920..e9dc27aab46 100644 --- a/packages/rs-drive-abci/src/query/group_queries/group_infos/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/group_queries/group_infos/v0/mod.rs @@ -33,7 +33,7 @@ impl Platform { let contract_id: Identifier = check_validation_result_with_data!(contract_id.try_into().map_err(|_| { QueryError::InvalidArgument( - "token_id must be a valid identifier (32 bytes long)".to_string(), + "contract id must be a valid identifier (32 bytes long)".to_string(), ) })); diff --git a/packages/rs-drive-abci/src/query/group_queries/mod.rs b/packages/rs-drive-abci/src/query/group_queries/mod.rs index b9ae74339a8..d720872115a 100644 --- a/packages/rs-drive-abci/src/query/group_queries/mod.rs +++ b/packages/rs-drive-abci/src/query/group_queries/mod.rs @@ -1,2 +1,4 @@ +mod group_action_signers; +mod group_actions; mod group_info; mod group_infos; diff --git a/packages/rs-drive-abci/src/query/service.rs b/packages/rs-drive-abci/src/query/service.rs index 7ac860eac19..24f14039a58 100644 --- a/packages/rs-drive-abci/src/query/service.rs +++ b/packages/rs-drive-abci/src/query/service.rs @@ -10,8 +10,7 @@ use crate::utils::spawn_blocking_task_with_name_if_supported; use async_trait::async_trait; use dapi_grpc::platform::v0::platform_server::Platform as PlatformService; use dapi_grpc::platform::v0::{ - BroadcastStateTransitionRequest, BroadcastStateTransitionResponse, - GetActiveGroupActionsRequest, GetActiveGroupActionsResponse, GetConsensusParamsRequest, + BroadcastStateTransitionRequest, BroadcastStateTransitionResponse, GetConsensusParamsRequest, GetConsensusParamsResponse, GetContestedResourceIdentityVotesRequest, GetContestedResourceIdentityVotesResponse, GetContestedResourceVoteStateRequest, GetContestedResourceVoteStateResponse, GetContestedResourceVotersForIdentityRequest, @@ -21,9 +20,10 @@ use dapi_grpc::platform::v0::{ GetDataContractResponse, GetDataContractsRequest, GetDataContractsResponse, GetDocumentsRequest, GetDocumentsResponse, GetEpochsInfoRequest, GetEpochsInfoResponse, GetEvonodesProposedEpochBlocksByIdsRequest, GetEvonodesProposedEpochBlocksByRangeRequest, - GetEvonodesProposedEpochBlocksResponse, GetGroupInfoRequest, GetGroupInfoResponse, - GetGroupInfosRequest, GetGroupInfosResponse, GetIdentitiesBalancesRequest, - GetIdentitiesBalancesResponse, GetIdentitiesContractKeysRequest, + GetEvonodesProposedEpochBlocksResponse, GetGroupActionSignersRequest, + GetGroupActionSignersResponse, GetGroupActionsRequest, GetGroupActionsResponse, + GetGroupInfoRequest, GetGroupInfoResponse, GetGroupInfosRequest, GetGroupInfosResponse, + GetIdentitiesBalancesRequest, GetIdentitiesBalancesResponse, GetIdentitiesContractKeysRequest, GetIdentitiesContractKeysResponse, GetIdentitiesTokenBalancesRequest, GetIdentitiesTokenBalancesResponse, GetIdentitiesTokenInfosRequest, GetIdentitiesTokenInfosResponse, GetIdentityBalanceAndRevisionRequest, @@ -712,17 +712,29 @@ impl PlatformService for QueryService { .await } - // async fn get_active_group_actions( - // &self, - // request: Request, - // ) -> Result, Status> { - // self.handle_blocking_query( - // request, - // Platform::::query_active_group_actions, - // "get_active_group_actions", - // ) - // .await - // } + async fn get_group_actions( + &self, + request: Request, + ) -> Result, Status> { + self.handle_blocking_query( + request, + Platform::::query_group_actions, + "get_group_actions", + ) + .await + } + + async fn get_group_action_signers( + &self, + request: Request, + ) -> Result, Status> { + self.handle_blocking_query( + request, + Platform::::query_group_action_signers, + "get_group_action_signers", + ) + .await + } } fn query_error_into_status(error: QueryError) -> Status { diff --git a/packages/rs-drive/src/drive/group/estimated_costs/for_add_group_action/v0/mod.rs b/packages/rs-drive/src/drive/group/estimated_costs/for_add_group_action/v0/mod.rs index 5923c6a14df..6a9a63b68e3 100644 --- a/packages/rs-drive/src/drive/group/estimated_costs/for_add_group_action/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/estimated_costs/for_add_group_action/v0/mod.rs @@ -1,8 +1,8 @@ use crate::drive::Drive; use crate::drive::group::paths::{ - group_action_path, group_action_root_path, group_action_signers_path, group_contract_path, - group_path, group_root_path, + group_action_path, group_action_signers_path, group_active_action_root_path, + group_contract_path, group_path, group_root_path, }; use crate::util::type_constants::DEFAULT_HASH_SIZE_U8; use dpp::data_contract::GroupContractPosition; @@ -127,7 +127,7 @@ impl Drive { ); estimated_costs_only_with_layer_info.insert( - KeyInfoPath::from_known_path(group_action_root_path( + KeyInfoPath::from_known_path(group_active_action_root_path( contract_id.as_slice(), &group_contract_position.to_be_bytes(), )), diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/mod.rs new file mode 100644 index 00000000000..6b02d74e94a --- /dev/null +++ b/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/mod.rs @@ -0,0 +1,128 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action::GroupAction; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; +use std::collections::BTreeMap; + +mod v0; + +impl Drive { + /// Fetches the `GroupAction` for the given action ID and group contract position. + /// + /// This function queries the GroveDB to fetch `GroupAction`s associated with a specific + /// group contract position and `action_status`. The method selects the appropriate version of + /// `fetch_action_infos` based on the `platform_version` provided. + /// + /// # Parameters + /// - `contract_id`: The identifier of the contract that the action belongs to. + /// - `group_contract_position`: The position of the group contract in the data structure. + /// - `action_status`: The status of the group actions to fetch. + /// - `start_action_id`: An optional starting action ID and inclusion flag. + /// - `limit`: An optional limit on the number of group actions to fetch. + /// - `transaction`: The transaction argument used for the query. + /// - `platform_version`: The version of the platform that determines the correct method version. + /// + /// # Returns + /// - `Ok(GroupAction)`: The `GroupAction` for the specified action ID and contract position. + /// - `Err(Error)`: If an error occurs, a generic error is returned. + /// + /// # Errors + /// - `DriveError::UnknownVersionMismatch`: If the `platform_version` does not match any known versions. + pub fn fetch_action_infos( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .fetch + .fetch_action_infos + { + 0 => self.fetch_action_infos_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_action_infos".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Fetches the `GroupAction` and adds corresponding operations to the drive for the given action ID and group contract position. + /// + /// This function is similar to `fetch_action_infos` but also adds operations to the drive for state changes or queries. + /// It fetches `GroupAction`s based on the specified `action_status`. Additionally, it supports cost estimation by interacting with the layer information if provided. + /// + /// # Parameters + /// - `contract_id`: The identifier of the contract that the action belongs to. + /// - `group_contract_position`: The position of the group contract in the data structure. + /// - `action_status`: The status of the group actions to fetch. + /// - `start_action_id`: An optional starting action ID and inclusion flag. + /// - `limit`: An optional limit on the number of group actions to fetch. + /// - `transaction`: The transaction argument used for the query. + /// - `drive_operations`: A mutable reference to a vector that stores low-level drive operations. + /// - `platform_version`: The version of the platform that determines the correct method version. + /// + /// # Returns + /// - `Ok(GroupAction)`: The `GroupAction` for the specified action ID and contract position, along with any added operations. + /// - `Err(Error)`: If an error occurs, a generic error is returned. + /// + /// # Errors + /// - `DriveError::UnknownVersionMismatch`: If the `platform_version` does not match any known versions. + pub(crate) fn fetch_action_infos_and_add_operations( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .fetch + .fetch_action_infos + { + 0 => self.fetch_action_infos_and_add_operations_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_action_infos_and_add_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/v0/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/v0/mod.rs new file mode 100644 index 00000000000..d8086476af9 --- /dev/null +++ b/packages/rs-drive/src/drive/group/fetch/fetch_action_infos/v0/mod.rs @@ -0,0 +1,86 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action::GroupAction; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use dpp::serialization::PlatformDeserializable; +use dpp::version::PlatformVersion; +use grovedb::query_result_type::QueryResultType; +use grovedb::Element::Item; +use grovedb::TransactionArg; +use std::collections::BTreeMap; + +impl Drive { + pub(super) fn fetch_action_infos_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.fetch_action_infos_and_add_operations_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + &mut vec![], + platform_version, + ) + } + + pub(super) fn fetch_action_infos_and_add_operations_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path_query = Drive::group_action_infos_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + start_action_id.map(|(s, i)| (s.to_buffer(), i)), + limit, + ); + + self.grove_get_raw_path_query( + &path_query, + transaction, + QueryResultType::QueryPathKeyElementTrioResultType, + drive_operations, + &platform_version.drive, + )? + .0 + .to_path_key_elements() + .into_iter() + .map(|(path, _, element)| { + let Some(last_path_component) = path.last() else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "we should always have a path not be empty".to_string(), + ))); + }; + let action_id = Identifier::from_bytes(last_path_component)?; + + match element { + Item(value, ..) => Ok((action_id, GroupAction::deserialize_from_bytes(&value)?)), + _ => Err(Error::Drive(DriveError::CorruptedDriveState( + "element should be an item representing the group action".to_string(), + ))), + } + }) + .collect() + } +} diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/mod.rs new file mode 100644 index 00000000000..0556350c6e3 --- /dev/null +++ b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/mod.rs @@ -0,0 +1,133 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::group::GroupMemberPower; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; +use std::collections::BTreeMap; + +mod v0; + +impl Drive { + /// Fetches the signers and their respective powers for a specific action in a group. + /// + /// This method retrieves the list of signers for an action associated with a given contract, + /// group, and action status. It selects the appropriate version of the method based on the + /// provided platform version. + /// + /// # Arguments + /// + /// * `contract_id` - The identifier of the contract associated with the action. + /// * `group_contract_position` - The position of the group within the contract. + /// * `action_status` - The status of the action (e.g., active or closed). + /// * `action_id` - The identifier of the action for which to fetch signers. + /// * `transaction` - An optional transaction argument for database operations. + /// * `platform_version` - The platform version to determine which method version to call. + /// + /// # Returns + /// + /// A `Result` containing a `BTreeMap` where the keys are the signer identifiers and the values + /// are their respective powers, or an `Error` if the operation fails. + /// + /// # Errors + /// + /// This method returns an `Error` if: + /// * The platform version is unknown. + /// * An internal issue occurs during the fetching process. + pub fn fetch_action_signers( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .fetch + .fetch_action_signers + { + 0 => self.fetch_action_signers_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_action_signers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Fetches the signers and their respective powers for a specific action and adds the + /// associated operations for database queries. + /// + /// This method extends the functionality of `fetch_action_signers` by including additional + /// database operations that can be executed as part of a larger transaction. The appropriate + /// version of the method is selected based on the provided platform version. + /// + /// # Arguments + /// + /// * `contract_id` - The identifier of the contract associated with the action. + /// * `group_contract_position` - The position of the group within the contract. + /// * `action_status` - The status of the action (e.g., active or closed). + /// * `action_id` - The identifier of the action for which to fetch signers. + /// * `transaction` - An optional transaction argument for database operations. + /// * `drive_operations` - A mutable vector to which low-level database operations will be added. + /// * `platform_version` - The platform version to determine which method version to call. + /// + /// # Returns + /// + /// A `Result` containing a `BTreeMap` where the keys are the signer identifiers and the values + /// are their respective powers, or an `Error` if the operation fails. + /// + /// # Errors + /// + /// This method returns an `Error` if: + /// * The platform version is unknown. + /// * An internal issue occurs during the fetching process. + pub(crate) fn fetch_action_signers_and_add_operations( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .fetch + .fetch_action_signers + { + 0 => self.fetch_action_signers_and_add_operations_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_action_signers_and_add_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs new file mode 100644 index 00000000000..c0a64a9b632 --- /dev/null +++ b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs @@ -0,0 +1,78 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::group::GroupMemberPower; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::version::PlatformVersion; +use grovedb::query_result_type::QueryResultType; +use grovedb::Element::SumItem; +use grovedb::TransactionArg; +use std::collections::BTreeMap; + +impl Drive { + pub(super) fn fetch_action_signers_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.fetch_action_signers_and_add_operations_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + &mut vec![], + platform_version, + ) + } + + pub(super) fn fetch_action_signers_and_add_operations_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path_query = Drive::group_action_signers_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + action_id.to_buffer(), + ); + + self.grove_get_raw_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + drive_operations, + &platform_version.drive, + )? + .0 + .to_key_elements_btree_map() + .into_iter() + .map(|(key, element)| match element { + SumItem(value, ..) => Ok(( + key.try_into()?, + value.try_into().map_err(|e| { + Error::Drive(DriveError::CorruptedDriveState( + "signed power should be encodable on a u32 integer".to_string(), + )) + })?, + )), + _ => Err(Error::Drive(DriveError::CorruptedDriveState( + "element should be a sum item representing member signed power".to_string(), + ))), + }) + .collect() + } +} diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/mod.rs similarity index 91% rename from packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/mod.rs rename to packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/mod.rs index 99c38f711eb..7f99fad7f5a 100644 --- a/packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/mod.rs +++ b/packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/mod.rs @@ -32,7 +32,7 @@ impl Drive { /// /// # Errors /// - `DriveError::UnknownVersionMismatch`: If the `platform_version` does not match any known versions. - pub fn fetch_action_id_info( + pub fn fetch_active_action_info( &self, contract_id: Identifier, group_contract_position: GroupContractPosition, @@ -45,9 +45,9 @@ impl Drive { .methods .group .fetch - .fetch_action_id_info + .fetch_active_action_info { - 0 => self.fetch_action_id_info_v0( + 0 => self.fetch_active_action_info_v0( contract_id, group_contract_position, action_id, @@ -55,7 +55,7 @@ impl Drive { platform_version, ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "fetch_action_id_info".to_string(), + method: "fetch_active_action_info".to_string(), known_versions: vec![0], received: version, })), @@ -83,7 +83,7 @@ impl Drive { /// /// # Errors /// - `DriveError::UnknownVersionMismatch`: If the `platform_version` does not match any known versions. - pub(crate) fn fetch_action_id_info_and_add_operations( + pub(crate) fn fetch_active_action_info_and_add_operations( &self, contract_id: Identifier, group_contract_position: GroupContractPosition, @@ -100,9 +100,9 @@ impl Drive { .methods .group .fetch - .fetch_action_id_info + .fetch_active_action_info { - 0 => self.fetch_action_id_info_and_add_operations_v0( + 0 => self.fetch_active_action_info_and_add_operations_v0( contract_id, group_contract_position, action_id, @@ -112,7 +112,7 @@ impl Drive { platform_version, ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "fetch_action_id_signers_and_add_operations".to_string(), + method: "fetch_active_action_info_and_add_operations".to_string(), known_versions: vec![0], received: version, })), diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/v0/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/v0/mod.rs similarity index 96% rename from packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/v0/mod.rs rename to packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/v0/mod.rs index 723e7312cd4..1e807d289e4 100644 --- a/packages/rs-drive/src/drive/group/fetch/fetch_action_id_info/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/fetch/fetch_active_action_info/v0/mod.rs @@ -15,7 +15,7 @@ use grovedb::batch::KeyInfoPath; use grovedb::{EstimatedLayerInformation, TransactionArg, TreeType}; impl Drive { - pub(super) fn fetch_action_id_info_v0( + pub(super) fn fetch_active_action_info_v0( &self, contract_id: Identifier, group_contract_position: GroupContractPosition, @@ -45,7 +45,7 @@ impl Drive { Ok(group_action) } - pub(super) fn fetch_action_id_info_and_add_operations_v0( + pub(super) fn fetch_active_action_info_and_add_operations_v0( &self, contract_id: Identifier, group_contract_position: GroupContractPosition, diff --git a/packages/rs-drive/src/drive/group/fetch/mod.rs b/packages/rs-drive/src/drive/group/fetch/mod.rs index 8df377a7a90..32186f8a6e2 100644 --- a/packages/rs-drive/src/drive/group/fetch/mod.rs +++ b/packages/rs-drive/src/drive/group/fetch/mod.rs @@ -1,7 +1,9 @@ mod fetch_action_id_has_signer; -mod fetch_action_id_info; mod fetch_action_id_info_keep_serialized; mod fetch_action_id_signers_power; +mod fetch_action_infos; +mod fetch_action_signers; +mod fetch_active_action_info; mod fetch_group_info; mod fetch_group_infos; mod queries; diff --git a/packages/rs-drive/src/drive/group/fetch/queries.rs b/packages/rs-drive/src/drive/group/fetch/queries.rs index 7dbd1223c50..e78e6cca882 100644 --- a/packages/rs-drive/src/drive/group/fetch/queries.rs +++ b/packages/rs-drive/src/drive/group/fetch/queries.rs @@ -1,7 +1,11 @@ -use crate::drive::group::paths::group_contract_path_vec; +use crate::drive::group::paths::{ + group_contract_path_vec, group_path_vec, ACTION_INFO_KEY, ACTION_SIGNERS_KEY, + GROUP_ACTIVE_ACTIONS_KEY, GROUP_CLOSED_ACTIONS_KEY, GROUP_INFO_KEY, +}; use crate::drive::Drive; use crate::query::{Query, QueryItem}; use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; use grovedb::{PathQuery, SizedQuery}; use std::ops::RangeFull; @@ -11,11 +15,10 @@ impl Drive { contract_id: [u8; 32], group_contract_position: GroupContractPosition, ) -> PathQuery { - let group_contract_path = group_contract_path_vec(&contract_id); - PathQuery::new_single_key( - group_contract_path, - group_contract_position.to_be_bytes().to_vec(), - ) + let group_path = group_path_vec(&contract_id, group_contract_position); + let mut path_query = PathQuery::new_single_key(group_path, GROUP_INFO_KEY.to_vec()); + path_query.query.limit = Some(1); + path_query } /// The query for the group infos inside a contract. @@ -35,6 +38,8 @@ impl Drive { } else { query.insert_item(QueryItem::RangeFull(RangeFull)) } + + query.set_subquery_key(GROUP_INFO_KEY.to_vec()); PathQuery { path: group_contract_path, query: SizedQuery { @@ -44,4 +49,74 @@ impl Drive { }, } } + + /// Gets the active group actions + pub fn group_action_infos_query( + contract_id: [u8; 32], + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_at: Option<([u8; 32], bool)>, + limit: Option, + ) -> PathQuery { + let mut group_actions_path = group_path_vec(&contract_id, group_contract_position); + match action_status { + GroupActionStatus::ActionActive => { + group_actions_path.push(GROUP_ACTIVE_ACTIONS_KEY.to_vec()) + } + GroupActionStatus::ActionClosed => { + group_actions_path.push(GROUP_CLOSED_ACTIONS_KEY.to_vec()) + } + } + let mut query = Query::new_with_direction(true); + if let Some((start_at, start_at_included)) = start_at { + if start_at_included { + query.insert_item(QueryItem::RangeFrom(start_at.to_vec()..)) + } else { + query.insert_item(QueryItem::RangeAfter(start_at.to_vec()..)) + } + } else { + query.insert_item(QueryItem::RangeFull(RangeFull)) + } + + query.set_subquery_key(ACTION_INFO_KEY.to_vec()); + + PathQuery { + path: group_actions_path, + query: SizedQuery { + query, + limit, + offset: None, + }, + } + } + + /// Gets the action signers query + pub fn group_action_signers_query( + contract_id: [u8; 32], + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: [u8; 32], + ) -> PathQuery { + let mut group_actions_path = group_path_vec(&contract_id, group_contract_position); + match action_status { + GroupActionStatus::ActionActive => { + group_actions_path.push(GROUP_ACTIVE_ACTIONS_KEY.to_vec()) + } + GroupActionStatus::ActionClosed => { + group_actions_path.push(GROUP_CLOSED_ACTIONS_KEY.to_vec()) + } + } + group_actions_path.push(action_id.to_vec()); + group_actions_path.push(ACTION_SIGNERS_KEY.to_vec()); + let query = Query::new_range_full(); + + PathQuery { + path: group_actions_path, + query: SizedQuery { + query, + limit: None, + offset: None, + }, + } + } } diff --git a/packages/rs-drive/src/drive/group/insert/add_group_action/v0/mod.rs b/packages/rs-drive/src/drive/group/insert/add_group_action/v0/mod.rs index f45e9ec03c7..f7d1dea6d4e 100644 --- a/packages/rs-drive/src/drive/group/insert/add_group_action/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/insert/add_group_action/v0/mod.rs @@ -1,6 +1,6 @@ use crate::drive::group::paths::{ - group_action_path, group_action_root_path, group_action_signers_path_vec, ACTION_INFO_KEY, - ACTION_SIGNERS_KEY, + group_action_path, group_action_signers_path_vec, group_active_action_root_path, + ACTION_INFO_KEY, ACTION_SIGNERS_KEY, }; use crate::drive::Drive; use crate::error::Error; @@ -133,7 +133,7 @@ impl Drive { } let group_contract_position_bytes = group_contract_position.to_be_bytes().to_vec(); - let group_action_root_path = group_action_root_path( + let group_action_root_path = group_active_action_root_path( contract_id.as_slice(), group_contract_position_bytes.as_slice(), ); diff --git a/packages/rs-drive/src/drive/group/paths.rs b/packages/rs-drive/src/drive/group/paths.rs index 07449e982fc..b4cbe648714 100644 --- a/packages/rs-drive/src/drive/group/paths.rs +++ b/packages/rs-drive/src/drive/group/paths.rs @@ -69,7 +69,7 @@ pub fn group_path_vec( /// Group action path -pub fn group_action_root_path<'a>( +pub fn group_active_action_root_path<'a>( contract_id: &'a [u8], group_contract_position_bytes: &'a [u8], ) -> [&'a [u8]; 4] { @@ -83,7 +83,7 @@ pub fn group_action_root_path<'a>( /// Group action path vector -pub fn group_action_root_path_vec( +pub fn group_active_action_root_path_vec( contract_id: &[u8], group_contract_position: GroupContractPosition, ) -> Vec> { diff --git a/packages/rs-drive/src/drive/group/prove/mod.rs b/packages/rs-drive/src/drive/group/prove/mod.rs index c6fa82c789e..dc5360bb8c2 100644 --- a/packages/rs-drive/src/drive/group/prove/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/mod.rs @@ -1,2 +1,4 @@ +mod prove_action_infos; +mod prove_action_signers; mod prove_group_info; mod prove_group_infos; diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_infos/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_infos/mod.rs new file mode 100644 index 00000000000..f5b6cec806a --- /dev/null +++ b/packages/rs-drive/src/drive/group/prove/prove_action_infos/mod.rs @@ -0,0 +1,132 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +mod v0; +impl Drive { + /// Generates a proof of active action information within a specific group of a contract. + /// + /// This method produces a cryptographic proof that validates and retrieves active action information + /// associated with a specific group in the given contract based on the `action_status`. The proof can be limited to a subset of actions + /// based on the `start_action_id` and `limit` parameters. It supports multiple versions for backward + /// compatibility and forwards the request to the appropriate versioned implementation. + /// + /// # Arguments + /// - `contract_id`: The identifier of the contract containing the group. + /// - `group_contract_position`: The position of the group within the contract whose actions are to be proven. + /// - `action_status`: The status of the group actions to prove. + /// - `start_action_id`: An optional starting action ID, combined with a [`StartAtIncluded`] flag to specify whether + /// the start position is inclusive. + /// - `limit`: An optional limit on the number of actions to include in the proof. + /// - `transaction`: The transaction context for the operation. + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok(Vec)`: On success, returns a serialized proof as a vector of bytes. + /// - `Err(Error)`: If the operation fails, returns an [`Error`] indicating the cause of failure. + /// + /// # Errors + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - Any other errors propagated from the versioned implementation. + pub fn prove_action_infos( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .prove + .prove_action_infos + { + 0 => self.prove_action_infos_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_action_infos".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Generates a proof of active action information within a specific group of a contract and adds operations. + /// + /// This method extends the functionality of `prove_action_infos` by additionally allowing + /// operations to be added to a provided `drive_operations` list. It produces a cryptographic proof + /// of active action information associated with a specific group in the given contract based on the `action_status`, and supports + /// different versions for backward compatibility. + /// + /// # Arguments + /// - `contract_id`: The identifier of the contract containing the group. + /// - `group_contract_position`: The position of the group within the contract whose actions are to be proven. + /// - `action_status`: The status of the group actions to prove. + /// - `start_action_id`: An optional starting action ID, combined with a [`StartAtIncluded`] flag to specify whether + /// the start position is inclusive. + /// - `limit`: An optional limit on the number of actions to include in the proof. + /// - `transaction`: The transaction context for the operation. + /// - `drive_operations`: A mutable reference to a vector where additional low-level operations can be appended. + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok(Vec)`: On success, returns a serialized proof as a vector of bytes. + /// - `Err(Error)`: If the operation fails, returns an [`Error`] indicating the cause of failure. + /// + /// # Errors + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - Any other errors propagated from the versioned implementation. + pub(crate) fn prove_action_infos_operations( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .prove + .prove_action_infos + { + 0 => self.prove_action_infos_operations_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_action_infos_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs new file mode 100644 index 00000000000..1deadafda50 --- /dev/null +++ b/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs @@ -0,0 +1,536 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + pub(super) fn prove_action_infos_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.prove_action_infos_operations_v0( + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + transaction, + &mut vec![], + platform_version, + ) + } + + pub(super) fn prove_action_infos_operations_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path_query = Drive::group_action_infos_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + start_action_id.map(|(s, i)| (s.to_buffer(), i)), + limit, + ); + self.grove_get_proved_path_query( + &path_query, + transaction, + drive_operations, + &platform_version.drive, + ) + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::group::v0::GroupV0; + use dpp::data_contract::group::Group; + use dpp::data_contract::v1::DataContractV1; + use dpp::data_contract::DataContract; + use dpp::group::action_event::GroupActionEvent; + use dpp::group::group_action::v0::GroupActionV0; + use dpp::group::group_action::GroupAction; + use dpp::identifier::Identifier; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::tokens::token_event::TokenEvent; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_action_infos_with_multiple_actions() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + let identity_3 = Identity::random_identity(3, Some(9), platform_version) + .expect("expected a platform identity"); + + let identity_3_id = identity_3.id(); + + // Create a data contract with multiple tokens + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ]), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Contract and group details + let contract_id = contract.id(); + let group_contract_position = 0; + + // Create actions + let action_id_1 = Identifier::random(); + let action_id_2 = Identifier::random(); + + let action_1 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Mint(100, identity_1_id, None)), + }); + + let action_2 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Burn(50, None)), + }); + + // Add actions using `add_group_action` + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_1.clone()), + action_id_1, + identity_1_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 1"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_2.clone()), + action_id_2, + identity_2_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 2"); + + // Fetch actions directly + let fetched_actions = drive + .fetch_action_infos( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + None, + Some(10), + None, + platform_version, + ) + .expect("expected to fetch active actions"); + + assert_eq!( + fetched_actions, + BTreeMap::from([ + (action_id_1, action_1.clone()), + (action_id_2, action_2.clone()) + ]), + "unexpected fetched actions" + ); + + // Prove actions + let proof = drive + .prove_action_infos_v0( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + None, + Some(10), + None, + platform_version, + ) + .expect("should not error when proving active actions"); + + // Verify proof + let proved_actions: BTreeMap = + Drive::verify_action_infos_in_contract( + proof.as_slice(), + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + None, + Some(10), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert proved actions match fetched actions + assert_eq!(proved_actions, fetched_actions, "unexpected proved actions"); + } + + #[test] + fn should_prove_no_action_infos_for_non_existent_contract() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let non_existent_contract_id = Identifier::random(); + let group_contract_position = 0; + + // Prove actions for non-existent contract + let proof = drive + .prove_action_infos_v0( + non_existent_contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + None, + Some(10), + None, + platform_version, + ) + .expect("should not error when proving active actions for non-existent contract"); + + // Verify proof + let proved_actions: BTreeMap = + Drive::verify_action_infos_in_contract( + proof.as_slice(), + non_existent_contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + None, + Some(10), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert no actions exist + assert_eq!( + proved_actions, + BTreeMap::new(), + "expected no proved actions" + ); + } + + #[test] + fn should_fetch_and_prove_action_infos_with_start_action_id() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + let identity_2_id = identity_2.id(); + + let identity_3 = Identity::random_identity(3, Some(9), platform_version) + .expect("expected a platform identity"); + + let identity_3_id = identity_3.id(); + + // Create a data contract with multiple tokens + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ]), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Contract and group details + let contract_id = contract.id(); + let group_contract_position = 0; + + // Create actions + let action_id_1 = Identifier::new([1; 32]); + let action_id_2 = Identifier::new([2; 32]); + let action_id_3 = Identifier::new([3; 32]); + let action_id_4 = Identifier::new([4; 32]); + + let action_1 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Mint(200, identity_1_id, None)), + }); + + let action_2 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Burn(50, None)), + }); + + let action_3 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Transfer( + identity_2_id, + None, + None, + None, + 150, + )), + }); + + let action_4 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Transfer( + identity_3_id, + None, + None, + None, + 150, + )), + }); + + // Add actions using `add_group_action` + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_1.clone()), + action_id_1, + identity_1_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 1"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_2.clone()), + action_id_2, + identity_1_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 2"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_3.clone()), + action_id_3, + identity_2_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 3"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_4.clone()), + action_id_4, + identity_3_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 3"); + + // Fetch actions starting from action_id_2 + let fetched_actions = drive + .fetch_action_infos( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + Some((action_id_2, true)), + Some(2), + None, + platform_version, + ) + .expect("expected to fetch active actions"); + + assert_eq!( + fetched_actions, + BTreeMap::from([ + (action_id_2, action_2.clone()), + (action_id_3, action_3.clone()) + ]), + "unexpected fetched actions" + ); + + // Prove actions starting from action_id_2 + let proof = drive + .prove_action_infos_v0( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + Some((action_id_2, true)), + Some(2), + None, + platform_version, + ) + .expect("should not error when proving active actions"); + + // Verify proof + let proved_actions: BTreeMap = + Drive::verify_action_infos_in_contract( + proof.as_slice(), + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + Some((action_id_2, true)), + Some(2), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert proved actions match fetched actions + assert_eq!(proved_actions, fetched_actions, "unexpected proved actions"); + } +} diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_signers/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_signers/mod.rs new file mode 100644 index 00000000000..cd6ee8d520e --- /dev/null +++ b/packages/rs-drive/src/drive/group/prove/prove_action_signers/mod.rs @@ -0,0 +1,124 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +mod v0; +impl Drive { + /// Generates a cryptographic proof of the signers for a specified action in a group within a contract. + /// + /// # Arguments + /// + /// * `contract_id` - The unique identifier of the contract containing the group. + /// * `group_contract_position` - The position of the group within the contract. + /// * `action_status` - The status of the action (e.g., active or closed). + /// * `action_id` - The unique identifier of the action for which to prove signers. + /// * `transaction` - The transaction context for this operation. + /// * `platform_version` - The platform version to use for determining method compatibility. + /// + /// # Returns + /// + /// Returns a `Result` containing: + /// * `Vec` - The serialized cryptographic proof of the signers. + /// * `Error` - An error if the operation fails or if the platform version is unsupported. + /// + /// # Errors + /// + /// Returns an `Error` if: + /// * The specified platform version is not supported. + /// * Internal database or transaction errors occur. + pub fn prove_action_signers( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .prove + .prove_action_signers + { + 0 => self.prove_action_signers_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_action_signers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Generates a cryptographic proof of the signers for a specified action in a group within a contract + /// and appends the required operations for this proof to the provided list of drive operations. + /// + /// # Arguments + /// + /// * `contract_id` - The unique identifier of the contract containing the group. + /// * `group_contract_position` - The position of the group within the contract. + /// * `action_status` - The status of the action (e.g., active or closed). + /// * `action_id` - The unique identifier of the action for which to prove signers. + /// * `transaction` - The transaction context for this operation. + /// * `drive_operations` - A mutable reference to a list of low-level drive operations to which this operation will be appended. + /// * `platform_version` - The platform version to use for determining method compatibility. + /// + /// # Returns + /// + /// Returns a `Result` containing: + /// * `Vec` - The serialized cryptographic proof of the signers. + /// * `Error` - An error if the operation fails or if the platform version is unsupported. + /// + /// # Errors + /// + /// Returns an `Error` if: + /// * The specified platform version is not supported. + /// * Internal database or transaction errors occur. + pub(crate) fn prove_action_signers_operations( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .group + .prove + .prove_action_signers + { + 0 => self.prove_action_signers_operations_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_action_signers_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs new file mode 100644 index 00000000000..509b3749319 --- /dev/null +++ b/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs @@ -0,0 +1,479 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + pub(super) fn prove_action_signers_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.prove_action_signers_operations_v0( + contract_id, + group_contract_position, + action_status, + action_id, + transaction, + &mut vec![], + platform_version, + ) + } + + pub(super) fn prove_action_signers_operations_v0( + &self, + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path_query = Drive::group_action_signers_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + action_id.to_buffer(), + ); + self.grove_get_proved_path_query( + &path_query, + transaction, + drive_operations, + &platform_version.drive, + ) + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::group::v0::GroupV0; + use dpp::data_contract::group::{Group, GroupMemberPower}; + use dpp::data_contract::v1::DataContractV1; + use dpp::data_contract::DataContract; + use dpp::group::action_event::GroupActionEvent; + use dpp::group::group_action::v0::GroupActionV0; + use dpp::group::group_action::GroupAction; + use dpp::identifier::Identifier; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::tokens::token_event::TokenEvent; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_action_signers_with_multiple_actions() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + let identity_3 = Identity::random_identity(3, Some(9), platform_version) + .expect("expected a platform identity"); + + let identity_3_id = identity_3.id(); + + // Create a data contract with multiple tokens + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ]), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Contract and group details + let contract_id = contract.id(); + let group_contract_position = 0; + + // Create actions + let action_id_1 = Identifier::random(); + let action_id_2 = Identifier::random(); + + let action_1 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Mint(100, identity_1_id, None)), + }); + + let action_2 = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Burn(50, None)), + }); + + // Add actions using `add_group_action` + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_1.clone()), + action_id_1, + identity_1_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 1"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_1.clone()), + action_id_1, + identity_2_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 1"); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action_2.clone()), + action_id_2, + identity_2_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action 2"); + + // Fetch actions directly + let fetched_actions = drive + .fetch_action_signers( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + action_id_1, + None, + platform_version, + ) + .expect("expected to fetch active actions"); + + assert_eq!( + fetched_actions, + BTreeMap::from([(identity_1_id, 1), (identity_2_id, 1)]), + "unexpected fetched powers" + ); + + // Prove actions + let proof = drive + .prove_action_signers_v0( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + action_id_1, + None, + platform_version, + ) + .expect("should not error when proving active actions"); + + // Verify proof + let proved_actions: BTreeMap = Drive::verify_action_signers( + proof.as_slice(), + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + action_id_1, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert proved actions match fetched actions + assert_eq!(proved_actions, fetched_actions, "unexpected proved actions"); + } + + #[test] + fn should_prove_no_action_signers_for_non_existent_action() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + // Create a data contract with groups + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + )]), + tokens: BTreeMap::new(), + }); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Contract and group details + let contract_id = contract.id(); + let group_contract_position = 0; + let non_existent_action_id = Identifier::random(); + + // Prove action signers for a non-existent action + let proof = drive + .prove_action_signers_v0( + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + non_existent_action_id, + None, + platform_version, + ) + .expect("should not error when proving action signers for non-existent action"); + + // Verify proof + let proved_signers: BTreeMap = Drive::verify_action_signers( + proof.as_slice(), + contract_id, + group_contract_position, + GroupActionStatus::ActionActive, + non_existent_action_id, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert no signers exist + assert!(proved_signers.is_empty(), "expected no signers"); + } + + #[test] + fn should_prove_action_signers_for_closed_action_if_still_active() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + // Create a data contract with groups + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + )]), + tokens: BTreeMap::new(), + }); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Contract and group details + let contract_id = contract.id(); + let group_contract_position = 0; + + // Create a closed action + let action_id = Identifier::random(); + let action = GroupAction::V0(GroupActionV0 { + event: GroupActionEvent::TokenEvent(TokenEvent::Burn(50, None)), + }); + + drive + .add_group_action( + contract_id, + group_contract_position, + Some(action.clone()), + action_id, + identity_2_id, + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add action"); + + // Fetch closed action signers + let fetched_signers = drive + .fetch_action_signers( + contract_id, + group_contract_position, + GroupActionStatus::ActionClosed, + action_id, + None, + platform_version, + ) + .expect("expected to fetch closed action signers"); + + assert_eq!( + fetched_signers, + BTreeMap::new(), + "unexpected fetched powers" + ); + + // Prove closed action signers + let proof = drive + .prove_action_signers_v0( + contract_id, + group_contract_position, + GroupActionStatus::ActionClosed, + action_id, + None, + platform_version, + ) + .expect("should not error when proving closed action signers"); + + // Verify proof + let proved_signers: BTreeMap = Drive::verify_action_signers( + proof.as_slice(), + contract_id, + group_contract_position, + GroupActionStatus::ActionClosed, + action_id, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert proved signers match fetched signers + assert_eq!(proved_signers, fetched_signers, "unexpected proved signers"); + } +} diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_info/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_info/mod.rs index 5fd307c25ee..a985301f6d5 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_info/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_info/mod.rs @@ -2,13 +2,10 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; -use dpp::data_contract::group::Group; use dpp::data_contract::GroupContractPosition; use dpp::identifier::Identifier; -use grovedb::batch::KeyInfoPath; -use grovedb::{EstimatedLayerInformation, TransactionArg}; +use grovedb::TransactionArg; use platform_version::version::PlatformVersion; -use std::collections::HashMap; mod v0; diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs index 0dd20e36e01..189cc63a2a0 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs @@ -43,3 +43,157 @@ impl Drive { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::group::v0::GroupV0; + use dpp::data_contract::group::Group; + use dpp::data_contract::v1::DataContractV1; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::prelude::DataContract; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_group_info_for_existing_group() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + // Create identities for group members + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + let identity_2_id = identity_2.id(); + + // Create a data contract with a single group + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 2)].into(), + required_power: 3, + }), + )]), + tokens: Default::default(), + }); + + let contract_id = contract.id(); + + // Insert the contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Prove group info for group at position 0 + let proof = drive + .prove_group_info_v0(contract_id, 0, None, platform_version) + .expect("should not error when proving group info"); + + // Verify the proof + let (root_hash, group) = + Drive::verify_group_info(proof.as_slice(), contract_id, 0, false, platform_version) + .expect("expected proof verification to succeed"); + + // Assert root hash is valid + assert!(!root_hash.is_empty(), "root hash should not be empty"); + + // Assert group matches the expected value + assert_eq!( + group, + Some(Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 2)].into(), + required_power: 3, + })), + "unexpected group info" + ); + } + + #[test] + fn should_prove_no_group_info_for_non_existent_group() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + // Create a data contract without groups + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: Default::default(), + }); + + let contract_id = contract.id(); + + // Insert the contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Prove group info for a non-existent group at position 0 + let proof = drive + .prove_group_info_v0(contract_id, 0, None, platform_version) + .expect("should not error when proving group info for a non-existent group"); + + // Verify the proof + let (root_hash, group) = + Drive::verify_group_info(proof.as_slice(), contract_id, 0, false, platform_version) + .expect("expected proof verification to succeed"); + + // Assert root hash is valid + assert!(!root_hash.is_empty(), "root hash should not be empty"); + + // Assert group is None + assert!(group.is_none(), "expected no group info, but got some"); + } +} diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_infos/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_infos/mod.rs index 27b153c2b19..3b882557dea 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_infos/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_infos/mod.rs @@ -2,13 +2,11 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; -use dpp::data_contract::group::Group; use dpp::data_contract::GroupContractPosition; use dpp::identifier::Identifier; use dpp::prelude::StartAtIncluded; use grovedb::TransactionArg; use platform_version::version::PlatformVersion; -use std::collections::BTreeMap; mod v0; impl Drive { diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs index d1260102bd8..cf65df54006 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs @@ -48,3 +48,320 @@ impl Drive { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::group::v0::GroupV0; + use dpp::data_contract::group::Group; + use dpp::data_contract::v1::DataContractV1; + use dpp::data_contract::GroupContractPosition; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::prelude::DataContract; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_group_infos_with_multiple_groups() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + let identity_3 = Identity::random_identity(3, Some(9), platform_version) + .expect("expected a platform identity"); + + let identity_3_id = identity_3.id(); + + // Create a data contract with groups + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ]), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let contract_id = contract.id(); + + // Insert contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Prove group infos + let proof = drive + .prove_group_infos_v0( + contract_id, + None, // No start position + Some(10), // Limit + None, + platform_version, + ) + .expect("should not error when proving group infos"); + + println!("{}", hex::encode(&proof)); + + // Verify proof + let proved_group_infos: BTreeMap = + Drive::verify_group_infos_in_contract( + proof.as_slice(), + contract_id, + None, + Some(10), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert group infos match expected values + assert_eq!( + proved_group_infos, + BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ]), + "unexpected group infos" + ); + } + + #[test] + fn should_prove_group_infos_with_start_position_and_limit() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity_1 = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_1_id = identity_1.id(); + + let identity_2 = Identity::random_identity(3, Some(506), platform_version) + .expect("expected a platform identity"); + + let identity_2_id = identity_2.id(); + + let identity_3 = Identity::random_identity(3, Some(9), platform_version) + .expect("expected a platform identity"); + + let identity_3_id = identity_3.id(); + + // Create a data contract with groups + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: BTreeMap::from([ + ( + 0, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_2_id, 1)].into(), + required_power: 2, + }), + ), + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }), + ), + ( + 2, + Group::V0(GroupV0 { + members: [(identity_2_id, 1), (identity_3_id, 2)].into(), + required_power: 3, + }), + ), + ( + 3, + Group::V0(GroupV0 { + members: [(identity_1_id, 1), (identity_3_id, 1)].into(), + required_power: 2, + }), + ), + ]), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let contract_id = contract.id(); + + // Insert contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Prove group infos starting from position 1 with a limit of 2 + let proof = drive + .prove_group_infos_v0( + contract_id, + Some((1, true)), + Some(2), + None, + platform_version, + ) + .expect("should not error when proving group infos"); + + // Verify proof + let proved_group_infos: BTreeMap = + Drive::verify_group_infos_in_contract( + proof.as_slice(), + contract_id, + Some((1, true)), + Some(2), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert group infos match expected values + assert_eq!( + proved_group_infos, + BTreeMap::from([ + ( + 1, + Group::V0(GroupV0 { + members: [(identity_1_id, 2), (identity_2_id, 1), (identity_3_id, 1)] + .into(), + required_power: 2, + }) + ), + ( + 2, + Group::V0(GroupV0 { + members: [(identity_2_id, 1), (identity_3_id, 2)].into(), + required_power: 3, + }), + ), + ]), + "unexpected group infos" + ); + } + + #[test] + fn should_prove_no_group_infos_for_empty_contract() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let contract_id = Identifier::random(); + + // Prove group infos for an empty contract + let proof = drive + .prove_group_infos_v0(contract_id, None, Some(10), None, platform_version) + .expect("should not error when proving group infos for an empty contract"); + + // Verify proof + let proved_group_infos: BTreeMap = + Drive::verify_group_infos_in_contract( + proof.as_slice(), + contract_id, + None, + Some(10), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert no group infos exist for the empty contract + assert!( + proved_group_infos.is_empty(), + "expected no group infos, but got: {:?}", + proved_group_infos + ); + } +} diff --git a/packages/rs-drive/src/drive/tokens/balance/queries.rs b/packages/rs-drive/src/drive/tokens/balance/queries.rs index a02a3216db4..1804724e2ac 100644 --- a/packages/rs-drive/src/drive/tokens/balance/queries.rs +++ b/packages/rs-drive/src/drive/tokens/balance/queries.rs @@ -1,7 +1,5 @@ use crate::drive::balances::total_tokens_root_supply_path_vec; -use crate::drive::tokens::paths::{ - token_balances_path_vec, token_balances_root_path_vec, tokens_root_path_vec, TOKEN_BALANCES_KEY, -}; +use crate::drive::tokens::paths::{token_balances_path_vec, token_balances_root_path_vec}; use crate::drive::Drive; use crate::error::Error; use crate::query::{Query, QueryItem}; diff --git a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs index 60b85c9dbb6..8472cfb8aa2 100644 --- a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs +++ b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs @@ -4,7 +4,6 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::balances::total_tokens_balance::TotalTokensBalance; -use dpp::version::drive_versions::DriveVersion; use grovedb::TransactionArg; use platform_version::version::PlatformVersion; diff --git a/packages/rs-drive/src/drive/tokens/info/queries.rs b/packages/rs-drive/src/drive/tokens/info/queries.rs index c4e80a5af11..8f5a4443806 100644 --- a/packages/rs-drive/src/drive/tokens/info/queries.rs +++ b/packages/rs-drive/src/drive/tokens/info/queries.rs @@ -1,6 +1,5 @@ use crate::drive::tokens::paths::{ - token_identity_infos_path_vec, token_identity_infos_root_path_vec, - token_statuses_root_path_vec, tokens_root_path_vec, TOKEN_STATUS_INFO_KEY, + token_identity_infos_path_vec, token_identity_infos_root_path_vec, token_statuses_root_path_vec, }; use crate::drive::Drive; use crate::query::{Query, QueryItem}; diff --git a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs index 26f2ad535fc..bf442bd2a80 100644 --- a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs @@ -3,12 +3,8 @@ mod v0; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; -use crate::fees::op::LowLevelDriveOperation; -use dpp::balances::credits::TokenAmount; use dpp::version::PlatformVersion; -use grovedb::batch::KeyInfoPath; -use grovedb::{EstimatedLayerInformation, TransactionArg}; -use std::collections::HashMap; +use grovedb::TransactionArg; impl Drive { /// Proves token's total supply and aggregated identity balances diff --git a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs index 7dde90d7ef3..64ee4a9dd36 100644 --- a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs @@ -197,7 +197,9 @@ mod tests { groups: Default::default(), tokens: BTreeMap::from([( 0, - TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + TokenConfiguration::V0( + TokenConfigurationV0::default_most_restrictive().with_base_supply(0), + ), )]), }); diff --git a/packages/rs-drive/src/verify/group/mod.rs b/packages/rs-drive/src/verify/group/mod.rs new file mode 100644 index 00000000000..d77cec3ea0f --- /dev/null +++ b/packages/rs-drive/src/verify/group/mod.rs @@ -0,0 +1,4 @@ +mod verify_action_signers; +mod verify_active_action_infos; +mod verify_group_info; +mod verify_group_infos_in_contract; diff --git a/packages/rs-drive/src/verify/group/verify_action_signers/mod.rs b/packages/rs-drive/src/verify/group/verify_action_signers/mod.rs new file mode 100644 index 00000000000..e708038af44 --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_action_signers/mod.rs @@ -0,0 +1,78 @@ +mod v0; + +use crate::drive::Drive; +use dpp::data_contract::group::GroupMemberPower; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the proof of action signer information within a contract. + /// + /// This method validates and extracts action signer information stored in a contract based on the provided proof. + /// It uses the proof to confirm the integrity and authenticity of the action signer data. The method supports + /// different versions for backward compatibility and forwards the verification logic to the appropriate versioned implementation. + /// + /// # Type Parameters + /// - `T`: The output container type that implements `FromIterator`. This is used to collect the verified action signer information + /// as pairs of [`Identifier`] and [`GroupMemberPower`]. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the active_action information. + /// - `contract_id`: The identifier of the contract whose active_action information is being verified. + /// - `start_active_action_contract_position`: An optional starting position for the active_action query, combined with a [`StartAtIncluded`] flag + /// to indicate whether the start position is inclusive. + /// - `limit`: An optional limit on the number of active_actions to verify. + /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof (useful for optimizations). + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok((RootHash, T))`: On success, returns a tuple containing: + /// - `RootHash`: The root hash of the Merkle tree, confirming the proof's validity. + /// - `T`: A collection of verified active_action information as pairs of [`GroupContractPosition`] and [`Group`]. + /// + /// # Errors + /// - [`Error::Proof`]: If the proof is invalid, corrupted, or contains unexpected data structures. + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - Any other errors propagated from the versioned implementation. + pub fn verify_action_signers>( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .group + .verify_action_signers + { + 0 => Self::verify_action_signers_v0( + proof, + contract_id, + group_contract_position, + action_status, + action_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_action_signers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/group/verify_action_signers/v0/mod.rs b/packages/rs-drive/src/verify/group/verify_action_signers/v0/mod.rs new file mode 100644 index 00000000000..2cffb68635f --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_action_signers/v0/mod.rs @@ -0,0 +1,72 @@ +use crate::drive::Drive; +use grovedb::Element::SumItem; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::data_contract::group::GroupMemberPower; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_action_signers_v0>( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + action_id: Identifier, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Drive::group_action_signers_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + action_id.to_buffer(), + ); + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + let values = proved_key_values + .into_iter() + .filter_map(|(_, key, element)| { + let id: Identifier = match key.try_into() { + Ok(id) => id, + Err(_) => { + return Some(Err(Error::Proof(ProofError::IncorrectProof( + "identifier was not 32 bytes long".to_string(), + )))) + } + }; + match element { + Some(SumItem(value, ..)) => { + let signing_power: GroupMemberPower = match value.try_into() { + Ok(signing_power) => signing_power, + Err(_) => { + return Some(Err(Error::Proof(ProofError::IncorrectProof( + "signed power should be encodable on a u32 integer".to_string(), + )))) + } + }; + + Some(Ok((id, signing_power))) + } + None => None, + _ => Some(Err(Error::Proof(ProofError::IncorrectProof( + "element should be a sum item representing member signed power".to_string(), + )) + .into())), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } +} diff --git a/packages/rs-drive/src/verify/group/verify_active_action_infos/mod.rs b/packages/rs-drive/src/verify/group/verify_active_action_infos/mod.rs new file mode 100644 index 00000000000..d0d81ecd694 --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_active_action_infos/mod.rs @@ -0,0 +1,81 @@ +mod v0; + +use crate::drive::Drive; +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action::GroupAction; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the proof of action information within a contract. + /// + /// This method validates and extracts action information stored in a contract based on the provided proof. + /// It uses the proof to confirm the integrity and authenticity of the action data. The method supports + /// different versions for backward compatibility and forwards the verification logic to the appropriate versioned implementation. + /// + /// # Type Parameters + /// - `T`: The output container type that implements `FromIterator`. This is used to collect the verified action information + /// as pairs of [`Identifier`] and [`GroupAction`]. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the active_action information. + /// - `contract_id`: The identifier of the contract whose active_action information is being verified. + /// - `start_active_action_contract_position`: An optional starting position for the active_action query, combined with a [`StartAtIncluded`] flag + /// to indicate whether the start position is inclusive. + /// - `limit`: An optional limit on the number of active_actions to verify. + /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof (useful for optimizations). + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok((RootHash, T))`: On success, returns a tuple containing: + /// - `RootHash`: The root hash of the Merkle tree, confirming the proof's validity. + /// - `T`: A collection of verified active_action information as pairs of [`GroupContractPosition`] and [`Group`]. + /// + /// # Errors + /// - [`Error::Proof`]: If the proof is invalid, corrupted, or contains unexpected data structures. + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - Any other errors propagated from the versioned implementation. + pub fn verify_action_infos_in_contract>( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .group + .verify_action_infos + { + 0 => Self::verify_action_infos_in_contract_v0( + proof, + contract_id, + group_contract_position, + action_status, + start_action_id, + limit, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_action_infos_in_contract".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/group/verify_active_action_infos/v0/mod.rs b/packages/rs-drive/src/verify/group/verify_active_action_infos/v0/mod.rs new file mode 100644 index 00000000000..972ddc10827 --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_active_action_infos/v0/mod.rs @@ -0,0 +1,74 @@ +use crate::drive::Drive; +use grovedb::Element::Item; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::data_contract::GroupContractPosition; +use dpp::group::group_action::GroupAction; +use dpp::group::group_action_status::GroupActionStatus; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use dpp::serialization::PlatformDeserializable; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_action_infos_in_contract_v0>( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + action_status: GroupActionStatus, + start_action_id: Option<(Identifier, StartAtIncluded)>, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Drive::group_action_infos_query( + contract_id.to_buffer(), + group_contract_position, + action_status, + start_action_id.map(|(s, i)| (s.to_buffer(), i)), + limit, + ); + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + let values = proved_key_values + .into_iter() + .filter_map(|(path, _, element)| { + let Some(last_path_component) = path.last() else { + return Some(Err(Error::Proof(ProofError::IncorrectProof( + "last path component is empty".to_string(), + )))); + }; + let action_id = match Identifier::from_bytes(last_path_component) { + Ok(action_id) => action_id, + Err(e) => return Some(Err(e.into())), + }; + + match element { + Some(Item(value, ..)) => { + let active_action = match GroupAction::deserialize_from_bytes(&value) { + Ok(active_action) => active_action, + + Err(e) => return Some(Err(e.into())), + }; + Some(Ok((action_id, active_action))) + } + None => None, + Some(element) => Some(Err(Error::Proof(ProofError::IncorrectProof(format!( + "group action should be in an item, however a {} was returned", + element.type_str() + ))))), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } +} diff --git a/packages/rs-drive/src/verify/group/verify_group_info/mod.rs b/packages/rs-drive/src/verify/group/verify_group_info/mod.rs new file mode 100644 index 00000000000..ded3f812feb --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_group_info/mod.rs @@ -0,0 +1,69 @@ +mod v0; + +use crate::drive::Drive; +use dpp::data_contract::group::Group; +use dpp::data_contract::GroupContractPosition; +use dpp::identifier::Identifier; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the proof of a single group's information within a contract. + /// + /// This method validates and extracts a specific group's information stored in a contract based on the provided proof. + /// It ensures the integrity and authenticity of the data associated with the specified group position in the contract. + /// The method supports multiple versions for backward compatibility and forwards the verification logic + /// to the appropriate versioned implementation. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the group information. + /// - `contract_id`: The identifier of the contract containing the group information. + /// - `group_contract_position`: The position of the group within the contract to verify. + /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof (useful for optimizations). + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok((RootHash, Option))`: On success, returns a tuple containing: + /// - `RootHash`: The root hash of the Merkle tree, confirming the proof's validity. + /// - `Option`: The verified group information if it exists, or `None` if the group is absent. + /// - `Err(Error)`: If verification fails, returns an [`Error`] indicating the cause of failure. + /// + /// # Errors + /// - [`Error::Proof`]: If the proof is invalid, corrupted, or contains unexpected data structures. + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - [`Error::GroveDB`]: If the data deserialization or conversion fails during proof verification. + pub fn verify_group_info( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + match platform_version + .drive + .methods + .verify + .group + .verify_group_info + { + 0 => Self::verify_group_info_v0( + proof, + contract_id, + group_contract_position, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_group_info".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/group/verify_group_info/v0/mod.rs b/packages/rs-drive/src/verify/group/verify_group_info/v0/mod.rs new file mode 100644 index 00000000000..0b2ae2397c3 --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_group_info/v0/mod.rs @@ -0,0 +1,57 @@ +use crate::drive::Drive; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::data_contract::group::Group; +use dpp::data_contract::GroupContractPosition; +use dpp::identifier::Identifier; +use dpp::serialization::PlatformDeserializable; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_group_info_v0( + proof: &[u8], + contract_id: Identifier, + group_contract_position: GroupContractPosition, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + let path_query = Self::group_info_for_contract_id_and_group_contract_position_query( + contract_id.to_buffer(), + group_contract_position, + ); + let (root_hash, mut proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + + if proved_key_values.len() != 1 { + return Err(Error::Proof(ProofError::CorruptedProof( + "we should always get back one element".to_string(), + ))); + } + + let element = proved_key_values.remove(0).2; + + let group = element + .map(|element| element.into_item_bytes().map_err(Error::GroveDB)) + .transpose()? + .map(|bytes| Group::deserialize_from_bytes(&bytes)) + .transpose()?; + + Ok((root_hash, group)) + } +} diff --git a/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/mod.rs b/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/mod.rs new file mode 100644 index 00000000000..0d20771bc67 --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/mod.rs @@ -0,0 +1,77 @@ +mod v0; + +use crate::drive::Drive; +use dpp::data_contract::group::Group; +use dpp::data_contract::GroupContractPosition; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the proof of group information within a contract. + /// + /// This method validates and extracts group information stored in a contract based on the provided proof. + /// It uses the proof to confirm the integrity and authenticity of the group data. The method supports + /// different versions for backward compatibility and forwards the verification logic to the appropriate versioned implementation. + /// + /// # Type Parameters + /// - `T`: The output container type that implements `FromIterator`. This is used to collect the verified group information + /// as pairs of [`GroupContractPosition`] and [`Group`]. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the group information. + /// - `contract_id`: The identifier of the contract whose group information is being verified. + /// - `start_group_contract_position`: An optional starting position for the group query, combined with a [`StartAtIncluded`] flag + /// to indicate whether the start position is inclusive. + /// - `limit`: An optional limit on the number of groups to verify. + /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof (useful for optimizations). + /// - `platform_version`: A reference to the platform version, used to determine the appropriate versioned implementation. + /// + /// # Returns + /// - `Ok((RootHash, T))`: On success, returns a tuple containing: + /// - `RootHash`: The root hash of the Merkle tree, confirming the proof's validity. + /// - `T`: A collection of verified group information as pairs of [`GroupContractPosition`] and [`Group`]. + /// - `Err(Error)`: If verification fails, returns an [`Error`] indicating the cause of failure. + /// + /// # Errors + /// - [`Error::Proof`]: If the proof is invalid, corrupted, or contains unexpected data structures. + /// - [`Error::Drive(DriveError::UnknownVersionMismatch)`]: If the method is called with an unsupported platform version. + /// - Any other errors propagated from the versioned implementation. + pub fn verify_group_infos_in_contract>( + proof: &[u8], + contract_id: Identifier, + start_group_contract_position: Option<(GroupContractPosition, StartAtIncluded)>, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .group + .verify_group_infos_in_contract + { + 0 => Self::verify_group_infos_in_contract_v0( + proof, + contract_id, + start_group_contract_position, + limit, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_group_infos_in_contract".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/v0/mod.rs b/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/v0/mod.rs new file mode 100644 index 00000000000..e594605f95b --- /dev/null +++ b/packages/rs-drive/src/verify/group/verify_group_infos_in_contract/v0/mod.rs @@ -0,0 +1,79 @@ +use crate::drive::Drive; +use grovedb::Element::Item; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::data_contract::group::Group; +use dpp::data_contract::GroupContractPosition; +use dpp::identifier::Identifier; +use dpp::prelude::StartAtIncluded; +use dpp::serialization::PlatformDeserializable; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_group_infos_in_contract_v0< + T: FromIterator<(GroupContractPosition, Group)>, + >( + proof: &[u8], + contract_id: Identifier, + start_group_contract_position: Option<(GroupContractPosition, StartAtIncluded)>, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Self::group_infos_for_contract_id_query( + contract_id.to_buffer(), + start_group_contract_position, + limit, + ); + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + let values = proved_key_values + .into_iter() + .filter_map(|(path, _, element)| { + let key_bytes = match path.last() { + Some(contract_position_bytes) => contract_position_bytes, + None => { + return Some(Err(Error::Proof(ProofError::CorruptedProof( + "path can't be empty in proof".to_string(), + )))) + } + }; + let key_bytes: [u8; 2] = match key_bytes.clone().try_into().map_err(|_| { + Error::Proof(ProofError::IncorrectValueSize( + "group contract position incorrect size", + )) + }) { + Ok(bytes) => bytes, + + Err(e) => return Some(Err(e)), + }; + let group_contract_position: GroupContractPosition = + GroupContractPosition::from_be_bytes(key_bytes); + match element { + Some(Item(value, ..)) => { + let group = match Group::deserialize_from_bytes(&value) { + Ok(group) => group, + + Err(e) => return Some(Err(e.into())), + }; + Some(Ok((group_contract_position, group))) + } + None => None, + Some(element) => Some(Err(Error::Proof(ProofError::IncorrectProof(format!( + "group should be in an item, however a {} was returned", + element.type_str() + ))))), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } +} diff --git a/packages/rs-drive/src/verify/mod.rs b/packages/rs-drive/src/verify/mod.rs index 18474b4bbb7..98d19c7bb06 100644 --- a/packages/rs-drive/src/verify/mod.rs +++ b/packages/rs-drive/src/verify/mod.rs @@ -10,9 +10,13 @@ pub mod single_document; /// System components (Epoch info etc...) verification methods on proofs pub mod system; +/// Group proof verification module +pub mod group; /// Verifies that a state transition contents exist in the proof pub mod state_transition; -mod tokens; +/// Token proof verification module +pub mod tokens; +/// Voting proof verification module pub mod voting; /// Represents the root hash of the grovedb tree diff --git a/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs index 7b7e9b82878..8cb213ff901 100644 --- a/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs +++ b/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs @@ -2,7 +2,6 @@ mod v0; use crate::drive::Drive; use crate::error::drive::DriveError; -use dpp::tokens::info::IdentityTokenInfo; use dpp::tokens::status::TokenStatus; use crate::error::Error; diff --git a/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs index 1c2b950104d..4515948dfd6 100644 --- a/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs +++ b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs @@ -2,14 +2,9 @@ mod v0; use crate::drive::Drive; use crate::error::drive::DriveError; -use dpp::balances::credits::TokenAmount; -use dpp::balances::total_single_token_balance::TotalSingleTokenBalance; -use dpp::balances::total_tokens_balance::TotalTokensBalance; - use crate::error::Error; - use crate::verify::RootHash; - +use dpp::balances::total_single_token_balance::TotalSingleTokenBalance; use dpp::version::PlatformVersion; impl Drive { diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs index bb2e8559b74..aea9f871338 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs @@ -37,6 +37,8 @@ pub struct DriveAbciQueryTokenVersions { pub struct DriveAbciQueryGroupVersions { pub group_info: FeatureVersionBounds, pub group_infos: FeatureVersionBounds, + pub group_actions: FeatureVersionBounds, + pub group_action_signers: FeatureVersionBounds, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs index 2db3bde39c3..ff92cdfcc61 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs @@ -209,5 +209,15 @@ pub const DRIVE_ABCI_QUERY_VERSIONS_V1: DriveAbciQueryVersions = DriveAbciQueryV max_version: 0, default_current_version: 0, }, + group_actions: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + group_action_signers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs index 20ebc594662..5d4e390d7e3 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs @@ -13,17 +13,21 @@ pub struct DriveGroupMethodVersions { #[derive(Clone, Debug, Default)] pub struct DriveGroupFetchMethodVersions { pub fetch_action_id_signers_power: FeatureVersion, - pub fetch_action_id_info: FeatureVersion, + pub fetch_active_action_info: FeatureVersion, pub fetch_action_id_info_keep_serialized: FeatureVersion, pub fetch_action_id_has_signer: FeatureVersion, pub fetch_group_info: FeatureVersion, pub fetch_group_infos: FeatureVersion, + pub fetch_action_infos: FeatureVersion, + pub fetch_action_signers: FeatureVersion, } #[derive(Clone, Debug, Default)] pub struct DriveGroupProveMethodVersions { pub prove_group_info: FeatureVersion, pub prove_group_infos: FeatureVersion, + pub prove_action_infos: FeatureVersion, + pub prove_action_signers: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/v1.rs index 365678957b7..259a9029f6e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/v1.rs @@ -6,15 +6,19 @@ use crate::version::drive_versions::drive_group_method_versions::{ pub const DRIVE_GROUP_METHOD_VERSIONS_V1: DriveGroupMethodVersions = DriveGroupMethodVersions { fetch: DriveGroupFetchMethodVersions { fetch_action_id_signers_power: 0, - fetch_action_id_info: 0, + fetch_active_action_info: 0, fetch_action_id_info_keep_serialized: 0, fetch_action_id_has_signer: 0, fetch_group_info: 0, fetch_group_infos: 0, + fetch_action_infos: 0, + fetch_action_signers: 0, }, prove: DriveGroupProveMethodVersions { prove_group_info: 0, prove_group_infos: 0, + prove_action_infos: 0, + prove_action_signers: 0, }, insert: DriveGroupInsertMethodVersions { add_new_groups: 0, diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs index 5b2309458d4..e7b9919c935 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs @@ -7,6 +7,7 @@ pub struct DriveVerifyMethodVersions { pub contract: DriveVerifyContractMethodVersions, pub document: DriveVerifyDocumentMethodVersions, pub identity: DriveVerifyIdentityMethodVersions, + pub group: DriveVerifyGroupMethodVersions, pub token: DriveVerifyTokenMethodVersions, pub single_document: DriveVerifySingleDocumentMethodVersions, pub system: DriveVerifySystemMethodVersions, @@ -43,6 +44,13 @@ pub struct DriveVerifyIdentityMethodVersions { pub verify_identity_revision_for_identity_id: FeatureVersion, } +#[derive(Clone, Debug, Default)] +pub struct DriveVerifyGroupMethodVersions { + pub verify_group_info: FeatureVersion, + pub verify_group_infos_in_contract: FeatureVersion, + pub verify_action_infos: FeatureVersion, +} + #[derive(Clone, Debug, Default)] pub struct DriveVerifyTokenMethodVersions { pub verify_token_balances_for_identity_ids: FeatureVersion, diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs index 0daa0cdd0b6..4872470b08e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs @@ -1,6 +1,6 @@ use crate::version::drive_versions::drive_verify_method_versions::{ DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, - DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, + DriveVerifyGroupMethodVersions, DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, DriveVerifySingleDocumentMethodVersions, DriveVerifyStateTransitionMethodVersions, DriveVerifySystemMethodVersions, DriveVerifyTokenMethodVersions, DriveVerifyVoteMethodVersions, }; @@ -29,6 +29,11 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri verify_identities_contract_keys: 0, verify_identity_revision_for_identity_id: 0, }, + group: DriveVerifyGroupMethodVersions { + verify_group_info: 0, + verify_group_infos_in_contract: 0, + verify_action_infos: 0, + }, token: DriveVerifyTokenMethodVersions { verify_token_balances_for_identity_ids: 0, verify_token_balances_for_identity_id: 0, diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index 9dc5f2d253c..9a4de6099fd 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -347,6 +347,16 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { max_version: 0, default_current_version: 0, }, + group_actions: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + group_action_signers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }, }, },