From 6eed123bd58d13934d07aca5a152890a2d6d9ac4 Mon Sep 17 00:00:00 2001 From: Andrew Dirksen Date: Tue, 12 May 2020 10:42:18 -0700 Subject: [PATCH] immutable blob store --- runtime/src/blob.rs | 223 +++++++++++++++++++++++++++++++------ runtime/src/lib.rs | 127 ++++++++++----------- runtime/src/revoke.rs | 210 ++++++++-------------------------- runtime/src/template.rs | 146 ------------------------ runtime/src/test_common.rs | 141 +++++++++++++++++++++++ 5 files changed, 437 insertions(+), 410 deletions(-) delete mode 100644 runtime/src/template.rs create mode 100644 runtime/src/test_common.rs diff --git a/runtime/src/blob.rs b/runtime/src/blob.rs index 6532471a4..e5e2f39ab 100644 --- a/runtime/src/blob.rs +++ b/runtime/src/blob.rs @@ -1,39 +1,37 @@ -use crate as dock; +//! Generic immutable single-owner storage. +use crate as dock; +use crate::did; +use alloc::vec::Vec; +use codec::{Decode, Encode}; use frame_support::{ - decl_error, decl_module, decl_storage, dispatch::DispatchError, - dispatch::DispatchResult, ensure, fail, + decl_error, decl_module, decl_storage, dispatch::DispatchResult, ensure, traits::Get, }; use system::ensure_signed; -use codec::{Decode, Encode}; -use alloc::vec::Vec; - -use crate::did::{self, Did, DidSignature}; /// Size of the blob id in bytes pub const ID_BYTE_SIZE: usize = 32; -/// Maximum size of the blob in bytes -// implementer may choose to implement this as a dynamic config option settable with the `parameter_type!` macro -pub const BLOB_MAX_BYTE_SIZE: usize = 1024; -/// The type of the blob id -pub type Id = [u8; ID_BYTE_SIZE]; +/// The unique name for a blob. +pub type BlobId = [u8; ID_BYTE_SIZE]; -/// When a new blob is being registered, the following object is sent -/// When a blob is queried, the following object is returned. +/// When a new blob is being registered, the following object is sent. #[derive(Encode, Decode, Clone, PartialEq, Debug)] pub struct Blob { - id: dock::blob::Id, + id: BlobId, blob: Vec, - author: dock::did::Did, + author: did::Did, } -pub trait Trait: system::Trait + did::Trait {} +pub trait Trait: system::Trait + did::Trait { + /// Blobs larger than this will not be accepted. + type MaxBlobSize: Get; +} decl_error! { /// Error for the token module. - pub enum Error for Module { - /// The blob is greater than `BLOB_MAX_BYTE_SIZE` + pub enum BlobError for Module { + /// The blob is greater than `MaxBlobSize` BlobTooBig, /// There is already a blob with same id BlobAlreadyExists, @@ -44,31 +42,184 @@ decl_error! { } } -/// For each blob id, its author's DID and the blob is stored in the map decl_storage! { - trait Store for Module as BlobModule { - Blobs get(fn id): map hasher(blake2_128_concat) dock::blob::Id => Option<(dock::did::Did, Vec)>; + trait Store for Module as Blob { + Blobs get(fn get_blob): map hasher(blake2_128_concat) + dock::blob::BlobId => Option<(dock::did::Did, Vec)>; } } decl_module! { pub struct Module for enum Call where origin: T::Origin { + /// Create a new immutable blob. + #[weight = 10_000] + pub fn new( + origin, + blob: dock::blob::Blob, + signature: dock::did::DidSignature, + ) -> DispatchResult { + Module::::new_(origin, blob, signature) + } + } +} - type Error = Error; +impl Module { + fn new_( + origin: ::Origin, + blob: Blob, + signature: did::DidSignature, + ) -> DispatchResult { + ensure_signed(origin)?; - /// Register a new blob after ensuring blob with the same id is not registered and then - /// verifying `did`'s signature on the blob - /// `schema_detail` contains the id, author DID and the blob. The size of blob should be at - /// most `BLOB_MAX_BYTE_SIZE` bytes. The `blob` is wrapped in a [StateChange][statechange] before - /// serializing for signature verification. - /// `signature` is the signature of the blob author on the `blob` - // TODO: Use weight proportional to blob size - #[weight = 10_000] - pub fn new(origin, blob: dock::blob::Blob, signature: dock::did::DidSignature) -> DispatchResult { - ensure_signed(origin)?; - // TODO: Write the API - Ok(()) + // check + ensure!( + T::MaxBlobSize::get() as usize >= blob.blob.len(), + BlobError::::BlobTooBig + ); + ensure!( + !Blobs::contains_key(&blob.id), + BlobError::::BlobAlreadyExists + ); + let payload = crate::StateChange::Blob(blob.clone()).encode(); + let valid = did::Module::::verify_sig_from_did(&signature, &payload, &blob.author)?; + ensure!(valid, BlobError::::InvalidSig); + + // execute + Blobs::insert(blob.id, (blob.author, blob.blob)); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_common::*; + pub type BlobMod = crate::blob::Module; + + /// create a random byte array with set len + fn random_bytes(len: usize) -> Vec { + let ret: Vec = (0..len).map(|_| rand::random()).collect(); + assert_eq!(ret.len(), len); + ret + } + + fn create_blob( + id: BlobId, + content: Vec, + author: did::Did, + author_kp: sr25519::Pair, + ) -> DispatchResult { + let bl = Blob { + id, + blob: content, + author, + }; + let sig = sign(&crate::StateChange::Blob(bl.clone()), &author_kp); + BlobMod::new(Origin::signed(ABBA), bl.clone(), sig) + } + + fn get_max_blob_size() -> usize { + ::MaxBlobSize::get() as usize + } + + #[test] + fn add_blob() { + fn add(size: usize) { + let id: BlobId = rand::random(); + let noise = random_bytes(size); + let (author, author_kp) = newdid(); + assert_eq!(Blobs::get(id), None); + create_blob(id, noise.clone(), author, author_kp).unwrap(); + // Can retrieve a valid blob and the blob contents and author match the given ones. + assert_eq!(Blobs::get(id), Some((author, noise))); + } + + ext().execute_with(|| { + // Can add a blob with unique id, blob data of < MaxBlobSize bytes and a valid signature. + add(get_max_blob_size() - 1); + add(get_max_blob_size() - 2); + add(0); + // Can add a blob with unique id, blob data of MaxBlobSize bytes and a valid signature. + add(get_max_blob_size()); + }); + } + + #[test] + fn err_blob_too_big() { + fn add_too_big(size: usize) { + let (author, author_kp) = newdid(); + let noise = random_bytes(size); + let id = rand::random(); + assert_eq!(Blobs::get(id), None); + let err = create_blob(id, noise, author, author_kp).unwrap_err(); + assert_eq!(err, BlobError::::BlobTooBig.into()); + assert_eq!(Blobs::get(id), None); } + ext().execute_with(|| { + add_too_big(get_max_blob_size() + 1); + add_too_big(get_max_blob_size() + 2); + }); + } + + #[test] + fn err_blob_already_exists() { + ext().execute_with(|| { + // Adding a blob with already used id fails with error BlobAlreadyExists. + let id = rand::random(); + let (author, author_kp) = newdid(); + assert_eq!(Blobs::get(id), None); + create_blob(id, random_bytes(10), author, author_kp.clone()).unwrap(); + let err = create_blob(id, random_bytes(10), author, author_kp).unwrap_err(); + assert_eq!(err, BlobError::::BlobAlreadyExists.into()); + }); + } + + #[test] + fn err_did_does_not_exist() { + ext().execute_with(|| { + // Adding a blob with an unregistered DID fails with error DidDoesNotExist. + let author = rand::random(); + let author_kp = gen_kp(); + let err = create_blob(rand::random(), random_bytes(10), author, author_kp).unwrap_err(); + assert_eq!(err, BlobError::::DidDoesNotExist.into()); + }); } -} \ No newline at end of file + + #[test] + fn err_invalid_sig() { + ext().execute_with(|| { + { + // An invalid signature while adding a blob should fail with error InvalidSig. + let (author, author_kp) = newdid(); + let bl = Blob { + id: rand::random(), + blob: random_bytes(10), + author, + }; + let remreg = crate::revoke::RemoveRegistry { + registry_id: rand::random(), + last_modified: 10, + }; + let sig = sign(&crate::StateChange::RemoveRegistry(remreg), &author_kp); + let err = BlobMod::new(Origin::signed(ABBA), bl.clone(), sig).unwrap_err(); + assert_eq!(err, BlobError::::InvalidSig.into()); + } + + { + // signature by other party + let (author, _) = newdid(); + let (_, author_kp) = newdid(); + let bl = Blob { + id: rand::random(), + blob: random_bytes(10), + author, + }; + let sig = sign(&crate::StateChange::Blob(bl.clone()), &author_kp); + let err = BlobMod::new(Origin::signed(ABBA), bl.clone(), sig).unwrap_err(); + assert_eq!(err, BlobError::::InvalidSig.into()); + } + }) + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3c4757e42..139652275 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,10 +16,12 @@ pub use wasm::WASM_BINARY; extern crate alloc; +pub mod blob; pub mod did; pub mod revoke; -mod blob; -mod template; + +#[cfg(test)] +mod test_common; use codec::{Decode, Encode}; use grandpa::fg_primitives; @@ -31,8 +33,9 @@ use sp_runtime::traits::{ BlakeTwo256, Block as BlockT, ConvertInto, IdentifyAccount, IdentityLookup, Verify, }; use sp_runtime::{ - ApplyExtrinsicResult, generic, create_runtime_str, impl_opaque_keys, MultiSignature, - transaction_validity::{TransactionValidity, TransactionSource}, + create_runtime_str, generic, impl_opaque_keys, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, MultiSignature, }; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -42,12 +45,13 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use balances::Call as BalancesCall; pub use frame_support::{ - StorageValue, construct_runtime, parameter_types, + construct_runtime, parameter_types, traits::Randomness, weights::{ - Weight, constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + Weight, }, + StorageValue, }; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -91,7 +95,7 @@ pub enum StateChange { Revoke(revoke::Revoke), UnRevoke(revoke::UnRevoke), RemoveRegistry(revoke::RemoveRegistry), - Blob(blob::Blob) + Blob(blob::Blob), } /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know @@ -150,7 +154,7 @@ pub fn native_version() -> NativeVersion { parameter_types! { pub const BlockHashCount: BlockNumber = 250; /// We allow for 2 seconds of compute with a 6 second average block time. - pub const MaximumBlockWeight: Weight = 2 * WEIGHT_PER_SECOND; + pub const MaximumBlockWeight: Weight = 2 * WEIGHT_PER_SECOND; pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; pub const Version: RuntimeVersion = VERSION; @@ -184,7 +188,7 @@ impl system::Trait for Runtime { /// The weight of database operations that the runtime can invoke. type DbWeight = RocksDbWeight; /// The weight of the overhead invoked on the block import process, independent of the - /// extrinsics included in that block. + /// extrinsics included in that block. type BlockExecutionWeight = BlockExecutionWeight; /// The base weight of any extrinsic processed by the runtime, independent of the /// logic of that extrinsic. (Signature verification, nonce increment, fee, etc...) @@ -256,11 +260,6 @@ impl sudo::Trait for Runtime { type Call = Call; } -/// Used for the module template in `./template.rs` -impl template::Trait for Runtime { - type Event = Event; -} - impl did::Trait for Runtime { type Event = Event; //type DIDByteSize = DIDByteSize; @@ -268,7 +267,13 @@ impl did::Trait for Runtime { impl revoke::Trait for Runtime {} -impl blob::Trait for Runtime {} +parameter_types! { + pub const MaxBlobSize: u32 = 1024; +} + +impl blob::Trait for Runtime { + type MaxBlobSize = MaxBlobSize; +} construct_runtime!( pub enum Runtime where @@ -284,11 +289,9 @@ construct_runtime!( Balances: balances::{Module, Call, Storage, Config, Event}, TransactionPayment: transaction_payment::{Module, Storage}, Sudo: sudo::{Module, Call, Config, Storage, Event}, - // Used for the module template in `./template.rs` - TemplateModule: template::{Module, Call, Storage, Event}, DIDModule: did::{Module, Call, Storage, Event}, Revoke: revoke::{Module, Call, Storage}, - BlobModule: blob::{Module, Call, Storage} + BlobStore: blob::{Module, Call, Storage} } ); @@ -339,44 +342,44 @@ impl_runtime_apis! { } impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents( - block: Block, - data: sp_inherents::InherentData, - ) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - - fn random_seed() -> ::Hash { - RandomnessCollectiveFlip::random_seed() - } - } + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + + fn random_seed() -> ::Hash { + RandomnessCollectiveFlip::random_seed() + } + } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx) - } - } + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx) + } + } impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> u64 { @@ -389,16 +392,16 @@ impl_runtime_apis! { } impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(seed: Option>) -> Vec { - opaque::SessionKeys::generate(seed) - } - - fn decode_session_keys( - encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { - opaque::SessionKeys::decode_into_raw_public_keys(&encoded) - } - } + fn generate_session_keys(seed: Option>) -> Vec { + opaque::SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + opaque::SessionKeys::decode_into_raw_public_keys(&encoded) + } + } impl fg_primitives::GrandpaApi for Runtime { fn grandpa_authorities() -> GrandpaAuthorityList { diff --git a/runtime/src/revoke.rs b/runtime/src/revoke.rs index 03b6b07c5..adad9e51d 100644 --- a/runtime/src/revoke.rs +++ b/runtime/src/revoke.rs @@ -342,133 +342,6 @@ impl Module { } } -// utilities for other test module in this file -#[cfg(test)] -mod testcommon { - use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; - use sp_core::{Pair, H256}; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, - }; - - pub use crate::revoke::*; - pub use frame_support::dispatch::DispatchError; - pub use rand::random; - pub use sp_core::sr25519; - pub use std::iter::once; - - pub type TestMod = crate::revoke::Module; - - impl_outer_origin! { - pub enum Origin for Test {} - } - - #[derive(Clone, Eq, Debug, PartialEq)] - pub struct Test; - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); - } - - impl system::Trait for Test { - type Origin = Origin; - type Call = (); - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type DbWeight = (); - type BlockExecutionWeight = (); - type ExtrinsicBaseWeight = (); - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - } - - impl did::Trait for Test { - type Event = (); - } - - impl crate::revoke::Trait for Test {} - - pub const ABBA: u64 = 0; - pub const RGA: RegistryId = [0u8; 32]; - pub const RA: RevokeId = [0u8; 32]; - pub const RB: RevokeId = [1u8; 32]; - pub const RC: RevokeId = [2u8; 32]; - pub const DIDA: Did = [0u8; 32]; - pub const DIDB: Did = [1u8; 32]; - pub const DIDC: Did = [2u8; 32]; - - /// check whether test externalies are available - pub fn in_ext() -> bool { - std::panic::catch_unwind(|| sp_io::storage::exists(&[])).is_ok() - } - - #[test] - pub fn meta_in_ext() { - assert!(!in_ext()); - ext().execute_with(|| assert!(in_ext())); - } - - pub fn ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() - } - - // create a OneOf policy - pub fn oneof(dids: &[Did]) -> Policy { - Policy::OneOf(dids.iter().cloned().collect()) - } - - /// generate a random keypair - pub fn gen_kp() -> sr25519::Pair { - sr25519::Pair::generate_with_phrase(None).0 - } - - // Create did for `did`. Return the randomly generated signing key. - // The did public key is controlled by some non-existent account (normally a security - // concern), but that doesn't matter for our purposes. - pub fn create_did(did: did::Did) -> sr25519::Pair { - let kp = gen_kp(); - did::Module::::new( - Origin::signed(ABBA), - did, - did::KeyDetail::new( - [100; 32], - did::PublicKey::Sr25519(did::Bytes32 { - value: kp.public().0, - }), - ), - ) - .unwrap(); - kp - } - - pub fn sign(payload: &crate::StateChange, keypair: &sr25519::Pair) -> DidSignature { - DidSignature::Sr25519(did::Bytes64 { - value: keypair.sign(&payload.encode()).0, - }) - } -} - #[cfg(test)] /// Tests every failure case in the module. /// If a failure case is not covered, thats a bug. @@ -477,7 +350,8 @@ mod testcommon { /// Tests in this module are named after the errors they check. /// For example, `#[test] fn invalidpolicy` exercises the RevErr::InvalidPolicy. mod errors { - use crate::revoke::testcommon::*; + use super::*; + use crate::test_common::*; #[test] fn invalidpolicy() { @@ -485,7 +359,7 @@ mod errors { return ext().execute_with(invalidpolicy); } - let err = TestMod::new_registry( + let err = RevoMod::new_registry( Origin::signed(ABBA), RGA, Registry { @@ -506,7 +380,7 @@ mod errors { fn assert_revoke_err(policy: Policy, signers: &[(Did, &sr25519::Pair)]) -> DispatchError { let regid: RegistryId = random(); - TestMod::new_registry( + RevoMod::new_registry( Origin::signed(ABBA), regid, Registry { @@ -532,7 +406,7 @@ mod errors { .collect(); dbg!(&revoke); dbg!(&proof); - TestMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap_err() + RevoMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap_err() } let (a, b, c) = (DIDA, DIDB, DIDC); @@ -574,7 +448,7 @@ mod errors { let kpa = create_did(DIDA); let reg = Registry { policy, add_only }; - TestMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); + RevoMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); let unrevoke = UnRevoke { registry_id, @@ -592,12 +466,12 @@ mod errors { last_modified, }; - TestMod::unrevoke(Origin::signed(ABBA), unrevoke.clone(), ur_proof.clone()).unwrap(); + RevoMod::unrevoke(Origin::signed(ABBA), unrevoke.clone(), ur_proof.clone()).unwrap(); assert_eq!( - TestMod::revoke(Origin::signed(ABBA), revoke, ur_proof.clone()).unwrap_err(), + RevoMod::revoke(Origin::signed(ABBA), revoke, ur_proof.clone()).unwrap_err(), RevErr::::NotAuthorized.into() ); - TestMod::unrevoke(Origin::signed(ABBA), unrevoke, ur_proof).unwrap(); + RevoMod::unrevoke(Origin::signed(ABBA), unrevoke, ur_proof).unwrap(); } #[test] @@ -610,8 +484,8 @@ mod errors { policy: oneof(&[DIDA]), add_only: false, }; - TestMod::new_registry(Origin::signed(ABBA), RGA, reg.clone()).unwrap(); - let err = TestMod::new_registry(Origin::signed(ABBA), RGA, reg).unwrap_err(); + RevoMod::new_registry(Origin::signed(ABBA), RGA, reg.clone()).unwrap(); + let err = RevoMod::new_registry(Origin::signed(ABBA), RGA, reg).unwrap_err(); assert_eq!(err, RevErr::::RegExists.into()); } @@ -626,7 +500,7 @@ mod errors { let noreg: Result<(), DispatchError> = Err(RevErr::::NoReg.into()); assert_eq!( - TestMod::revoke( + RevoMod::revoke( Origin::signed(ABBA), Revoke { registry_id, @@ -638,7 +512,7 @@ mod errors { noreg ); assert_eq!( - TestMod::unrevoke( + RevoMod::unrevoke( Origin::signed(ABBA), UnRevoke { registry_id, @@ -650,7 +524,7 @@ mod errors { noreg ); assert_eq!( - TestMod::remove_registry( + RevoMod::remove_registry( Origin::signed(ABBA), RemoveRegistry { registry_id, @@ -672,7 +546,7 @@ mod errors { let last_modified = 200u32; let err: Result<(), DispatchError> = Err(RevErr::::DifferentBlockNumber.into()); - TestMod::new_registry( + RevoMod::new_registry( Origin::signed(ABBA), registry_id, Registry { @@ -683,7 +557,7 @@ mod errors { .unwrap(); assert_eq!( - TestMod::revoke( + RevoMod::revoke( Origin::signed(ABBA), Revoke { registry_id, @@ -695,7 +569,7 @@ mod errors { err ); assert_eq!( - TestMod::unrevoke( + RevoMod::unrevoke( Origin::signed(ABBA), UnRevoke { registry_id, @@ -707,7 +581,7 @@ mod errors { err ); assert_eq!( - TestMod::remove_registry( + RevoMod::remove_registry( Origin::signed(ABBA), RemoveRegistry { registry_id, @@ -731,7 +605,7 @@ mod errors { let revoke_ids: BTreeSet<_> = [RA, RB, RC].iter().cloned().collect(); let kpa = create_did(DIDA); - TestMod::new_registry( + RevoMod::new_registry( Origin::signed(ABBA), registry_id, Registry { @@ -752,7 +626,7 @@ mod errors { )) .collect(); assert_eq!( - TestMod::unrevoke(Origin::signed(ABBA), unrevoke, ur_proof), + RevoMod::unrevoke(Origin::signed(ABBA), unrevoke, ur_proof), err ); @@ -769,7 +643,7 @@ mod errors { )) .collect(); assert_eq!( - TestMod::remove_registry(Origin::signed(ABBA), removeregistry, rr_proof), + RevoMod::remove_registry(Origin::signed(ABBA), removeregistry, rr_proof), err ); } @@ -797,7 +671,8 @@ mod errors { /// Tests in this module are named after the calls they check. /// For example, `#[test] fn new_registry` tests the happy path for Module::new_registry. mod calls { - use crate::revoke::testcommon::*; + use super::*; + use crate::test_common::*; #[test] fn new_registry() { @@ -815,7 +690,7 @@ mod calls { let reg_id = random(); let reg = Registry { policy, add_only }; assert!(!Registries::::contains_key(reg_id)); - TestMod::new_registry(Origin::signed(ABBA), reg_id, reg.clone()).unwrap(); + RevoMod::new_registry(Origin::signed(ABBA), reg_id, reg.clone()).unwrap(); assert!(Registries::::contains_key(reg_id)); let (created_reg, created_bloc) = Registries::::get(reg_id).unwrap(); assert_eq!(created_reg, reg); @@ -835,7 +710,7 @@ mod calls { let last_modified = 0u32; let kpa = create_did(DIDA); - TestMod::new_registry( + RevoMod::new_registry( Origin::signed(ABBA), registry_id, Registry { policy, add_only }, @@ -862,8 +737,10 @@ mod calls { )) .collect(); - TestMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); - assert!(ids.iter().all(|id| Revocations::contains_key(registry_id, id))); + RevoMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); + assert!(ids + .iter() + .all(|id| Revocations::contains_key(registry_id, id))); } } @@ -886,7 +763,7 @@ mod calls { AsrtNR, // assert not revoked } - TestMod::new_registry( + RevoMod::new_registry( Origin::signed(ABBA), registry_id, Registry { policy, add_only }, @@ -925,7 +802,7 @@ mod calls { sign(&crate::StateChange::Revoke(revoke.clone()), &kpa), )) .collect(); - TestMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); + RevoMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); } Action::UnRevo => { let unrevoke = UnRevoke { @@ -938,7 +815,7 @@ mod calls { sign(&crate::StateChange::UnRevoke(unrevoke.clone()), &kpa), )) .collect(); - TestMod::unrevoke(Origin::signed(ABBA), unrevoke, proof).unwrap(); + RevoMod::unrevoke(Origin::signed(ABBA), unrevoke, proof).unwrap(); } Action::AsrtRv => { assert!(revoke_ids @@ -967,7 +844,7 @@ mod calls { let kpa = create_did(DIDA); let reg = Registry { policy, add_only }; - TestMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); + RevoMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); assert!(Registries::::contains_key(registry_id)); // destroy reg @@ -980,7 +857,7 @@ mod calls { sign(&crate::StateChange::RemoveRegistry(rem.clone()), &kpa), )) .collect(); - TestMod::remove_registry(Origin::signed(ABBA), rem, proof).unwrap(); + RevoMod::remove_registry(Origin::signed(ABBA), rem, proof).unwrap(); // assert not exists assert!(!Registries::::contains_key(registry_id)); @@ -1002,7 +879,8 @@ mod calls { #[cfg(test)] /// Miscellaneous tests mod test { - use crate::revoke::testcommon::*; + use super::*; + use crate::test_common::*; #[test] /// Exercises Module::ensure_auth, both success and failure cases. @@ -1039,7 +917,7 @@ mod test { .iter() .map(|(did, kp)| (did.clone(), sign(&command, &kp))) .collect(); - let res = TestMod::ensure_auth(&command, &proof, &policy); + let res = RevoMod::ensure_auth(&command, &proof, &policy); assert_eq!(res.is_ok(), *expect_success); } } @@ -1056,10 +934,10 @@ mod test { let add_only = false; let reg = Registry { policy, add_only }; - assert_eq!(TestMod::get_revocation_registry(registry_id), None); - TestMod::new_registry(Origin::signed(ABBA), registry_id, reg.clone()).unwrap(); + assert_eq!(RevoMod::get_revocation_registry(registry_id), None); + RevoMod::new_registry(Origin::signed(ABBA), registry_id, reg.clone()).unwrap(); assert_eq!( - TestMod::get_revocation_registry(registry_id), + RevoMod::get_revocation_registry(registry_id), Some((reg, 0u64)) ); } @@ -1078,7 +956,7 @@ mod test { let kpa = create_did(DIDA); let revid: RevokeId = random(); let last_modified = 0u32; - TestMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); + RevoMod::new_registry(Origin::signed(ABBA), registry_id, reg).unwrap(); let revoke = Revoke { registry_id, revoke_ids: once(revid).collect(), @@ -1090,8 +968,8 @@ mod test { )) .collect(); - assert_eq!(TestMod::get_revocation_status(registry_id, revid), None); - TestMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); - assert_eq!(TestMod::get_revocation_status(registry_id, revid), Some(())); + assert_eq!(RevoMod::get_revocation_status(registry_id, revid), None); + RevoMod::revoke(Origin::signed(ABBA), revoke, proof).unwrap(); + assert_eq!(RevoMod::get_revocation_status(registry_id, revid), Some(())); } } diff --git a/runtime/src/template.rs b/runtime/src/template.rs deleted file mode 100644 index 043ec1c5d..000000000 --- a/runtime/src/template.rs +++ /dev/null @@ -1,146 +0,0 @@ -/// A runtime module template with necessary imports - -/// Feel free to remove or edit this file as needed. -/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs -/// If you remove this file, you can remove those references - -/// For more guidance on Substrate modules, see the example module -/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs -use frame_support::{decl_event, decl_module, decl_storage, dispatch::DispatchResult}; -use system::ensure_signed; - -/// The module's configuration trait. -pub trait Trait: system::Trait { - // TODO: Add other types and constants required configure this module. - - /// The overarching event type. - type Event: From> + Into<::Event>; -} - -// This module's storage items. -decl_storage! { - trait Store for Module as TemplateModule { - // Just a dummy storage item. - // Here we are declaring a StorageValue, `Something` as a Option - // `get(fn something)` is the default getter which returns either the stored `u32` or `None` if nothing stored - Something get(fn something): Option; - } -} - -// The module's dispatchable functions. -decl_module! { - /// The module declaration. - pub struct Module for enum Call where origin: T::Origin { - // Initializing events - // this is needed only if you are using events in your module - fn deposit_event() = default; - - // Just a dummy entry point. - // function that can be called by the external world as an extrinsics call - // takes a parameter of the type `AccountId`, stores it and emits an event - #[weight = 10_000] - pub fn do_something(origin, something: u32) -> DispatchResult { - // TODO: You only need this if you want to check it was signed. - let who = ensure_signed(origin)?; - - // TODO: Code to execute when something calls this. - // For example: the following line stores the passed in u32 in the storage - Something::put(something); - - // here we are raising the Something event - Self::deposit_event(RawEvent::SomethingStored(something, who)); - Ok(()) - } - } -} - -decl_event!( - pub enum Event - where - AccountId = ::AccountId, - { - // Just a dummy event. - // Event `Something` is declared with a parameter of the type `u32` and `AccountId` - // To emit this event, we call the deposit funtion, from our runtime funtions - SomethingStored(u32, AccountId), - } -); - -/// tests for this module -#[cfg(test)] -mod tests { - use super::*; - - use frame_support::{assert_ok, impl_outer_origin, parameter_types, weights::Weight}; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, - }; - - impl_outer_origin! { - pub enum Origin for Test {} - } - - // For testing the module, we construct most of a mock runtime. This means - // first constructing a configuration type (`Test`) which `impl`s each of the - // configuration traits of modules we want to use. - #[derive(Clone, Eq, PartialEq)] - pub struct Test; - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - } - - impl system::Trait for Test { - type Origin = Origin; - type Call = (); - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type BlockExecutionWeight = (); - type DbWeight = (); - type ExtrinsicBaseWeight = (); - type Version = (); - type ModuleToIndex = (); - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - } - impl Trait for Test { - type Event = (); - } - type TemplateModule = Module; - - // This function basically just builds a genesis storage key/value store according to - // our desired mockup. - fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() - } - - #[test] - fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - // Just a dummy test for the dummy funtion `do_something` - // calling the `do_something` function with a value 42 - assert_ok!(TemplateModule::do_something(Origin::signed(1), 42)); - // asserting that the stored value is equal to what we stored - assert_eq!(TemplateModule::something(), Some(42)); - }); - } -} diff --git a/runtime/src/test_common.rs b/runtime/src/test_common.rs new file mode 100644 index 000000000..3f897339d --- /dev/null +++ b/runtime/src/test_common.rs @@ -0,0 +1,141 @@ +//! Boilerplate for runtime module unit tests + +use crate::did; +use crate::did::{Did, DidSignature}; +use crate::revoke::{Policy, RegistryId, RevokeId}; +use codec::Encode; +use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use sp_core::{Pair, H256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub use frame_support::dispatch::DispatchError; +pub use rand::random; +pub use sp_core::sr25519; +pub use std::iter::once; + +pub type RevoMod = crate::revoke::Module; + +impl_outer_origin! { + pub enum Origin for Test {} +} + +#[derive(Clone, Eq, Debug, PartialEq)] +pub struct Test; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl system::Trait for Test { + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); +} + +impl crate::did::Trait for Test { + type Event = (); +} + +impl crate::revoke::Trait for Test {} + +parameter_types! { + pub const MaxBlobSize: u32 = 1024; +} + +impl crate::blob::Trait for Test { + type MaxBlobSize = MaxBlobSize; +} + +pub const ABBA: u64 = 0; +pub const RGA: RegistryId = [0u8; 32]; +pub const RA: RevokeId = [0u8; 32]; +pub const RB: RevokeId = [1u8; 32]; +pub const RC: RevokeId = [2u8; 32]; +pub const DIDA: Did = [0u8; 32]; +pub const DIDB: Did = [1u8; 32]; +pub const DIDC: Did = [2u8; 32]; + +/// check whether test externalies are available +pub fn in_ext() -> bool { + std::panic::catch_unwind(|| sp_io::storage::exists(&[])).is_ok() +} + +#[test] +pub fn meta_in_ext() { + assert!(!in_ext()); + ext().execute_with(|| assert!(in_ext())); +} + +pub fn ext() -> sp_io::TestExternalities { + system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into() +} + +// create a OneOf policy +pub fn oneof(dids: &[Did]) -> Policy { + Policy::OneOf(dids.iter().cloned().collect()) +} + +/// generate a random keypair +pub fn gen_kp() -> sr25519::Pair { + sr25519::Pair::generate_with_phrase(None).0 +} + +// Create did for `did`. Return the randomly generated signing key. +// The did public key is controlled by some non-existent account (normally a security +// concern), but that doesn't matter for our purposes. +pub fn create_did(did: did::Did) -> sr25519::Pair { + let kp = gen_kp(); + did::Module::::new( + Origin::signed(ABBA), + did, + did::KeyDetail::new( + [100; 32], + did::PublicKey::Sr25519(did::Bytes32 { + value: kp.public().0, + }), + ), + ) + .unwrap(); + kp +} + +/// create a did with a random id and random signing key +pub fn newdid() -> (Did, sr25519::Pair) { + let d: Did = rand::random(); + (d, create_did(d)) +} + +pub fn sign(payload: &crate::StateChange, keypair: &sr25519::Pair) -> DidSignature { + DidSignature::Sr25519(did::Bytes64 { + value: keypair.sign(&payload.encode()).0, + }) +}