From 7bfa6392fa3e1c7c11fe6ca1e6d0d0087f558339 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Tue, 28 Mar 2023 16:56:11 -0400 Subject: [PATCH 1/2] feat(#1095): new branch from main and merge old stateful-storage-signed-extension branch in #1095 --- Cargo.lock | 1 + node/cli/Cargo.toml | 2 +- node/cli/src/benchmarking.rs | 3 + pallets/stateful-storage/src/benchmarking.rs | 36 ++- pallets/stateful-storage/src/lib.rs | 78 ++++- .../src/stateful_signed_extension.rs | 294 ++++++++++++++++++ runtime/frequency/src/lib.rs | 2 + 7 files changed, 401 insertions(+), 15 deletions(-) create mode 100644 pallets/stateful-storage/src/stateful_signed_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 9f2c71192b..cc62762f9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2747,6 +2747,7 @@ dependencies = [ "log", "pallet-balances", "pallet-msa", + "pallet-stateful-storage", "pallet-transaction-payment", "parity-scale-codec", "polkadot-cli", diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index b4304cea63..29923f9296 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -23,6 +23,7 @@ common-runtime = { package = "common-runtime", path = "../../runtime/common", de frequency-runtime = { package = "frequency-runtime", path = "../../runtime/frequency", default-features = false } frequency-service = { package = "frequency-service", path = "../service", default-features = false, optional = true } pallet-msa = { package = "pallet-msa", path = "../../pallets/msa", default-features = false } +pallet-stateful-storage = { package = "pallet-stateful-storage", path = "../../pallets/stateful-storage", default-features = false } # Substrate frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", optional = true, branch = "polkadot-v0.9.36" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.36" } @@ -85,4 +86,3 @@ all-frequency-features = [ "frequency-rococo-testnet", "frequency-service/all-frequency-features" ] - diff --git a/node/cli/src/benchmarking.rs b/node/cli/src/benchmarking.rs index 8b38a55d00..7083a5ed9a 100644 --- a/node/cli/src/benchmarking.rs +++ b/node/cli/src/benchmarking.rs @@ -14,6 +14,7 @@ use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; use pallet_balances::Call as BalancesCall; use pallet_msa; +use pallet_stateful_storage; use pallet_transaction_payment; use sp_inherents::InherentDataProvider; use sp_timestamp; @@ -129,6 +130,7 @@ pub fn create_benchmark_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), pallet_msa::CheckFreeExtrinsicUse::::new(), + pallet_stateful_storage::StatefulSignedExtension::::new(), ); let raw_payload = sp_runtime::generic::SignedPayload::from_raw( @@ -144,6 +146,7 @@ pub fn create_benchmark_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/pallets/stateful-storage/src/benchmarking.rs b/pallets/stateful-storage/src/benchmarking.rs index b118577efc..fd521468b1 100644 --- a/pallets/stateful-storage/src/benchmarking.rs +++ b/pallets/stateful-storage/src/benchmarking.rs @@ -111,7 +111,11 @@ benchmarks! { ITEMIZED_STORAGE_PREFIX, &key).unwrap().unwrap_or_default().get_hash(); let actions = itemized_actions_populate::(num_of_items, T::MaxItemizedBlobSizeBytes::get() as usize, delete_actions); - }: _ (RawOrigin::Signed(caller), delegator_msa_id.into(), schema_id, content_hash, actions) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_itemized(&delegator_msa_id, &schema_id, &actions, false, &content_hash).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::apply_item_actions(RawOrigin::Signed(caller).into(), delegator_msa_id.into(), schema_id, content_hash, actions) + } verify { let page_result = get_itemized_page::(delegator_msa_id, schema_id); assert!(page_result.is_some()); @@ -132,7 +136,11 @@ benchmarks! { assert_ok!(create_schema::(PayloadLocation::Paginated)); assert_ok!(T::MsaBenchmarkHelper::add_key(provider_msa_id.into(), caller.clone())); assert_ok!(T::MsaBenchmarkHelper::set_delegation_relationship(provider_msa_id.into(), delegator_msa_id.into(), [schema_id].to_vec())); - }: _(RawOrigin::Signed(caller), delegator_msa_id.into(), schema_id, page_id, NONEXISTENT_PAGE_HASH, payload.try_into().unwrap()) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_paginated(&delegator_msa_id, &schema_id, &page_id, false, false, &NONEXISTENT_PAGE_HASH).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::upsert_page(RawOrigin::Signed(caller).into(), delegator_msa_id.into(), schema_id, page_id, NONEXISTENT_PAGE_HASH, payload.try_into().unwrap()) + } verify { let page_result = get_paginated_page::(delegator_msa_id, schema_id, page_id); assert!(page_result.is_some()); @@ -163,7 +171,11 @@ benchmarks! { PALLET_STORAGE_PREFIX, PAGINATED_STORAGE_PREFIX, &key).unwrap().unwrap().get_hash(); - }: _(RawOrigin::Signed(caller), delegator_msa_id.into(), schema_id, page_id, content_hash) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_paginated(&delegator_msa_id, &schema_id, &page_id, false, true, &content_hash).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::delete_page(RawOrigin::Signed(caller).into(), delegator_msa_id.into(), schema_id, page_id, content_hash) + } verify { let page_result = get_paginated_page::(delegator_msa_id, schema_id, page_id); assert!(page_result.is_none()); @@ -216,7 +228,11 @@ benchmarks! { }; let encode_data_new_key_data = wrap_binary_data(payload.encode()); let signature = delegator_account_public.sign(&encode_data_new_key_data).unwrap(); - }: _ (RawOrigin::Signed(caller), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_itemized(&payload.msa_id, &payload.schema_id, &payload.actions, true, &payload.target_hash).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::apply_item_actions_with_signature(RawOrigin::Signed(caller).into(), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + } verify { let page_result = get_itemized_page::(delegator_msa_id, schema_id); assert!(page_result.is_some()); @@ -253,7 +269,11 @@ benchmarks! { }; let encode_data_new_key_data = wrap_binary_data(payload.encode()); let signature = delegator_account_public.sign(&encode_data_new_key_data).unwrap(); - }: _(RawOrigin::Signed(caller), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_paginated(&payload.msa_id, &payload.schema_id, &payload.page_id, true, false, &payload.target_hash).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::upsert_page_with_signature(RawOrigin::Signed(caller).into(), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + } verify { let page_result = get_paginated_page::(delegator_msa_id, schema_id, page_id); assert!(page_result.is_some()); @@ -300,7 +320,11 @@ benchmarks! { }; let encode_data_new_key_data = wrap_binary_data(payload.encode()); let signature = delegator_account_public.sign(&encode_data_new_key_data).unwrap(); - }: _(RawOrigin::Signed(caller), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + }: { + // Explicity call SignedExtension checks because overhead benchmark will not trigger them. Map the returned error to any valid DispatchError. + let _ = StatefulStoragePallet::::validate_paginated(&payload.msa_id, &payload.schema_id, &payload.page_id, true, true, &payload.target_hash).map_err(|_| -> DispatchError {Error::::CorruptedState.into()})?; + StatefulStoragePallet::::delete_page_with_signature(RawOrigin::Signed(caller).into(), delegator_account.into(), MultiSignature::Sr25519(signature.into()), payload) + } verify { let page_result = get_paginated_page::(delegator_msa_id, schema_id, page_id); assert!(page_result.is_none()); diff --git a/pallets/stateful-storage/src/lib.rs b/pallets/stateful-storage/src/lib.rs index 93317989a3..48c31e007b 100644 --- a/pallets/stateful-storage/src/lib.rs +++ b/pallets/stateful-storage/src/lib.rs @@ -65,29 +65,40 @@ pub mod types; pub mod weights; +pub mod stateful_signed_extension; +pub use stateful_signed_extension::*; + use crate::{stateful_child_tree::StatefulChildTree, types::*}; +// Weird compiler issue--warns about unused PageId import when building, but if missing here, `cargo test` fails due to missing import. +#[allow(unused_imports)] +use common_primitives::stateful_storage::PageId; use common_primitives::{ - msa::{ - DelegatorId, MessageSourceId, MsaLookup, MsaValidator, ProviderId, SchemaGrantValidator, - }, + msa::{DelegatorId, MessageSourceId, MsaValidator, ProviderId, SchemaGrantValidator}, node::Verify, schema::{PayloadLocation, SchemaId, SchemaProvider, SchemaResponse, SchemaSetting}, stateful_storage::{ - ItemizedStoragePageResponse, ItemizedStorageResponse, PageHash, PageId, - PaginatedStorageResponse, + ItemizedStoragePageResponse, ItemizedStorageResponse, PageHash, PaginatedStorageResponse, }, utils::wrap_binary_data, }; -use frame_support::{dispatch::DispatchResult, ensure, pallet_prelude::*, traits::Get}; -use frame_system::pallet_prelude::*; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; pub use pallet::*; -use sp_core::{bounded::BoundedVec, crypto::AccountId32}; +use sp_core::bounded::BoundedVec; use sp_runtime::{traits::Convert, DispatchError, MultiSignature}; pub use weights::*; #[frame_support::pallet] pub mod pallet { use super::*; + use common_primitives::{ + msa::{MessageSourceId, MsaLookup, MsaValidator, SchemaGrantValidator}, + schema::{SchemaId, SchemaProvider}, + stateful_storage::{PageHash, PageId}, + }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_core::crypto::AccountId32; + use sp_runtime::{traits::Convert, MultiSignature}; #[pallet::config] pub trait Config: frame_system::Config { @@ -647,6 +658,57 @@ impl Pallet { Ok(()) } + /// Verify that the target page state is not stale and return the current page. + /// + /// # Errors + /// * [`Error::CorruptedState`] + /// * [`Error::StalePageState`] + /// + fn get_checked_itemized_page( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + target_hash: &PageHash, + ) -> Result<(ItemizedPage, PageHash), DispatchError> { + let page = StatefulChildTree::::try_read::<_, ItemizedPage>( + msa_id, + PALLET_STORAGE_PREFIX, + ITEMIZED_STORAGE_PREFIX, + &(*schema_id,), + ) + .map_err(|_| Error::::CorruptedState)? + .unwrap_or_default(); + + let prev_content_hash = page.get_hash(); + ensure!(*target_hash == prev_content_hash, Error::::StalePageState); + Ok((page, prev_content_hash)) + } + + /// Verify that the target page state is not stale + /// + /// # Errors + /// * [`Error::CorruptedState`] + /// * [`Error::StalePageState`] + /// + fn check_paginated_state( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + page_id: &PageId, + target_hash: &PageHash, + ) -> DispatchResult { + let page = StatefulChildTree::::try_read::<_, PaginatedPage>( + msa_id, + PALLET_STORAGE_PREFIX, + PAGINATED_STORAGE_PREFIX, + &(*schema_id, *page_id), + ) + .map_err(|_| Error::::CorruptedState)? + .unwrap_or_default(); + + let prev_content_hash = page.get_hash(); + ensure!(*target_hash == prev_content_hash, Error::::StalePageState); + Ok(()) + } + /// Updates an itemized storage by applying provided actions and deposit events /// /// # Events diff --git a/pallets/stateful-storage/src/stateful_signed_extension.rs b/pallets/stateful-storage/src/stateful_signed_extension.rs new file mode 100644 index 0000000000..1e3f80036a --- /dev/null +++ b/pallets/stateful-storage/src/stateful_signed_extension.rs @@ -0,0 +1,294 @@ +//! Substrate Signed Extension for validating requests to the stateful-storage pallet +use crate::{types::ItemAction, Call, Config, Pallet}; +use core::marker::PhantomData; + +use codec::{Decode, Encode}; +use common_primitives::{ + msa::MessageSourceId, + schema::{PayloadLocation, SchemaId}, + stateful_storage::{PageHash, PageId}, +}; +use frame_support::{ + dispatch::{DispatchInfo, Dispatchable}, + pallet_prelude::ValidTransaction, + traits::IsSubType, + BoundedVec, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError}, + DispatchError, +}; + +/// The SignedExtension trait is implemented on CheckFreeExtrinsicUse to validate that a provider +/// has not already been revoked if the calling extrinsic is revoking a provider to an MSA. The +/// purpose of this is to ensure that the revoke_delegation_by_delegator extrinsic cannot be +/// repeatedly called and flood the network. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct StatefulSignedExtension(PhantomData); + +/// Validator trait to be used in validating Stateful Storage +/// requests from a Signed Extension. +pub trait StatefulStorageValidate { + /// Validates the parameters to an Itemized extrinsic call + fn validate_itemized( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + actions: &BoundedVec, C::MaxItemizedActionsCount>, + is_payload_signed: bool, + target_hash: &PageHash, + ) -> TransactionValidity; + + /// Validates the parameters to a Paginated extrinsic call + fn validate_paginated( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + page_id: &PageId, + is_payload_signed: bool, + page_delete: bool, + target_hash: &PageHash, + ) -> TransactionValidity; +} + +impl StatefulStorageValidate for Pallet { + /// Validates the following criteria for an Itemized Stateful Storage extrinsic: + /// + /// * schema_id represents a valid schema + /// * schema `PayloadLocation` is `Paginated` + /// * schema allows signed payloads if `is_payload_signed` == true + /// * schema is not `AppendOnly` if `page_delete` == true + /// * page can be read/parsed + /// * known page state hash matches provided target hash + /// + /// Returns a `ValidTransaction` or wrapped [`pallet::Error`] + /// + /// # Arguments: + /// * account_id: the account id associated with the MSA owning the storage + /// * msa_id: the `MessageSourceAccount` + /// * schema_id: the `SchemaId` for the Paginated storage + /// * actions: Array of `ItemAction` representing adds/deletes to Itemized storage + /// * is_payload_signed: whether we were called with a signed payload + /// * target_hash: the content hash representing the known state of the Paginated storage. + /// + /// # Errors (as u8 wrapped by `InvalidTransaction::Custom`) + /// * [`Error::InvalidSchemaId`] + /// * [`Error::SchemaPayloadLocationMismatch`] + /// * [`Error::SchemaNotSupported`] + /// * [`Error::CorruptedState`] + /// * [`Error::StalePageState`] + /// + fn validate_itemized( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + actions: &BoundedVec, T::MaxItemizedActionsCount>, + is_payload_signed: bool, + target_hash: &PageHash, + ) -> TransactionValidity { + const TAG_PREFIX: &str = "StatefulStorageItemized"; + let is_pruning = actions.iter().any(|a| matches!(a, ItemAction::Delete { .. })); + + Self::check_schema_for_write( + *schema_id, + PayloadLocation::Itemized, + is_payload_signed, + is_pruning, + ) + .map_err(|e| map_dispatch_error(e))?; + + Self::get_checked_itemized_page(msa_id, schema_id, target_hash) + .map_err(|e| map_dispatch_error(e))?; + + return ValidTransaction::with_tag_prefix(TAG_PREFIX) + .and_provides((msa_id, schema_id)) + .build() + } + + /// Validates the following criteria for a Paginated Stateful Storage extrinsic: + /// + /// * schema_id represents a valid schema + /// * schema `PayloadLocation` is `Paginated` + /// * schema allows signed payloads if `is_payload_signed` == true + /// * schema is not `AppendOnly` if `page_delete` == true + /// * page can be read + /// * known page state hash matches provided target hash + /// + /// Returns a `ValidTransaction` or wrapped [`pallet::Error`] + /// + /// # Arguments: + /// * account_id: the account id associated with the MSA owning the storage + /// * msa_id: the `MessageSourceAccount` + /// * schema_id: the `SchemaId` for the Paginated storage + /// * page_id: the `PageId` to be validated + /// * is_payload_signed: whether we were called with a signed payload + /// * page_delete: whether the invoking operation is a page deletion + /// * target_hash: the content hash representing the known state of the Paginated storage. + /// + /// # Errors (as u8 wrapped by `InvalidTransaction::Custom`) + /// * [`Error::InvalidSchemaId`] + /// * [`Error::SchemaPayloadLocationMismatch`] + /// * [`Error::SchemaNotSupported`] + /// * [`Error::CorruptedState`] + /// * [`Error::StalePageState`] + /// + fn validate_paginated( + msa_id: &MessageSourceId, + schema_id: &SchemaId, + page_id: &PageId, + is_payload_signed: bool, + page_delete: bool, + target_hash: &PageHash, + ) -> TransactionValidity { + const TAG_PREFIX: &str = "StatefulStoragePaginated"; + + Self::check_schema_for_write( + *schema_id, + PayloadLocation::Paginated, + is_payload_signed, + page_delete, + ) + .map_err(|e| map_dispatch_error(e))?; + + Self::check_paginated_state(msa_id, schema_id, page_id, target_hash) + .map_err(|e| map_dispatch_error(e))?; + + return ValidTransaction::with_tag_prefix(TAG_PREFIX) + .and_provides((msa_id, schema_id, page_id)) + .build() + } +} + +/// Map a module DispatchError to an InvalidTransaction::Custom error +pub fn map_dispatch_error(err: DispatchError) -> InvalidTransaction { + InvalidTransaction::Custom(match err { + DispatchError::Module(module_err) => module_err.index, + _ => 255u8, + }) +} + +impl StatefulSignedExtension { + /// Create new `SignedExtension`. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl sp_std::fmt::Debug for StatefulSignedExtension { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "StatefulSignedExtension<{:?}>", self.0) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for StatefulSignedExtension +where + T::RuntimeCall: Dispatchable + IsSubType>, +{ + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "StatefulSignedExtension"; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } + + /// Frequently called by the transaction queue to validate all free MSA extrinsics: + /// Returns a `ValidTransaction` or wrapped [`ValidityError`] + /// * revoke_delegation_by_provider + /// * revoke_delegation_by_delegator + /// * delete_msa_public_key + /// * retire_msa + /// Validate functions for the above MUST prevent errors in the extrinsic logic to prevent spam. + /// + /// Arguments: + /// who: AccountId calling the extrinsic + /// call: The pallet extrinsic being called + /// unused: _info, _len + /// + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + match call.is_sub_type() { + Some(Call::apply_item_actions { + state_owner_msa_id, + schema_id, + actions, + target_hash, + .. + }) => Pallet::::validate_itemized( + state_owner_msa_id, + schema_id, + actions, + false, + target_hash, + ), + Some(Call::apply_item_actions_with_signature { payload, .. }) => + Pallet::::validate_itemized( + &payload.msa_id, + &payload.schema_id, + &payload.actions, + true, + &payload.target_hash, + ), + Some(Call::upsert_page { + state_owner_msa_id, schema_id, page_id, target_hash, .. + }) => Pallet::::validate_paginated( + state_owner_msa_id, + schema_id, + page_id, + false, + false, + target_hash, + ), + Some(Call::upsert_page_with_signature { payload, .. }) => + Pallet::::validate_paginated( + &payload.msa_id, + &payload.schema_id, + &payload.page_id, + true, + false, + &payload.target_hash, + ), + Some(Call::delete_page { state_owner_msa_id, schema_id, page_id, target_hash }) => + Pallet::::validate_paginated( + state_owner_msa_id, + schema_id, + page_id, + false, + true, + target_hash, + ), + Some(Call::delete_page_with_signature { payload, .. }) => + Pallet::::validate_paginated( + &payload.msa_id, + &payload.schema_id, + &payload.page_id, + true, + true, + &payload.target_hash, + ), + _ => Ok(Default::default()), + } + } +} diff --git a/runtime/frequency/src/lib.rs b/runtime/frequency/src/lib.rs index dac245cece..9bb4409533 100644 --- a/runtime/frequency/src/lib.rs +++ b/runtime/frequency/src/lib.rs @@ -72,6 +72,7 @@ pub use sp_runtime::BuildStorage; pub use pallet_msa; pub use pallet_schemas; +pub use pallet_stateful_storage; // Polkadot Imports use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; @@ -154,6 +155,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, pallet_msa::CheckFreeExtrinsicUse, + pallet_stateful_storage::StatefulSignedExtension, ); /// A Block signed with a Justification pub type SignedBlock = generic::SignedBlock; From 5b880d3c9bc2069d767ba0b2744a29419c537e8e Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Wed, 29 Mar 2023 13:16:13 -0400 Subject: [PATCH 2/2] feat(#1095): update error reporting #1095 --- integration-tests/package-lock.json | 2 +- integration-tests/scaffolding/helpers.ts | 124 ++++++++------- .../handleAppendOnly.test.ts | 94 ++++++------ .../handleItemized.test.ts | 28 ++-- .../handlePaginated.test.ts | 36 +++-- .../handleSignatureRequired.test.ts | 144 +++++++++--------- .../src/stateful_signed_extension.rs | 6 +- 7 files changed, 223 insertions(+), 211 deletions(-) diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json index 9f47a3eec7..3de22f9b10 100644 --- a/integration-tests/package-lock.json +++ b/integration-tests/package-lock.json @@ -697,7 +697,7 @@ "node_modules/@frequency-chain/api-augment": { "version": "0.0.0", "resolved": "file:../js/api-augment/dist/frequency-chain-api-augment-0.0.0.tgz", - "integrity": "sha512-A653d75diX2MnknWC5x1Q6iKkVatFNt4QPrD1rAniTg2PVSIFSMzYedSUdZMK7a42gEb6ioqMBBAvdi+TNjArw==", + "integrity": "sha512-5lq2PZity1d9Uz+iKFYByeuTpHhRn9ddfXmzhm3OQxXTf27s9a3M5xvYu36yTOX/bSLAK/GYCgFzKlzJACHNsQ==", "license": "Apache-2.0", "dependencies": { "@polkadot/api": "^9.14.2", diff --git a/integration-tests/scaffolding/helpers.ts b/integration-tests/scaffolding/helpers.ts index 913596db67..974e3b8be8 100644 --- a/integration-tests/scaffolding/helpers.ts +++ b/integration-tests/scaffolding/helpers.ts @@ -13,11 +13,11 @@ import { PaginatedUpsertSignaturePayload } from "./extrinsicHelpers"; import { EXISTENTIAL_DEPOSIT } from "./rootHooks"; -import {MessageSourceId, PageHash} from "@frequency-chain/api-augment/interfaces"; +import { MessageSourceId, PageHash } from "@frequency-chain/api-augment/interfaces"; export interface DevAccount { - uri: string, - keys: KeyringPair, + uri: string, + keys: KeyringPair, } export let devAccounts: DevAccount[] = []; @@ -26,35 +26,35 @@ export let devAccounts: DevAccount[] = []; export type Sr25519Signature = { Sr25519: `0x${string}` } export function signPayloadSr25519(keys: KeyringPair, data: Codec): Sr25519Signature { - return { Sr25519: u8aToHex(keys.sign(u8aWrapBytes(data.toU8a()))) } + return { Sr25519: u8aToHex(keys.sign(u8aWrapBytes(data.toU8a()))) } } export async function generateDelegationPayload(payloadInputs: AddProviderPayload, expirationOffset?: number): Promise { - let { expiration, ...payload } = payloadInputs; - if (!expiration) { - expiration = (await getBlockNumber()) + (expirationOffset || 5); - } + let { expiration, ...payload } = payloadInputs; + if (!expiration) { + expiration = (await getBlockNumber()) + (expirationOffset || 5); + } - return { - expiration, - ...payload, - } + return { + expiration, + ...payload, + } } export async function getBlockNumber(): Promise { - return (await ExtrinsicHelper.getLastBlock()).block.header.number.toNumber() + return (await ExtrinsicHelper.getLastBlock()).block.header.number.toNumber() } export async function generateAddKeyPayload(payloadInputs: AddKeyData, expirationOffset: number = 5, blockNumber?: number): Promise { - let { expiration, ...payload } = payloadInputs; - if (!expiration) { - expiration = (blockNumber || (await getBlockNumber())) + expirationOffset; - } + let { expiration, ...payload } = payloadInputs; + if (!expiration) { + expiration = (blockNumber || (await getBlockNumber())) + expirationOffset; + } - return { - expiration, - ...payload, - } + return { + expiration, + ...payload, + } } export async function generateItemizedSignaturePayload(payloadInputs: ItemizedSignaturePayload, expirationOffset?: number): Promise { @@ -94,77 +94,75 @@ export async function generatePaginatedDeleteSignaturePayload(payloadInputs: Pag } export function createKeys(name: string = 'first pair'): KeyringPair { - const mnemonic = mnemonicGenerate(); - // create & add the pair to the keyring with the type and some additional - // metadata specified - const keyring = new Keyring({ type: 'sr25519' }); - const keypair = keyring.addFromUri(mnemonic, { name }, 'sr25519'); + const mnemonic = mnemonicGenerate(); + // create & add the pair to the keyring with the type and some additional + // metadata specified + const keyring = new Keyring({ type: 'sr25519' }); + const keypair = keyring.addFromUri(mnemonic, { name }, 'sr25519'); - return keypair; + return keypair; } export async function fundKeypair(source: KeyringPair, dest: KeyringPair, amount: bigint, nonce?: number): Promise { - await ExtrinsicHelper.transferFunds(source, dest, amount).signAndSend(nonce); + await ExtrinsicHelper.transferFunds(source, dest, amount).signAndSend(nonce); } export async function createAndFundKeypair(amount = EXISTENTIAL_DEPOSIT, keyName?: string, devAccount?: KeyringPair, nonce?: number): Promise { - const keypair = createKeys(keyName); + const keypair = createKeys(keyName); - // Transfer funds from source (usually pre-funded dev account) to new account - await fundKeypair((devAccount || devAccounts[0].keys), keypair, amount, nonce); + // Transfer funds from source (usually pre-funded dev account) to new account + await fundKeypair((devAccount || devAccounts[0].keys), keypair, amount, nonce); - return keypair; + return keypair; } export function log(...args: any[]) { - if (env.verbose) { - console.log(...args); - } + if (env.verbose) { + console.log(...args); + } } export async function createProviderKeysAndId(): Promise<[KeyringPair, u64]> { - let providerKeys = await createAndFundKeypair(); - let createProviderMsaOp = ExtrinsicHelper.createMsa(providerKeys); - let providerId = new u64(ExtrinsicHelper.api.registry, 0) - await createProviderMsaOp.fundAndSend(); - let createProviderOp = ExtrinsicHelper.createProvider(providerKeys, "PrivateProvider"); - let [providerEvent] = await createProviderOp.fundAndSend(); - if (providerEvent && ExtrinsicHelper.api.events.msa.ProviderCreated.is(providerEvent)) { - providerId = providerEvent.data.providerId; - } - return [providerKeys, providerId]; -} - -export async function createDelegator(): Promise<[KeyringPair, u64]> { + let providerId = new u64(ExtrinsicHelper.api.registry, 0); + let [providerKeys, _] = await createMsa(); + let createProviderOp = ExtrinsicHelper.createProvider(providerKeys, "PrivateProvider"); + let [providerEvent] = await createProviderOp.fundAndSend(); + if (providerEvent && ExtrinsicHelper.api.events.msa.ProviderCreated.is(providerEvent)) { + providerId = providerEvent.data.providerId; + } + return [providerKeys, providerId]; +} + +export async function createMsa(): Promise<[KeyringPair, u64]> { let keys = await createAndFundKeypair(); - let delegator_msa_id = new u64(ExtrinsicHelper.api.registry, 0); + let msa_id = new u64(ExtrinsicHelper.api.registry, 0); const createMsa = ExtrinsicHelper.createMsa(keys); await createMsa.fundOperation(); const [msaCreatedEvent, _] = await createMsa.signAndSend(); if (msaCreatedEvent && ExtrinsicHelper.api.events.msa.MsaCreated.is(msaCreatedEvent)) { - delegator_msa_id = msaCreatedEvent.data.msaId; + msa_id = msaCreatedEvent.data.msaId; } - return [keys, delegator_msa_id]; + return [keys, msa_id]; } export async function createDelegatorAndDelegation(schemaId: u16, providerId: u64, providerKeys: KeyringPair): Promise<[KeyringPair, u64]> { - // Create a delegator msa - const [keys, delegator_msa_id] = await createDelegator(); + // Create a delegator msa + const [keys, delegator_msa_id] = await createMsa(); - // Grant delegation to the provider - const payload = await generateDelegationPayload({ - authorizedMsaId: providerId, - schemaIds: [schemaId], - }); - const addProviderData = ExtrinsicHelper.api.registry.createType("PalletMsaAddProvider", payload); + // Grant delegation to the provider + const payload = await generateDelegationPayload({ + authorizedMsaId: providerId, + schemaIds: [schemaId], + }); + const addProviderData = ExtrinsicHelper.api.registry.createType("PalletMsaAddProvider", payload); - const grantDelegationOp = ExtrinsicHelper.grantDelegation(keys, providerKeys, signPayloadSr25519(keys, addProviderData), payload); - await grantDelegationOp.fundOperation(); - await grantDelegationOp.signAndSend(); + const grantDelegationOp = ExtrinsicHelper.grantDelegation(keys, providerKeys, signPayloadSr25519(keys, addProviderData), payload); + await grantDelegationOp.fundOperation(); + await grantDelegationOp.signAndSend(); - return [keys, delegator_msa_id]; + return [keys, delegator_msa_id]; } export async function getCurrentItemizedHash(msa_id: MessageSourceId, schemaId: u16): Promise { diff --git a/integration-tests/stateful-pallet-storage/handleAppendOnly.test.ts b/integration-tests/stateful-pallet-storage/handleAppendOnly.test.ts index a521151d3e..35fb1ab481 100644 --- a/integration-tests/stateful-pallet-storage/handleAppendOnly.test.ts +++ b/integration-tests/stateful-pallet-storage/handleAppendOnly.test.ts @@ -10,69 +10,69 @@ import { KeyringPair } from "@polkadot/keyring/types"; import { ExtrinsicHelper } from "../scaffolding/extrinsicHelpers"; import { AVRO_CHAT_MESSAGE } from "./fixtures/itemizedSchemaType"; import { MessageSourceId, SchemaId } from "@frequency-chain/api-augment/interfaces"; -import {Bytes, u16} from "@polkadot/types"; +import { Bytes, u16 } from "@polkadot/types"; describe("📗 Stateful Pallet Storage AppendOnly Schemas", () => { - let itemizedSchemaId: SchemaId; - let msa_id: MessageSourceId; - let providerId: MessageSourceId; - let providerKeys: KeyringPair; - let sudoKey: KeyringPair; + let itemizedSchemaId: SchemaId; + let msa_id: MessageSourceId; + let providerId: MessageSourceId; + let providerKeys: KeyringPair; + let sudoKey: KeyringPair; - before(async function () { - // Using Alice as sudoKey - sudoKey = devAccounts[0].keys; + before(async function () { + // Using Alice as sudoKey + sudoKey = devAccounts[0].keys; - // Create a provider for the MSA, the provider will be used to grant delegation - [providerKeys, providerId] = await createProviderKeysAndId(); - assert.notEqual(providerId, undefined, "setup should populate providerId"); - assert.notEqual(providerKeys, undefined, "setup should populate providerKeys"); + // Create a provider for the MSA, the provider will be used to grant delegation + [providerKeys, providerId] = await createProviderKeysAndId(); + assert.notEqual(providerId, undefined, "setup should populate providerId"); + assert.notEqual(providerKeys, undefined, "setup should populate providerKeys"); - // Create a schema for Itemized PayloadLocation - const createSchema = ExtrinsicHelper.createSchemaWithSettingsGov(providerKeys, sudoKey, AVRO_CHAT_MESSAGE, "AvroBinary", "Itemized", "AppendOnly"); - const [event] = await createSchema.sudoSignAndSend(); - if (event && createSchema.api.events.schemas.SchemaCreated.is(event)) { - itemizedSchemaId = event.data.schemaId; - } - assert.notEqual(itemizedSchemaId, undefined, "setup should populate schemaId"); + // Create a schema for Itemized PayloadLocation + const createSchema = ExtrinsicHelper.createSchemaWithSettingsGov(providerKeys, sudoKey, AVRO_CHAT_MESSAGE, "AvroBinary", "Itemized", "AppendOnly"); + const [event] = await createSchema.sudoSignAndSend(); + if (event && createSchema.api.events.schemas.SchemaCreated.is(event)) { + itemizedSchemaId = event.data.schemaId; + } + assert.notEqual(itemizedSchemaId, undefined, "setup should populate schemaId"); - // Create a MSA for the delegator and delegate to the provider for the itemized schema - [, msa_id] = await createDelegatorAndDelegation(itemizedSchemaId, providerId, providerKeys); - assert.notEqual(msa_id, undefined, "setup should populate msa_id"); - }); + // Create a MSA for the delegator and delegate to the provider for the itemized schema + [, msa_id] = await createDelegatorAndDelegation(itemizedSchemaId, providerId, providerKeys); + assert.notEqual(msa_id, undefined, "setup should populate msa_id"); + }); - describe("Itemized With AppendOnly Storage Tests", () => { + describe("Itemized With AppendOnly Storage Tests", () => { - it("should not be able to call delete action", async function () { + it("should not be able to call delete action", async function () { - // Add and update actions - let payload_1 = new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"); + // Add and update actions + let payload_1 = new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"); - const add_action = { - "Add": payload_1 - } + const add_action = { + "Add": payload_1 + } - let payload_2 = new Bytes(ExtrinsicHelper.api.registry, "Hello World Again From Frequency"); + let payload_2 = new Bytes(ExtrinsicHelper.api.registry, "Hello World Again From Frequency"); - const update_action = { - "Add": payload_2 - } + const update_action = { + "Add": payload_2 + } - const idx_1: u16 = new u16(ExtrinsicHelper.api.registry, 1) + const idx_1: u16 = new u16(ExtrinsicHelper.api.registry, 1) - const delete_action = { - "Delete": idx_1 - } - const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); + const delete_action = { + "Delete": idx_1 + } + const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); - let add_actions = [add_action, update_action, delete_action]; + let add_actions = [add_action, update_action, delete_action]; - let itemized_add_result_1 = ExtrinsicHelper.applyItemActions(providerKeys, itemizedSchemaId, msa_id, add_actions, target_hash); - await assert.rejects(async () => { - await itemized_add_result_1.fundAndSend(); - }, { - name: 'UnsupportedOperationForSchema', - section: 'statefulStorage', + let itemized_add_result_1 = ExtrinsicHelper.applyItemActions(providerKeys, itemizedSchemaId, msa_id, add_actions, target_hash); + await assert.rejects(async () => { + await itemized_add_result_1.fundAndSend(); + }, { + name: 'RpcError', + message: /Custom error: 3/, // UnsupportedOperationForSchema }); }); }); diff --git a/integration-tests/stateful-pallet-storage/handleItemized.test.ts b/integration-tests/stateful-pallet-storage/handleItemized.test.ts index 117f745e26..cf4514e9b1 100644 --- a/integration-tests/stateful-pallet-storage/handleItemized.test.ts +++ b/integration-tests/stateful-pallet-storage/handleItemized.test.ts @@ -1,7 +1,7 @@ // Integration tests for pallets/stateful-pallet-storage/handleItemized.ts import "@frequency-chain/api-augment"; import assert from "assert"; -import {createDelegatorAndDelegation, createProviderKeysAndId, getCurrentItemizedHash} from "../scaffolding/helpers"; +import { createDelegatorAndDelegation, createProviderKeysAndId, getCurrentItemizedHash } from "../scaffolding/helpers"; import { KeyringPair } from "@polkadot/keyring/types"; import { ExtrinsicHelper } from "../scaffolding/extrinsicHelpers"; import { AVRO_CHAT_MESSAGE } from "../stateful-pallet-storage/fixtures/itemizedSchemaType"; @@ -83,8 +83,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await itemized_add_result_1.fundAndSend(); }, { - name: 'InvalidSchemaId', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 2/, // InvalidSchemaId }); }).timeout(10000); @@ -99,8 +99,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await itemized_add_result_1.fundAndSend(); }, { - name: 'SchemaPayloadLocationMismatch', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 4/, // SchemaPayloadLocationMismatch }); }).timeout(10000); @@ -139,7 +139,10 @@ describe("📗 Stateful Pallet Storage", () => { let add_actions = [add_action, update_action]; let itemized_add_result_1 = ExtrinsicHelper.applyItemActions(providerKeys, schemaId_deletable, msa_id, add_actions, 0); - await assert.rejects(itemized_add_result_1.fundAndSend(), { name: 'StalePageState' }); + await assert.rejects(itemized_add_result_1.fundAndSend(), { + name: 'RpcError', + message: /Custom error: 9/, // StalePageState + }); }).timeout(10000); }); @@ -175,8 +178,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await itemized_remove_result_1.fundAndSend(); }, { - name: 'InvalidSchemaId', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 2/, // InvalidSchemaId }); }).timeout(10000); @@ -190,8 +193,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await itemized_remove_result_1.fundAndSend(); }, { - name: 'SchemaPayloadLocationMismatch', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 4/, // SchemaPayloadLocationMismatch }); }).timeout(10000); @@ -218,7 +221,10 @@ describe("📗 Stateful Pallet Storage", () => { } let remove_actions = [remove_action]; let op = ExtrinsicHelper.applyItemActions(providerKeys, schemaId_deletable, msa_id, remove_actions, 0); - await assert.rejects(op.fundAndSend(), { name: 'StalePageState' }) + await assert.rejects(op.fundAndSend(), { + name: 'RpcError', + message: /Custom error: 9/, // StalePageState + }) }).timeout(10000); }); diff --git a/integration-tests/stateful-pallet-storage/handlePaginated.test.ts b/integration-tests/stateful-pallet-storage/handlePaginated.test.ts index faab14bacc..87706e2b8f 100644 --- a/integration-tests/stateful-pallet-storage/handlePaginated.test.ts +++ b/integration-tests/stateful-pallet-storage/handlePaginated.test.ts @@ -1,7 +1,7 @@ // Integration tests for pallets/stateful-pallet-storage/handlepaginated.ts import "@frequency-chain/api-augment"; import assert from "assert"; -import {createProviderKeysAndId, createDelegatorAndDelegation, getCurrentPaginatedHash} from "../scaffolding/helpers"; +import { createProviderKeysAndId, createDelegatorAndDelegation, getCurrentPaginatedHash, createMsa } from "../scaffolding/helpers"; import { KeyringPair } from "@polkadot/keyring/types"; import { ExtrinsicHelper } from "../scaffolding/extrinsicHelpers"; import { AVRO_CHAT_MESSAGE } from "./fixtures/itemizedSchemaType"; @@ -12,6 +12,7 @@ describe("📗 Stateful Pallet Storage", () => { let schemaId: SchemaId; let schemaId_unsupported: SchemaId; let msa_id: MessageSourceId; + let undelegated_msa_id: MessageSourceId; let providerId: MessageSourceId; let providerKeys: KeyringPair; @@ -40,6 +41,9 @@ describe("📗 Stateful Pallet Storage", () => { // Create a MSA for the delegator and delegate to the provider [, msa_id] = await createDelegatorAndDelegation(schemaId, providerId, providerKeys); assert.notEqual(msa_id, undefined, "setup should populate msa_id"); + + // Create another MSA which will not have a delegation + [, undelegated_msa_id] = await createMsa(); }); describe("Paginated Storage Upsert/Remove Tests 😊/😥", () => { @@ -84,8 +88,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.fundAndSend(); }, { - name: 'InvalidSchemaId', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 2/, //'InvalidSchemaId', }); }).timeout(10000); @@ -98,8 +102,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.fundAndSend(); }, { - name: 'SchemaPayloadLocationMismatch', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 4/, //'SchemaPayloadLocationMismatch', }); }).timeout(10000); @@ -107,15 +111,15 @@ describe("📗 Stateful Pallet Storage", () => { let page_id = 0; let payload_1 = new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"); - let bad_msa_id = new u64(ExtrinsicHelper.api.registry, 999) - let target_hash = await getCurrentPaginatedHash(msa_id, schemaId, page_id) - let paginated_add_result_1 = ExtrinsicHelper.upsertPage(providerKeys, schemaId, bad_msa_id, page_id, payload_1, target_hash); + let target_hash = await getCurrentPaginatedHash(undelegated_msa_id, schemaId, page_id) + let paginated_add_result_1 = ExtrinsicHelper.upsertPage(providerKeys, schemaId, undelegated_msa_id, page_id, payload_1, target_hash); await assert.rejects(async () => { await paginated_add_result_1.fundAndSend(); }, { name: 'UnauthorizedDelegate', section: 'statefulStorage', + // message: /Custom error: 9/, //'StalePageState', }); }).timeout(10000); @@ -128,8 +132,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.fundAndSend(); }, { - name: 'StalePageState', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 9/, //'StalePageState', }); }).timeout(10000); }); @@ -144,8 +148,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.signAndSend(); }, { - name: 'InvalidSchemaId', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 2/, //'InvalidSchemaId', }); }).timeout(10000); @@ -156,8 +160,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.signAndSend(); }, { - name: 'SchemaPayloadLocationMismatch', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 4/, //'SchemaPayloadLocationMismatch', }); }).timeout(10000); @@ -180,8 +184,8 @@ describe("📗 Stateful Pallet Storage", () => { await assert.rejects(async () => { await paginated_add_result_1.signAndSend(); }, { - name: 'StalePageState', - section: 'statefulStorage', + name: 'RpcError', + message: /Custom error: 9/, //'StalePageState', }); }).timeout(10000); }); diff --git a/integration-tests/stateful-pallet-storage/handleSignatureRequired.test.ts b/integration-tests/stateful-pallet-storage/handleSignatureRequired.test.ts index 97cb863301..d360566d24 100644 --- a/integration-tests/stateful-pallet-storage/handleSignatureRequired.test.ts +++ b/integration-tests/stateful-pallet-storage/handleSignatureRequired.test.ts @@ -2,7 +2,7 @@ import "@frequency-chain/api-augment"; import assert from "assert"; import { - createDelegator, + createMsa, createProviderKeysAndId, generateItemizedSignaturePayload, generatePaginatedDeleteSignaturePayload, generatePaginatedUpsertSignaturePayload, getCurrentItemizedHash, getCurrentPaginatedHash, @@ -12,79 +12,79 @@ import { KeyringPair } from "@polkadot/keyring/types"; import { ExtrinsicHelper } from "../scaffolding/extrinsicHelpers"; import { AVRO_CHAT_MESSAGE } from "../stateful-pallet-storage/fixtures/itemizedSchemaType"; import { MessageSourceId, SchemaId } from "@frequency-chain/api-augment/interfaces"; -import {Bytes, u16} from "@polkadot/types"; +import { Bytes, u16 } from "@polkadot/types"; describe("📗 Stateful Pallet Storage Signature Required", () => { - let itemizedSchemaId: SchemaId; - let paginatedSchemaId: SchemaId; - let msa_id: MessageSourceId; - let providerId: MessageSourceId; - let providerKeys: KeyringPair; - let delegatorKeys: KeyringPair; - - before(async function () { - - // Create a provider for the MSA, the provider will be used to grant delegation - [providerKeys, providerId] = await createProviderKeysAndId(); - assert.notEqual(providerId, undefined, "setup should populate providerId"); - assert.notEqual(providerKeys, undefined, "setup should populate providerKeys"); - - // Create a schema for Itemized PayloadLocation - const createSchema = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, "AvroBinary", "Itemized"); - const [event] = await createSchema.fundAndSend(); - if (event && createSchema.api.events.schemas.SchemaCreated.is(event)) { - itemizedSchemaId = event.data.schemaId; - } - assert.notEqual(itemizedSchemaId, undefined, "setup should populate schemaId"); - // Create a schema for Paginated PayloadLocation - const createSchema2 = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, "AvroBinary", "Paginated"); - const [event2] = await createSchema2.fundAndSend(); - assert.notEqual(event2, undefined, "setup should return a SchemaCreated event"); - if (event2 && createSchema2.api.events.schemas.SchemaCreated.is(event2)) { - paginatedSchemaId = event2.data.schemaId; - assert.notEqual(paginatedSchemaId, undefined, "setup should populate schemaId"); - } - - // Create a MSA for the delegator - [delegatorKeys, msa_id] = await createDelegator(); - assert.notEqual(delegatorKeys, undefined, "setup should populate delegator_key"); - assert.notEqual(msa_id, undefined, "setup should populate msa_id"); - }); - - describe("Itemized With Signature Storage Tests", () => { - - it("should be able to call applyItemizedActionWithSignature and apply actions", async function () { - - // Add and update actions - let payload_1 = new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"); - - const add_action = { - "Add": payload_1 - } - - let payload_2 = new Bytes(ExtrinsicHelper.api.registry, "Hello World Again From Frequency"); - - const update_action = { - "Add": payload_2 - } - - const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); - - let add_actions = [add_action, update_action]; - const payload = await generateItemizedSignaturePayload({ - msaId: msa_id, - targetHash: target_hash, - schemaId: itemizedSchemaId, - actions: add_actions, - }); - const itemizedPayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStorageItemizedSignaturePayload", payload); - let itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignature(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, itemizedPayloadData), payload); - const [pageUpdateEvent1, chainEvents] = await itemized_add_result_1.fundAndSend(); - assert.notEqual(chainEvents["system.ExtrinsicSuccess"], undefined, "should have returned an ExtrinsicSuccess event"); - assert.notEqual(chainEvents["transactionPayment.TransactionFeePaid"], undefined, "should have returned a TransactionFeePaid event"); - assert.notEqual(pageUpdateEvent1, undefined, "should have returned a PalletStatefulStorageItemizedActionApplied event"); - }).timeout(10000); - }); + let itemizedSchemaId: SchemaId; + let paginatedSchemaId: SchemaId; + let msa_id: MessageSourceId; + let providerId: MessageSourceId; + let providerKeys: KeyringPair; + let delegatorKeys: KeyringPair; + + before(async function () { + + // Create a provider for the MSA, the provider will be used to grant delegation + [providerKeys, providerId] = await createProviderKeysAndId(); + assert.notEqual(providerId, undefined, "setup should populate providerId"); + assert.notEqual(providerKeys, undefined, "setup should populate providerKeys"); + + // Create a schema for Itemized PayloadLocation + const createSchema = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, "AvroBinary", "Itemized"); + const [event] = await createSchema.fundAndSend(); + if (event && createSchema.api.events.schemas.SchemaCreated.is(event)) { + itemizedSchemaId = event.data.schemaId; + } + assert.notEqual(itemizedSchemaId, undefined, "setup should populate schemaId"); + // Create a schema for Paginated PayloadLocation + const createSchema2 = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, "AvroBinary", "Paginated"); + const [event2] = await createSchema2.fundAndSend(); + assert.notEqual(event2, undefined, "setup should return a SchemaCreated event"); + if (event2 && createSchema2.api.events.schemas.SchemaCreated.is(event2)) { + paginatedSchemaId = event2.data.schemaId; + assert.notEqual(paginatedSchemaId, undefined, "setup should populate schemaId"); + } + + // Create a MSA for the delegator + [delegatorKeys, msa_id] = await createMsa(); + assert.notEqual(delegatorKeys, undefined, "setup should populate delegator_key"); + assert.notEqual(msa_id, undefined, "setup should populate msa_id"); + }); + + describe("Itemized With Signature Storage Tests", () => { + + it("should be able to call applyItemizedActionWithSignature and apply actions", async function () { + + // Add and update actions + let payload_1 = new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"); + + const add_action = { + "Add": payload_1 + } + + let payload_2 = new Bytes(ExtrinsicHelper.api.registry, "Hello World Again From Frequency"); + + const update_action = { + "Add": payload_2 + } + + const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); + + let add_actions = [add_action, update_action]; + const payload = await generateItemizedSignaturePayload({ + msaId: msa_id, + targetHash: target_hash, + schemaId: itemizedSchemaId, + actions: add_actions, + }); + const itemizedPayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStorageItemizedSignaturePayload", payload); + let itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignature(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, itemizedPayloadData), payload); + const [pageUpdateEvent1, chainEvents] = await itemized_add_result_1.fundAndSend(); + assert.notEqual(chainEvents["system.ExtrinsicSuccess"], undefined, "should have returned an ExtrinsicSuccess event"); + assert.notEqual(chainEvents["transactionPayment.TransactionFeePaid"], undefined, "should have returned a TransactionFeePaid event"); + assert.notEqual(pageUpdateEvent1, undefined, "should have returned a PalletStatefulStorageItemizedActionApplied event"); + }).timeout(10000); + }); describe("Paginated With Signature Storage Tests", () => { diff --git a/pallets/stateful-storage/src/stateful_signed_extension.rs b/pallets/stateful-storage/src/stateful_signed_extension.rs index 1e3f80036a..710c8e6998 100644 --- a/pallets/stateful-storage/src/stateful_signed_extension.rs +++ b/pallets/stateful-storage/src/stateful_signed_extension.rs @@ -162,7 +162,11 @@ impl StatefulStorageValidate for Pallet { /// Map a module DispatchError to an InvalidTransaction::Custom error pub fn map_dispatch_error(err: DispatchError) -> InvalidTransaction { InvalidTransaction::Custom(match err { - DispatchError::Module(module_err) => module_err.index, + DispatchError::Module(module_err) => + ::decode(&mut module_err.error.as_slice()) + .unwrap_or_default() + .try_into() + .unwrap_or_default(), _ => 255u8, }) }