diff --git a/packages/rs-dpp/src/data_contract/data_contract.rs b/packages/rs-dpp/src/data_contract/data_contract.rs index 3d00fbd2791..cd248c5be96 100644 --- a/packages/rs-dpp/src/data_contract/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/data_contract.rs @@ -270,6 +270,10 @@ impl DataContract { self.entropy } + pub fn set_entropy(&mut self, entropy: [u8; 32]) { + self.entropy = entropy; + } + pub fn owner_id(&self) -> &Identifier { &self.owner_id } diff --git a/packages/rs-dpp/src/data_contract/data_contract_factory.rs b/packages/rs-dpp/src/data_contract/data_contract_factory.rs index b47b12e7f9b..fe30ef7590f 100644 --- a/packages/rs-dpp/src/data_contract/data_contract_factory.rs +++ b/packages/rs-dpp/src/data_contract/data_contract_factory.rs @@ -100,7 +100,9 @@ impl DataContractFactory { let cbor = value_to_cbor(JsonValue::Object(root_map), Some(1))?; - DataContract::from_cbor(cbor) + let mut data_contract = DataContract::from_cbor(cbor)?; + data_contract.set_entropy(entropy); + Ok(data_contract) } /// Create Data Contract from plain object diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs index 6e55139da35..d19b2ed19a6 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs @@ -243,7 +243,10 @@ impl StateTransitionConvert for DocumentsBatchTransition { } fn signature_property_paths() -> Vec<&'static str> { - vec![property_names::SIGNATURE] + vec![ + property_names::SIGNATURE, + property_names::SIGNATURE_PUBLIC_KEY_ID, + ] } fn to_json(&self, skip_signature: bool) -> Result { diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs index 111c1348f55..0ec38c12942 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs @@ -1,9 +1,11 @@ use std::convert::TryInto; +use async_trait::async_trait; use futures::future::join_all; use itertools::Itertools; use crate::data_contract::errors::DataContractNotPresentError; +use crate::validation::{AsyncDataValidator, SimpleValidationResult}; use crate::{ block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window, consensus::ConsensusError, @@ -27,6 +29,40 @@ use super::{ validate_documents_uniqueness_by_indices::validate_documents_uniqueness_by_indices, }; +pub struct DocumentsBatchTransitionStateValidator +where + SR: StateRepositoryLike, +{ + state_repository: SR, +} + +#[async_trait(?Send)] +impl AsyncDataValidator for DocumentsBatchTransitionStateValidator +where + SR: StateRepositoryLike, +{ + type Item = DocumentsBatchTransition; + + async fn validate( + &self, + data: &DocumentsBatchTransition, + ) -> Result { + validate_document_batch_transition_state(&self.state_repository, data).await + } +} + +impl DocumentsBatchTransitionStateValidator +where + SR: StateRepositoryLike, +{ + pub fn new(state_repository: SR) -> DocumentsBatchTransitionStateValidator + where + SR: StateRepositoryLike, + { + DocumentsBatchTransitionStateValidator { state_repository } + } +} + pub async fn validate_document_batch_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &DocumentsBatchTransition, diff --git a/packages/rs-dpp/src/errors/errors.rs b/packages/rs-dpp/src/errors/errors.rs index 7f5b6f796d7..66e6dde4f13 100644 --- a/packages/rs-dpp/src/errors/errors.rs +++ b/packages/rs-dpp/src/errors/errors.rs @@ -13,7 +13,10 @@ use crate::state_transition::errors::{ PublicKeySecurityLevelNotMetError, StateTransitionError, StateTransitionIsNotSignedError, WrongPublicKeyPurposeError, }; -use crate::{CompatibleProtocolVersionIsNotDefinedError, NonConsensusError, SerdeParsingError}; +use crate::{ + CompatibleProtocolVersionIsNotDefinedError, DashPlatformProtocolInitError, NonConsensusError, + SerdeParsingError, +}; use platform_value::Error as ValueError; #[derive(Error, Debug)] @@ -170,3 +173,9 @@ impl From for ProtocolError { ProtocolError::ParsingError(e.to_string()) } } + +impl From for ProtocolError { + fn from(e: DashPlatformProtocolInitError) -> Self { + ProtocolError::Generic(e.to_string()) + } +} diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs index de412a90ad6..680867e8774 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs @@ -2,8 +2,45 @@ use crate::consensus::state::identity::IdentityAlreadyExistsError; use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; use crate::state_repository::StateRepositoryLike; use crate::state_transition::StateTransitionLike; -use crate::validation::ValidationResult; -use crate::NonConsensusError; +use crate::validation::{AsyncDataValidator, SimpleValidationResult, ValidationResult}; +use crate::{NonConsensusError, ProtocolError}; +use async_trait::async_trait; + +pub struct IdentityCreateTransitionStateValidator +where + SR: StateRepositoryLike, +{ + state_repository: SR, +} + +#[async_trait(?Send)] +impl AsyncDataValidator for IdentityCreateTransitionStateValidator +where + SR: StateRepositoryLike, +{ + type Item = IdentityCreateTransition; + + async fn validate( + &self, + data: &IdentityCreateTransition, + ) -> Result { + validate_identity_create_transition_state(&self.state_repository, data) + .await + .map_err(|err| err.into()) + } +} + +impl IdentityCreateTransitionStateValidator +where + SR: StateRepositoryLike, +{ + pub fn new(state_repository: SR) -> IdentityCreateTransitionStateValidator + where + SR: StateRepositoryLike, + { + IdentityCreateTransitionStateValidator { state_repository } + } +} /// Validate that identity exists /// @@ -14,8 +51,9 @@ use crate::NonConsensusError; /// 2. Verify ownership proof signature, as it requires special transaction to be implemented pub async fn validate_identity_create_transition_state( state_repository: &impl StateRepositoryLike, - state_transition: IdentityCreateTransition, + state_transition: &IdentityCreateTransition, ) -> Result, NonConsensusError> { + // TODO: refactor to return ProtocolError? let mut result = ValidationResult::default(); let identity_id = state_transition.get_identity_id(); @@ -56,7 +94,8 @@ mod test { .expect_fetch_identity_balance() .return_once(|_, _| Ok(Some(1))); - let result = validate_identity_create_transition_state(&state_repository, transition).await; + let result = + validate_identity_create_transition_state(&state_repository, &transition).await; assert!(result.is_ok()); } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs index c1374d5186d..fd2dd71381c 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs @@ -1,7 +1,45 @@ use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; use crate::state_repository::StateRepositoryLike; -use crate::validation::ValidationResult; -use crate::NonConsensusError; +use crate::validation::{AsyncDataValidator, SimpleValidationResult, ValidationResult}; +use crate::{NonConsensusError, ProtocolError}; +use async_trait::async_trait; + +pub struct IdentityTopUpTransitionStateValidator +where + SR: StateRepositoryLike, +{ + state_repository: SR, +} + +#[async_trait(?Send)] +impl AsyncDataValidator for IdentityTopUpTransitionStateValidator +where + SR: StateRepositoryLike, +{ + type Item = IdentityTopUpTransition; + + async fn validate( + &self, + data: &IdentityTopUpTransition, + ) -> Result { + validate_identity_topup_transition_state(data, &self.state_repository) + .await + .map(|result| result.into()) + .map_err(|err| err.into()) + } +} + +impl IdentityTopUpTransitionStateValidator +where + SR: StateRepositoryLike, +{ + pub fn new(state_repository: SR) -> IdentityTopUpTransitionStateValidator + where + SR: StateRepositoryLike, + { + IdentityTopUpTransitionStateValidator { state_repository } + } +} /// Validate that identity exists /// @@ -11,7 +49,7 @@ use crate::NonConsensusError; /// 1. We need to check that outpoint exists (not now) /// 2. Verify ownership proof signature, as it requires special transaction to be implemented pub async fn validate_identity_topup_transition_state( - _state_transition: IdentityTopUpTransition, + _state_transition: &IdentityTopUpTransition, _state_repository: &impl StateRepositoryLike, ) -> Result, NonConsensusError> { Ok(ValidationResult::default()) diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs index fe674c5a32f..3d45a8ff519 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs @@ -241,7 +241,10 @@ impl StateTransitionConvert for IdentityUpdateTransition { } fn signature_property_paths() -> Vec<&'static str> { - vec![property_names::SIGNATURE] + vec![ + property_names::SIGNATURE, + property_names::SIGNATURE_PUBLIC_KEY_ID, + ] } fn to_object(&self, skip_signature: bool) -> Result { diff --git a/packages/rs-dpp/src/state_transition/abstract_state_transition.rs b/packages/rs-dpp/src/state_transition/abstract_state_transition.rs index 11944eaf417..4102c763bf3 100644 --- a/packages/rs-dpp/src/state_transition/abstract_state_transition.rs +++ b/packages/rs-dpp/src/state_transition/abstract_state_transition.rs @@ -4,6 +4,8 @@ use dashcore::signer; use serde::Serialize; use serde_json::Value as JsonValue; +use crate::consensus::ConsensusError; +use crate::errors::consensus::signature::SignatureError; use crate::state_transition::errors::{ InvalidIdentityPublicKeyTypeError, StateTransitionIsNotSignedError, }; @@ -118,11 +120,13 @@ pub trait StateTransitionLike: )); } let data_hash = self.hash(true)?; - Ok(signer::verify_hash_signature( - &data_hash, - self.get_signature(), - public_key_hash, - )?) + signer::verify_hash_signature(&data_hash, self.get_signature(), public_key_hash).or_else( + |_| { + Err(ProtocolError::from(ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError, + ))) + }, + ) } /// Verifies an ECDSA signature with the public key @@ -133,11 +137,12 @@ pub trait StateTransitionLike: )); } let data = self.to_buffer(true)?; - Ok(signer::verify_data_signature( - &data, - self.get_signature(), - public_key, - )?) + + signer::verify_data_signature(&data, self.get_signature(), public_key).or_else(|_| { + Err(ProtocolError::from(ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError, + ))) + }) } /// Verifies a BLS signature with the public key @@ -156,6 +161,11 @@ pub trait StateTransitionLike: bls.verify_signature(self.get_signature(), &data, public_key) .map(|_| ()) + .or_else(|_| { + Err(ProtocolError::from(ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError, + ))) + }) } /// returns true if state transition is a document state transition diff --git a/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs b/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs index 1ad538becef..b97a716e659 100644 --- a/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs +++ b/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs @@ -77,7 +77,11 @@ where KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), )), - } + }?; + + self.set_signature_public_key_id(identity_public_key.id); + + Ok(()) } fn verify_signature( diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index 8f6868d70d2..2fe8842692a 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -17,7 +17,9 @@ use crate::identity::state_transition::identity_update_transition::identity_upda mod abstract_state_transition; mod abstract_state_transition_identity_signed; +mod state_transition_facade; mod state_transition_factory; +pub use state_transition_facade::*; pub use state_transition_factory::*; use self::state_transition_execution_context::StateTransitionExecutionContext; diff --git a/packages/rs-dpp/src/state_transition/state_transition_facade.rs b/packages/rs-dpp/src/state_transition/state_transition_facade.rs new file mode 100644 index 00000000000..f1d1bc031a7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transition_facade.rs @@ -0,0 +1,419 @@ +use std::ops::Deref; + +use std::sync::Arc; +use serde_json::Value; +use crate::{BlsModule, ProtocolError}; +use crate::data_contract::state_transition::data_contract_create_transition::validation::state::validate_data_contract_create_transition_basic::DataContractCreateTransitionBasicValidator; +use crate::data_contract::state_transition::data_contract_update_transition::validation::basic::DataContractUpdateTransitionBasicValidator; +use crate::document::validation::basic::validate_documents_batch_transition_basic::DocumentBatchTransitionBasicValidator; +use crate::identity::state_transition::asset_lock_proof::{AssetLockProofValidator, AssetLockPublicKeyHashFetcher, AssetLockTransactionOutputFetcher, AssetLockTransactionValidator, ChainAssetLockProofStructureValidator, InstantAssetLockProofStructureValidator}; +use crate::identity::state_transition::identity_create_transition::validation::basic::IdentityCreateTransitionBasicValidator; +use crate::identity::state_transition::identity_credit_withdrawal_transition::validation::basic::validate_identity_credit_withdrawal_transition_basic::IdentityCreditWithdrawalTransitionBasicValidator; +use crate::identity::state_transition::identity_topup_transition::validation::basic::IdentityTopUpTransitionBasicValidator; +use crate::identity::state_transition::identity_update_transition::validate_identity_update_transition_basic::ValidateIdentityUpdateTransitionBasic; +use crate::identity::state_transition::validate_public_key_signatures::PublicKeysSignaturesValidator; +use crate::identity::validation::PublicKeysValidator; + +use crate::state_repository::StateRepositoryLike; +use crate::state_transition::{ + StateTransition, StateTransitionFactory, StateTransitionFactoryOptions, +}; + +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::state_transition::validation::validate_state_transition_basic::StateTransitionBasicValidator; +use crate::state_transition::validation::validate_state_transition_by_type::StateTransitionByTypeValidator; +use crate::state_transition::validation::validate_state_transition_fee::StateTransitionFeeValidator; +use crate::state_transition::validation::validate_state_transition_identity_signature::validate_state_transition_identity_signature; +use crate::state_transition::validation::validate_state_transition_key_signature::StateTransitionKeySignatureValidator; +use crate::state_transition::validation::validate_state_transition_state::StateTransitionStateValidator; +use crate::validation::{ + AsyncDataValidator, AsyncDataValidatorWithContext, SimpleValidationResult, +}; +use crate::version::ProtocolVersionValidator; + +#[derive(Clone)] +pub struct StateTransitionFacade +where + SR: StateRepositoryLike + Clone, + BLS: BlsModule + Clone, +{ + state_repository: Arc, + basic_validator: + Arc>>, + state_validator: Arc>, + key_signature_validator: Arc>, + fee_validator: Arc>, + bls: BLS, + factory: Arc>, +} + +impl StateTransitionFacade +where + SR: StateRepositoryLike + Clone, + BLS: BlsModule + Clone, +{ + pub fn new( + state_repository: SR, + adapter: BLS, + protocol_version_validator: Arc, + ) -> Result { + let wrapped_state_repository = Arc::new(state_repository.clone()); + + let state_transition_factory = { + let pk_validator = + Arc::new(PublicKeysValidator::new(adapter.clone()).map_err(ProtocolError::from)?); + let pk_sig_validator = Arc::new(PublicKeysSignaturesValidator::new(adapter.clone())); + + let asset_lock_tx_validator = Arc::new(AssetLockTransactionValidator::new( + wrapped_state_repository.clone(), + )); + + let asset_lock_validator = Arc::new(AssetLockProofValidator::new( + InstantAssetLockProofStructureValidator::new( + wrapped_state_repository.clone(), + asset_lock_tx_validator.clone(), + ) + .map_err(ProtocolError::from)?, + ChainAssetLockProofStructureValidator::new( + wrapped_state_repository.clone(), + asset_lock_tx_validator, + ) + .map_err(ProtocolError::from)?, + )); + + StateTransitionFactory::new( + wrapped_state_repository.clone(), + StateTransitionBasicValidator::new( + wrapped_state_repository.clone(), + StateTransitionByTypeValidator::new( + DataContractCreateTransitionBasicValidator::new( + protocol_version_validator.clone(), + )?, + DataContractUpdateTransitionBasicValidator::new( + wrapped_state_repository.clone(), + protocol_version_validator.clone(), + ) + .map_err(ProtocolError::from)?, + IdentityCreateTransitionBasicValidator::new( + protocol_version_validator.deref().clone(), + pk_validator.clone(), + pk_validator.clone(), + asset_lock_validator.clone(), + adapter.clone(), + pk_sig_validator.clone(), + ) + .map_err(ProtocolError::from)?, + ValidateIdentityUpdateTransitionBasic::new( + ProtocolVersionValidator::default(), + pk_validator, + pk_sig_validator, + )?, + IdentityTopUpTransitionBasicValidator::new( + ProtocolVersionValidator::default(), + asset_lock_validator, + ) + .map_err(ProtocolError::from)?, + IdentityCreditWithdrawalTransitionBasicValidator::new( + protocol_version_validator.clone(), + ) + .map_err(ProtocolError::from)?, + DocumentBatchTransitionBasicValidator::new( + wrapped_state_repository.clone(), + protocol_version_validator.clone(), + ), + ), + ), + ) + }; + + let state_transition_basic_validator = { + let protocol_version_validator = Arc::new(ProtocolVersionValidator::default()); + + let pk_validator = + Arc::new(PublicKeysValidator::new(adapter.clone()).map_err(|_| { + ProtocolError::Generic(String::from("Unable to initialize PublicKeysValidator")) + })?); + let pk_sig_validator = Arc::new(PublicKeysSignaturesValidator::new(adapter.clone())); + + let asset_lock_tx_validator = Arc::new(AssetLockTransactionValidator::new( + wrapped_state_repository.clone(), + )); + + let asset_lock_validator = Arc::new(AssetLockProofValidator::new( + InstantAssetLockProofStructureValidator::new( + wrapped_state_repository.clone(), + asset_lock_tx_validator.clone(), + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize InstantAssetLockProofStructureValidator", + )) + })?, + ChainAssetLockProofStructureValidator::new( + wrapped_state_repository.clone(), + asset_lock_tx_validator, + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize ChainAssetLockProofStructureValidator", + )) + })?, + )); + + StateTransitionBasicValidator::new( + wrapped_state_repository.clone(), + StateTransitionByTypeValidator::new( + DataContractCreateTransitionBasicValidator::new( + protocol_version_validator.clone(), + )?, + DataContractUpdateTransitionBasicValidator::new( + wrapped_state_repository.clone(), + protocol_version_validator.clone(), + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize DataContractUpdateTransitionBasicValidator", + )) + })?, + IdentityCreateTransitionBasicValidator::new( + protocol_version_validator.deref().clone(), + pk_validator.clone(), + pk_validator.clone(), + asset_lock_validator.clone(), + adapter.clone(), + pk_sig_validator.clone(), + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize IdentityCreateTransitionBasicValidator", + )) + })?, + ValidateIdentityUpdateTransitionBasic::new( + ProtocolVersionValidator::default(), + pk_validator, + pk_sig_validator, + )?, + IdentityTopUpTransitionBasicValidator::new( + ProtocolVersionValidator::default(), + asset_lock_validator, + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize IdentityTopUpTransitionBasicValidator", + )) + })?, + IdentityCreditWithdrawalTransitionBasicValidator::new( + protocol_version_validator.clone(), + ) + .map_err(|_| { + ProtocolError::Generic(String::from( + "Unable to initialize IdentityCreditWithdrawalTransitionBasicValidator", + )) + })?, + DocumentBatchTransitionBasicValidator::new( + wrapped_state_repository.clone(), + protocol_version_validator.clone(), + ), + ), + ) + }; + + let state_transition_key_signature_validator = { + let asset_lock_transaction_output_fetcher = + AssetLockTransactionOutputFetcher::new(wrapped_state_repository.clone()); + + let asset_public_key_hash_fetcher = AssetLockPublicKeyHashFetcher::new( + wrapped_state_repository.clone(), + asset_lock_transaction_output_fetcher, + ); + + StateTransitionKeySignatureValidator::new( + wrapped_state_repository.clone(), + asset_public_key_hash_fetcher, + ) + }; + + let state_transition_fee_validator = + StateTransitionFeeValidator::new(wrapped_state_repository.clone()); + + let state_transition_state_validator = StateTransitionStateValidator::new(state_repository); + + Ok(Self { + state_repository: wrapped_state_repository, + factory: Arc::new(state_transition_factory), + basic_validator: Arc::new(state_transition_basic_validator), + key_signature_validator: Arc::new(state_transition_key_signature_validator), + fee_validator: Arc::new(state_transition_fee_validator), + state_validator: Arc::new(state_transition_state_validator), + bls: adapter, + }) + } + + pub async fn create_from_object( + &self, + state_transition: Value, + skip_validation: bool, + ) -> Result { + self.factory + .create_from_object( + state_transition, + Some(StateTransitionFactoryOptions { skip_validation }), + ) + .await + } + + pub async fn create_from_buffer( + &self, + buffer: &[u8], + skip_validation: bool, + ) -> Result { + self.factory + .create_from_buffer( + buffer, + Some(StateTransitionFactoryOptions { skip_validation }), + ) + .await + } + + pub async fn validate( + &self, + state_transition: &StateTransition, + // TODO: revisit after https://github.com/dashpay/platform/pull/809 is merged + // we need to pass state_transition_json here because `StateTransition#to_object` output + // does not pass basic validation, and we use other means of producing JSON value for validation + state_transition_json: &Value, + execution_context: &StateTransitionExecutionContext, + options: ValidateOptions, + ) -> Result { + let mut result = SimpleValidationResult::default(); + + if options.basic { + result.merge( + self.validate_basic(state_transition_json, execution_context) + .await?, + ); + } + + if !result.is_valid() { + return Ok(result); + } + + if options.signature { + result.merge(self.validate_signature(state_transition.clone()).await?); + } + + if !result.is_valid() { + return Ok(result); + } + + if options.fee { + result.merge(self.validate_fee(state_transition).await?); + } + + if !result.is_valid() { + return Ok(result); + } + + if options.state { + result.merge(self.validate_state(state_transition).await?); + } + + Ok(result) + } + + pub async fn validate_basic( + &self, + state_transition: &Value, + execution_context: &StateTransitionExecutionContext, + ) -> Result { + self.basic_validator + .validate(state_transition, execution_context) + .await + } + + pub async fn validate_signature( + &self, + mut state_transition: StateTransition, + ) -> Result { + // TODO: can we avoid duplicated code here? + return match state_transition { + StateTransition::DataContractCreate(ref mut st) => { + validate_state_transition_identity_signature( + self.state_repository.clone(), + st, + &self.bls, + ) + .await + } + StateTransition::DataContractUpdate(ref mut st) => { + validate_state_transition_identity_signature( + self.state_repository.clone(), + st, + &self.bls, + ) + .await + } + StateTransition::DocumentsBatch(ref mut st) => { + validate_state_transition_identity_signature( + self.state_repository.clone(), + st, + &self.bls, + ) + .await + } + StateTransition::IdentityCreditWithdrawal(ref mut st) => { + validate_state_transition_identity_signature( + self.state_repository.clone(), + st, + &self.bls, + ) + .await + } + StateTransition::IdentityUpdate(ref mut st) => { + validate_state_transition_identity_signature( + self.state_repository.clone(), + st, + &self.bls, + ) + .await + } + _ => { + self.key_signature_validator + .validate(&state_transition) + .await + } + }; + } + + pub async fn validate_fee( + &self, + state_transition: &StateTransition, + ) -> Result { + self.fee_validator.validate(state_transition).await + } + + pub async fn validate_state( + &self, + state_transition: &StateTransition, + ) -> Result { + self.state_validator.validate(state_transition).await + } +} + +pub struct ValidateOptions { + pub basic: bool, + pub signature: bool, + pub fee: bool, + pub state: bool, +} + +impl Default for ValidateOptions { + fn default() -> Self { + Self { + basic: true, + signature: true, + fee: true, + state: true, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/validation/mod.rs b/packages/rs-dpp/src/state_transition/validation/mod.rs index 5a938c7adea..d44e7c550db 100644 --- a/packages/rs-dpp/src/state_transition/validation/mod.rs +++ b/packages/rs-dpp/src/state_transition/validation/mod.rs @@ -3,3 +3,4 @@ pub mod validate_state_transition_by_type; pub mod validate_state_transition_fee; pub mod validate_state_transition_identity_signature; pub mod validate_state_transition_key_signature; +pub mod validate_state_transition_state; diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs index 9c246c6a166..68452bee026 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs @@ -26,7 +26,7 @@ impl StateTransitionFeeValidator where SR: StateRepositoryLike, { - fn new(state_repository: Arc) -> Self { + pub fn new(state_repository: Arc) -> Self { let asset_lock_transition_output_fetcher = AssetLockTransactionOutputFetcher::new(state_repository.clone()); StateTransitionFeeValidator { diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs index 0db9ef7601f..4ee297966b1 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use std::{collections::HashSet, convert::TryInto}; use anyhow::Context; @@ -31,7 +32,7 @@ lazy_static! { } pub async fn validate_state_transition_identity_signature( - state_repository: &impl StateRepositoryLike, + state_repository: Arc, state_transition: &mut impl StateTransitionIdentitySigned, bls: &impl BlsModule, ) -> Result, ProtocolError> { @@ -317,7 +318,7 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -342,7 +343,7 @@ mod test { .returning(move |_, _| Ok(None)); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -375,7 +376,7 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -407,7 +408,7 @@ mod test { state_transition.return_error = Some(0); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -440,7 +441,7 @@ mod test { state_transition.return_error = Some(1); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -471,7 +472,7 @@ mod test { state_transition.get_execution_context().enable_dry_run(); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -498,7 +499,7 @@ mod test { state_transition.return_error = Some(2); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) @@ -529,7 +530,7 @@ mod test { state_transition.return_error = Some(3); let result = validate_state_transition_identity_signature( - &state_repository_mock, + Arc::new(state_repository_mock), &mut state_transition, &bls, ) diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs new file mode 100644 index 00000000000..c827c5e6706 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs @@ -0,0 +1,94 @@ +use crate::state_repository::StateRepositoryLike; +use std::sync::Arc; +use crate::data_contract::state_transition::data_contract_create_transition::validation::state::validate_data_contract_create_transition_state::DataContractCreateTransitionStateValidator; +use crate::data_contract::state_transition::data_contract_update_transition::validation::state::validate_data_contract_update_transition_state::DataContractUpdateTransitionStateValidator; +use crate::document::validation::state::validate_documents_batch_transition_state::DocumentsBatchTransitionStateValidator; +use crate::identity::state_transition::identity_create_transition::validation::state::IdentityCreateTransitionStateValidator; +use crate::identity::state_transition::identity_credit_withdrawal_transition::validation::state::validate_identity_credit_withdrawal_transition_state::IdentityCreditWithdrawalTransitionValidator; +use crate::identity::state_transition::identity_topup_transition::validation::state::IdentityTopUpTransitionStateValidator; +use crate::identity::state_transition::identity_update_transition::validate_identity_update_transition_state::IdentityUpdateTransitionStateValidator; +use crate::identity::state_transition::identity_update_transition::validate_public_keys::IdentityUpdatePublicKeysValidator; +use crate::ProtocolError; +use crate::state_transition::StateTransition; +use crate::validation::{AsyncDataValidator, SimpleValidationResult}; + +pub struct StateTransitionStateValidator +where + SR: StateRepositoryLike + Clone, +{ + state_repository: SR, + data_contract_create_validator: DataContractCreateTransitionStateValidator, + data_contract_update_validator: DataContractUpdateTransitionStateValidator, + identity_create_validator: IdentityCreateTransitionStateValidator, + identity_update_validator: + IdentityUpdateTransitionStateValidator, + identity_top_up_validator: IdentityTopUpTransitionStateValidator, + identity_credit_withdrawal_validator: IdentityCreditWithdrawalTransitionValidator, + document_batch_validator: DocumentsBatchTransitionStateValidator, +} + +impl StateTransitionStateValidator +where + SR: StateRepositoryLike + Clone, +{ + pub fn new(state_repository: SR) -> Self { + let wrapped_state_repository = Arc::new(state_repository.clone()); + + let data_contract_create_validator = + DataContractCreateTransitionStateValidator::new(state_repository.clone()); + let data_contract_update_validator = + DataContractUpdateTransitionStateValidator::new(state_repository.clone()); + let identity_create_validator = + IdentityCreateTransitionStateValidator::new(state_repository.clone()); + let identity_update_validator = IdentityUpdateTransitionStateValidator::new( + wrapped_state_repository.clone(), + Arc::new(IdentityUpdatePublicKeysValidator {}), + ); + let identity_top_up_validator = + IdentityTopUpTransitionStateValidator::new(state_repository.clone()); + let identity_credit_withdrawal_validator = + IdentityCreditWithdrawalTransitionValidator::new(wrapped_state_repository); + let document_batch_validator = + DocumentsBatchTransitionStateValidator::new(state_repository.clone()); + + StateTransitionStateValidator { + state_repository, + data_contract_create_validator, + data_contract_update_validator, + identity_create_validator, + identity_update_validator, + identity_top_up_validator, + identity_credit_withdrawal_validator, + document_batch_validator, + } + } + + pub async fn validate( + &self, + state_transition: &StateTransition, + ) -> Result { + match state_transition { + StateTransition::DataContractCreate(st) => { + self.data_contract_create_validator.validate(st).await + } + StateTransition::DataContractUpdate(st) => { + self.data_contract_update_validator.validate(st).await + } + StateTransition::IdentityCreate(st) => { + self.identity_create_validator.validate(st).await + } + StateTransition::IdentityUpdate(st) => self + .identity_update_validator + .validate(st) + .await + .map_err(|e| ProtocolError::from(e)), + StateTransition::IdentityTopUp(st) => self.identity_top_up_validator.validate(st).await, + StateTransition::IdentityCreditWithdrawal(st) => self + .identity_credit_withdrawal_validator + .validate_identity_credit_withdrawal_transition_state(st) + .await + .map_err(|e| ProtocolError::from(e)), + StateTransition::DocumentsBatch(st) => self.document_batch_validator.validate(st).await, + } + } +} diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs index 60ddaf9290c..32ca4d6b002 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs @@ -170,7 +170,6 @@ fn to_object_with_signature_skipped() { let expected_raw_state_transition = json!({ "protocolVersion" : 1, "type" : 5, - "signaturePublicKeyId": 0, "identityId" : transition.identity_id.to_buffer(), "revision": 0, "disablePublicKeys" : [0], diff --git a/packages/wasm-dpp/lib/test/fixtures/getDataContractFixture.js b/packages/wasm-dpp/lib/test/fixtures/getDataContractFixture.js index e8fb1b3ae24..01ef859a448 100644 --- a/packages/wasm-dpp/lib/test/fixtures/getDataContractFixture.js +++ b/packages/wasm-dpp/lib/test/fixtures/getDataContractFixture.js @@ -1,7 +1,8 @@ const protocolVersion = require('@dashevo/dpp/lib/version/protocolVersion'); +const JsIdentifier = require('@dashevo/dpp/lib/identifier/Identifier'); const generateRandomIdentifierAsync = require('../utils/generateRandomIdentifierAsync'); const { default: loadWasmDpp } = require('../../..'); -let { DataContractFactory, Identifier, DataContractValidator } = require('../../..'); +let { DataContractFactory, DataContractValidator } = require('../../..'); let randomOwnerId = null; @@ -11,7 +12,7 @@ let randomOwnerId = null; * @return {Promise} */ module.exports = async function getDataContractFixture(ownerId = randomOwnerId) { - ({ DataContractFactory, Identifier, DataContractValidator } = await loadWasmDpp()); + ({ DataContractFactory, DataContractValidator } = await loadWasmDpp()); if (!randomOwnerId) { randomOwnerId = await generateRandomIdentifierAsync(); @@ -189,7 +190,7 @@ module.exports = async function getDataContractFixture(ownerId = randomOwnerId) identifierField: { type: 'array', byteArray: true, - contentMediaType: Identifier.MEDIA_TYPE, + contentMediaType: JsIdentifier.MEDIA_TYPE, minItems: 32, maxItems: 32, }, diff --git a/packages/wasm-dpp/lib/test/fixtures/getDocumentsFixture.js b/packages/wasm-dpp/lib/test/fixtures/getDocumentsFixture.js new file mode 100644 index 00000000000..33e2bd06f15 --- /dev/null +++ b/packages/wasm-dpp/lib/test/fixtures/getDocumentsFixture.js @@ -0,0 +1,43 @@ +const crypto = require('crypto'); + +const getDataContractFixture = require('./getDataContractFixture'); + +const { default: loadWasmDpp } = require('../../..'); +let { DocumentFactory, DocumentValidator, ProtocolVersionValidator } = require('../../..'); +const generateRandomIdentifierAsync = require('../utils/generateRandomIdentifierAsync'); + +/** + * @param {DataContract} [dataContract] + * @return {Promise} + */ +module.exports = async function getDocumentsFixture( + dataContract, +) { + if (!dataContract) { + // eslint-disable-next-line no-param-reassign + dataContract = await getDataContractFixture(); + } + + ({ DocumentFactory, DocumentValidator, ProtocolVersionValidator } = await loadWasmDpp()); + + const documentValidator = new DocumentValidator(new ProtocolVersionValidator()); + const factory = new DocumentFactory(1, documentValidator, {}); + + const ownerId = await generateRandomIdentifierAsync(); + + return [ + factory.create(dataContract, ownerId, 'niceDocument', { name: 'Cutie' }), + factory.create(dataContract, ownerId, 'prettyDocument', { lastName: 'Shiny' }), + factory.create(dataContract, ownerId, 'prettyDocument', { lastName: 'Sweety' }), + factory.create(dataContract, ownerId, 'indexedDocument', { firstName: 'William', lastName: 'Birkin' }), + factory.create(dataContract, ownerId, 'indexedDocument', { firstName: 'Leon', lastName: 'Kennedy' }), + factory.create(dataContract, ownerId, 'noTimeDocument', { name: 'ImOutOfTime' }), + factory.create(dataContract, ownerId, 'uniqueDates', { firstName: 'John' }), + factory.create(dataContract, ownerId, 'indexedDocument', { firstName: 'Bill', lastName: 'Gates' }), + factory.create(dataContract, ownerId, 'withByteArrays', { + byteArrayField: crypto.randomBytes(10), + identifierField: (await generateRandomIdentifierAsync()).toBuffer(), + }), + factory.create(dataContract, ownerId, 'optionalUniqueIndexedDocument', { firstName: 'Jacques-Yves', lastName: 'Cousteau' }), + ]; +}; diff --git a/packages/wasm-dpp/src/dash_platform_protocol.rs b/packages/wasm-dpp/src/dash_platform_protocol.rs index 25117d6fb0c..b71ca7948ca 100644 --- a/packages/wasm-dpp/src/dash_platform_protocol.rs +++ b/packages/wasm-dpp/src/dash_platform_protocol.rs @@ -7,33 +7,49 @@ use crate::document_facade::DocumentFacadeWasm; use crate::fetch_and_validate_data_contract::DataContractFetcherAndValidatorWasm; use crate::identity_facade::IdentityFacadeWasm; use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; -use crate::DataContractFacadeWasm; +use crate::{with_js_error, DataContractFacadeWasm}; use crate::{DocumentFactoryWASM, DocumentValidatorWasm}; use dpp::identity::validation::PublicKeysValidator; -use dpp::version::{ProtocolVersionValidator, LATEST_VERSION}; +use crate::state_transition_facade::StateTransitionFacadeWasm; +use dpp::version::{ProtocolVersionValidator, COMPATIBILITY_MAP, LATEST_VERSION}; + +use serde::{Deserialize, Serialize}; #[wasm_bindgen(js_name=DashPlatformProtocol)] pub struct DashPlatformProtocol { identity: IdentityFacadeWasm, document: DocumentFacadeWasm, data_contract: DataContractFacadeWasm, + state_transition: StateTransitionFacadeWasm, +} + +#[derive(Serialize, Deserialize)] +pub struct DPPOptions { + #[serde(rename = "protocolVersion")] + pub current_protocol_version: Option, } #[wasm_bindgen(js_class=DashPlatformProtocol)] impl DashPlatformProtocol { #[wasm_bindgen(constructor)] pub fn new( + options: JsValue, bls_adapter: JsBlsAdapter, state_repository: ExternalStateRepositoryLike, - ) -> DashPlatformProtocol { - let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); - let bls = BlsAdapter(bls_adapter); - - // TODO: add protocol version to the constructor - let protocol_version = LATEST_VERSION; - // TODO: remove default validator and make a real one instead - let protocol_version_validator = Arc::new(ProtocolVersionValidator::default()); + ) -> Result { + // TODO: wrap whole thing around rs-dpp/dash_platform_protocol? + let options: DPPOptions = with_js_error!(serde_wasm_bindgen::from_value(options))?; + let wrapped_state_repository = + ExternalStateRepositoryLikeWrapper::new(state_repository.clone()); + let bls = BlsAdapter(bls_adapter.clone()); + + let protocol_version = options.current_protocol_version.unwrap_or(LATEST_VERSION); + let protocol_version_validator = Arc::new(ProtocolVersionValidator::new( + protocol_version, + LATEST_VERSION, + COMPATIBILITY_MAP.clone(), + )); let public_keys_validator = Arc::new(PublicKeysValidator::new(bls).unwrap()); let identity_facade = @@ -46,13 +62,20 @@ impl DashPlatformProtocol { ); let data_contract_facade = - DataContractFacadeWasm::new(protocol_version, protocol_version_validator); + DataContractFacadeWasm::new(protocol_version, protocol_version_validator.clone()); - Self { + let state_transition_facade = StateTransitionFacadeWasm::new( + state_repository, + bls_adapter, + protocol_version_validator, + )?; + + Ok(Self { document: document_facade, identity: identity_facade, data_contract: data_contract_facade, - } + state_transition: state_transition_facade, + }) } #[wasm_bindgen(getter = dataContract)] @@ -69,6 +92,11 @@ impl DashPlatformProtocol { pub fn identity(&self) -> IdentityFacadeWasm { self.identity.clone() } + + #[wasm_bindgen(getter = stateTransition)] + pub fn state_transition(&self) -> StateTransitionFacadeWasm { + self.state_transition.clone() + } } fn init_document_facade( diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs index ebb3605e5bb..30cce3f6b4c 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs @@ -15,10 +15,12 @@ use dpp::{ use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; +use crate::bls_adapter::{BlsAdapter, JsBlsAdapter}; use crate::errors::protocol_error::from_protocol_error; use crate::{ buffer::Buffer, errors::from_dpp_err, identifier::IdentifierWrapper, with_js_error, - DataContractParameters, DataContractWasm, StateTransitionExecutionContextWasm, + DataContractParameters, DataContractWasm, IdentityPublicKeyWasm, + StateTransitionExecutionContextWasm, }; #[derive(Clone)] @@ -147,4 +149,21 @@ impl DataContractCreateTransitionWasm { .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) .map_err(|e| e.into()) } + + #[wasm_bindgen] + pub fn sign( + &mut self, + identity_public_key: &IdentityPublicKeyWasm, + private_key: Vec, + bls: JsBlsAdapter, + ) -> Result<(), JsValue> { + let bls_adapter = BlsAdapter(bls); + self.0 + .sign( + &identity_public_key.to_owned().into(), + &private_key, + &bls_adapter, + ) + .map_err(from_dpp_err) + } } diff --git a/packages/wasm-dpp/src/document/document_facade.rs b/packages/wasm-dpp/src/document/document_facade.rs index af569de9742..2aac27780b9 100644 --- a/packages/wasm-dpp/src/document/document_facade.rs +++ b/packages/wasm-dpp/src/document/document_facade.rs @@ -7,7 +7,7 @@ use crate::{ utils::{get_class_name, IntoWasm}, validation::ValidationResultWasm, DataContractWasm, DocumentFactoryWASM, DocumentValidatorWasm, DocumentWasm, - DocumentsBatchTransitionWASM, + DocumentsBatchTransitionWasm, }; #[derive(Clone)] @@ -50,7 +50,7 @@ impl DocumentFacadeWasm { #[wasm_bindgen(js_name=create)] pub fn create( &self, - data_contract: DataContractWasm, + data_contract: &DataContractWasm, js_owner_id: &JsValue, document_type: &str, data: &JsValue, @@ -84,7 +84,7 @@ impl DocumentFacadeWasm { pub fn create_state_transition( &self, documents: &JsValue, // documents_container: DocumentsContainer, - ) -> Result { + ) -> Result { self.factory.create_state_transition(documents) } diff --git a/packages/wasm-dpp/src/document/factory.rs b/packages/wasm-dpp/src/document/factory.rs index 0cfb07c3839..f6ef0fc8da6 100644 --- a/packages/wasm-dpp/src/document/factory.rs +++ b/packages/wasm-dpp/src/document/factory.rs @@ -19,7 +19,7 @@ use crate::{ utils::{ replace_identifiers_with_bytes_without_failing, IntoWasm, ToSerdeJSONExt, WithJsError, }, - DataContractWasm, DocumentWasm, DocumentsBatchTransitionWASM, + DataContractWasm, DocumentWasm, DocumentsBatchTransitionWasm, }; use super::validator::DocumentValidatorWasm; @@ -96,7 +96,7 @@ impl DocumentFactoryWASM { #[wasm_bindgen(js_name=create)] pub fn create( &self, - data_contract: DataContractWasm, + data_contract: &DataContractWasm, js_owner_id: &JsValue, document_type: &str, data: &JsValue, @@ -106,7 +106,7 @@ impl DocumentFactoryWASM { let document = self .0 .create( - data_contract.into(), + data_contract.to_owned().into(), owner_id, document_type.to_string(), dynamic_data, @@ -120,7 +120,7 @@ impl DocumentFactoryWASM { pub fn create_state_transition( &self, documents: &JsValue, - ) -> Result { + ) -> Result { let documents_by_action = extract_documents_by_action(documents)?; let batch_transition = self .0 diff --git a/packages/wasm-dpp/src/document/mod.rs b/packages/wasm-dpp/src/document/mod.rs index cdab208fd96..8904ac2651c 100644 --- a/packages/wasm-dpp/src/document/mod.rs +++ b/packages/wasm-dpp/src/document/mod.rs @@ -28,7 +28,7 @@ pub mod generate_document_id; pub mod state_transition; mod validator; -pub use document_batch_transition::DocumentsBatchTransitionWASM; +pub use document_batch_transition::DocumentsBatchTransitionWasm; pub use factory::DocumentFactoryWASM; pub use validator::DocumentValidatorWasm; diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs index 41db5053094..86d2a564906 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs @@ -4,13 +4,13 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, utils::WithJsError, - DocumentsBatchTransitionWASM, + DocumentsBatchTransitionWasm, }; #[wasm_bindgen(js_name=applyDocumentsBatchTransition)] pub async fn apply_documents_batch_transition_wasm( state_repository: ExternalStateRepositoryLike, - transition: &DocumentsBatchTransitionWASM, + transition: &DocumentsBatchTransitionWasm, ) -> Result<(), JsValue> { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs index c74c48af9c4..37e76fec7b2 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs @@ -35,7 +35,7 @@ pub mod validation; #[derive(Clone, Debug)] #[wasm_bindgen(js_name = DocumentsBatchTransition)] -pub struct DocumentsBatchTransitionWASM(DocumentsBatchTransition); +pub struct DocumentsBatchTransitionWasm(DocumentsBatchTransition); #[derive(Debug, Serialize, Deserialize, Default, Clone, Copy)] #[serde(rename_all = "camelCase")] @@ -47,12 +47,12 @@ pub struct ToObjectOptions { } #[wasm_bindgen(js_class=DocumentsBatchTransition)] -impl DocumentsBatchTransitionWASM { +impl DocumentsBatchTransitionWasm { #[wasm_bindgen(constructor)] pub fn from_raw_object( js_raw_transition: JsValue, data_contracts: Array, - ) -> Result { + ) -> Result { let data_contracts_array_js = Array::from(&data_contracts); let mut data_contracts: Vec = vec![]; @@ -244,7 +244,11 @@ impl DocumentsBatchTransitionWASM { bls: JsBlsAdapter, ) -> Result<(), JsValue> { self.0 - .sign(identity_public_key.inner(), private_key, &BlsAdapter(bls)) + .sign( + &identity_public_key.to_owned().into(), + private_key, + &BlsAdapter(bls), + ) .with_js_error() } @@ -362,14 +366,14 @@ impl DocumentsBatchTransitionWASM { } } -impl From for DocumentsBatchTransitionWASM { +impl From for DocumentsBatchTransitionWasm { fn from(t: DocumentsBatchTransition) -> Self { - DocumentsBatchTransitionWASM(t) + DocumentsBatchTransitionWasm(t) } } -impl From for DocumentsBatchTransition { - fn from(t: DocumentsBatchTransitionWASM) -> Self { +impl From for DocumentsBatchTransition { + fn from(t: DocumentsBatchTransitionWasm) -> Self { t.0 } } diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs index ca199406405..11aeffa8e03 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs @@ -5,13 +5,13 @@ use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, utils::WithJsError, validation::ValidationResultWasm, - DocumentsBatchTransitionWASM, + DocumentsBatchTransitionWasm, }; #[wasm_bindgen(js_name = "validateDocumentsBatchTransitionState")] pub async fn validate_documents_batch_transition_state_wasm( state_repository: ExternalStateRepositoryLike, - state_transition: &DocumentsBatchTransitionWASM, + state_transition: &DocumentsBatchTransitionWasm, ) -> Result { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs index 6f240cb7775..e4f5f7abd7c 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs @@ -27,7 +27,7 @@ impl IdentityCreateTransitionStateValidator { ) -> Result { let validation_result = validate_identity_create_transition_state( &self.state_repository, - state_transition.to_owned().into(), + &state_transition.to_owned().into(), ) .await .map_err(|e| from_dpp_err(e.into()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs index 04ad94d491c..b8e38429c99 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs @@ -26,7 +26,7 @@ impl IdentityTopUpTransitionStateValidator { state_transition: &IdentityTopUpTransitionWasm, ) -> Result { let validation_result = validate_identity_topup_transition_state( - state_transition.to_owned().into(), + &state_transition.to_owned().into(), &self.state_repository, ) .await diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs index fcccf72c41a..1047dcbcd9d 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs @@ -432,13 +432,17 @@ impl IdentityUpdateTransitionWasm { #[wasm_bindgen] pub fn sign( &mut self, - identity_public_key: IdentityPublicKeyWasm, + identity_public_key: &IdentityPublicKeyWasm, private_key: Vec, bls: JsBlsAdapter, ) -> Result<(), JsValue> { let bls_adapter = BlsAdapter(bls); self.0 - .sign(&identity_public_key.into(), &private_key, &bls_adapter) + .sign( + &identity_public_key.to_owned().into(), + &private_key, + &bls_adapter, + ) .map_err(from_dpp_err) } } diff --git a/packages/wasm-dpp/src/state_repository.rs b/packages/wasm-dpp/src/state_repository.rs index 31774661635..ac2b6a7db3a 100644 --- a/packages/wasm-dpp/src/state_repository.rs +++ b/packages/wasm-dpp/src/state_repository.rs @@ -36,6 +36,7 @@ use crate::{ #[wasm_bindgen] extern "C" { + #[derive(Clone)] pub type ExternalStateRepositoryLike; #[wasm_bindgen(catch, structural, method, js_name=fetchDataContract)] diff --git a/packages/wasm-dpp/src/state_transition/conversion.rs b/packages/wasm-dpp/src/state_transition/conversion.rs index 80266630705..2a95be7f025 100644 --- a/packages/wasm-dpp/src/state_transition/conversion.rs +++ b/packages/wasm-dpp/src/state_transition/conversion.rs @@ -1,7 +1,7 @@ use crate::utils::generic_of_js_val; use crate::{ DataContractCreateTransitionWasm, DataContractUpdateTransitionWasm, - DocumentsBatchTransitionWASM, IdentityCreateTransitionWasm, IdentityTopUpTransitionWasm, + DocumentsBatchTransitionWasm, IdentityCreateTransitionWasm, IdentityTopUpTransitionWasm, IdentityUpdateTransitionWasm, }; use dpp::state_transition::{StateTransition, StateTransitionType}; @@ -9,6 +9,17 @@ use std::convert::TryInto; use wasm_bindgen::__rt::Ref; use wasm_bindgen::{JsCast, JsError, JsValue}; +pub fn state_transition_wasm_to_object(state_transition: &JsValue) -> Result { + let to_object_value = js_sys::Reflect::get(state_transition, &JsValue::from_str("toObject")) + .map_err(|_| JsError::new("No 'toObject' property present in state transition"))?; + + let to_object_function: &js_sys::Function = to_object_value + .dyn_ref::() + .ok_or_else(|| JsError::new("'toObject' is not a function"))?; + + to_object_function.call0(state_transition) +} + pub fn create_state_transition_from_wasm_instance( js_value: &JsValue, ) -> Result { @@ -44,8 +55,8 @@ pub fn create_state_transition_from_wasm_instance( Ok(StateTransition::DataContractCreate(st.clone().into())) } StateTransitionType::DocumentsBatch => { - let st: Ref = generic_of_js_val::< - DocumentsBatchTransitionWASM, + let st: Ref = generic_of_js_val::< + DocumentsBatchTransitionWasm, >( js_value, "DocumentsBatchTransition" )?; diff --git a/packages/wasm-dpp/src/state_transition/mod.rs b/packages/wasm-dpp/src/state_transition/mod.rs index bf2a695a324..a755f3db21a 100644 --- a/packages/wasm-dpp/src/state_transition/mod.rs +++ b/packages/wasm-dpp/src/state_transition/mod.rs @@ -3,6 +3,7 @@ use dpp::state_transition::state_transition_execution_context::StateTransitionEx use wasm_bindgen::prelude::*; pub mod errors; +pub mod state_transition_facade; pub mod state_transition_factory; pub mod validation; diff --git a/packages/wasm-dpp/src/state_transition/state_transition_facade.rs b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs new file mode 100644 index 00000000000..c28ab38385e --- /dev/null +++ b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs @@ -0,0 +1,263 @@ +use crate::bls_adapter::{BlsAdapter, JsBlsAdapter}; +use crate::errors::from_dpp_err; +use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; +use crate::state_transition_factory::StateTransitionFactoryWasm; +use crate::utils::ToSerdeJSONExt; +use crate::validation::ValidationResultWasm; +use crate::with_js_error; +use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use dpp::state_transition::{StateTransitionFacade, StateTransitionLike, ValidateOptions}; +use dpp::version::ProtocolVersionValidator; +use serde::Deserialize; + +use std::sync::Arc; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; + +#[derive(Clone)] +#[wasm_bindgen(js_name = StateTransitionFacade)] +pub struct StateTransitionFacadeWasm( + StateTransitionFacade, +); + +impl StateTransitionFacadeWasm { + pub fn new( + state_repository: ExternalStateRepositoryLike, + bls_adapter: JsBlsAdapter, + protocol_version_validator: Arc, + ) -> Result { + let state_repository_wrapper = ExternalStateRepositoryLikeWrapper::new(state_repository); + + let adapter = BlsAdapter(bls_adapter); + + let state_transition_facade = StateTransitionFacade::new( + state_repository_wrapper, + adapter, + protocol_version_validator, + ) + .map_err(from_dpp_err)?; + + Ok(StateTransitionFacadeWasm(state_transition_facade)) + } +} + +#[wasm_bindgen(js_class = StateTransitionFacade)] +impl StateTransitionFacadeWasm { + #[wasm_bindgen(js_name = createFromObject)] + pub async fn create_from_object( + &self, + raw_state_transition: JsValue, + options: JsValue, + ) -> Result { + let options: FromObjectOptions = if options.is_object() { + with_js_error!(serde_wasm_bindgen::from_value(options))? + } else { + Default::default() + }; + + let raw_state_transition = raw_state_transition.with_serde_to_json_value()?; + + let result = self + .0 + .create_from_object( + raw_state_transition, + options.skip_validation.unwrap_or(false), + ) + .await; + + StateTransitionFactoryWasm::state_transition_wasm_from_factory_result(result) + } + + #[wasm_bindgen(js_name = createFromBuffer)] + pub async fn create_from_buffer( + &self, + state_transition_buffer: Vec, + options: JsValue, + ) -> Result { + let options: FromObjectOptions = if options.is_object() { + with_js_error!(serde_wasm_bindgen::from_value(options))? + } else { + Default::default() + }; + + let result = self + .0 + .create_from_buffer( + &state_transition_buffer, + options.skip_validation.unwrap_or(false), + ) + .await; + + StateTransitionFactoryWasm::state_transition_wasm_from_factory_result(result) + } + + #[wasm_bindgen] + pub async fn validate( + &self, + raw_state_transition: JsValue, + options: JsValue, + ) -> Result { + let options: ValidateOptionsWasm = if options.is_object() { + with_js_error!(serde_wasm_bindgen::from_value(options))? + } else { + Default::default() + }; + + let (state_transition, state_transition_json, execution_context) = + if let Ok(state_transition) = + super::super::conversion::create_state_transition_from_wasm_instance( + &raw_state_transition, + ) + { + let execution_context = state_transition.get_execution_context().to_owned(); + // TODO: revisit after https://github.com/dashpay/platform/pull/809 is merged + // we use this workaround to produce JSON value for validation because + // state_transition.to_object() returns value that does not pass basic validation + let state_transition_json = + super::super::conversion::state_transition_wasm_to_object( + &raw_state_transition, + )? + .with_serde_to_json_value()?; + (state_transition, state_transition_json, execution_context) + } else { + let state_transition_json = raw_state_transition.with_serde_to_json_value()?; + let execution_context = StateTransitionExecutionContext::default(); + let state_transition = self + .0 + .create_from_object(state_transition_json.clone(), true) + .await + .map_err(from_dpp_err)?; + (state_transition, state_transition_json, execution_context) + }; + + let validation_result = self + .0 + .validate( + &state_transition, + &state_transition_json, + &execution_context, + options.into(), + ) + .await + .map_err(from_dpp_err)?; + + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } + + #[wasm_bindgen(js_name = validateBasic)] + pub async fn validate_basic( + &self, + raw_state_transition: JsValue, + ) -> Result { + let state_transition_json; + let execution_context; + + if let Ok(state_transition) = + super::super::conversion::create_state_transition_from_wasm_instance( + &raw_state_transition, + ) + { + execution_context = state_transition.get_execution_context().to_owned(); + // TODO: revisit after https://github.com/dashpay/platform/pull/809 is merged + // we use this workaround to produce JSON value for validation because + // state_transition.to_object() returns value that does not pass basic validation + state_transition_json = + super::super::conversion::state_transition_wasm_to_object(&raw_state_transition)? + .with_serde_to_json_value()?; + } else { + state_transition_json = raw_state_transition.with_serde_to_json_value()?; + execution_context = StateTransitionExecutionContext::default(); + } + + let validation_result = self + .0 + .validate_basic(&state_transition_json, &execution_context) + .await + .map_err(from_dpp_err)?; + + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } + + #[wasm_bindgen(js_name = validateSignature)] + pub async fn validate_signature( + &self, + raw_state_transition: JsValue, + ) -> Result { + let state_transition = + super::super::conversion::create_state_transition_from_wasm_instance( + &raw_state_transition, + )?; + + let validation_result = self + .0 + .validate_signature(state_transition) + .await + .map_err(from_dpp_err)?; + + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } + + #[wasm_bindgen(js_name = validateFee)] + pub async fn validate_fee( + &self, + raw_state_transition: JsValue, + ) -> Result { + let state_transition = + super::super::conversion::create_state_transition_from_wasm_instance( + &raw_state_transition, + )?; + + let validation_result = self + .0 + .validate_fee(&state_transition) + .await + .map_err(from_dpp_err)?; + + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } + + #[wasm_bindgen(js_name = validateState)] + pub async fn validate_state( + &self, + raw_state_transition: JsValue, + ) -> Result { + let state_transition = + super::super::conversion::create_state_transition_from_wasm_instance( + &raw_state_transition, + )?; + + let validation_result = self + .0 + .validate_state(&state_transition) + .await + .map_err(from_dpp_err)?; + + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } +} + +#[derive(Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct FromObjectOptions { + pub skip_validation: Option, +} + +#[derive(Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct ValidateOptionsWasm { + pub basic: Option, + pub signature: Option, + pub fee: Option, + pub state: Option, +} + +impl From for ValidateOptions { + fn from(options: ValidateOptionsWasm) -> Self { + Self { + basic: options.basic.unwrap_or(true), + signature: options.signature.unwrap_or(true), + fee: options.fee.unwrap_or(true), + state: options.state.unwrap_or(true), + } + } +} diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index 7df0c38d751..4da5f6d42e4 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -19,7 +19,7 @@ use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, state_transition::errors::invalid_state_transition_error::InvalidStateTransitionErrorWasm, with_js_error, DataContractCreateTransitionWasm, DataContractUpdateTransitionWasm, - DocumentsBatchTransitionWASM, IdentityCreateTransitionWasm, IdentityTopUpTransitionWasm, + DocumentsBatchTransitionWasm, IdentityCreateTransitionWasm, IdentityTopUpTransitionWasm, IdentityUpdateTransitionWasm, }; @@ -38,6 +38,47 @@ impl From } } +impl StateTransitionFactoryWasm { + pub fn state_transition_wasm_from_factory_result( + result: Result, + ) -> Result { + match result { + Ok(state_transition) => match state_transition { + StateTransition::DataContractCreate(st) => { + Ok(DataContractCreateTransitionWasm::from(st).into()) + } + StateTransition::DataContractUpdate(st) => { + Ok(DataContractUpdateTransitionWasm::from(st).into()) + } + StateTransition::IdentityCreate(st) => { + Ok(IdentityCreateTransitionWasm::from(st).into()) + } + StateTransition::IdentityUpdate(st) => { + Ok(IdentityUpdateTransitionWasm::from(st).into()) + } + StateTransition::IdentityTopUp(st) => { + Ok(IdentityTopUpTransitionWasm::from(st).into()) + } + StateTransition::DocumentsBatch(st) => { + Ok(DocumentsBatchTransitionWasm::from(st).into()) + } + _ => Err("Unsupported state transition type".into()), + }, + Err(dpp::ProtocolError::StateTransitionError(e)) => match e { + StateTransitionError::InvalidStateTransitionError { + errors, + raw_state_transition, + } => Err(InvalidStateTransitionErrorWasm::new( + errors, + serde_wasm_bindgen::to_value(&raw_state_transition)?, + ) + .into()), + }, + Err(other) => Err(from_dpp_err(other)), + } + } +} + #[wasm_bindgen(js_class = StateTransitionFactory)] impl StateTransitionFactoryWasm { #[wasm_bindgen(constructor)] @@ -172,43 +213,4 @@ impl StateTransitionFactoryWasm { Self::state_transition_wasm_from_factory_result(result) } - - fn state_transition_wasm_from_factory_result( - result: Result, - ) -> Result { - match result { - Ok(state_transition) => match state_transition { - StateTransition::DataContractCreate(st) => { - Ok(DataContractCreateTransitionWasm::from(st).into()) - } - StateTransition::DataContractUpdate(st) => { - Ok(DataContractUpdateTransitionWasm::from(st).into()) - } - StateTransition::IdentityCreate(st) => { - Ok(IdentityCreateTransitionWasm::from(st).into()) - } - StateTransition::IdentityUpdate(st) => { - Ok(IdentityUpdateTransitionWasm::from(st).into()) - } - StateTransition::IdentityTopUp(st) => { - Ok(IdentityTopUpTransitionWasm::from(st).into()) - } - StateTransition::DocumentsBatch(st) => { - Ok(DocumentsBatchTransitionWASM::from(st).into()) - } - _ => Err("Unsupported state transition type".into()), - }, - Err(dpp::ProtocolError::StateTransitionError(e)) => match e { - StateTransitionError::InvalidStateTransitionError { - errors, - raw_state_transition, - } => Err(InvalidStateTransitionErrorWasm::new( - errors, - serde_wasm_bindgen::to_value(&raw_state_transition)?, - ) - .into()), - }, - Err(other) => Err(from_dpp_err(other)), - } - } } diff --git a/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js b/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js index e99cb705041..3dda287b469 100644 --- a/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js +++ b/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js @@ -45,7 +45,7 @@ describe('DocumentFacade', () => { stateRepositoryMock.fetchDataContract.resolves(dataContract); blsAdapter = await getBlsAdapterMock(); - dpp = new DashPlatformProtocol(blsAdapter, stateRepositoryMock); + dpp = new DashPlatformProtocol({}, blsAdapter, stateRepositoryMock); documentsJs = getDocumentsFixture(dataContractJs); documents = documentsJs.map((d) => { diff --git a/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js b/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js index d0a93bef855..16674bd4125 100644 --- a/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js +++ b/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js @@ -1,25 +1,16 @@ const { PrivateKey } = require('@dashevo/dashcore-lib'); -const DashPlatformProtocol = require('@dashevo/dpp/lib/DashPlatformProtocol'); - -const DataContractCreateTransition = require('@dashevo/dpp/lib/dataContract/stateTransition/DataContractCreateTransition/DataContractCreateTransition'); - -const ValidationResult = require('@dashevo/dpp/lib/validation/ValidationResult'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); const getIdentityCreateTransitionFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityCreateTransitionFixture'); const createStateRepositoryMock = require('@dashevo/dpp/lib/test/mocks/createStateRepositoryMock'); -const DataContractFactory = require('@dashevo/dpp/lib/dataContract/DataContractFactory'); -const DocumentFactory = require('@dashevo/dpp/lib/document/DocumentFactory'); - -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); +const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); +const getDocumentsFixture = require('../../../lib/test/fixtures/getDocumentsFixture'); +const getDataContractFixture = require('../../../lib/test/fixtures/getDataContractFixture'); -const MissingOptionError = require('@dashevo/dpp/lib/errors/MissingOptionError'); -const createDPPMock = require('@dashevo/dpp/lib/test/mocks/createDPPMock'); -const SomeConsensusError = require('@dashevo/dpp/lib/test/mocks/SomeConsensusError'); +const { default: loadWasmDpp } = require('../../../dist'); +const getBlsAdapterMock = require('../../../lib/test/mocks/getBlsAdapterMock'); +const generateRandomIdentifierAsync = require('../../../lib/test/utils/generateRandomIdentifierAsync'); describe('StateTransitionFacade', () => { let dpp; @@ -28,85 +19,99 @@ describe('StateTransitionFacade', () => { let stateRepositoryMock; let dataContract; let identityPublicKey; + let identity; + + let DashPlatformProtocol; + let DataContractFactory; + let DataContractValidator; + let DataContractCreateTransition; + let Identity; + let ValidationResult; + let IdentityPublicKey; + let DocumentFactory; + let DocumentValidator; + let ProtocolVersionValidator; + let IdentityCreateTransition; + let UnsupportedProtocolVersionError; + let InvalidStateTransitionSignatureError; + let DataContractAlreadyPresentError; + let BalanceIsNotEnoughError; + + before(async () => { + ({ + DashPlatformProtocol, + ValidationResult, + DataContractCreateTransition, + Identity, + DataContractValidator, + DataContractFactory, + IdentityPublicKey, + DocumentFactory, + DocumentValidator, + ProtocolVersionValidator, + IdentityCreateTransition, + UnsupportedProtocolVersionError, + InvalidStateTransitionSignatureError, + DataContractAlreadyPresentError, + BalanceIsNotEnoughError, + } = await loadWasmDpp()); + }); beforeEach(async function beforeEach() { const privateKeyModel = new PrivateKey(); - const privateKey = privateKeyModel.toWIF(); + const privateKey = privateKeyModel.toBuffer(); const publicKey = privateKeyModel.toPublicKey().toBuffer(); const publicKeyId = 1; - identityPublicKey = new IdentityPublicKey() - .setId(publicKeyId) - .setType(IdentityPublicKey.TYPES.ECDSA_SECP256K1) - .setData(publicKey) - .setPurpose(IdentityPublicKey.PURPOSES.AUTHENTICATION) - .setSecurityLevel(IdentityPublicKey.PURPOSES.MASTER); + identityPublicKey = new IdentityPublicKey({ + id: publicKeyId, + type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, + data: publicKey, + purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, + securityLevel: IdentityPublicKey.SECURITY_LEVELS.HIGH, + readOnly: false, + }); - dataContract = getDataContractFixture(); + dataContract = await getDataContractFixture(); - const dataContractFactory = new DataContractFactory(createDPPMock(), undefined); + const dataContractValidator = new DataContractValidator(); + const dataContractFactory = new DataContractFactory(1, dataContractValidator); - dataContractCreateTransition = dataContractFactory.createDataContractCreateTransition( + dataContractCreateTransition = await dataContractFactory.createDataContractCreateTransition( dataContract, ); - await dataContractCreateTransition.sign(identityPublicKey, privateKey); + await dataContractCreateTransition.sign( + identityPublicKey, + privateKey, + ); - const documentFactory = new DocumentFactory(createDPPMock(), undefined, undefined); + const documentValidator = new DocumentValidator(new ProtocolVersionValidator()); + const documentFactory = new DocumentFactory(1, documentValidator, stateRepositoryMock); documentsBatchTransition = documentFactory.createStateTransition({ - create: getDocumentsFixture(dataContract), + create: await getDocumentsFixture(dataContract), }); await documentsBatchTransition.sign(identityPublicKey, privateKey); - const getPublicKeyById = this.sinonSandbox.stub().returns(identityPublicKey); - const getBalance = this.sinonSandbox.stub().returns(10000); - - const identity = { - getPublicKeyById, - type: 2, - getBalance, - }; + const identityObjectJS = getIdentityFixture().toObject(); + identityObjectJS.id = await generateRandomIdentifierAsync(); + identityObjectJS.balance = 10000000; + identity = new Identity(identityObjectJS); + identity.setPublicKeys([identityPublicKey]); const blockTime = Date.now(); stateRepositoryMock = createStateRepositoryMock(this.sinonSandbox); stateRepositoryMock.fetchIdentity.resolves(identity); stateRepositoryMock.fetchLatestPlatformBlockTime.resolves(blockTime); + stateRepositoryMock.fetchDataContract.resolves(null); - dpp = new DashPlatformProtocol({ - stateRepository: stateRepositoryMock, - }); + const blsAdapter = await getBlsAdapterMock(); - await dpp.initialize(); + dpp = new DashPlatformProtocol({}, blsAdapter, stateRepositoryMock); }); describe('createFromObject', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.createFromObject( - dataContractCreateTransition.toObject(), - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - - it('should skip checking for state repository if skipValidation is set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - await dpp.stateTransition.createFromObject( - dataContractCreateTransition.toObject(), - { skipValidation: true }, - ); - }); - it('should create State Transition from plain object', async () => { const result = await dpp.stateTransition.createFromObject( dataContractCreateTransition.toObject(), @@ -119,32 +124,6 @@ describe('StateTransitionFacade', () => { }); describe('createFromBuffer', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.createFromBuffer( - dataContractCreateTransition.toBuffer(), - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - - it('should skip checking for state repository if skipValidation is set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - await dpp.stateTransition.createFromBuffer( - dataContractCreateTransition.toBuffer(), - { skipValidation: true }, - ); - }); - it('should create State Transition from string', async () => { const result = await dpp.stateTransition.createFromBuffer( dataContractCreateTransition.toBuffer(), @@ -157,94 +136,53 @@ describe('StateTransitionFacade', () => { }); describe('validate', () => { - let validateBasicSpy; - let validateSignatureSpy; - let validateFeeSpy; - let validateStateSpy; - - beforeEach(function beforeEach() { - validateBasicSpy = this.sinonSandbox.spy( - dpp.stateTransition, - 'validateBasic', - ); - - validateSignatureSpy = this.sinonSandbox.spy( - dpp.stateTransition, - 'validateSignature', - ); - - validateFeeSpy = this.sinonSandbox.spy( - dpp.stateTransition, - 'validateFee', - ); - - validateStateSpy = this.sinonSandbox.spy( - dpp.stateTransition, - 'validateState', - ); - }); - it('should return invalid result if State Transition structure is invalid', async () => { const rawStateTransition = dataContractCreateTransition.toObject(); - delete rawStateTransition.protocolVersion; + rawStateTransition.protocolVersion = 100; const result = await dpp.stateTransition.validate(rawStateTransition); expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.false(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(rawStateTransition); - expect(validateSignatureSpy).to.not.be.called(); - expect(validateFeeSpy).to.not.be.called(); - expect(validateStateSpy).to.not.be.called(); + const errors = result.getErrors(); + expect(errors).to.have.lengthOf(1); + expect(errors[0]).to.be.instanceOf(UnsupportedProtocolVersionError); }); it('should return invalid result if State Transition signature is invalid', async () => { - dataContractCreateTransition.signature = Buffer.alloc(65).fill(1); + const rawStateTransition = dataContractCreateTransition.toObject(); + rawStateTransition.signature = Buffer.alloc(65).fill(1); - const result = await dpp.stateTransition.validate(dataContractCreateTransition); + const result = await dpp.stateTransition.validate(rawStateTransition); expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.false(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateSignatureSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateFeeSpy).to.not.be.called(); - expect(validateStateSpy).to.not.be.called(); + const errors = result.getErrors(); + expect(errors).to.have.lengthOf(1); + expect(errors[0]).to.be.instanceOf(InvalidStateTransitionSignatureError); }); it('should return invalid result if not enough balance to pay fee for State Transition', async () => { - const consensusError = new SomeConsensusError('error'); - - dpp.stateTransition.validateStateTransitionFee = () => new ValidationResult([consensusError]); - + identity.setBalance(0); const result = await dpp.stateTransition.validate(dataContractCreateTransition); expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.false(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateSignatureSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateFeeSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateStateSpy).to.not.be.called(); + const errors = result.getErrors(); + expect(errors).to.have.lengthOf(1); + expect(errors[0]).to.be.instanceOf(BalanceIsNotEnoughError); }); it('should return invalid result if State Transition is invalid against state', async () => { - const consensusError = new SomeConsensusError('error'); - - dpp.stateTransition.validateStateTransitionState = () => ( - new ValidationResult([consensusError]) - ); + stateRepositoryMock.fetchDataContract.resolves(dataContract); const result = await dpp.stateTransition.validate(dataContractCreateTransition); expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.false(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateSignatureSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateFeeSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateStateSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); + const errors = result.getErrors(); + expect(errors).to.have.lengthOf(1); + expect(errors[0]).to.be.instanceOf(DataContractAlreadyPresentError); }); it('should validate DataContractCreateTransition', async () => { @@ -254,53 +192,22 @@ describe('StateTransitionFacade', () => { expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.true(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateSignatureSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateFeeSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); - expect(validateStateSpy).to.be.calledOnceWithExactly(dataContractCreateTransition); }); - it('should validate DocumentsBatchTransition', async function it() { + it('should validate DocumentsBatchTransition', async () => { stateRepositoryMock.fetchDocuments.resolves([]); stateRepositoryMock.fetchDataContract.resolves(dataContract); - stateRepositoryMock.fetchIdentity.resolves({ - getPublicKeyById: this.sinonSandbox.stub().returns(identityPublicKey), - getBalance: this.sinonSandbox.stub().returns(10000), - }); - const result = await dpp.stateTransition.validate( documentsBatchTransition, ); expect(result).to.be.an.instanceOf(ValidationResult); expect(result.isValid()).to.be.true(); - - expect(validateBasicSpy).to.be.calledOnceWithExactly(documentsBatchTransition); - expect(validateSignatureSpy).to.be.calledOnceWithExactly(documentsBatchTransition); - expect(validateFeeSpy).to.be.calledOnceWithExactly(documentsBatchTransition); - expect(validateStateSpy).to.be.calledOnceWithExactly(documentsBatchTransition); }); }); describe('validateBasic', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.validateBasic( - dataContractCreateTransition.toObject(), - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - it('should validate State Transition', async () => { const result = await dpp.stateTransition.validateBasic( dataContractCreateTransition.toObject(), @@ -312,22 +219,6 @@ describe('StateTransitionFacade', () => { }); describe('validateSignature', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.validateSignature( - dataContractCreateTransition, - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - it('should validate identity signed State Transition', async () => { const result = await dpp.stateTransition.validateSignature( dataContractCreateTransition, @@ -342,10 +233,13 @@ describe('StateTransitionFacade', () => { 'af432c476f65211f45f48f1d42c9c0b497e56696aa1736b40544ef1a496af837', ); - const identityCreateTransition = getIdentityCreateTransitionFixture(oneTimePrivateKey); + const identityCreateTransitionJS = getIdentityCreateTransitionFixture(oneTimePrivateKey); + const identityCreateTransition = new IdentityCreateTransition( + identityCreateTransitionJS.toObject(), + ); await identityCreateTransition.signByPrivateKey( - oneTimePrivateKey, + oneTimePrivateKey.toBuffer(), IdentityPublicKey.TYPES.ECDSA_SECP256K1, ); @@ -359,22 +253,6 @@ describe('StateTransitionFacade', () => { }); describe('validateFee', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.validateFee( - dataContractCreateTransition, - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - it('should validate State Transition', async () => { const result = await dpp.stateTransition.validateFee( dataContractCreateTransition, @@ -386,22 +264,6 @@ describe('StateTransitionFacade', () => { }); describe('validateState', () => { - it('should throw MissingOption if stateRepository is not set', async () => { - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - try { - await dpp.stateTransition.validateState( - dataContractCreateTransition, - ); - - expect.fail('MissingOption should be thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingOptionError); - expect(e.getOptionName()).to.equal('stateRepository'); - } - }); - it('should validate State Transition', async () => { const result = await dpp.stateTransition.validateState( dataContractCreateTransition,