diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs index c08d2b76cc6..56c79fec42a 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs @@ -74,14 +74,14 @@ pub async fn validate_state_transition_key_signature( let tmp_execution_context = StateTransitionExecutionContext::default(); // Target identity must exist - let identity = state_repository - .fetch_identity(target_identity_id, &tmp_execution_context) + let balance = state_repository + .fetch_identity_balance(target_identity_id, &tmp_execution_context) .await?; // Collect operations back from temporary context execution_context.add_operations(tmp_execution_context.get_operations()); - if identity.is_none() { + if balance.is_none() { result.add_error(SignatureError::IdentityNotFoundError { identity_id: *target_identity_id, }); @@ -300,7 +300,7 @@ mod test { .into(); state_repository - .expect_fetch_identity() + .expect_fetch_identity_balance() .return_once(|_, _| Ok(None)); let result = validate_state_transition_key_signature( 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 fb85588dadd..aae36cf54cc 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 @@ -20,6 +20,7 @@ use crate::{ DataContractParameters, DataContractWasm, StateTransitionExecutionContextWasm, }; +#[derive(Clone)] #[wasm_bindgen(js_name=DataContractCreateTransition)] pub struct DataContractCreateTransitionWasm(DataContractCreateTransition); diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs index 6db939dbf2c..c885937196a 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs @@ -19,6 +19,7 @@ use crate::{ DataContractParameters, DataContractWasm, StateTransitionExecutionContextWasm, }; +#[derive(Clone)] #[wasm_bindgen(js_name=DataContractUpdateTransition)] pub struct DataContractUpdateTransitionWasm(DataContractUpdateTransition); 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 f5fb53f967e..6d73dc6ae01 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 @@ -26,7 +26,7 @@ use crate::{ }; pub mod document_transition; -#[derive(Debug)] +#[derive(Clone, Debug)] #[wasm_bindgen(js_name = DocumentsBatchTransition)] pub struct DocumentsBatchTransitionWASM(DocumentsBatchTransition); @@ -48,6 +48,12 @@ pub struct ToObjectOptions { skip_identifiers_conversion: bool, } +impl From for DocumentsBatchTransition { + fn from(val: DocumentsBatchTransitionWASM) -> Self { + val.0 + } +} + #[wasm_bindgen(js_class=DocumentsContainer)] impl DocumentsContainer { #[wasm_bindgen(constructor)] diff --git a/packages/wasm-dpp/src/state_transition/conversion.rs b/packages/wasm-dpp/src/state_transition/conversion.rs new file mode 100644 index 00000000000..80266630705 --- /dev/null +++ b/packages/wasm-dpp/src/state_transition/conversion.rs @@ -0,0 +1,93 @@ +use crate::utils::generic_of_js_val; +use crate::{ + DataContractCreateTransitionWasm, DataContractUpdateTransitionWasm, + DocumentsBatchTransitionWASM, IdentityCreateTransitionWasm, IdentityTopUpTransitionWasm, + IdentityUpdateTransitionWasm, +}; +use dpp::state_transition::{StateTransition, StateTransitionType}; +use std::convert::TryInto; +use wasm_bindgen::__rt::Ref; +use wasm_bindgen::{JsCast, JsError, JsValue}; + +pub fn create_state_transition_from_wasm_instance( + js_value: &JsValue, +) -> Result { + let get_type_value = js_sys::Reflect::get(js_value, &JsValue::from_str("getType")) + .map_err(|_| JsError::new("No 'getType' property present in state transition"))?; + + let get_type_function: &js_sys::Function = get_type_value + .dyn_ref::() + .ok_or_else(|| JsError::new("'getType' is not a function"))?; + + let raw_state_transition_type = get_type_function + .call0(js_value)? + .as_f64() + .ok_or(JsError::new("Error calling 'getType' function"))? + as u8; + + let state_transition_type: StateTransitionType = + raw_state_transition_type.try_into().map_err(|_| { + JsError::new(&format!( + "Unknown state transition type: {}", + raw_state_transition_type + )) + })?; + + match state_transition_type { + StateTransitionType::DataContractCreate => { + let st: Ref = + generic_of_js_val::( + js_value, + "DataContractCreateTransition", + )?; + + Ok(StateTransition::DataContractCreate(st.clone().into())) + } + StateTransitionType::DocumentsBatch => { + let st: Ref = generic_of_js_val::< + DocumentsBatchTransitionWASM, + >( + js_value, "DocumentsBatchTransition" + )?; + + Ok(StateTransition::DocumentsBatch(st.clone().into())) + } + StateTransitionType::IdentityCreate => { + let st: Ref = generic_of_js_val::< + IdentityCreateTransitionWasm, + >( + js_value, "IdentityCreateTransition" + )?; + + Ok(StateTransition::IdentityCreate(st.clone().into())) + } + StateTransitionType::IdentityTopUp => { + let st: Ref = generic_of_js_val::< + IdentityTopUpTransitionWasm, + >( + js_value, "IdentityTopUpTransition" + )?; + + Ok(StateTransition::IdentityTopUp(st.clone().into())) + } + StateTransitionType::DataContractUpdate => { + let st: Ref = + generic_of_js_val::( + js_value, + "DataContractUpdateTransition", + )?; + + Ok(StateTransition::DataContractUpdate(st.clone().into())) + } + StateTransitionType::IdentityUpdate => { + let st: Ref = generic_of_js_val::< + IdentityUpdateTransitionWasm, + >( + js_value, "IdentityUpdateTransition" + )?; + + Ok(StateTransition::IdentityUpdate(st.clone().into())) + } + _ => Err(JsError::new("Unsupported state transition type").into()), + } +} diff --git a/packages/wasm-dpp/src/state_transition/mod.rs b/packages/wasm-dpp/src/state_transition/mod.rs index 6a9c5d6cf7b..c6a8c6717e7 100644 --- a/packages/wasm-dpp/src/state_transition/mod.rs +++ b/packages/wasm-dpp/src/state_transition/mod.rs @@ -1,6 +1,11 @@ use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use wasm_bindgen::prelude::*; +pub mod validation; +pub use validation::*; + +pub(crate) mod conversion; + #[wasm_bindgen(js_name=StateTransitionExecutionContext)] pub struct StateTransitionExecutionContextWasm(StateTransitionExecutionContext); diff --git a/packages/wasm-dpp/src/state_transition/validation/mod.rs b/packages/wasm-dpp/src/state_transition/validation/mod.rs new file mode 100644 index 00000000000..5d88fff9cb9 --- /dev/null +++ b/packages/wasm-dpp/src/state_transition/validation/mod.rs @@ -0,0 +1 @@ +pub mod validate_state_transition_key_signature; diff --git a/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs b/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs new file mode 100644 index 00000000000..0188aa7c445 --- /dev/null +++ b/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs @@ -0,0 +1,62 @@ +use crate::errors::from_dpp_err; +use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; +use crate::validation::ValidationResultWasm; +use dpp::identity::state_transition::asset_lock_proof::{ + AssetLockPublicKeyHashFetcher, AssetLockTransactionOutputFetcher, +}; +use dpp::state_transition::validation::validate_state_transition_key_signature::StateTransitionKeySignatureValidator; +use dpp::validation::AsyncDataValidator; +use std::sync::Arc; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +#[wasm_bindgen(js_name=StateTransitionKeySignatureValidator)] +pub struct StateTransitionKeySignatureValidatorWasm( + StateTransitionKeySignatureValidator, +); + +impl From> + for StateTransitionKeySignatureValidatorWasm +{ + fn from( + validator: StateTransitionKeySignatureValidator, + ) -> Self { + Self(validator) + } +} + +#[wasm_bindgen(js_class=StateTransitionKeySignatureValidator)] +impl StateTransitionKeySignatureValidatorWasm { + #[wasm_bindgen(constructor)] + pub fn new(state_repository: ExternalStateRepositoryLike) -> Self { + let state_repository_wrapper = + Arc::new(ExternalStateRepositoryLikeWrapper::new(state_repository)); + + let tx_output_fetcher = + AssetLockTransactionOutputFetcher::new(state_repository_wrapper.clone()); + + let public_key_hash_fetcher = + AssetLockPublicKeyHashFetcher::new(state_repository_wrapper.clone(), tx_output_fetcher); + + StateTransitionKeySignatureValidator::new(state_repository_wrapper, public_key_hash_fetcher) + .into() + } + + #[wasm_bindgen] + pub async fn validate( + &self, + state_transition: JsValue, + ) -> Result { + let state_transition = + super::super::conversion::create_state_transition_from_wasm_instance( + &state_transition, + )?; + + let validation_result = self + .0 + .validate(&state_transition) + .await + .map_err(from_dpp_err)?; + Ok(validation_result.map(|_| JsValue::undefined()).into()) + } +} diff --git a/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionKeySignatureFactory.spec.js b/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionKeySignatureFactory.spec.js index d6bb452fc7c..53f4fbc5a4f 100644 --- a/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionKeySignatureFactory.spec.js +++ b/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionKeySignatureFactory.spec.js @@ -1,69 +1,84 @@ -const validateStateTransitionKeySignatureFactory = require('@dashevo/dpp/lib/stateTransition/validation/validateStateTransitionKeySignatureFactory'); +const { Transaction, PrivateKey, Script } = require('@dashevo/dashcore-lib'); const getIdentityCreateTransitionFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityCreateTransitionFixture'); -const InvalidStateTransitionSignatureError = require('@dashevo/dpp/lib/errors/consensus/signature/InvalidStateTransitionSignatureError'); +const createStateRepositoryMock = require('@dashevo/dpp/lib/test/mocks/createStateRepositoryMock'); +const getIdentityTopUpTransitionFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityTopUpTransitionFixture'); +const { expectValidationError } = require('../../../../lib/test/expect/expectError'); -const { expectValidationError } = require('@dashevo/dpp/lib/test/expect/expectError'); - -const ValidationResult = require('@dashevo/dpp/lib/validation/ValidationResult'); -const StateTransitionExecutionContext = require('@dashevo/dpp/lib/stateTransition/StateTransitionExecutionContext'); +const { default: loadWasmDpp } = require('../../../../dist'); describe('validateStateTransitionKeySignatureFactory', () => { - let publicKeyHash; let stateTransition; - let stateTransitionHash; - let verifyHashSignatureMock; + let stateRepositoryMock; let validateStateTransitionKeySignature; - let fetchAssetLockPublicKeyHashMock; - let executionContext; - - beforeEach(function beforeEach() { - publicKeyHash = Buffer.alloc(20).fill(1); - stateTransition = getIdentityCreateTransitionFixture(); - stateTransitionHash = stateTransition.hash({ skipSignature: true }); + let InvalidStateTransitionSignatureError; + let StateTransitionKeySignatureValidator; + let StateTransitionExecutionContext; + let IdentityCreateTransition; + let IdentityTopUpTransition; + let IdentityNotFoundError; + let ValidationResult; + + before(async () => { + ({ + InvalidStateTransitionSignatureError, + StateTransitionKeySignatureValidator, + StateTransitionExecutionContext, + IdentityCreateTransition, + IdentityTopUpTransition, + IdentityNotFoundError, + ValidationResult, + } = await loadWasmDpp()); + }); - executionContext = new StateTransitionExecutionContext(); + beforeEach(function beforeEach() { + const stateTransitionJS = getIdentityCreateTransitionFixture(); + const rawStateTransition = stateTransitionJS.toObject(); - stateTransition.setExecutionContext(executionContext); + stateTransition = new IdentityCreateTransition(rawStateTransition); - verifyHashSignatureMock = this.sinonSandbox.stub(); + stateRepositoryMock = createStateRepositoryMock(this.sinonSandbox); - fetchAssetLockPublicKeyHashMock = this.sinonSandbox.stub().resolves(publicKeyHash); + const validator = new StateTransitionKeySignatureValidator(stateRepositoryMock); - validateStateTransitionKeySignature = validateStateTransitionKeySignatureFactory( - verifyHashSignatureMock, - fetchAssetLockPublicKeyHashMock, - ); + validateStateTransitionKeySignature = (st) => validator.validate(st); }); it('should return invalid result if signature is not valid', async () => { - verifyHashSignatureMock.returns(false); - const result = await validateStateTransitionKeySignature( stateTransition, ); - expectValidationError(result, InvalidStateTransitionSignatureError); + await expectValidationError(result, InvalidStateTransitionSignatureError); const [error] = result.getErrors(); expect(error.getCode()).to.equal(2002); - - expect(fetchAssetLockPublicKeyHashMock).to.be.calledOnceWithExactly( - stateTransition.getAssetLockProof(), - executionContext, - ); - - expect(verifyHashSignatureMock).to.be.calledOnceWithExactly( - stateTransitionHash, - stateTransition.getSignature(), - publicKeyHash, - ); }); it('should return valid result if signature is valid', async () => { - verifyHashSignatureMock.returns(true); + const rawStateTransition = stateTransition.toObject(); + + // Sign state transition and provide relevant public key to transaction output + const { transaction: rawTransaction } = rawStateTransition.assetLockProof; + + const transaction = new Transaction(Buffer.from(rawTransaction)); + + const privateKey = new PrivateKey('9b67f852093bc61cea0eeca38599dbfba0de28574d2ed9b99d10d33dc1bde7b2'); + const publicKey = privateKey.toPublicKey(); + + transaction.outputs[0] + .setScript(Script.buildDataOut(publicKey.hash)); + + rawStateTransition.assetLockProof.transaction = transaction.toBuffer(); + + stateTransition = new IdentityCreateTransition(rawStateTransition); + + await stateTransition.signByPrivateKey( + privateKey.toBuffer(), + 0, + ); const result = await validateStateTransitionKeySignature( stateTransition, @@ -71,16 +86,28 @@ describe('validateStateTransitionKeySignatureFactory', () => { expect(result).to.be.instanceof(ValidationResult); expect(result.isValid()).to.be.true(); + }); + + it('should return IdentityNotFoundError if identity not exist on topup transaction', async function () { + const stateTransitionJS = getIdentityTopUpTransitionFixture(); + stateTransition = new IdentityTopUpTransition(stateTransitionJS.toObject()); + stateRepositoryMock.fetchIdentityBalance.resolves(undefined); - expect(fetchAssetLockPublicKeyHashMock).to.be.calledOnceWithExactly( - stateTransition.getAssetLockProof(), - executionContext, + const result = await validateStateTransitionKeySignature( + stateTransition, ); - expect(verifyHashSignatureMock).to.be.calledOnceWithExactly( - stateTransitionHash, - stateTransition.getSignature(), - publicKeyHash, + await expectValidationError(result, IdentityNotFoundError); + + const [error] = result.getErrors(); + + expect(error.getCode()).to.equal(2000); + + const { match } = this.sinonSandbox; + expect(stateRepositoryMock.fetchIdentityBalance).to.be.calledOnceWithExactly( + match((identityId) => Buffer.from(identityId.toBuffer()) + .equals(stateTransition.getIdentityId().toBuffer())), + match.instanceOf(StateTransitionExecutionContext), ); }); });