Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ pub async fn validate_state_transition_key_signature<SR: StateRepositoryLike>(
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,
});
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
DataContractParameters, DataContractWasm, StateTransitionExecutionContextWasm,
};

#[derive(Clone)]
#[wasm_bindgen(js_name=DataContractCreateTransition)]
pub struct DataContractCreateTransitionWasm(DataContractCreateTransition);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::{
DataContractParameters, DataContractWasm, StateTransitionExecutionContextWasm,
};

#[derive(Clone)]
#[wasm_bindgen(js_name=DataContractUpdateTransition)]
pub struct DataContractUpdateTransitionWasm(DataContractUpdateTransition);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
};
pub mod document_transition;

#[derive(Debug)]
#[derive(Clone, Debug)]
#[wasm_bindgen(js_name = DocumentsBatchTransition)]
pub struct DocumentsBatchTransitionWASM(DocumentsBatchTransition);

Expand All @@ -48,6 +48,12 @@ pub struct ToObjectOptions {
skip_identifiers_conversion: bool,
}

impl From<DocumentsBatchTransitionWASM> for DocumentsBatchTransition {
fn from(val: DocumentsBatchTransitionWASM) -> Self {
val.0
}
}

#[wasm_bindgen(js_class=DocumentsContainer)]
impl DocumentsContainer {
#[wasm_bindgen(constructor)]
Expand Down
93 changes: 93 additions & 0 deletions packages/wasm-dpp/src/state_transition/conversion.rs
Original file line number Diff line number Diff line change
@@ -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<StateTransition, JsValue> {
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::<js_sys::Function>()
.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<DataContractCreateTransitionWasm> =
generic_of_js_val::<DataContractCreateTransitionWasm>(
js_value,
"DataContractCreateTransition",
)?;

Ok(StateTransition::DataContractCreate(st.clone().into()))
}
StateTransitionType::DocumentsBatch => {
let st: Ref<DocumentsBatchTransitionWASM> = generic_of_js_val::<
DocumentsBatchTransitionWASM,
>(
js_value, "DocumentsBatchTransition"
)?;

Ok(StateTransition::DocumentsBatch(st.clone().into()))
}
StateTransitionType::IdentityCreate => {
let st: Ref<IdentityCreateTransitionWasm> = generic_of_js_val::<
IdentityCreateTransitionWasm,
>(
js_value, "IdentityCreateTransition"
)?;

Ok(StateTransition::IdentityCreate(st.clone().into()))
}
StateTransitionType::IdentityTopUp => {
let st: Ref<IdentityTopUpTransitionWasm> = generic_of_js_val::<
IdentityTopUpTransitionWasm,
>(
js_value, "IdentityTopUpTransition"
)?;

Ok(StateTransition::IdentityTopUp(st.clone().into()))
}
StateTransitionType::DataContractUpdate => {
let st: Ref<DataContractUpdateTransitionWasm> =
generic_of_js_val::<DataContractUpdateTransitionWasm>(
js_value,
"DataContractUpdateTransition",
)?;

Ok(StateTransition::DataContractUpdate(st.clone().into()))
}
StateTransitionType::IdentityUpdate => {
let st: Ref<IdentityUpdateTransitionWasm> = generic_of_js_val::<
IdentityUpdateTransitionWasm,
>(
js_value, "IdentityUpdateTransition"
)?;

Ok(StateTransition::IdentityUpdate(st.clone().into()))
}
_ => Err(JsError::new("Unsupported state transition type").into()),
}
}
5 changes: 5 additions & 0 deletions packages/wasm-dpp/src/state_transition/mod.rs
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
1 change: 1 addition & 0 deletions packages/wasm-dpp/src/state_transition/validation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod validate_state_transition_key_signature;
Original file line number Diff line number Diff line change
@@ -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<ExternalStateRepositoryLikeWrapper>,
);

impl From<StateTransitionKeySignatureValidator<ExternalStateRepositoryLikeWrapper>>
for StateTransitionKeySignatureValidatorWasm
{
fn from(
validator: StateTransitionKeySignatureValidator<ExternalStateRepositoryLikeWrapper>,
) -> 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<ValidationResultWasm, JsValue> {
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())
}
}
Original file line number Diff line number Diff line change
@@ -1,86 +1,113 @@
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,
);

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),
);
});
});