From 64bc656faf5b108e3212af2bc8ea69697c240fb5 Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Mon, 18 Jul 2022 18:29:37 -0500 Subject: [PATCH 1/8] Configured Cargo.toml for the confidential docs pallet. Developed functions module for the confidential docs pallet. Developed main module for the confidential docs pallet. Developed types module for the confidential docs pallet. Developed mock module for the confidential docs pallet. Developed tests module for the confidential docs pallet. Configured runtime Cargo toml to add the confidential docs pallet. Configured runtime to include the confidential docs pallet. --- Cargo.lock | 15 ++ pallets/confidential-docs/Cargo.toml | 40 ++++++ pallets/confidential-docs/README.md | 1 + pallets/confidential-docs/src/functions.rs | 74 ++++++++++ pallets/confidential-docs/src/lib.rs | 158 +++++++++++++++++++++ pallets/confidential-docs/src/mock.rs | 78 ++++++++++ pallets/confidential-docs/src/tests.rs | 110 ++++++++++++++ pallets/confidential-docs/src/types.rs | 36 +++++ runtime/Cargo.toml | 2 + runtime/src/lib.rs | 5 + 10 files changed, 519 insertions(+) create mode 100644 pallets/confidential-docs/Cargo.toml create mode 100644 pallets/confidential-docs/README.md create mode 100644 pallets/confidential-docs/src/functions.rs create mode 100644 pallets/confidential-docs/src/lib.rs create mode 100644 pallets/confidential-docs/src/mock.rs create mode 100644 pallets/confidential-docs/src/tests.rs create mode 100644 pallets/confidential-docs/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 67215a19..9907b0fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2137,6 +2137,7 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-collective", + "pallet-confidential-docs", "pallet-fruniques", "pallet-gated-marketplace", "pallet-grandpa", @@ -4005,6 +4006,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-confidential-docs" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-fruniques" version = "0.1.0-dev" diff --git a/pallets/confidential-docs/Cargo.toml b/pallets/confidential-docs/Cargo.toml new file mode 100644 index 00000000..e0865153 --- /dev/null +++ b/pallets/confidential-docs/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "pallet-confidential-docs" +version = "4.0.0-dev" +description = "Provides backend services for the confidentials docs solution" +authors = ["Hashed Pallet { + + pub fn do_set_vault(owner: T::AccountId, user_id: UserId, public_key: PublicKey, cid: CID) -> DispatchResult { + Self::validate_cid(&cid)?; + ensure!(!>::contains_key(&user_id), >::UserAlreadyHasVault); + ensure!(!>::contains_key(&owner), >::AccountAlreadyHasPublicKey); + let vault = Vault{ + cid: cid.clone(), + owner: owner.clone() + }; + >::insert(user_id, vault.clone()); + >::insert(owner.clone(), public_key.clone()); + + Self::deposit_event(Event::VaultStored(user_id, public_key, vault)); + Ok(()) + } + + pub fn do_set_document(owner: T::AccountId, cid: CID, doc_name: DocName, doc_desc: DocDesc) -> DispatchResult { + Self::validate_cid(&cid)?; + Self::validate_doc_name(&doc_name)?; + Self::validate_doc_desc(&doc_desc)?; + let doc = Document{ + name: doc_name, + description: doc_desc + }; + >::insert(owner.clone(), cid.clone(), doc.clone()); + Self::deposit_event(Event::DocStored(owner, cid, doc)); + Ok(()) + } + + pub fn do_share_document(owner: T::AccountId, cid: CID, mut shared_doc: SharedDocument) -> DispatchResult { + shared_doc.from = owner; + Self::validate_cid(&cid)?; + Self::validate_shared_doc(&shared_doc)?; + ensure!(>::contains_key(owner, cid), >::DocumentAlreadySharedWithUser); + >::insert(owner.clone(), cid.clone(), doc.clone()); + Self::deposit_event(Event::DocStored(owner, cid, doc)); + Ok(()) + } + + fn validate_shared_doc(shared_doc: &SharedDocument)->DispatchResult{ + let SharedDocument { + name, + description, + from, + to, + original_cid, + } = shared_doc; + Self::validate_cid(original_cid)?; + Self::validate_doc_name(name)?; + Self::validate_doc_desc(description)?; + ensure!(from != to, >::DocumentSharedWithSelf); + Ok(()) + } + fn validate_cid(cid: &CID)->DispatchResult{ + ensure!(cid.len() > 0, >::CIDNoneValue); + Ok(()) + } + + fn validate_doc_name(doc_name: &DocName)->DispatchResult{ + ensure!(doc_name.len() >= T::DocNameMinLen::get().try_into().unwrap(), >::DocNameTooShort); + Ok(()) + } + + fn validate_doc_desc(doc_desc: &DocDesc)->DispatchResult{ + ensure!(doc_desc.len() >= T::DocDescMinLen::get().try_into().unwrap(), >::DocDescTooShort); + Ok(()) + } +} \ No newline at end of file diff --git a/pallets/confidential-docs/src/lib.rs b/pallets/confidential-docs/src/lib.rs new file mode 100644 index 00000000..78869112 --- /dev/null +++ b/pallets/confidential-docs/src/lib.rs @@ -0,0 +1,158 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/// Edit this file to define custom logic or remove it if it is not needed. +/// Learn more about FRAME and the core library of Substrate FRAME pallets: +/// +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +// #[cfg(feature = "runtime-benchmarks")] +// mod benchmarking; + +pub mod types; +mod functions; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{pallet_prelude::*, transactional}; + use frame_system::pallet_prelude::*; + use crate::types::*; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type Event: From> + IsType<::Event>; + + #[pallet::constant] + type DocNameMinLen: Get; + #[pallet::constant] + type DocNameMaxLen: Get; + #[pallet::constant] + type DocDescMinLen: Get; + #[pallet::constant] + type DocDescMaxLen: Get; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + // The pallet's runtime storage items. + // https://docs.substrate.io/v3/runtime/storage + #[pallet::storage] + #[pallet::getter(fn vaults)] + pub(super) type Vaults = StorageMap< + _, + Blake2_256, + UserId, //user identifier + Vault, + OptionQuery + >; + + #[pallet::storage] + #[pallet::getter(fn public_keys)] + pub(super) type PublicKeys = StorageMap< + _, + Blake2_256, + T::AccountId, + PublicKey, + OptionQuery + >; + + #[pallet::storage] + #[pallet::getter(fn documents)] + pub(super) type Documents = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + CID, + Document, + OptionQuery + >; + + #[pallet::storage] + #[pallet::getter(fn shared_documents)] + pub(super) type SharedDocuments = StorageMap< + _, + Blake2_256, + CID, + SharedDocument, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn shared_documents_by_to)] + pub(super) type SharedDocumentsByTo = StorageMap< + _, + Blake2_256, + T::AccountId, + CID, + OptionQuery + >; + + // Pallets use events to inform users when important changes are made. + // https://docs.substrate.io/v3/runtime/events-and-errors + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event documentation should end with an array that provides descriptive names for event + /// parameters. [something, who] + VaultStored(UserId, PublicKey, Vault), + DocStored(T::AccountId, CID, Document), + SharedDocStored(T::AccountId, CID, SharedDocument), + } + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + /// Empty CID + CIDNoneValue, + /// Document Name is too short + DocNameTooShort, + /// Document Desc is too short + DocDescTooShort, + /// Errors should have helpful documentation associated with them. + StorageOverflow, + /// The user already has a vault + UserAlreadyHasVault, + /// The user already has a public key + AccountAlreadyHasPublicKey, + /// The document has already been share with user + DocumentAlreadySharedWithUser, + /// Shared with self + DocumentSharedWithSelf, + + } + + #[pallet::call] + impl Pallet { + + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_vault(origin: OriginFor, user_id: UserId, public_key: PublicKey, cid: CID) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_vault(who, user_id, public_key, cid) + } + + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_document(origin: OriginFor, cid: CID, doc_name: DocName, doc_desc: DocDesc) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_document(who, cid, doc_name, doc_desc) + } + + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn share_document(origin: OriginFor, cid: CID, shared_doc: SharedDocument) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_share_document(who, cid, shared_doc) + } + } +} \ No newline at end of file diff --git a/pallets/confidential-docs/src/mock.rs b/pallets/confidential-docs/src/mock.rs new file mode 100644 index 00000000..ff43ef79 --- /dev/null +++ b/pallets/confidential-docs/src/mock.rs @@ -0,0 +1,78 @@ +use crate as pallet_confidential_docs; +use frame_support::parameter_types; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + ConfidentialDocs: pallet_confidential_docs::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const DocNameMinLen: u32 = 3; + pub const DocNameMaxLen: u32 = 30; + pub const DocDescMinLen: u32 = 5; + pub const DocDescMaxLen: u32 = 100; +} + + +impl pallet_confidential_docs::Config for Test { + type Event = Event; + type DocNameMinLen = DocNameMinLen; + type DocNameMaxLen = DocNameMaxLen; + type DocDescMinLen = DocDescMinLen; + type DocDescMaxLen = DocDescMaxLen; + +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/pallets/confidential-docs/src/tests.rs b/pallets/confidential-docs/src/tests.rs new file mode 100644 index 00000000..df30f332 --- /dev/null +++ b/pallets/confidential-docs/src/tests.rs @@ -0,0 +1,110 @@ +use crate::{mock::*, Error, types::*}; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, sp_io::hashing::blake2_256}; + + +fn generate_user_id(id: &str) -> UserId { + format!("user id: {}", id).using_encoded(blake2_256) +} + +fn generate_public_key(id: &str) -> PublicKey { + format!("public key: {}", id).using_encoded(blake2_256) +} + +fn generate_cid(id: &str) -> CID { + format!("cid{}", id).encode().try_into().unwrap() +} + +fn generate_document(id: &str) -> Document{ + Document{ + name: format!("doc name:{}", id).encode().try_into().unwrap(), + description: format!("doc desc:{}", id).encode().try_into().unwrap() + } +} + + + +#[test] +fn set_vault_works() { + new_test_ext().execute_with(|| { + let user_id = generate_user_id("1"); + let public_key = generate_public_key("1"); + let cid = generate_cid("1"); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); + // Read pallet storage and assert an expected result. + assert_eq!(ConfidentialDocs::vaults(user_id), Some(Vault{ + cid, + owner: 1 + })); + assert_eq!(ConfidentialDocs::public_keys(1), Some(public_key)); + }); +} + +#[test] +fn set_vault_should_fail_for_empty_cid() { + new_test_ext().execute_with(|| { + let user_id = generate_user_id("1"); + let public_key = generate_public_key("1"); + let cid: CID = Vec::new().try_into().unwrap(); + assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), Error::::CIDNoneValue); + }); +} + +#[test] +fn set_vault_should_fail_for_user_with_vault() { + new_test_ext().execute_with(|| { + let user_id = generate_user_id("1"); + let public_key = generate_public_key("1"); + let cid = generate_cid("1"); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); + assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), Error::::UserAlreadyHasVault); + }); +} + +#[test] +fn set_vault_should_fail_for_account_with_public_key() { + new_test_ext().execute_with(|| { + let public_key = generate_public_key("1"); + let cid = generate_cid("1"); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("1"), public_key, cid.clone())); + assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("2"), public_key, cid), Error::::AccountAlreadyHasPublicKey); + }); +} + +#[test] +fn set_document_works() { + new_test_ext().execute_with(|| { + let cid = generate_cid("1"); + let document = generate_document("1"); + assert_ok!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), document.description.clone())); + // Read pallet storage and assert an expected result. + assert_eq!(ConfidentialDocs::documents(1, cid), Some(document)); + }); +} + +#[test] +fn set_document_should_fail_for_empty_cid() { + new_test_ext().execute_with(|| { + let cid: CID = Vec::new().try_into().unwrap(); + let document = generate_document("1"); + assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), document.description.clone()), Error::::CIDNoneValue); + }); +} + +#[test] +fn set_document_should_fail_for_name_too_short() { + new_test_ext().execute_with(|| { + let cid: CID = Vec::new().try_into().unwrap(); + let document = generate_document("1"); + assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), "as".encode().try_into().unwrap(), document.description.clone()), Error::::CIDNoneValue); + }); +} + +#[test] +fn set_document_should_fail_for_description_too_short() { + new_test_ext().execute_with(|| { + let cid: CID = Vec::new().try_into().unwrap(); + let document = generate_document("1"); + assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), "desc".encode().try_into().unwrap()), Error::::CIDNoneValue); + }); +} diff --git a/pallets/confidential-docs/src/types.rs b/pallets/confidential-docs/src/types.rs new file mode 100644 index 00000000..e2207761 --- /dev/null +++ b/pallets/confidential-docs/src/types.rs @@ -0,0 +1,36 @@ + +use super::*; +use frame_support::pallet_prelude::*; + +pub type CID = BoundedVec>; +pub type PublicKey = [u8;32]; +pub type UserId = [u8;32]; +pub type DocName = BoundedVec::DocNameMaxLen>; +pub type DocDesc = BoundedVec::DocDescMaxLen>; + +#[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct Vault{ + pub cid: CID, + pub owner: T::AccountId, +} + +#[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct Document{ + pub name: DocName, + pub description: DocDesc, +} + +#[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct SharedDocument{ + pub name: DocName, + pub description: DocDesc, + pub from: T::AccountId, + pub to: T::AccountId, + pub original_cid: CID +} \ No newline at end of file diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index dbb7231d..50282264 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -65,6 +65,7 @@ pallet-template = { version = "4.0.0-dev", default-features = false, path = "../ pallet-fruniques = { version = "0.1.0-dev", default-features = false, path = "../pallets/fruniques" } pallet-nbv-storage = { version = "4.0.0-dev", default-features = false, path = "../pallets/nbv-storage" } pallet-gated-marketplace = { version = "4.0.0-dev", default-features = false, path = "../pallets/gated-marketplace" } +pallet-confidential-docs = { version = "4.0.0-dev", default-features = false, path = "../pallets/confidential-docs" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.23" } @@ -101,6 +102,7 @@ std = [ "pallet-node-authorization/std", "pallet-nbv-storage/std", "pallet-gated-marketplace/std", + "pallet-confidential-docs/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4761c942..d417c8e5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -594,6 +594,10 @@ impl pallet_nbv_storage::Config for Runtime { type MaxProposalsPerVault = MaxProposalsPerVault; } +impl pallet_confidential_docs::Config for Runtime { + type Event = Event; +} + parameter_types! { pub const MaxRecursions: u32 = 10; @@ -686,6 +690,7 @@ construct_runtime!( GatedMarketplace: pallet_gated_marketplace, Assets: pallet_assets, NBVStorage: pallet_nbv_storage, + ConfidentialDocs: pallet_confidential_docs, } ); From 435557f958b81193d50f94c0fc90540747d66da5 Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Wed, 20 Jul 2022 16:42:08 -0500 Subject: [PATCH 2/8] Developed do_set_owned_document function to enable the updating of metadata of existing documents in the functions module of the confidential docs pallet. Moved share document validate functionality to its own function in the functions module of the confidential docs pallet. Developed validate shared doc function in the functions module of the confidential docs pallet. Developed validate owned doc function in the functions module of the confidential docs pallet. Developed validate has public key function in the functions module of the confidential docs pallet. Added MaxOwnedDocs and MaxSharedToDocs consts to the confidential docs pallet. Updated the storage for the owned docs in the confidential docs pallet. Added owned docs by owner storage in the confidential docs pallet. Updated ShareDocs storage in the confidential docs pallet. Added Shared docs by to storage in the confidential docs pallet. Updated OwnedDocStored event in the confidential docs pallet. Updated SharedDocStored event in the confidential docs pallet. Added errors in the confidential docs pallet. Developed kill_storage extrinsic in the confidential docs pallet. Updated pallet config in the mocks module in the confidential docs pallet. Developed generate_doc_name method in the tests module in the confidential docs pallet. Developed generate_desc_name method in the tests module in the confidential docs pallet.Developed generate_owned_doc method in the tests module in the confidential docs pallet. Developed generate_shared_doc method in the tests module in the confidential docs pallet. Developed set vault method in the tests module in the confidential docs pallet. Developed tests to verify the owned doc functionality in the confidential docs pallet. Developed tests to verify the shared doc functionality in the confidential docs pallet. Updated shareDoc type in the confidential docs pallet. Updated ownedDoc type in the confidential docs pallet. Updated the confidential docs pallet config in the runtime. --- pallets/confidential-docs/src/functions.rs | 76 ++++-- pallets/confidential-docs/src/lib.rs | 93 ++++++-- pallets/confidential-docs/src/mock.rs | 8 +- pallets/confidential-docs/src/tests.rs | 264 ++++++++++++++++++--- pallets/confidential-docs/src/types.rs | 8 +- runtime/src/lib.rs | 21 +- 6 files changed, 390 insertions(+), 80 deletions(-) diff --git a/pallets/confidential-docs/src/functions.rs b/pallets/confidential-docs/src/functions.rs index 63646dc1..240c1ee1 100644 --- a/pallets/confidential-docs/src/functions.rs +++ b/pallets/confidential-docs/src/functions.rs @@ -20,41 +20,77 @@ impl Pallet { Ok(()) } - pub fn do_set_document(owner: T::AccountId, cid: CID, doc_name: DocName, doc_desc: DocDesc) -> DispatchResult { - Self::validate_cid(&cid)?; - Self::validate_doc_name(&doc_name)?; - Self::validate_doc_desc(&doc_desc)?; - let doc = Document{ - name: doc_name, - description: doc_desc - }; - >::insert(owner.clone(), cid.clone(), doc.clone()); - Self::deposit_event(Event::DocStored(owner, cid, doc)); + pub fn do_set_owned_document(owner: T::AccountId, mut owned_doc: OwnedDoc) -> DispatchResult { + owned_doc.owner = owner.clone(); + Self::validate_owned_doc(&owned_doc)?; + let OwnedDoc { + cid, + .. + } = owned_doc.clone(); + if let Some(doc) = >::get(&cid) { + ensure!(doc.owner == owner, >::NotDocOwner); + } else { + >::try_mutate(&owner, |owned_vec| { + owned_vec.try_push(cid.clone()) + }).map_err(|_| >::ExceedMaxOwnedDocs)?; + } + >::insert(cid.clone(), owned_doc.clone()); + Self::deposit_event(Event::OwnedDocStored(owned_doc)); Ok(()) } - pub fn do_share_document(owner: T::AccountId, cid: CID, mut shared_doc: SharedDocument) -> DispatchResult { + pub fn do_share_document(owner: T::AccountId, mut shared_doc: SharedDoc) -> DispatchResult { shared_doc.from = owner; - Self::validate_cid(&cid)?; Self::validate_shared_doc(&shared_doc)?; - ensure!(>::contains_key(owner, cid), >::DocumentAlreadySharedWithUser); - >::insert(owner.clone(), cid.clone(), doc.clone()); - Self::deposit_event(Event::DocStored(owner, cid, doc)); + let SharedDoc { + cid, + to, + .. + } = shared_doc.clone(); + + >::try_mutate(&to, |shared_vec| { + shared_vec.try_push(cid.clone()) + }).map_err(|_| >::ExceedMaxSharedToDocs)?; + + >::insert(cid.clone(), shared_doc.clone()); + Self::deposit_event(Event::SharedDocStored(shared_doc)); Ok(()) } - fn validate_shared_doc(shared_doc: &SharedDocument)->DispatchResult{ - let SharedDocument { + fn validate_owned_doc(owned_doc: &OwnedDoc)->DispatchResult{ + let OwnedDoc { + cid, + name, + description, + owner + } = owned_doc; + Self::validate_cid(cid)?; + Self::validate_doc_name(name)?; + Self::validate_doc_desc(description)?; + Self::validate_has_public_key(owner)?; + Ok(()) + } + + fn validate_shared_doc(shared_doc: &SharedDoc)->DispatchResult{ + let SharedDoc { + cid, name, description, from, to, - original_cid, } = shared_doc; - Self::validate_cid(original_cid)?; + Self::validate_cid(cid)?; Self::validate_doc_name(name)?; Self::validate_doc_desc(description)?; - ensure!(from != to, >::DocumentSharedWithSelf); + ensure!(from != to, >::DocSharedWithSelf); + ensure!(!>::contains_key(cid), >::DocAlreadyShared); + Self::validate_has_public_key(from)?; + Self::validate_has_public_key(to)?; + Ok(()) + } + + fn validate_has_public_key(who: &T::AccountId)->DispatchResult{ + ensure!(>::contains_key(who), >::AccountHasNoPublicKey); Ok(()) } fn validate_cid(cid: &CID)->DispatchResult{ diff --git a/pallets/confidential-docs/src/lib.rs b/pallets/confidential-docs/src/lib.rs index 78869112..86535755 100644 --- a/pallets/confidential-docs/src/lib.rs +++ b/pallets/confidential-docs/src/lib.rs @@ -29,6 +29,12 @@ pub mod pallet { /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; + type RemoveOrigin: EnsureOrigin; + + #[pallet::constant] + type MaxOwnedDocs: Get; + #[pallet::constant] + type MaxSharedToDocs: Get; #[pallet::constant] type DocNameMinLen: Get; #[pallet::constant] @@ -66,35 +72,43 @@ pub mod pallet { >; #[pallet::storage] - #[pallet::getter(fn documents)] - pub(super) type Documents = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, - Blake2_128Concat, + #[pallet::getter(fn owned_docs)] + pub(super) type OwnedDocs = StorageMap< + _, + Blake2_256, CID, - Document, + OwnedDoc, OptionQuery >; #[pallet::storage] - #[pallet::getter(fn shared_documents)] - pub(super) type SharedDocuments = StorageMap< + #[pallet::getter(fn owned_docs_by_owner)] + pub(super) type OwnedDocsByOwner = StorageMap< + _, + Blake2_256, + T::AccountId, + BoundedVec, + ValueQuery + >; + + #[pallet::storage] + #[pallet::getter(fn shared_docs)] + pub(super) type SharedDocs = StorageMap< _, Blake2_256, CID, - SharedDocument, + SharedDoc, OptionQuery, >; #[pallet::storage] - #[pallet::getter(fn shared_documents_by_to)] - pub(super) type SharedDocumentsByTo = StorageMap< + #[pallet::getter(fn shared_docs_by_to)] + pub(super) type SharedDocsByTo = StorageMap< _, Blake2_256, T::AccountId, - CID, - OptionQuery + BoundedVec, + ValueQuery >; // Pallets use events to inform users when important changes are made. @@ -105,8 +119,8 @@ pub mod pallet { /// Event documentation should end with an array that provides descriptive names for event /// parameters. [something, who] VaultStored(UserId, PublicKey, Vault), - DocStored(T::AccountId, CID, Document), - SharedDocStored(T::AccountId, CID, SharedDocument), + OwnedDocStored(OwnedDoc), + SharedDocStored(SharedDoc), } // Errors inform users that something went wrong. @@ -124,10 +138,18 @@ pub mod pallet { UserAlreadyHasVault, /// The user already has a public key AccountAlreadyHasPublicKey, - /// The document has already been share with user - DocumentAlreadySharedWithUser, + /// User is not document owner + NotDocOwner, + /// The document has already been shared + DocAlreadyShared, /// Shared with self - DocumentSharedWithSelf, + DocSharedWithSelf, + /// Account has no public key + AccountHasNoPublicKey, + /// Max owned documents has been exceeded + ExceedMaxOwnedDocs, + /// Max documents shared with the "to" account has been exceeded + ExceedMaxSharedToDocs, } @@ -143,16 +165,41 @@ pub mod pallet { #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_document(origin: OriginFor, cid: CID, doc_name: DocName, doc_desc: DocDesc) -> DispatchResult { + pub fn set_owned_document(origin: OriginFor, owned_doc: OwnedDoc) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_set_document(who, cid, doc_name, doc_desc) + Self::do_set_owned_document(who, owned_doc) } #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn share_document(origin: OriginFor, cid: CID, shared_doc: SharedDocument) -> DispatchResult { + pub fn share_document(origin: OriginFor, shared_doc: SharedDoc) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_share_document(who, cid, shared_doc) + Self::do_share_document(who, shared_doc) + } + + /// Kill all the stored data. + /// + /// This function is used to kill ALL the stored data. + /// Use with caution! + /// + /// ### Parameters: + /// - `origin`: The user who performs the action. + /// + /// ### Considerations: + /// - This function is only available to the `admin` with sudo access. + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn kill_storage( + origin: OriginFor, + ) -> DispatchResult{ + T::RemoveOrigin::ensure_origin(origin.clone())?; + >::remove_all(None); + >::remove_all(None); + >::remove_all(None); + >::remove_all(None); + >::remove_all(None); + >::remove_all(None); + Ok(()) } } } \ No newline at end of file diff --git a/pallets/confidential-docs/src/mock.rs b/pallets/confidential-docs/src/mock.rs index ff43ef79..d9901e0b 100644 --- a/pallets/confidential-docs/src/mock.rs +++ b/pallets/confidential-docs/src/mock.rs @@ -1,6 +1,7 @@ use crate as pallet_confidential_docs; use frame_support::parameter_types; use frame_system as system; +use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -56,7 +57,9 @@ impl system::Config for Test { } parameter_types! { - pub const DocNameMinLen: u32 = 3; + pub const MaxOwnedDocs: u32 = 100; + pub const MaxSharedToDocs: u32 = 100; + pub const DocNameMinLen: u32 = 4; pub const DocNameMaxLen: u32 = 30; pub const DocDescMinLen: u32 = 5; pub const DocDescMaxLen: u32 = 100; @@ -65,6 +68,9 @@ parameter_types! { impl pallet_confidential_docs::Config for Test { type Event = Event; + type RemoveOrigin = EnsureRoot; + type MaxOwnedDocs = MaxOwnedDocs; + type MaxSharedToDocs = MaxSharedToDocs; type DocNameMinLen = DocNameMinLen; type DocNameMaxLen = DocNameMaxLen; type DocDescMinLen = DocDescMinLen; diff --git a/pallets/confidential-docs/src/tests.rs b/pallets/confidential-docs/src/tests.rs index df30f332..cd8b8f9d 100644 --- a/pallets/confidential-docs/src/tests.rs +++ b/pallets/confidential-docs/src/tests.rs @@ -1,7 +1,7 @@ -use crate::{mock::*, Error, types::*}; +use crate::{mock::*, types::*, Error}; use codec::Encode; use frame_support::{assert_noop, assert_ok, sp_io::hashing::blake2_256}; - +use frame_system as system; fn generate_user_id(id: &str) -> UserId { format!("user id: {}", id).using_encoded(blake2_256) @@ -12,16 +12,52 @@ fn generate_public_key(id: &str) -> PublicKey { } fn generate_cid(id: &str) -> CID { - format!("cid{}", id).encode().try_into().unwrap() + format!("cid: {}", id).encode().try_into().unwrap() +} + +fn generate_doc_name(id: &str) -> DocName { + format!("doc name:{}", id).encode().try_into().unwrap() +} + +fn generate_doc_desc(id: &str) -> DocDesc { + format!("doc desc:{}", id).encode().try_into().unwrap() } -fn generate_document(id: &str) -> Document{ - Document{ - name: format!("doc name:{}", id).encode().try_into().unwrap(), - description: format!("doc desc:{}", id).encode().try_into().unwrap() +fn generate_owned_doc( + id: &str, + owner: ::AccountId, +) -> OwnedDoc { + OwnedDoc { + cid: generate_cid(id), + name: generate_doc_name(id), + description: generate_doc_desc(id), + owner, } } +fn generate_shared_doc( + id: &str, + from: ::AccountId, + to: ::AccountId, +) -> SharedDoc { + SharedDoc { + cid: generate_cid(id), + name: generate_doc_name(id), + description: generate_doc_desc(id), + from, + to, + } +} + +fn set_vault(who: ::AccountId) { + let id = &who.to_string(); + assert_ok!(ConfidentialDocs::set_vault( + Origin::signed(who), + generate_user_id(id), + generate_public_key(id), + generate_cid(id) + )); +} #[test] @@ -32,10 +68,7 @@ fn set_vault_works() { let cid = generate_cid("1"); assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); // Read pallet storage and assert an expected result. - assert_eq!(ConfidentialDocs::vaults(user_id), Some(Vault{ - cid, - owner: 1 - })); + assert_eq!(ConfidentialDocs::vaults(user_id), Some(Vault { cid, owner: 1 })); assert_eq!(ConfidentialDocs::public_keys(1), Some(public_key)); }); } @@ -46,7 +79,10 @@ fn set_vault_should_fail_for_empty_cid() { let user_id = generate_user_id("1"); let public_key = generate_public_key("1"); let cid: CID = Vec::new().try_into().unwrap(); - assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), Error::::CIDNoneValue); + assert_noop!( + ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), + Error::::CIDNoneValue + ); }); } @@ -57,7 +93,10 @@ fn set_vault_should_fail_for_user_with_vault() { let public_key = generate_public_key("1"); let cid = generate_cid("1"); assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); - assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), Error::::UserAlreadyHasVault); + assert_noop!( + ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), + Error::::UserAlreadyHasVault + ); }); } @@ -66,45 +105,206 @@ fn set_vault_should_fail_for_account_with_public_key() { new_test_ext().execute_with(|| { let public_key = generate_public_key("1"); let cid = generate_cid("1"); - assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("1"), public_key, cid.clone())); - assert_noop!(ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("2"), public_key, cid), Error::::AccountAlreadyHasPublicKey); + assert_ok!(ConfidentialDocs::set_vault( + Origin::signed(1), + generate_user_id("1"), + public_key, + cid.clone() + )); + assert_noop!( + ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("2"), public_key, cid), + Error::::AccountAlreadyHasPublicKey + ); }); } #[test] -fn set_document_works() { +fn set_owned_document_works() { new_test_ext().execute_with(|| { - let cid = generate_cid("1"); - let document = generate_document("1"); - assert_ok!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), document.description.clone())); + let owner = 1; + set_vault(owner); + let mut doc1 = generate_owned_doc("1", owner); + assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone())); + assert_eq!(ConfidentialDocs::owned_docs(&doc1.cid), Some(doc1.clone())); + let owned_docs = ConfidentialDocs::owned_docs_by_owner(owner); + let expected_cid_vec = vec!(doc1.cid.clone()); + assert_eq!(owned_docs.into_inner(), expected_cid_vec); + doc1.name = generate_doc_name("2"); + doc1.description = generate_doc_desc("2"); + assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone())); + assert_eq!(ConfidentialDocs::owned_docs(&doc1.cid), Some(doc1.clone())); + let owned_docs = ConfidentialDocs::owned_docs_by_owner(owner); + assert_eq!(owned_docs.into_inner(), expected_cid_vec); + }); +} + +#[test] +fn set_owned_document_should_fail_for_updating_non_owned_doc() { + new_test_ext().execute_with(|| { + let owner = 1; + set_vault(owner); + let mut doc1 = generate_owned_doc("1", owner); + assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone())); + doc1.name = generate_doc_name("2"); + let owner = 2; + set_vault(owner); + assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone()), Error::::NotDocOwner); + }); +} + +#[test] +fn set_owned_document_should_fail_for_empty_cid() { + new_test_ext().execute_with(|| { + let mut doc = generate_owned_doc("1", 1); + doc.cid = Vec::new().try_into().unwrap(); + assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(1), doc.clone()), Error::::CIDNoneValue); + }); +} + +#[test] +fn set_owned_document_should_fail_for_name_too_short() { + new_test_ext().execute_with(|| { + let mut doc = generate_owned_doc("1", 1); + doc.name = "as".encode().try_into().unwrap(); + assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(1), doc.clone()), Error::::DocNameTooShort); + }); +} + +#[test] +fn set_owned_document_should_fail_for_description_too_short() { + new_test_ext().execute_with(|| { + let mut doc = generate_owned_doc("1", 1); + doc.description = "des".encode().try_into().unwrap(); + assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(1), doc.clone()), Error::::DocDescTooShort); + }); +} + +#[test] +fn set_owned_document_should_fail_for_owner_with_no_public_key() { + new_test_ext().execute_with(|| { + let owner = 1; + let doc = generate_owned_doc("1", owner); + assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc.clone()), Error::::AccountHasNoPublicKey); + }); +} + +#[test] +fn share_document_works() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + set_vault(to); + set_vault(from); + let shared_doc1 = generate_shared_doc("1", from, to); + assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc1.clone())); // Read pallet storage and assert an expected result. - assert_eq!(ConfidentialDocs::documents(1, cid), Some(document)); + assert_eq!(ConfidentialDocs::shared_docs(&shared_doc1.cid), Some(shared_doc1.clone())); + let shared_docs = ConfidentialDocs::shared_docs_by_to(to); + let mut expected_cid_vec = vec!(shared_doc1.cid.clone()); + assert_eq!(shared_docs.into_inner(), expected_cid_vec); + + let from = 3; + set_vault(3); + let shared_doc2 = generate_shared_doc("2", from, to); + assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc2.clone())); + assert_eq!(ConfidentialDocs::shared_docs(&shared_doc2.cid), Some(shared_doc2.clone())); + let shared_docs = ConfidentialDocs::shared_docs_by_to(to); + expected_cid_vec.push(shared_doc2.cid.clone()); + assert_eq!(shared_docs.into_inner(), expected_cid_vec); }); } #[test] -fn set_document_should_fail_for_empty_cid() { +fn share_document_should_fail_for_empty_cid() { new_test_ext().execute_with(|| { - let cid: CID = Vec::new().try_into().unwrap(); - let document = generate_document("1"); - assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), document.description.clone()), Error::::CIDNoneValue); + let mut shared_doc = generate_shared_doc("1", 1, 2); + shared_doc.cid = Vec::new().try_into().unwrap(); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(1), shared_doc.clone()), + Error::::CIDNoneValue + ); }); } #[test] -fn set_document_should_fail_for_name_too_short() { +fn share_document_should_fail_for_name_too_short() { new_test_ext().execute_with(|| { - let cid: CID = Vec::new().try_into().unwrap(); - let document = generate_document("1"); - assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), "as".encode().try_into().unwrap(), document.description.clone()), Error::::CIDNoneValue); + let mut shared_doc = generate_shared_doc("1", 1, 2); + shared_doc.name = "as".encode().try_into().unwrap(); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(1), shared_doc.clone()), + Error::::DocNameTooShort + ); }); } #[test] -fn set_document_should_fail_for_description_too_short() { +fn share_document_should_fail_for_desc_too_short() { new_test_ext().execute_with(|| { - let cid: CID = Vec::new().try_into().unwrap(); - let document = generate_document("1"); - assert_noop!(ConfidentialDocs::set_document(Origin::signed(1), cid.clone(), document.name.clone(), "desc".encode().try_into().unwrap()), Error::::CIDNoneValue); + let mut shared_doc = generate_shared_doc("1", 1, 2); + shared_doc.description = "des".encode().try_into().unwrap(); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(1), shared_doc.clone()), + Error::::DocDescTooShort + ); + }); +} + +#[test] +fn share_document_should_fail_for_share_to_self() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 1; + set_vault(to); + let shared_doc = generate_shared_doc("1", from, to); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), + Error::::DocSharedWithSelf + ); }); } + +#[test] +fn share_document_should_fail_for_doc_already_shared() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + set_vault(to); + set_vault(from); + let shared_doc = generate_shared_doc("1", from, to); + assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone())); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), + Error::::DocAlreadyShared + ); + }); +} + +#[test] +fn share_document_should_fail_for_from_with_no_public_key() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + set_vault(to); + let shared_doc = generate_shared_doc("1", from, to); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), + Error::::AccountHasNoPublicKey + ); + }); +} + +#[test] +fn share_document_should_fail_for_to_with_no_public_key() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + set_vault(from); + let shared_doc = generate_shared_doc("1", from, to); + assert_noop!( + ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), + Error::::AccountHasNoPublicKey + ); + }); +} + diff --git a/pallets/confidential-docs/src/types.rs b/pallets/confidential-docs/src/types.rs index e2207761..b00cfd1f 100644 --- a/pallets/confidential-docs/src/types.rs +++ b/pallets/confidential-docs/src/types.rs @@ -19,18 +19,20 @@ pub struct Vault{ #[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] -pub struct Document{ +pub struct OwnedDoc{ + pub cid: CID, pub name: DocName, pub description: DocDesc, + pub owner: T::AccountId, } #[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] -pub struct SharedDocument{ +pub struct SharedDoc{ + pub cid: CID, pub name: DocName, pub description: DocDesc, pub from: T::AccountId, pub to: T::AccountId, - pub original_cid: CID } \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d417c8e5..b3ba5168 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -117,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { /// up by `pallet_aura` to implement `fn slot_duration()`. /// /// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; +pub const MILLISECS_PER_BLOCK: u64 = 3000; // NOTE: Currently it is not possible to change the slot duration after the chain has started. // Attempting to do so will brick block production. @@ -594,8 +594,27 @@ impl pallet_nbv_storage::Config for Runtime { type MaxProposalsPerVault = MaxProposalsPerVault; } +parameter_types! { + pub const MaxOwnedDocs: u32 = 100; + pub const MaxSharedToDocs: u32 = 100; + pub const DocNameMinLen: u32 = 3; + pub const DocNameMaxLen: u32 = 50; + pub const DocDescMinLen: u32 = 5; + pub const DocDescMaxLen: u32 = 100; +} + impl pallet_confidential_docs::Config for Runtime { type Event = Event; + type RemoveOrigin = EnsureOneOf< + EnsureRoot, + pallet_collective::EnsureProportionAtLeast, + >; + type MaxOwnedDocs = MaxOwnedDocs; + type MaxSharedToDocs = MaxSharedToDocs; + type DocNameMinLen = DocNameMinLen; + type DocNameMaxLen = DocNameMaxLen; + type DocDescMinLen = DocDescMinLen; + type DocDescMaxLen = DocDescMaxLen; } From eedad01efd2da7e87841a03d6b13f873191f480b Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Mon, 1 Aug 2022 08:38:19 -0500 Subject: [PATCH 3/8] Added README file to the confidential docs pallet. Documented types module of the confidential docs pallet. Documented main module of the confidential docs pallet. --- pallets/confidential-docs/README.md | 138 ++++++++++++++++++++++++- pallets/confidential-docs/src/lib.rs | 52 +++++++--- pallets/confidential-docs/src/types.rs | 22 +++- 3 files changed, 198 insertions(+), 14 deletions(-) diff --git a/pallets/confidential-docs/README.md b/pallets/confidential-docs/README.md index 8d751a42..ff005161 100644 --- a/pallets/confidential-docs/README.md +++ b/pallets/confidential-docs/README.md @@ -1 +1,137 @@ -License: Unlicense \ No newline at end of file +# Confidential documents +Provides the backend services and metadata storage for the confidential docs solution + +- [Confidential documents](#confidential-documents) + - [Overview](#overview) + - [Interface](#interface) + - [Dispachable functions](#dispachable-functions) + - [Getters](#getters) + - [Usage](#usage) + - [Polkadot-js api (javascript library)](#polkadot-js-api-javascript-library) + - [Create a vault](#create-a-vault) + - [Get a vault](#get-a-vault) + - [Get a public key](#get-a-public-key) + - [Create an owned confidential document](#create-an-owned-confidential-document) + - [Get an owned confidential document by CID](#get-an-owned-confidential-document-by-cid) + - [Share a confidential document](#share-a-confidential-document) + - [Get a shared confidential document by CID](#get-a-shared-confidential-document-by-cid) +## Overview + +This module allows a user to: +- Create their vault. The vault stores the cipher private key used to cipher the user documents. The way the user vault is ciphered depends on the login method used by the user. +- Create on owned confidential document that only the user has access to +- Update the metadata of an owned confidential document +- Share a confidential document with another user + +## Interface + +### Dispachable functions +- `set_vault` Creates the calling user's vault and sets their public cipher key +- `set_owned_document` Creates a new owned document or updates an existing owned document's metadata +- `share_document` Creates a shared document + +### Getters +- `vaults` +- `public_keys` +- `owned_docs` +- `owned_docs_by_owner` +- `shared_docs` +- `shared_docs_by_to` + +## Usage + +The following examples will be using these prefunded accounts and testing data: + +```bash +# Alice's mnemonic seed +"//Alice" +# Alice's public address +"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + +# Bob's mnemonic seed +"//Bob" +# Bob's public address +"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" +``` + +### Polkadot-js api (javascript library) + +#### Create a vault +```js +const response = await api.tx.confidentialDocs.setVault(userId, publicKey, cid).signAndSend(alice); +``` + +#### Get a vault +```js +const vault = await api.query.confidentialDocs.vaults(userId); + console.log(vault.toHuman()); +``` +```bash +# Output should look like this: +{ + cid: 'QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr', + owner: '5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua' +} +``` + +#### Get a public key +```js +const publicKey = await api.query.confidentialDocs.publicKeys(address); + console.log(markets.toHuman()); +``` +```bash +# Output should look like this: +'0xabe44a53e2c1a5c7fa2f920338136d0ddc3aba23eacaf708e3871bc856a34b75' +``` + +#### Create an owned confidential document +```js +const response = await api.tx.confidentialDocs.setOwnedDocument({ + "cid": "QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr", + "name": "name", + "description": "desc", + "owner": "5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua" + }).signAndSend(alice); +``` + +#### Get an owned confidential document by CID +```js +const ownedDoc = await api.query.confidentialDocs.ownedDocs(cid); + console.log(ownedDoc.toHuman()); +``` +```bash +# Output should look like this: +{ + "cid": "QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr", + "name": "name", + "description": "desc", + "owner": "5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua" +} +``` + +#### Share a confidential document +```js +const response = await api.tx.confidentialDocs.shareDocument({ + "cid": "QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr", + "name": "name", + "description": "desc", + "to": "5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua", + "from": "5FWtfhKTuGKm9yWqzApwTfiUL4UPWukJzEcCTGYDiYHsdKaG" + }).signAndSend(alice); +``` + +#### Get a shared confidential document by CID +```js +const sharedDoc = await api.query.confidentialDocs.sharedDocs(cid); + console.log(sharedDoc.toHuman()); +``` +```bash +# Output should look like this: +{ + "cid": "QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr", + "name": "name", + "description": "desc", + "to": "5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua", + "from": "5FWtfhKTuGKm9yWqzApwTfiUL4UPWukJzEcCTGYDiYHsdKaG" +} +``` \ No newline at end of file diff --git a/pallets/confidential-docs/src/lib.rs b/pallets/confidential-docs/src/lib.rs index 86535755..18417660 100644 --- a/pallets/confidential-docs/src/lib.rs +++ b/pallets/confidential-docs/src/lib.rs @@ -1,8 +1,8 @@ +//! The confidential docs pallet provides the backend services and metadata +//! storage for the confidential docs solution + #![cfg_attr(not(feature = "std"), no_std)] -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// pub use pallet::*; #[cfg(test)] @@ -19,28 +19,34 @@ mod functions; #[frame_support::pallet] pub mod pallet { + //! Provides the backend services and metadata storage for the confidential docs solution use frame_support::{pallet_prelude::*, transactional}; use frame_system::pallet_prelude::*; use crate::types::*; - /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. + type Event: From> + IsType<::Event>; type RemoveOrigin: EnsureOrigin; + /// Maximum number of confidential documents that a user can own #[pallet::constant] type MaxOwnedDocs: Get; + /// Maximum number of confidential documents that can be shared to a user #[pallet::constant] type MaxSharedToDocs: Get; + /// Minimum length for a document name #[pallet::constant] type DocNameMinLen: Get; + /// Maximum length for a document name #[pallet::constant] type DocNameMaxLen: Get; + /// Minimum length for a document description #[pallet::constant] type DocDescMinLen: Get; + /// Maximum length for a document description #[pallet::constant] type DocDescMaxLen: Get; } @@ -49,8 +55,7 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); - // The pallet's runtime storage items. - // https://docs.substrate.io/v3/runtime/storage + #[pallet::storage] #[pallet::getter(fn vaults)] pub(super) type Vaults = StorageMap< @@ -111,19 +116,18 @@ pub mod pallet { ValueQuery >; - // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/v3/runtime/events-and-errors + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Event documentation should end with an array that provides descriptive names for event - /// parameters. [something, who] + /// Vault stored VaultStored(UserId, PublicKey, Vault), + /// Owned confidential document stored OwnedDocStored(OwnedDoc), + /// Shared confidential document stored SharedDocStored(SharedDoc), } - // Errors inform users that something went wrong. #[pallet::error] pub enum Error { /// Empty CID @@ -156,6 +160,16 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Create a vault + /// + /// Creates the calling user's vault and sets their public key + /// . + /// ### Parameters: + /// - `origin`: The user that is configuring their vault + /// - `user_id`: User identifier generated from their login method, their address if using + /// native login or user id if using SSO + /// - `public key`: The users cipher public key + /// - `cid`: The IPFS CID that contains the vaults data #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn set_vault(origin: OriginFor, user_id: UserId, public_key: PublicKey, cid: CID) -> DispatchResult { @@ -163,6 +177,13 @@ pub mod pallet { Self::do_set_vault(who, user_id, public_key, cid) } + /// Create/Update an owned document + /// + /// Creates a new owned document or updates an existing owned document's metadata + /// . + /// ### Parameters: + /// - `origin`: The user that is creating/updating an owned document + /// - `owned_doc`: Metadata related to the owned document #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn set_owned_document(origin: OriginFor, owned_doc: OwnedDoc) -> DispatchResult { @@ -170,6 +191,13 @@ pub mod pallet { Self::do_set_owned_document(who, owned_doc) } + /// Share a document + /// + /// Creates a shared document + /// . + /// ### Parameters: + /// - `origin`: The user that is creating the shared document + /// - `shared_doc`: Metadata related to the shared document #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn share_document(origin: OriginFor, shared_doc: SharedDoc) -> DispatchResult { diff --git a/pallets/confidential-docs/src/types.rs b/pallets/confidential-docs/src/types.rs index b00cfd1f..f0de5f34 100644 --- a/pallets/confidential-docs/src/types.rs +++ b/pallets/confidential-docs/src/types.rs @@ -1,38 +1,58 @@ - +//! Defines the types required by the confidential docs pallet use super::*; use frame_support::pallet_prelude::*; +/// Defines the type used by fields that store an IPFS CID pub type CID = BoundedVec>; +/// Defines the type used by fields that store a public key pub type PublicKey = [u8;32]; +/// Defines the type used by fields that store a UserId pub type UserId = [u8;32]; +/// Defines the type used by fields that store a document name pub type DocName = BoundedVec::DocNameMaxLen>; +/// Defines the type used by fields that store a document description pub type DocDesc = BoundedVec::DocDescMaxLen>; +/// User vault, the vault stores the cipher private key used to cipher the user documents. +/// The way the user vault is ciphered depends on the login method used by the user #[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct Vault{ + /// IPFS CID where the vault data is stored pub cid: CID, + /// Owner of the vault pub owner: T::AccountId, } +/// Owned confidential document #[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct OwnedDoc{ + /// IPFS CID where the document data is stored pub cid: CID, + /// User provided name for the document pub name: DocName, + /// User provided description for the document pub description: DocDesc, + /// Owner of the document pub owner: T::AccountId, } +/// Shared confidential document #[derive(CloneNoBound,Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen, PartialEq)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct SharedDoc{ + /// IPFS CID where the document data is stored pub cid: CID, + /// User provided name for the document pub name: DocName, + /// User provided description for the document pub description: DocDesc, + /// User that shared the document pub from: T::AccountId, + /// User to which the document was shared pub to: T::AccountId, } \ No newline at end of file From 22658f902f6eafd90e04c2392eedb8154e4d59f1 Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Mon, 1 Aug 2022 13:39:22 -0500 Subject: [PATCH 4/8] Finished documenting main module of the confidential docs pallet --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b3ba5168..3c302dd8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -117,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { /// up by `pallet_aura` to implement `fn slot_duration()`. /// /// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 3000; +pub const MILLISECS_PER_BLOCK: u64 = 6000; // NOTE: Currently it is not possible to change the slot duration after the chain has started. // Attempting to do so will brick block production. From 8c72cde0ac1bc65494d36ab0a4899c135f4d14bb Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Thu, 4 Aug 2022 21:41:31 -0500 Subject: [PATCH 5/8] Developed do_remove_owned_document method in the functions module of the confidential docs pallet. Developed do_update_shared_document_metadata method in the functions module of the confidential docs pallet. Developed do_remove_shared_document method in the functions module of the confidential docs pallet. Added MaxSharedFromDocs const to the confidential docs pallet. Added SharedDocsByFrom storage map to the confidential docs pallet. Developed share document method of the confidential docs pallet to store the from relationship with the document. Added NotDocSharee, CIDNotFound, DocNotFound, ExceedMaxSharedFromDocs errors to the confidential docs pallet. Added OwnedDocRemoved event to the confidential docs pallet. Added SharedDocUpdated event to the confidential docs pallet. Added SharedDocRemoved event to the confidential docs pallet. Developed remove_owned_document extrinsic in the confidential docs pallet. Developed update_shared_document_metadata extrinsic in the confidential docs pallet. Developed remove_shared_document extrinsic in the confidential docs pallet. Developed tests in the confidentail docs pallet to verify the new functionality. --- pallets/confidential-docs/src/functions.rs | 46 +++++ pallets/confidential-docs/src/lib.rs | 76 +++++++- pallets/confidential-docs/src/mock.rs | 2 + pallets/confidential-docs/src/tests.rs | 212 +++++++++++++++++++-- runtime/src/lib.rs | 4 +- 5 files changed, 319 insertions(+), 21 deletions(-) diff --git a/pallets/confidential-docs/src/functions.rs b/pallets/confidential-docs/src/functions.rs index 240c1ee1..9e8f826b 100644 --- a/pallets/confidential-docs/src/functions.rs +++ b/pallets/confidential-docs/src/functions.rs @@ -39,14 +39,32 @@ impl Pallet { Ok(()) } + pub fn do_remove_owned_document(owner: T::AccountId, cid: CID) -> DispatchResult { + let doc = >::try_get(&cid).map_err(|_| >::DocNotFound)?; + ensure!(doc.owner == owner, >::NotDocOwner); + >::try_mutate(&owner, |owned_vec| { + let cid_index = owned_vec.iter().position(|c| *c==cid).ok_or(>::CIDNotFound)?; + owned_vec.remove(cid_index); + Ok(()) + }).map_err(|_:Error::| >::CIDNotFound)?; + >::remove(cid.clone()); + Self::deposit_event(Event::OwnedDocRemoved(doc)); + Ok(()) + } + pub fn do_share_document(owner: T::AccountId, mut shared_doc: SharedDoc) -> DispatchResult { shared_doc.from = owner; Self::validate_shared_doc(&shared_doc)?; let SharedDoc { cid, to, + from, .. } = shared_doc.clone(); + + >::try_mutate(&from, |shared_vec| { + shared_vec.try_push(cid.clone()) + }).map_err(|_| >::ExceedMaxSharedFromDocs)?; >::try_mutate(&to, |shared_vec| { shared_vec.try_push(cid.clone()) @@ -57,6 +75,34 @@ impl Pallet { Ok(()) } + pub fn do_update_shared_document_metadata(to: T::AccountId, mut shared_doc: SharedDoc) -> DispatchResult { + let doc = >::try_get(&shared_doc.cid).map_err(|_| >::DocNotFound)?; + ensure!(doc.to == to, >::NotDocSharee); + shared_doc.from = doc.from; + shared_doc.to = to; + >::insert(doc.cid.clone(), shared_doc.clone()); + Self::deposit_event(Event::SharedDocUpdated(shared_doc)); + Ok(()) + } + + pub fn do_remove_shared_document(to: T::AccountId, cid: CID) -> DispatchResult { + let doc = >::try_get(&cid).map_err(|_| >::DocNotFound)?; + ensure!(doc.to == to, >::NotDocSharee); + >::try_mutate(&to, |shared_vec| { + let cid_index = shared_vec.iter().position(|c| *c==cid).ok_or(>::CIDNotFound)?; + shared_vec.remove(cid_index); + Ok(()) + }).map_err(|_:Error::| >::CIDNotFound)?; + >::try_mutate(&doc.from, |shared_vec| { + let cid_index = shared_vec.iter().position(|c| *c==cid).ok_or(>::CIDNotFound)?; + shared_vec.remove(cid_index); + Ok(()) + }).map_err(|_:Error::| >::CIDNotFound)?; + >::remove(cid.clone()); + Self::deposit_event(Event::SharedDocRemoved(doc)); + Ok(()) + } + fn validate_owned_doc(owned_doc: &OwnedDoc)->DispatchResult{ let OwnedDoc { cid, diff --git a/pallets/confidential-docs/src/lib.rs b/pallets/confidential-docs/src/lib.rs index 18417660..e12dc4ea 100644 --- a/pallets/confidential-docs/src/lib.rs +++ b/pallets/confidential-docs/src/lib.rs @@ -34,6 +34,9 @@ pub mod pallet { /// Maximum number of confidential documents that a user can own #[pallet::constant] type MaxOwnedDocs: Get; + /// Maximum number of confidential documents that a user can share + #[pallet::constant] + type MaxSharedFromDocs: Get; /// Maximum number of confidential documents that can be shared to a user #[pallet::constant] type MaxSharedToDocs: Get; @@ -116,6 +119,16 @@ pub mod pallet { ValueQuery >; + #[pallet::storage] + #[pallet::getter(fn shared_docs_by_from)] + pub(super) type SharedDocsByFrom = StorageMap< + _, + Blake2_256, + T::AccountId, + BoundedVec, + ValueQuery + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -124,8 +137,14 @@ pub mod pallet { VaultStored(UserId, PublicKey, Vault), /// Owned confidential document stored OwnedDocStored(OwnedDoc), + /// Owned confidential document removed + OwnedDocRemoved(OwnedDoc), /// Shared confidential document stored SharedDocStored(SharedDoc), + /// Shared confidential document metadata updated + SharedDocUpdated(SharedDoc), + /// Shared confidential document removed + SharedDocRemoved(SharedDoc), } #[pallet::error] @@ -144,6 +163,12 @@ pub mod pallet { AccountAlreadyHasPublicKey, /// User is not document owner NotDocOwner, + /// User is not document whom with the document was shared + NotDocSharee, + /// CID not found + CIDNotFound, + /// Document not found + DocNotFound, /// The document has already been shared DocAlreadyShared, /// Shared with self @@ -154,6 +179,8 @@ pub mod pallet { ExceedMaxOwnedDocs, /// Max documents shared with the "to" account has been exceeded ExceedMaxSharedToDocs, + /// Max documents shared with the "from" account has been exceeded + ExceedMaxSharedFromDocs, } @@ -162,7 +189,7 @@ pub mod pallet { /// Create a vault /// - /// Creates the calling user's vault and sets their public key + /// Creates the calling user's vault and sets their public cipher key /// . /// ### Parameters: /// - `origin`: The user that is configuring their vault @@ -191,6 +218,20 @@ pub mod pallet { Self::do_set_owned_document(who, owned_doc) } + /// Remove an owned document + /// + /// Removes an owned document + /// . + /// ### Parameters: + /// - `origin`: The owner of the document + /// - `cid`: of the document to be removed + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn remove_owned_document(origin: OriginFor, cid: CID) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_remove_owned_document(who, cid) + } + /// Share a document /// /// Creates a shared document @@ -205,6 +246,38 @@ pub mod pallet { Self::do_share_document(who, shared_doc) } + /// Update share document metadata + /// + /// Updates share document metadata, only the user with which the document + /// was shared can update it + /// . + /// ### Parameters: + /// - `origin`: The "to" user of the shared document + /// - `shared_doc`: Metadata related to the shared document + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn update_shared_document_metadata(origin: OriginFor, shared_doc: SharedDoc) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_update_shared_document_metadata(who, shared_doc) + } + + + /// Remove a shared document + /// + /// Removes a shared document, only the user with whom the document was + /// is able to remove it + /// . + /// ### Parameters: + /// - `origin`: The "to" user of the shared document + /// - `cid`: of the document to be removed + #[transactional] + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn remove_shared_document(origin: OriginFor, cid: CID) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_remove_shared_document(who, cid) + } + + /// Kill all the stored data. /// /// This function is used to kill ALL the stored data. @@ -227,6 +300,7 @@ pub mod pallet { >::remove_all(None); >::remove_all(None); >::remove_all(None); + >::remove_all(None); Ok(()) } } diff --git a/pallets/confidential-docs/src/mock.rs b/pallets/confidential-docs/src/mock.rs index d9901e0b..4919dc61 100644 --- a/pallets/confidential-docs/src/mock.rs +++ b/pallets/confidential-docs/src/mock.rs @@ -59,6 +59,7 @@ impl system::Config for Test { parameter_types! { pub const MaxOwnedDocs: u32 = 100; pub const MaxSharedToDocs: u32 = 100; + pub const MaxSharedFromDocs: u32 = 100; pub const DocNameMinLen: u32 = 4; pub const DocNameMaxLen: u32 = 30; pub const DocDescMinLen: u32 = 5; @@ -71,6 +72,7 @@ impl pallet_confidential_docs::Config for Test { type RemoveOrigin = EnsureRoot; type MaxOwnedDocs = MaxOwnedDocs; type MaxSharedToDocs = MaxSharedToDocs; + type MaxSharedFromDocs = MaxSharedFromDocs; type DocNameMinLen = DocNameMinLen; type DocNameMaxLen = DocNameMaxLen; type DocDescMinLen = DocDescMinLen; diff --git a/pallets/confidential-docs/src/tests.rs b/pallets/confidential-docs/src/tests.rs index cd8b8f9d..f254247c 100644 --- a/pallets/confidential-docs/src/tests.rs +++ b/pallets/confidential-docs/src/tests.rs @@ -49,7 +49,7 @@ fn generate_shared_doc( } } -fn set_vault(who: ::AccountId) { +fn setup_vault(who: ::AccountId) { let id = &who.to_string(); assert_ok!(ConfidentialDocs::set_vault( Origin::signed(who), @@ -59,6 +59,54 @@ fn set_vault(who: ::AccountId) { )); } +fn setup_owned_doc(id: &str, owner: ::AccountId) -> OwnedDoc { + let doc = generate_owned_doc(id, owner); + assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc.clone())); + assert_owned_doc(&doc); + doc +} + +fn setup_shared_doc(id: &str, from: ::AccountId, to: ::AccountId) -> SharedDoc { + let doc = generate_shared_doc(id, from, to); + assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), doc.clone())); + assert_shared_doc(&doc); + doc +} + +fn assert_owned_doc(doc: &OwnedDoc){ + assert_eq!(ConfidentialDocs::owned_docs(&doc.cid), Some(doc.clone())); + let owned_docs = ConfidentialDocs::owned_docs_by_owner(doc.owner); + assert_eq!(owned_docs.contains(&doc.cid), true); +} + +fn assert_owned_doc_not_exists(cid: &CID, owner: ::AccountId){ + assert_eq!(ConfidentialDocs::owned_docs(cid), None); + let owned_docs = ConfidentialDocs::owned_docs_by_owner(owner); + assert_eq!(owned_docs.contains(cid), false); +} + + +fn assert_shared_doc(doc: &SharedDoc){ + let SharedDoc { + from, + to, + .. + } = doc; + assert_eq!(ConfidentialDocs::shared_docs(&doc.cid), Some(doc.clone())); + assert_eq!(ConfidentialDocs::shared_docs_by_to(to).contains(&doc.cid), true); + assert_eq!(ConfidentialDocs::shared_docs_by_from(from).contains(&doc.cid), true); +} + +fn assert_shared_doc_not_exists(doc: &SharedDoc){ + let SharedDoc { + from, + to, + .. + } = doc; + assert_eq!(ConfidentialDocs::shared_docs(&doc.cid), None); + assert_eq!(ConfidentialDocs::shared_docs_by_to(to).contains(&doc.cid), false); + assert_eq!(ConfidentialDocs::shared_docs_by_from(from).contains(&doc.cid), false); +} #[test] fn set_vault_works() { @@ -68,8 +116,10 @@ fn set_vault_works() { let cid = generate_cid("1"); assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); // Read pallet storage and assert an expected result. - assert_eq!(ConfidentialDocs::vaults(user_id), Some(Vault { cid, owner: 1 })); + let vault = Vault { cid, owner: 1 }; + assert_eq!(ConfidentialDocs::vaults(user_id), Some(vault)); assert_eq!(ConfidentialDocs::public_keys(1), Some(public_key)); + // assert_eq!(last_event(), Event::VaultStored(user_id, public_key, )) }); } @@ -122,7 +172,7 @@ fn set_vault_should_fail_for_account_with_public_key() { fn set_owned_document_works() { new_test_ext().execute_with(|| { let owner = 1; - set_vault(owner); + setup_vault(owner); let mut doc1 = generate_owned_doc("1", owner); assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone())); assert_eq!(ConfidentialDocs::owned_docs(&doc1.cid), Some(doc1.clone())); @@ -142,12 +192,12 @@ fn set_owned_document_works() { fn set_owned_document_should_fail_for_updating_non_owned_doc() { new_test_ext().execute_with(|| { let owner = 1; - set_vault(owner); + setup_vault(owner); let mut doc1 = generate_owned_doc("1", owner); assert_ok!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone())); doc1.name = generate_doc_name("2"); let owner = 2; - set_vault(owner); + setup_vault(owner); assert_noop!(ConfidentialDocs::set_owned_document(Origin::signed(owner), doc1.clone()), Error::::NotDocOwner); }); } @@ -188,29 +238,70 @@ fn set_owned_document_should_fail_for_owner_with_no_public_key() { }); } +#[test] +fn remove_owned_document_works() { + new_test_ext().execute_with(|| { + let owner = 1; + setup_vault(owner); + let doc1 = setup_owned_doc("1", owner); + let doc2 = setup_owned_doc("2", owner); + assert_ok!(ConfidentialDocs::remove_owned_document(Origin::signed(owner), doc1.cid.clone())); + assert_owned_doc_not_exists(&doc1.cid, owner); + assert_owned_doc(&doc2); + assert_ok!(ConfidentialDocs::remove_owned_document(Origin::signed(owner), doc2.cid.clone())); + assert_owned_doc_not_exists(&doc2.cid, owner); + }); +} + +#[test] +fn remove_owned_document_should_fail_for_non_existant_document() { + new_test_ext().execute_with(|| { + let owner = 1; + let doc1 = generate_owned_doc("1", owner); + assert_noop!(ConfidentialDocs::remove_owned_document(Origin::signed(owner), doc1.cid.clone()), Error::::DocNotFound); + }); +} + +#[test] +fn remove_owned_document_should_fail_for_not_document_owner() { + new_test_ext().execute_with(|| { + let owner = 1; + setup_vault(owner); + let doc1 = setup_owned_doc("1", owner); + let not_owner = 2; + assert_noop!(ConfidentialDocs::remove_owned_document(Origin::signed(not_owner), doc1.cid.clone()), Error::::NotDocOwner); + }); +} + #[test] fn share_document_works() { new_test_ext().execute_with(|| { let to = 1; let from = 2; - set_vault(to); - set_vault(from); + setup_vault(to); + setup_vault(from); let shared_doc1 = generate_shared_doc("1", from, to); assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc1.clone())); // Read pallet storage and assert an expected result. assert_eq!(ConfidentialDocs::shared_docs(&shared_doc1.cid), Some(shared_doc1.clone())); - let shared_docs = ConfidentialDocs::shared_docs_by_to(to); - let mut expected_cid_vec = vec!(shared_doc1.cid.clone()); - assert_eq!(shared_docs.into_inner(), expected_cid_vec); + let shared_docs_to = ConfidentialDocs::shared_docs_by_to(to); + let mut expected_cid_to_vec = vec!(shared_doc1.cid.clone()); + assert_eq!(shared_docs_to.into_inner(), expected_cid_to_vec); + let expected_cid_from_vec = vec!(shared_doc1.cid.clone()); + let shared_docs_from = ConfidentialDocs::shared_docs_by_from(from); + assert_eq!(shared_docs_from.into_inner(), expected_cid_from_vec); let from = 3; - set_vault(3); + setup_vault(3); let shared_doc2 = generate_shared_doc("2", from, to); assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc2.clone())); assert_eq!(ConfidentialDocs::shared_docs(&shared_doc2.cid), Some(shared_doc2.clone())); - let shared_docs = ConfidentialDocs::shared_docs_by_to(to); - expected_cid_vec.push(shared_doc2.cid.clone()); - assert_eq!(shared_docs.into_inner(), expected_cid_vec); + let shared_docs_to = ConfidentialDocs::shared_docs_by_to(to); + expected_cid_to_vec.push(shared_doc2.cid.clone()); + assert_eq!(shared_docs_to.into_inner(), expected_cid_to_vec); + let expected_cid_from_vec = vec!(shared_doc2.cid.clone()); + let shared_docs_from = ConfidentialDocs::shared_docs_by_from(from); + assert_eq!(shared_docs_from.into_inner(), expected_cid_from_vec); }); } @@ -255,7 +346,7 @@ fn share_document_should_fail_for_share_to_self() { new_test_ext().execute_with(|| { let to = 1; let from = 1; - set_vault(to); + setup_vault(to); let shared_doc = generate_shared_doc("1", from, to); assert_noop!( ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), @@ -269,8 +360,8 @@ fn share_document_should_fail_for_doc_already_shared() { new_test_ext().execute_with(|| { let to = 1; let from = 2; - set_vault(to); - set_vault(from); + setup_vault(to); + setup_vault(from); let shared_doc = generate_shared_doc("1", from, to); assert_ok!(ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone())); assert_noop!( @@ -285,7 +376,7 @@ fn share_document_should_fail_for_from_with_no_public_key() { new_test_ext().execute_with(|| { let to = 1; let from = 2; - set_vault(to); + setup_vault(to); let shared_doc = generate_shared_doc("1", from, to); assert_noop!( ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), @@ -299,7 +390,7 @@ fn share_document_should_fail_for_to_with_no_public_key() { new_test_ext().execute_with(|| { let to = 1; let from = 2; - set_vault(from); + setup_vault(from); let shared_doc = generate_shared_doc("1", from, to); assert_noop!( ConfidentialDocs::share_document(Origin::signed(from), shared_doc.clone()), @@ -307,4 +398,87 @@ fn share_document_should_fail_for_to_with_no_public_key() { ); }); } +#[test] +fn update_shared_document_metadata_works() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + setup_vault(to); + setup_vault(from); + let mut shared_doc1 = setup_shared_doc("1", from, to); + shared_doc1.name = generate_doc_name("2"); + shared_doc1.description = generate_doc_desc("2"); + assert_ok!(ConfidentialDocs::update_shared_document_metadata(Origin::signed(to), shared_doc1.clone())); + assert_shared_doc(&shared_doc1); + }); +} + +#[test] +fn update_shared_document_metadata_should_fail_for_non_existant_doc() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + let shared_doc1 = generate_shared_doc("1", from, to); + assert_noop!(ConfidentialDocs::update_shared_document_metadata(Origin::signed(to), shared_doc1.clone()), Error::::DocNotFound); + }); +} + +#[test] +fn update_shared_document_metadata_should_fail_for_not_doc_sharee() { + new_test_ext().execute_with(|| { + let to1 = 1; + let to2 = 2; + let from = 3; + setup_vault(to1); + setup_vault(to2); + setup_vault(from); + let shared_doc1 = setup_shared_doc("1", from, to1); + assert_noop!(ConfidentialDocs::update_shared_document_metadata(Origin::signed(to2), shared_doc1.clone()), Error::::NotDocSharee); + assert_noop!(ConfidentialDocs::update_shared_document_metadata(Origin::signed(from), shared_doc1.clone()), Error::::NotDocSharee); + }); +} + +#[test] +fn remove_shared_document_works() { + new_test_ext().execute_with(|| { + let to1 = 1; + let to2 = 2; + let from = 3; + setup_vault(to1); + setup_vault(to2); + setup_vault(from); + let shared_doc1 = setup_shared_doc("1", from, to1); + let shared_doc2 = setup_shared_doc("2", from, to2); + assert_ok!(ConfidentialDocs::remove_shared_document(Origin::signed(to1), shared_doc1.cid.clone())); + assert_shared_doc_not_exists(&shared_doc1); + assert_shared_doc(&shared_doc2); + assert_ok!(ConfidentialDocs::remove_shared_document(Origin::signed(to2), shared_doc2.cid.clone())); + assert_shared_doc_not_exists(&shared_doc2); + }); +} + +#[test] +fn remove_shared_document_should_fail_for_non_existant_doc() { + new_test_ext().execute_with(|| { + let to = 1; + let from = 2; + let shared_doc1 = generate_shared_doc("1", from, to); + assert_noop!(ConfidentialDocs::remove_shared_document(Origin::signed(to), shared_doc1.cid.clone()), Error::::DocNotFound); + }); +} + +#[test] +fn remove_shared_document_should_fail_for_not_doc_sharee() { + new_test_ext().execute_with(|| { + let to1 = 1; + let to2 = 2; + let from = 3; + setup_vault(to1); + setup_vault(to2); + setup_vault(from); + let shared_doc1 = setup_shared_doc("1", from, to1); + assert_noop!(ConfidentialDocs::remove_shared_document(Origin::signed(to2), shared_doc1.cid.clone()), Error::::NotDocSharee); + assert_noop!(ConfidentialDocs::remove_shared_document(Origin::signed(from), shared_doc1.cid.clone()), Error::::NotDocSharee); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3c302dd8..095ee76b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -117,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { /// up by `pallet_aura` to implement `fn slot_duration()`. /// /// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; +pub const MILLISECS_PER_BLOCK: u64 = 3000; // NOTE: Currently it is not possible to change the slot duration after the chain has started. // Attempting to do so will brick block production. @@ -596,6 +596,7 @@ impl pallet_nbv_storage::Config for Runtime { parameter_types! { pub const MaxOwnedDocs: u32 = 100; + pub const MaxSharedFromDocs: u32 = 100; pub const MaxSharedToDocs: u32 = 100; pub const DocNameMinLen: u32 = 3; pub const DocNameMaxLen: u32 = 50; @@ -610,6 +611,7 @@ impl pallet_confidential_docs::Config for Runtime { pallet_collective::EnsureProportionAtLeast, >; type MaxOwnedDocs = MaxOwnedDocs; + type MaxSharedFromDocs = MaxSharedFromDocs; type MaxSharedToDocs = MaxSharedToDocs; type DocNameMinLen = DocNameMinLen; type DocNameMaxLen = DocNameMaxLen; From 91bc651d1c97f961d053cb12134ff44c0f034587 Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Fri, 5 Aug 2022 13:36:27 -0500 Subject: [PATCH 6/8] Updated the confidential docs pallet README file --- pallets/confidential-docs/README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pallets/confidential-docs/README.md b/pallets/confidential-docs/README.md index ff005161..b638b65d 100644 --- a/pallets/confidential-docs/README.md +++ b/pallets/confidential-docs/README.md @@ -13,8 +13,11 @@ Provides the backend services and metadata storage for the confidential docs sol - [Get a public key](#get-a-public-key) - [Create an owned confidential document](#create-an-owned-confidential-document) - [Get an owned confidential document by CID](#get-an-owned-confidential-document-by-cid) + - [Remove an owned confidential document](#remove-an-owned-confidential-document) - [Share a confidential document](#share-a-confidential-document) - [Get a shared confidential document by CID](#get-a-shared-confidential-document-by-cid) + - [Update a shared confidential document's metadata](#update-a-shared-confidential-documents-metadata) + - [Remove a shared confidential document](#remove-a-shared-confidential-document) ## Overview This module allows a user to: @@ -28,7 +31,10 @@ This module allows a user to: ### Dispachable functions - `set_vault` Creates the calling user's vault and sets their public cipher key - `set_owned_document` Creates a new owned document or updates an existing owned document's metadata -- `share_document` Creates a shared document +- `remove_owned_document` Removes an owned document +- `share_document` Creates a shared document +- `update_shared_document_metadata` Updates share document metadata +- `remove_shared_document` Removes a shared document ### Getters - `vaults` @@ -37,6 +43,7 @@ This module allows a user to: - `owned_docs_by_owner` - `shared_docs` - `shared_docs_by_to` +- `shared_docs_by_from` ## Usage @@ -109,6 +116,11 @@ const ownedDoc = await api.query.confidentialDocs.ownedDocs(cid); } ``` +#### Remove an owned confidential document +```js +const response = await api.tx.confidentialDocs.removeOwnedDocument("QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr").signAndSend(alice); +``` + #### Share a confidential document ```js const response = await api.tx.confidentialDocs.shareDocument({ @@ -134,4 +146,18 @@ const sharedDoc = await api.query.confidentialDocs.sharedDocs(cid); "to": "5FSuxe2q7qCYKie8yqmM56U4ovD1YtBb3DoPzGKjwZ98vxua", "from": "5FWtfhKTuGKm9yWqzApwTfiUL4UPWukJzEcCTGYDiYHsdKaG" } +``` + +#### Update a shared confidential document's metadata +```js +const response = await api.tx.confidentialDocs.updateSharedDocumentMetadata({ + "cid": "QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr", + "name": "name", + "description": "desc" + }).signAndSend(alice); +``` + +#### Remove a shared confidential document +```js +const response = await api.tx.confidentialDocs.removeSharedDocument("QmeHEb5TF4zkP2H6Mg5TcrvDs5egPCJgWFBB7YZaLmK7jr").signAndSend(alice); ``` \ No newline at end of file From 1a06abc19c49ce4ec89cb24ce32e09fe8282bf54 Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Sat, 6 Aug 2022 09:32:00 -0500 Subject: [PATCH 7/8] Updated README file documentation. Added UserIds storage map to confidentail docs pallet. Developed code in set_vault extrinsic to enable the vault update, ensuring that who is updating the vault is the owner. Developed tests to verify the new set_vault functionality. --- pallets/confidential-docs/README.md | 2 +- pallets/confidential-docs/src/functions.rs | 13 ++++++-- pallets/confidential-docs/src/lib.rs | 19 ++++++++++-- pallets/confidential-docs/src/tests.rs | 36 +++++++++++++--------- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/pallets/confidential-docs/README.md b/pallets/confidential-docs/README.md index b638b65d..016d9c4f 100644 --- a/pallets/confidential-docs/README.md +++ b/pallets/confidential-docs/README.md @@ -29,7 +29,7 @@ This module allows a user to: ## Interface ### Dispachable functions -- `set_vault` Creates the calling user's vault and sets their public cipher key +- `set_vault` Creates/Updates the calling user's vault and sets their public cipher key - `set_owned_document` Creates a new owned document or updates an existing owned document's metadata - `remove_owned_document` Removes an owned document - `share_document` Creates a shared document diff --git a/pallets/confidential-docs/src/functions.rs b/pallets/confidential-docs/src/functions.rs index 9e8f826b..5d9f589e 100644 --- a/pallets/confidential-docs/src/functions.rs +++ b/pallets/confidential-docs/src/functions.rs @@ -1,5 +1,6 @@ use super::*; use frame_support::pallet_prelude::*; +use frame_support::sp_io::hashing::blake2_256; //use frame_system::pallet_prelude::*; use crate::types::*; @@ -7,14 +8,20 @@ impl Pallet { pub fn do_set_vault(owner: T::AccountId, user_id: UserId, public_key: PublicKey, cid: CID) -> DispatchResult { Self::validate_cid(&cid)?; - ensure!(!>::contains_key(&user_id), >::UserAlreadyHasVault); - ensure!(!>::contains_key(&owner), >::AccountAlreadyHasPublicKey); + let hashed_account = owner.using_encoded(blake2_256); + if let Some(uid) = >::get(&hashed_account){ + ensure!(uid == user_id, >::NotOwnerOfUserId); + } else { + ensure!(!>::contains_key(&user_id), >::NotOwnerOfVault); + } + let vault = Vault{ cid: cid.clone(), owner: owner.clone() }; >::insert(user_id, vault.clone()); - >::insert(owner.clone(), public_key.clone()); + >::insert(owner.clone(), public_key); + >::insert(hashed_account.clone(), user_id); Self::deposit_event(Event::VaultStored(user_id, public_key, vault)); Ok(()) diff --git a/pallets/confidential-docs/src/lib.rs b/pallets/confidential-docs/src/lib.rs index e12dc4ea..ffc134cc 100644 --- a/pallets/confidential-docs/src/lib.rs +++ b/pallets/confidential-docs/src/lib.rs @@ -79,6 +79,16 @@ pub mod pallet { OptionQuery >; + #[pallet::storage] + #[pallet::getter(fn users_ids)] + pub(super) type UserIds = StorageMap< + _, + Identity, + [u8; 32], + UserId, + OptionQuery, + >; + #[pallet::storage] #[pallet::getter(fn owned_docs)] pub(super) type OwnedDocs = StorageMap< @@ -157,6 +167,10 @@ pub mod pallet { DocDescTooShort, /// Errors should have helpful documentation associated with them. StorageOverflow, + /// Origin is not the owner of the user id + NotOwnerOfUserId, + /// Origin is not the owner of the vault + NotOwnerOfVault, /// The user already has a vault UserAlreadyHasVault, /// The user already has a public key @@ -181,15 +195,16 @@ pub mod pallet { ExceedMaxSharedToDocs, /// Max documents shared with the "from" account has been exceeded ExceedMaxSharedFromDocs, + } #[pallet::call] impl Pallet { - /// Create a vault + /// Create/Update a vault /// - /// Creates the calling user's vault and sets their public cipher key + /// Creates/Updates the calling user's vault and sets their public cipher key /// . /// ### Parameters: /// - `origin`: The user that is configuring their vault diff --git a/pallets/confidential-docs/src/tests.rs b/pallets/confidential-docs/src/tests.rs index f254247c..340ea342 100644 --- a/pallets/confidential-docs/src/tests.rs +++ b/pallets/confidential-docs/src/tests.rs @@ -111,14 +111,23 @@ fn assert_shared_doc_not_exists(doc: &SharedDoc){ #[test] fn set_vault_works() { new_test_ext().execute_with(|| { + let owner = 1; let user_id = generate_user_id("1"); let public_key = generate_public_key("1"); let cid = generate_cid("1"); - assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(owner), user_id, public_key, cid.clone())); + // Read pallet storage and assert an expected result. + let vault = Vault { cid, owner }; + assert_eq!(ConfidentialDocs::vaults(user_id), Some(vault)); + assert_eq!(ConfidentialDocs::public_keys(owner), Some(public_key)); + + let public_key = generate_public_key("2"); + let cid = generate_cid("2"); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(owner), user_id, public_key, cid.clone())); // Read pallet storage and assert an expected result. - let vault = Vault { cid, owner: 1 }; + let vault = Vault { cid, owner }; assert_eq!(ConfidentialDocs::vaults(user_id), Some(vault)); - assert_eq!(ConfidentialDocs::public_keys(1), Some(public_key)); + assert_eq!(ConfidentialDocs::public_keys(owner), Some(public_key)); // assert_eq!(last_event(), Event::VaultStored(user_id, public_key, )) }); } @@ -137,37 +146,34 @@ fn set_vault_should_fail_for_empty_cid() { } #[test] -fn set_vault_should_fail_for_user_with_vault() { +fn set_vault_should_fail_for_origin_not_owner_of_user_id() { new_test_ext().execute_with(|| { let user_id = generate_user_id("1"); let public_key = generate_public_key("1"); let cid = generate_cid("1"); assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); assert_noop!( - ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid), - Error::::UserAlreadyHasVault + ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("2"), public_key, cid), + Error::::NotOwnerOfUserId ); }); } #[test] -fn set_vault_should_fail_for_account_with_public_key() { +fn set_vault_should_fail_for_origin_not_owner_of_vault() { new_test_ext().execute_with(|| { + let user_id = generate_user_id("1"); let public_key = generate_public_key("1"); let cid = generate_cid("1"); - assert_ok!(ConfidentialDocs::set_vault( - Origin::signed(1), - generate_user_id("1"), - public_key, - cid.clone() - )); + assert_ok!(ConfidentialDocs::set_vault(Origin::signed(1), user_id, public_key, cid.clone())); assert_noop!( - ConfidentialDocs::set_vault(Origin::signed(1), generate_user_id("2"), public_key, cid), - Error::::AccountAlreadyHasPublicKey + ConfidentialDocs::set_vault(Origin::signed(2), user_id, public_key, cid), + Error::::NotOwnerOfVault ); }); } + #[test] fn set_owned_document_works() { new_test_ext().execute_with(|| { From f1374afa927c00ec7639a2218a01850eee5f91be Mon Sep 17 00:00:00 2001 From: Sebastian Montero Date: Tue, 9 Aug 2022 16:35:43 -0500 Subject: [PATCH 8/8] Updated block time --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 095ee76b..c40763ce 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -117,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { /// up by `pallet_aura` to implement `fn slot_duration()`. /// /// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 3000; +pub const MILLISECS_PER_BLOCK: u64 = 6000; // NOTE: Currently it is not possible to change the slot duration after the chain has started. // Attempting to do so will brick block production.