diff --git a/Cargo.lock b/Cargo.lock index ab7c0be65..d4aa212de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2589,6 +2589,7 @@ version = "0.1.0" dependencies = [ "calimero-sdk", "calimero-sdk-near", + "calimero-storage", ] [[package]] @@ -5494,6 +5495,7 @@ name = "only-peers" version = "0.1.0" dependencies = [ "calimero-sdk", + "calimero-storage", ] [[package]] diff --git a/apps/gen-ext/Cargo.toml b/apps/gen-ext/Cargo.toml index a29fe22be..8d34c86c6 100644 --- a/apps/gen-ext/Cargo.toml +++ b/apps/gen-ext/Cargo.toml @@ -10,5 +10,6 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -calimero-sdk = {path = "../../crates/sdk"} +calimero-sdk = { path = "../../crates/sdk" } calimero-sdk-near = { path = "../../crates/sdk/libs/near" } +calimero-storage = { path = "../../crates/storage" } diff --git a/apps/kv-store/src/__private.rs b/apps/kv-store/src/__private.rs deleted file mode 100644 index 471b84c67..000000000 --- a/apps/kv-store/src/__private.rs +++ /dev/null @@ -1,85 +0,0 @@ -use calimero_sdk::borsh::{from_slice, to_vec}; -use calimero_sdk::{app, env}; -use calimero_storage::collections::unordered_map::{Entry, UnorderedMap}; -use calimero_storage::entities::Data; -use calimero_storage::integration::Comparison; -use calimero_storage::interface::{Action, Interface, StorageError}; -use calimero_storage::sync::{self, SyncArtifact}; - -use crate::KvStore; - -#[app::logic] -impl KvStore { - pub fn __calimero_sync_next() -> Result<(), StorageError> { - let args = env::input().expect("fatal: missing input"); - - let artifact = - from_slice::(&args).map_err(StorageError::DeserializationError)?; - - let this = Interface::root::()?; - - match artifact { - SyncArtifact::Actions(actions) => { - for action in actions { - let _ignored = match action { - Action::Add { type_id, .. } | Action::Update { type_id, .. } => { - match type_id { - 1 => Interface::apply_action::(action)?, - 254 => Interface::apply_action::>(action)?, - 255 => { - Interface::apply_action::>(action)? - } - _ => return Err(StorageError::UnknownType(type_id)), - } - } - Action::Delete { .. } => { - todo!("how are we supposed to identify the entity to delete???????") - } - Action::Compare { .. } => { - todo!("how are we supposed to compare when `Comparison` needs `type_id`???????") - } - }; - } - - if let Some(this) = this { - return Interface::commit_root(this); - } - } - SyncArtifact::Comparisons(comparisons) => { - if comparisons.is_empty() { - sync::push_comparison(Comparison { - type_id: ::type_id(), - data: this - .as_ref() - .map(to_vec) - .transpose() - .map_err(StorageError::SerializationError)?, - comparison_data: Interface::generate_comparison_data(this.as_ref())?, - }); - } - - for Comparison { - type_id, - data, - comparison_data, - } in comparisons - { - match type_id { - 1 => Interface::compare_affective::(data, comparison_data)?, - 254 => Interface::compare_affective::>( - data, - comparison_data, - )?, - 255 => Interface::compare_affective::>( - data, - comparison_data, - )?, - _ => return Err(StorageError::UnknownType(type_id)), - }; - } - } - } - - Ok(()) - } -} diff --git a/apps/kv-store/src/lib.rs b/apps/kv-store/src/lib.rs index ebe163683..807b0b521 100644 --- a/apps/kv-store/src/lib.rs +++ b/apps/kv-store/src/lib.rs @@ -2,22 +2,16 @@ use std::collections::BTreeMap; +use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; use calimero_sdk::types::Error; use calimero_sdk::{app, env}; use calimero_storage::collections::UnorderedMap; -use calimero_storage::entities::Element; -use calimero_storage::AtomicUnit; - -mod __private; #[app::state(emits = for<'a> Event<'a>)] -#[derive(AtomicUnit, Clone, Debug, PartialEq, PartialOrd)] -#[root] -#[type_id(1)] +#[derive(Debug, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] +#[borsh(crate = "calimero_sdk::borsh")] pub struct KvStore { items: UnorderedMap, - #[storage] - storage: Element, } #[app::event] @@ -33,8 +27,7 @@ impl KvStore { #[app::init] pub fn init() -> KvStore { KvStore { - items: UnorderedMap::new().unwrap(), - storage: Element::root(), + items: UnorderedMap::new(), } } @@ -93,7 +86,10 @@ impl KvStore { app::emit!(Event::Removed { key }); - self.items.remove(key).map_err(Into::into) + self.items + .remove(key) + .map(|v| v.is_some()) + .map_err(Into::into) } pub fn clear(&mut self) -> Result<(), Error> { diff --git a/apps/only-peers/Cargo.toml b/apps/only-peers/Cargo.toml index cc3360bac..11b352580 100644 --- a/apps/only-peers/Cargo.toml +++ b/apps/only-peers/Cargo.toml @@ -11,3 +11,4 @@ crate-type = ["cdylib"] [dependencies] calimero-sdk = { path = "../../crates/sdk" } +calimero-storage = { path = "../../crates/storage" } diff --git a/apps/only-peers/src/lib.rs b/apps/only-peers/src/lib.rs index ded6f74b4..e8fa3d978 100644 --- a/apps/only-peers/src/lib.rs +++ b/apps/only-peers/src/lib.rs @@ -1,12 +1,14 @@ use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; use calimero_sdk::serde::Serialize; +use calimero_sdk::types::Error; use calimero_sdk::{app, env}; +use calimero_storage::collections::Vector; #[app::state(emits = for<'a> Event<'a>)] #[derive(BorshDeserialize, BorshSerialize, Default)] #[borsh(crate = "calimero_sdk::borsh")] pub struct OnlyPeers { - posts: Vec, + posts: Vector, } #[derive(BorshDeserialize, BorshSerialize, Default, Serialize)] @@ -16,10 +18,10 @@ pub struct Post { id: usize, title: String, content: String, - comments: Vec, + comments: Vector, } -#[derive(BorshDeserialize, BorshSerialize, Default, Serialize)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Default, Serialize)] #[borsh(crate = "calimero_sdk::borsh")] #[serde(crate = "calimero_sdk::serde")] pub struct Comment { @@ -48,40 +50,40 @@ impl OnlyPeers { OnlyPeers::default() } - pub fn post(&self, id: usize) -> Option<&Post> { + pub fn post(&self, id: usize) -> Result, Error> { env::log(&format!("Getting post with id: {:?}", id)); - self.posts.get(id) + Ok(self.posts.get(id)?) } - pub fn posts(&self) -> &[Post] { + pub fn posts(&self) -> Result, Error> { env::log("Getting all posts"); - &self.posts + Ok(self.posts.entries()?.collect()) } - pub fn create_post(&mut self, title: String, content: String) -> &Post { + pub fn create_post(&mut self, title: String, content: String) -> Result { env::log(&format!( "Creating post with title: {:?} and content: {:?}", title, content )); app::emit!(Event::PostCreated { - id: self.posts.len(), + id: self.posts.len()?, // todo! should we maybe only emit an ID, and let notified clients fetch the post? title: &title, content: &content, }); self.posts.push(Post { - id: self.posts.len(), + id: self.posts.len()?, title, content, - comments: Vec::new(), - }); + comments: Vector::new(), + })?; - match self.posts.last() { - Some(post) => post, + match self.posts.last()? { + Some(post) => Ok(post), None => env::unreachable(), } } @@ -91,13 +93,15 @@ impl OnlyPeers { post_id: usize, user: String, // todo! expose executor identity to app context text: String, - ) -> Option<&Comment> { + ) -> Result, Error> { env::log(&format!( "Creating comment under post with id: {:?} as user: {:?} with text: {:?}", post_id, user, text )); - let post = self.posts.get_mut(post_id)?; + let Some(mut post) = self.posts.get(post_id)? else { + return Ok(None); + }; app::emit!(Event::CommentCreated { post_id, @@ -106,8 +110,12 @@ impl OnlyPeers { text: &text, }); - post.comments.push(Comment { user, text }); + let comment = Comment { user, text }; + + post.comments.push(comment.clone())?; + + self.posts.update(post_id, post)?; - post.comments.last() + Ok(Some(comment)) } } diff --git a/apps/visited/src/lib.rs b/apps/visited/src/lib.rs index 563425900..22638befd 100644 --- a/apps/visited/src/lib.rs +++ b/apps/visited/src/lib.rs @@ -3,19 +3,15 @@ use std::result::Result; use calimero_sdk::app; +use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; use calimero_sdk::types::Error; use calimero_storage::collections::{UnorderedMap, UnorderedSet}; -use calimero_storage::entities::Element; -use calimero_storage::AtomicUnit; #[app::state] -#[derive(AtomicUnit, Clone, Debug, PartialEq, PartialOrd)] -#[root] -#[type_id(1)] +#[derive(Debug, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] +#[borsh(crate = "calimero_sdk::borsh")] pub struct VisitedCities { visited: UnorderedMap>, - #[storage] - storage: Element, } #[app::logic] @@ -23,16 +19,12 @@ impl VisitedCities { #[app::init] pub fn init() -> VisitedCities { VisitedCities { - visited: UnorderedMap::new().unwrap(), - storage: Element::root(), + visited: UnorderedMap::new(), } } pub fn add_person(&mut self, person: String) -> Result { - Ok(self - .visited - .insert(person, UnorderedSet::new().unwrap())? - .is_some()) + Ok(self.visited.insert(person, UnorderedSet::new())?.is_some()) } pub fn add_visited_city(&mut self, person: String, city: String) -> Result { diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index fecb5e625..2aac51b4c 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -346,17 +346,12 @@ impl Node { let shared_key = SharedKey::from_sk(&sender_key); - let artifact = &shared_key + let artifact = shared_key .decrypt(artifact, nonce) .ok_or_eyre("failed to decrypt message")?; let Some(outcome) = self - .execute( - &mut context, - "apply_state_delta", - to_vec(&artifact)?, - author_id, - ) + .execute(&mut context, "__calimero_sync_next", artifact, author_id) .await? else { bail!("application not installed"); @@ -505,11 +500,13 @@ impl Node { })?; } - if let Err(err) = self - .send_state_delta(&context, &outcome, executor_public_key) - .await - { - error!(%err, "Failed to send state delta."); + if !outcome.artifact.is_empty() { + if let Err(err) = self + .send_state_delta(&context, &outcome, executor_public_key) + .await + { + error!(%err, "Failed to send state delta."); + } } Ok(outcome) @@ -544,7 +541,7 @@ impl Node { if outcome.returns.is_ok() { if let Some(root_hash) = outcome.root_hash { - if outcome.artifact.is_empty() { + if outcome.artifact.is_empty() && method != "__calimero_sync_next" { eyre::bail!("context state changed, but no actions were generated, discarding execution outcome to mitigate potential state inconsistency"); } diff --git a/crates/node/src/sync/state.rs b/crates/node/src/sync/state.rs index d723dc28b..ec8396171 100644 --- a/crates/node/src/sync/state.rs +++ b/crates/node/src/sync/state.rs @@ -107,6 +107,13 @@ impl Node { }; if root_hash == context.root_hash { + debug!( + context_id=%context.id, + our_identity=%our_identity, + their_identity=%their_identity, + "Root hashes match, up to date", + ); + return Ok(()); } @@ -218,6 +225,13 @@ impl Node { .await?; if their_root_hash == context.root_hash { + debug!( + context_id=%context.id, + our_identity=%our_identity, + their_identity=%their_identity, + "Root hashes match, up to date", + ); + return Ok(()); } @@ -308,7 +322,9 @@ impl Node { "State sync outcome", ); - let our_new_nonce = thread_rng().gen::(); + let our_new_nonce = (!outcome.artifact.is_empty()) + .then(|| thread_rng().gen()) + .unwrap_or_default(); send( stream, @@ -323,6 +339,10 @@ impl Node { ) .await?; + if our_new_nonce == [0; 12] { + break; + } + our_nonce = our_new_nonce; } diff --git a/crates/sdk/macros/src/logic/method.rs b/crates/sdk/macros/src/logic/method.rs index 4f3b83e2d..57b508b16 100644 --- a/crates/sdk/macros/src/logic/method.rs +++ b/crates/sdk/macros/src/logic/method.rs @@ -1,5 +1,6 @@ use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::spanned::Spanned; use syn::{Error as SynError, GenericParam, Ident, ImplItemFn, Path, ReturnType, Visibility}; use crate::errors::{Errors, ParseError}; @@ -61,12 +62,14 @@ impl ToTokens for PublicLogicMethod<'_> { let input_lifetime = if self.has_refs { let lifetime = lifetimes::input(); - quote! { <#lifetime> } + quote_spanned! { name.span()=> + <#lifetime> + } } else { quote! {} }; - quote! { + quote_spanned! {name.span()=> #[derive(::calimero_sdk::serde::Deserialize)] #[serde(crate = "::calimero_sdk::serde")] struct #input_ident #input_lifetime { @@ -94,41 +97,44 @@ impl ToTokens for PublicLogicMethod<'_> { let (def, mut call) = match &self.self_type { Some(type_) => ( { - let mutability = match type_ { - SelfType::Mutable(_) => Some(quote! {mut}), - SelfType::Owned(_) | SelfType::Immutable(_) => None, + let (mutability, ty) = match type_ { + SelfType::Mutable(ty) => (Some(quote! {mut}), ty), + SelfType::Owned(ty) | SelfType::Immutable(ty) => (None, ty), }; - quote! { - let Some(#mutability app) = ::calimero_storage::interface::Interface::root::<#self_>().ok().flatten() + quote_spanned! {ty.span()=> + let Some(#mutability app) = ::calimero_storage::collections::Root::<#self_>::fetch() else { ::calimero_sdk::env::panic_str("Failed to find or read app state") }; } }, - quote! { app.#name(#(#arg_idents),*); }, + quote_spanned! {name.span()=> + app.#name(#(#arg_idents),*) + }, ), None => ( if init_method { - quote! { - if let Some(mut app) = ::calimero_storage::interface::Interface::root::<#self_>().ok().flatten() { + quote_spanned! {name.span()=> + if ::calimero_storage::collections::Root::<#self_>::fetch().is_some() { ::calimero_sdk::env::panic_str("Cannot initialize over already existing state.") }; - let mut app: #self_ = + let app = } } else { quote! {} }, - quote! { <#self_>::#name(#(#arg_idents),*); }, + quote_spanned! {name.span()=> + <#self_>::#name(#(#arg_idents),*) + }, ), }; - if let (Some(_), false) = (&self.ret, init_method) { - //only when it's not init - call = quote! { + if let (Some(ret), false) = (&self.ret, init_method) { + call = quote_spanned! {ret.ty.span()=> let output = #call; let output = { - #[expect(unused_imports)] + #[allow(unused_imports)] use ::calimero_sdk::__private::IntoResult; match ::calimero_sdk::__private::WrappedReturn::new(output) .into_result() @@ -140,22 +146,34 @@ impl ToTokens for PublicLogicMethod<'_> { ), } }; - ::calimero_sdk::env::value_return(&output); - }; + ::calimero_sdk::env::value_return(&output) + } } let state_finalizer = match (&self.self_type, init_method) { (Some(SelfType::Mutable(_)), _) | (_, true) => quote! { - if let Err(_) = ::calimero_storage::interface::Interface::commit_root(app) { - ::calimero_sdk::env::panic_str("Failed to commit app state") - } + app.commit(); }, _ => quote! {}, }; // todo! when generics are present, strip them let init_impl = if init_method { - quote! { + call = quote_spanned! {name.span()=> + ::calimero_storage::collections::Root::new(|| #call) + }; + + quote_spanned! {name.span()=> + #[cfg(target_arch = "wasm32")] + #[no_mangle] + pub extern "C" fn __calimero_sync_next() { + let Some(args) = ::calimero_sdk::env::input() else { + ::calimero_sdk::env::panic_str("Expected payload to sync method.") + }; + + ::calimero_storage::collections::Root::<#self_>::sync(&args).expect("fatal: sync failed"); + } + impl ::calimero_sdk::state::AppStateInit for #self_ { type Return = #ret; } @@ -164,7 +182,7 @@ impl ToTokens for PublicLogicMethod<'_> { quote! {} }; - quote! { + quote_spanned! {name.span()=> #[cfg(target_arch = "wasm32")] #[no_mangle] pub extern "C" fn #name() { @@ -176,7 +194,7 @@ impl ToTokens for PublicLogicMethod<'_> { #def - #call + #call; #state_finalizer } diff --git a/crates/storage-macros/src/lib.rs b/crates/storage-macros/src/lib.rs index 4cd1edcfa..3b6750bc1 100644 --- a/crates/storage-macros/src/lib.rs +++ b/crates/storage-macros/src/lib.rs @@ -1,7 +1,7 @@ use borsh as _; use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{parse_macro_input, Data, DeriveInput, Fields, LitInt, Type}; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; #[cfg(test)] mod integration_tests_package_usage { @@ -95,8 +95,9 @@ mod integration_tests_package_usage { /// ``` /// use calimero_storage::entities::Element; /// use calimero_storage_macros::AtomicUnit; +/// use borsh::{BorshSerialize, BorshDeserialize}; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// #[type_id(43)] /// struct Page { /// title: String, @@ -110,8 +111,9 @@ mod integration_tests_package_usage { /// ``` /// use calimero_storage::entities::Element; /// use calimero_storage_macros::{AtomicUnit, Collection}; +/// use borsh::{BorshSerialize, BorshDeserialize}; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// #[type_id(44)] /// struct Person { /// name: String, @@ -160,15 +162,6 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { let name = &input.ident; let where_clause = input.generics.make_where_clause().clone(); let (impl_generics, ty_generics, _) = input.generics.split_for_impl(); - let is_root = input.attrs.iter().any(|attr| attr.path().is_ident("root")); - let type_id = input - .attrs - .iter() - .find(|attr| attr.path().is_ident("type_id")) - .and_then(|attr| attr.parse_args::().ok()) - .expect("AtomicUnit derive requires a #[type_id(n)] attribute, where n is a u8") - .base10_parse::() - .expect("type_id must be a valid u8"); let fields = match &input.data { Data::Struct(data) => &data.fields, @@ -189,73 +182,6 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { .expect("You must designate one field with #[storage] for the Element"); let storage_ident = storage_field.ident.as_ref().unwrap(); - let storage_ty = &storage_field.ty; - - let field_implementations = named_fields - .iter() - .filter_map(|f| { - let ident = f.ident.as_ref().unwrap(); - let ty = &f.ty; - - let private = f.attrs.iter().any(|attr| attr.path().is_ident("private")); - let skip = f.attrs.iter().any(|attr| attr.path().is_ident("skip")); - - if skip || ident == storage_ident { - None - } else { - let setter = format_ident!("set_{}", ident); - - let setter_action = if private { - quote! { - self.#ident = value; - } - } else { - quote! { - self.#ident = value; - self.#storage_ident.update(); - } - }; - - Some(quote! { - #[doc = "Setter for the "] - #[doc = stringify!(#ident)] - #[doc = " field."] - pub fn #setter(&mut self, value: #ty) -> bool { - if self.#ident == value { - false - } else { - #setter_action - true - } - } - }) - } - }) - .collect::>(); - - let serializable_fields: Vec<_> = named_fields - .iter() - .filter(|f| { - !f.attrs - .iter() - .any(|attr| attr.path().is_ident("skip") || attr.path().is_ident("private")) - && f.ident.as_ref().unwrap() != storage_ident - }) - .map(|f| f.ident.as_ref().unwrap()) - .collect(); - - let regular_fields: Vec<_> = named_fields - .iter() - .filter(|f| { - !f.attrs.iter().any(|attr| { - attr.path().is_ident("skip") - || attr.path().is_ident("private") - || attr.path().is_ident("collection") - || attr.path().is_ident("storage") - }) - }) - .map(|f| f.ident.as_ref().unwrap()) - .collect(); let collection_fields: Vec<_> = named_fields .iter() @@ -267,130 +193,25 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { .map(|f| f.ident.as_ref().unwrap()) .collect(); - let collection_field_types: Vec<_> = named_fields - .iter() - .filter(|f| { - f.attrs - .iter() - .any(|attr| attr.path().is_ident("collection")) - }) - .map(|f| f.ty.clone()) - .collect(); - - let skipped_fields: Vec<_> = named_fields - .iter() - .filter(|f| { - f.attrs - .iter() - .any(|attr| attr.path().is_ident("skip") || attr.path().is_ident("private")) - }) - .map(|f| f.ident.as_ref().unwrap()) - .collect(); - - let mut data_where_clause = where_clause.clone(); + let mut serde_where_clause = where_clause.clone(); for ty in input.generics.type_params() { let ident = &ty.ident; - data_where_clause.predicates.push(syn::parse_quote!( + serde_where_clause.predicates.push(syn::parse_quote!( #ident: calimero_sdk::borsh::BorshSerialize + calimero_sdk::borsh::BorshDeserialize )); } - let deserialize_impl = quote! { - impl #impl_generics calimero_sdk::borsh::BorshDeserialize for #name #ty_generics #data_where_clause { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - let #storage_ident = #storage_ty::deserialize_reader(reader)?; - Ok(Self { - #storage_ident, - #(#serializable_fields: calimero_sdk::borsh::BorshDeserialize::deserialize_reader(reader)?,)* - #(#skipped_fields: Default::default(),)* - }) - } - } - }; - - let serialize_impl = quote! { - impl #impl_generics calimero_sdk::borsh::BorshSerialize for #name #ty_generics #data_where_clause { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - calimero_sdk::borsh::BorshSerialize::serialize(&self.#storage_ident, writer)?; - #(calimero_sdk::borsh::BorshSerialize::serialize(&self.#serializable_fields, writer)?;)* - Ok(()) - } - } - }; - - let root_impl = if is_root { - quote! { - fn is_root() -> bool { - true - } - } - } else { - quote! { - fn is_root() -> bool { - false - } - } - }; - - let mut local_where_clause = where_clause; - - for ty in input.generics.type_params() { - let ident = &ty.ident; - local_where_clause.predicates.push(syn::parse_quote!( - #ident: PartialEq - )); - } - let expanded = quote! { - impl #impl_generics #name #ty_generics #local_where_clause { - #(#field_implementations)* - } - - impl #impl_generics calimero_storage::entities::Data for #name #ty_generics #data_where_clause { - fn calculate_merkle_hash(&self) -> Result<[u8; 32], calimero_storage::interface::StorageError> { - use calimero_storage::exports::Digest; - let mut hasher = calimero_storage::exports::Sha256::new(); - hasher.update(self.element().id().as_bytes()); - #( - hasher.update( - &calimero_sdk::borsh::to_vec(&self.#regular_fields) - .map_err(calimero_storage::interface::StorageError::SerializationError)? - ); - )* - hasher.update( - &calimero_sdk::borsh::to_vec(&self.element().metadata()) - .map_err(calimero_storage::interface::StorageError::SerializationError)? - ); - Ok(hasher.finalize().into()) - } - - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - slice: &[u8], - ) -> Result<[u8; 32], calimero_storage::interface::StorageError> { - use calimero_sdk::borsh::BorshDeserialize; - match collection { - #( - stringify!(#collection_fields) => { - let child = <#collection_field_types #ty_generics as calimero_storage::entities::Collection>::Child::try_from_slice(slice) - .map_err(|e| calimero_storage::interface::StorageError::DeserializationError(e))?; - child.calculate_merkle_hash() - }, - )* - _ => Err(calimero_storage::interface::StorageError::UnknownCollectionType(collection.to_owned())), - } - } - + impl #impl_generics calimero_storage::entities::Data for #name #ty_generics #serde_where_clause { fn collections(&self) -> std::collections::BTreeMap> { use calimero_storage::entities::Collection; let mut collections = std::collections::BTreeMap::new(); #( collections.insert( stringify!(#collection_fields).to_owned(), - calimero_storage::interface::Interface::child_info_for(self.id(), &self.#collection_fields).unwrap_or_default() + calimero_storage::interface::MainInterface::child_info_for(self.id(), &self.#collection_fields).unwrap_or_default() ); )* collections @@ -403,22 +224,17 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { fn element_mut(&mut self) -> &mut calimero_storage::entities::Element { &mut self.#storage_ident } - - #root_impl - - fn type_id() -> u8 { - #type_id - } } - impl #impl_generics calimero_storage::entities::AtomicUnit for #name #ty_generics #data_where_clause {} - - #deserialize_impl - - #serialize_impl + impl #impl_generics calimero_storage::entities::AtomicUnit for #name #ty_generics #serde_where_clause {} }; - TokenStream::from(expanded) + TokenStream::from(quote! { + #[allow(unused_mut)] + const _: () = { + #expanded + }; + }) } /// Derives the [`Collection`](calimero_storage::entities::Collection) trait for @@ -459,9 +275,9 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { /// ``` /// use calimero_storage_macros::{AtomicUnit, Collection}; /// use calimero_storage::entities::{Data, Element}; +/// use borsh::{BorshSerialize, BorshDeserialize}; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -/// #[type_id(42)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// struct Book { /// title: String, /// pages: Pages, @@ -473,8 +289,7 @@ pub fn atomic_unit_derive(input: TokenStream) -> TokenStream { /// #[children(Page)] /// struct Pages; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -/// #[type_id(43)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// struct Page { /// content: String, /// #[storage] @@ -540,7 +355,7 @@ pub fn collection_derive(input: TokenStream) -> TokenStream { let data = (!data.is_empty()).then(|| quote! { { #(#data)* } }); let default_impl = quote! { - impl #impl_generics ::core::default::Default for #name #ty_generics #where_clause { + impl #impl_generics ::core::default::Default for #name #ty_generics { fn default() -> Self { Self #data } diff --git a/crates/storage-macros/tests/atomic_unit.rs b/crates/storage-macros/tests/atomic_unit.rs deleted file mode 100644 index 08b0508e8..000000000 --- a/crates/storage-macros/tests/atomic_unit.rs +++ /dev/null @@ -1,335 +0,0 @@ -#![allow(unused_crate_dependencies, reason = "Creates a lot of noise")] -// Lints specifically disabled for integration tests -#![allow( - non_snake_case, - unreachable_pub, - clippy::cast_lossless, - clippy::cast_precision_loss, - clippy::cognitive_complexity, - clippy::default_numeric_fallback, - clippy::exhaustive_enums, - clippy::exhaustive_structs, - clippy::expect_used, - clippy::indexing_slicing, - clippy::let_underscore_must_use, - clippy::let_underscore_untyped, - clippy::missing_assert_message, - clippy::missing_panics_doc, - clippy::mod_module_files, - clippy::must_use_candidate, - clippy::panic, - clippy::print_stdout, - clippy::tests_outside_test_module, - clippy::unwrap_in_result, - clippy::unwrap_used, - reason = "Not useful in tests" -)] - -use borsh::{to_vec, BorshDeserialize}; -use calimero_storage::address::Path; -use calimero_storage::entities::{Data, Element}; -use calimero_storage::exports::{Digest, Sha256}; -use calimero_storage::interface::Interface; -use calimero_storage_macros::AtomicUnit; - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[root] -#[type_id(1)] -struct Private { - public: String, - #[private] - private: String, - #[storage] - storage: Element, -} - -impl Private { - fn new(path: &Path) -> Self { - Self { - public: String::new(), - private: String::new(), - storage: Element::new(path, None), - } - } -} - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[root] -#[type_id(2)] -struct Simple { - name: String, - value: i32, - #[storage] - storage: Element, -} - -impl Simple { - fn new(path: &Path) -> Self { - Self { - name: String::new(), - value: 0, - storage: Element::new(path, None), - } - } -} - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[root] -#[type_id(3)] -struct Skipped { - included: String, - #[skip] - skipped: String, - #[storage] - storage: Element, -} - -impl Skipped { - fn new(path: &Path) -> Self { - Self { - included: String::new(), - skipped: String::new(), - storage: Element::new(path, None), - } - } -} - -#[cfg(test)] -mod basics { - use super::*; - - #[test] - fn creation() { - let path = Path::new("::root::node").unwrap(); - let unit = Simple::new(&path); - - assert_eq!(unit.path(), path); - assert_eq!(unit.element().path(), path); - assert!(unit.element().is_dirty()); - } - - #[test] - fn getters() { - let path = Path::new("::root::node").unwrap(); - let unit = Simple::new(&path); - - assert_eq!(unit.name, ""); - assert_eq!(unit.value, 0); - } - - #[test] - fn setters__set() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - - _ = unit.set_name("Test Name".to_owned()); - _ = unit.set_value(42); - - assert_eq!(unit.name, "Test Name"); - assert_eq!(unit.value, 42); - } - - #[test] - fn setters__confirm_set() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - assert_ne!(unit.name, "Test Name"); - assert_ne!(unit.value, 42); - - assert!(unit.set_name("Test Name".to_owned())); - assert!(unit.set_value(42)); - assert_eq!(unit.name, "Test Name"); - assert_eq!(unit.value, 42); - } - - #[test] - fn setters__confirm_not_set() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - assert_ne!(unit.name, "Test Name"); - assert_ne!(unit.value, 42); - - assert!(unit.set_name("Test Name".to_owned())); - assert!(unit.set_value(42)); - assert_eq!(unit.name, "Test Name"); - assert_eq!(unit.value, 42); - assert!(!unit.set_name("Test Name".to_owned())); - assert!(!unit.set_value(42)); - } - - #[test] - fn setters__confirm_set_dirty() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - assert!(Interface::save(&mut unit).unwrap()); - assert!(!unit.element().is_dirty()); - - assert!(unit.set_name("Test Name".to_owned())); - assert!(unit.element().is_dirty()); - } - - #[test] - fn setters__confirm_not_set_not_dirty() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - assert!(Interface::save(&mut unit).unwrap()); - assert!(!unit.element().is_dirty()); - - assert!(unit.set_name("Test Name".to_owned())); - assert!(unit.element().is_dirty()); - assert!(Interface::save(&mut unit).unwrap()); - assert!(!unit.set_name("Test Name".to_owned())); - assert!(!unit.element().is_dirty()); - } -} - -#[cfg(test)] -mod visibility { - use super::*; - - #[test] - fn private_field() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Private::new(&path); - - _ = unit.set_public("Public".to_owned()); - _ = unit.set_private("Private".to_owned()); - - let serialized = to_vec(&unit).unwrap(); - let deserialized = Private::try_from_slice(&serialized).unwrap(); - - assert_eq!(unit.public, deserialized.public); - assert_ne!(unit.private, deserialized.private); - assert_eq!(deserialized.private, ""); - } - - #[test] - fn public_field() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - - _ = unit.set_name("Public".to_owned()); - - let serialized = to_vec(&unit).unwrap(); - let deserialized = Simple::try_from_slice(&serialized).unwrap(); - - assert_eq!(unit.name, deserialized.name); - } - - #[test] - fn skipped_field() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Skipped::new(&path); - - _ = unit.set_included("Public".to_owned()); - // Skipping fields also skips the setters - // _ = unit.set_skipped("Skipped".to_owned()); - unit.skipped = "Skipped".to_owned(); - - let serialized = to_vec(&unit).unwrap(); - let deserialized = Skipped::try_from_slice(&serialized).unwrap(); - - assert_eq!(unit.included, deserialized.included); - // Skipping fields also skips the getters - // assert_ne!(unit.skipped(), deserialized.skipped()); - assert_ne!(unit.skipped, deserialized.skipped); - assert_eq!(deserialized.skipped, ""); - } -} - -#[cfg(test)] -mod hashing { - use super::*; - - #[test] - fn private_field() { - let path = Path::new("::root::node::leaf").unwrap(); - let mut unit = Private::new(&path); - - _ = unit.set_public("Public".to_owned()); - _ = unit.set_private("Private".to_owned()); - - let mut hasher = Sha256::new(); - hasher.update(unit.id().as_bytes()); - hasher.update(to_vec(&unit.public).unwrap()); - hasher.update(to_vec(&unit.element().metadata()).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(unit.calculate_merkle_hash().unwrap(), expected_hash); - - _ = unit.set_private("Test 1".to_owned()); - assert_eq!(unit.calculate_merkle_hash().unwrap(), expected_hash); - - _ = unit.set_public("Test 2".to_owned()); - assert_ne!(unit.calculate_merkle_hash().unwrap(), expected_hash); - } - - #[test] - fn public_field() { - let path = Path::new("::root::node::leaf").unwrap(); - let mut unit = Simple::new(&path); - - _ = unit.set_name("Public".to_owned()); - _ = unit.set_value(42); - - let mut hasher = Sha256::new(); - hasher.update(unit.id().as_bytes()); - hasher.update(to_vec(&unit.name).unwrap()); - hasher.update(to_vec(&unit.value).unwrap()); - hasher.update(to_vec(&unit.element().metadata()).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(unit.calculate_merkle_hash().unwrap(), expected_hash); - } - - #[test] - fn skipped_field() { - let path = Path::new("::root::node::leaf").unwrap(); - let mut unit = Skipped::new(&path); - - _ = unit.set_included("Public".to_owned()); - // Skipping fields also skips the setters - // _ = unit.set_skipped("Skipped".to_owned()); - unit.skipped = "Skipped".to_owned(); - - let mut hasher = Sha256::new(); - hasher.update(unit.id().as_bytes()); - hasher.update(to_vec(&unit.included).unwrap()); - hasher.update(to_vec(&unit.element().metadata()).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(unit.calculate_merkle_hash().unwrap(), expected_hash); - - unit.skipped = "Test 1".to_owned(); - assert_eq!(unit.calculate_merkle_hash().unwrap(), expected_hash); - - _ = unit.set_included("Test 2".to_owned()); - assert_ne!(unit.calculate_merkle_hash().unwrap(), expected_hash); - } -} - -#[cfg(test)] -mod traits { - use super::*; - - #[test] - fn serialization_and_deserialization() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - - _ = unit.set_name("Test Name".to_owned()); - _ = unit.set_value(42); - - let serialized = to_vec(&unit).unwrap(); - let deserialized = Simple::try_from_slice(&serialized).unwrap(); - - assert_eq!(unit, deserialized); - assert_eq!(unit.id(), deserialized.id()); - assert_eq!(unit.name, deserialized.name); - assert_eq!(unit.path(), deserialized.path()); - assert_eq!(unit.value, deserialized.value); - assert_eq!(unit.element().id(), deserialized.element().id()); - assert_eq!(unit.element().path(), deserialized.element().path()); - assert_eq!(unit.element().metadata(), deserialized.element().metadata()); - } -} diff --git a/crates/storage-macros/tests/collection.rs b/crates/storage-macros/tests/collection.rs deleted file mode 100644 index 7145ef22a..000000000 --- a/crates/storage-macros/tests/collection.rs +++ /dev/null @@ -1,184 +0,0 @@ -#![allow(unused_crate_dependencies, reason = "Creates a lot of noise")] -// Lints specifically disabled for integration tests -#![allow( - non_snake_case, - unreachable_pub, - clippy::cast_lossless, - clippy::cast_precision_loss, - clippy::cognitive_complexity, - clippy::default_numeric_fallback, - clippy::exhaustive_enums, - clippy::exhaustive_structs, - clippy::expect_used, - clippy::indexing_slicing, - clippy::let_underscore_must_use, - clippy::let_underscore_untyped, - clippy::missing_assert_message, - clippy::missing_panics_doc, - clippy::mod_module_files, - clippy::must_use_candidate, - clippy::panic, - clippy::print_stdout, - clippy::tests_outside_test_module, - clippy::unwrap_in_result, - clippy::unwrap_used, - reason = "Not useful in tests" -)] - -use borsh::{to_vec, BorshDeserialize}; -use calimero_storage::address::Path; -use calimero_storage::entities::{Data, Element}; -use calimero_storage::exports::{Digest, Sha256}; -use calimero_storage_macros::{AtomicUnit, Collection}; - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(22)] -struct Child { - content: String, - #[storage] - storage: Element, -} - -impl Child { - fn new(path: &Path) -> Self { - Self { - content: String::new(), - storage: Element::new(path, None), - } - } -} - -#[derive(Collection, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[children(Child)] -struct Group; - -impl Group { - const fn new() -> Self { - Self {} - } -} - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(21)] -#[root] -struct Parent { - title: String, - #[collection] - children: Group, - #[storage] - storage: Element, -} - -impl Parent { - fn new(path: &Path) -> Self { - Self { - title: String::new(), - children: Group::new(), - storage: Element::new(path, None), - } - } -} - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(23)] -#[root] -struct Simple { - name: String, - value: i32, - #[storage] - storage: Element, -} - -impl Simple { - fn new(path: &Path) -> Self { - Self { - name: String::new(), - value: 0, - storage: Element::new(path, None), - } - } -} - -#[cfg(test)] -mod hierarchy { - use super::*; - - #[test] - fn parent_child() { - let parent_path = Path::new("::root::node").unwrap(); - let mut parent = Parent::new(&parent_path); - _ = parent.set_title("Parent Title".to_owned()); - - let child_path = Path::new("::root::node::leaf").unwrap(); - let mut child = Child::new(&child_path); - _ = child.set_content("Child Content".to_owned()); - - assert_eq!(parent.title, "Parent Title"); - - // TODO: Add in tests for loading and checking children - } - - #[test] - fn compile_fail() { - trybuild::TestCases::new().compile_fail("tests/compile_fail/collection.rs"); - } -} - -#[cfg(test)] -mod hashing { - use super::*; - - #[test] - fn calculate_merkle_hash__child() { - let mut child = Child::new(&Path::new("::root::node::leaf").unwrap()); - _ = child.set_content("Child Content".to_owned()); - - let mut hasher = Sha256::new(); - hasher.update(child.id().as_bytes()); - hasher.update(to_vec(&child.content).unwrap()); - hasher.update(to_vec(&child.element().metadata()).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(child.calculate_merkle_hash().unwrap(), expected_hash); - } - - #[test] - fn calculate_merkle_hash__parent() { - let mut parent = Parent::new(&Path::new("::root::node").unwrap()); - _ = parent.set_title("Parent Title".to_owned()); - - let mut hasher = Sha256::new(); - hasher.update(parent.id().as_bytes()); - hasher.update(to_vec(&parent.title).unwrap()); - hasher.update(to_vec(&parent.element().metadata()).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(parent.calculate_merkle_hash().unwrap(), expected_hash); - } -} - -#[cfg(test)] -mod traits { - use super::*; - - #[test] - fn serialization_and_deserialization() { - let path = Path::new("::root::node").unwrap(); - let mut unit = Simple::new(&path); - - _ = unit.set_name("Test Name".to_owned()); - _ = unit.set_value(42); - - let serialized = to_vec(&unit).unwrap(); - let deserialized = Simple::try_from_slice(&serialized).unwrap(); - - assert_eq!(unit, deserialized); - assert_eq!(unit.id(), deserialized.id()); - assert_eq!(unit.name, deserialized.name); - assert_eq!(unit.path(), deserialized.path()); - assert_eq!(unit.value, deserialized.value); - assert_eq!(unit.element().id(), deserialized.element().id()); - assert_eq!(unit.element().path(), deserialized.element().path()); - assert_eq!(unit.element().metadata(), deserialized.element().metadata()); - } -} diff --git a/crates/storage-macros/tests/compile_fail/collection.rs b/crates/storage-macros/tests/compile_fail/collection.rs deleted file mode 100644 index 2c1ecc34a..000000000 --- a/crates/storage-macros/tests/compile_fail/collection.rs +++ /dev/null @@ -1,37 +0,0 @@ -use calimero_storage::address::Path; -use calimero_storage::entities::{Data, Element}; -use calimero_storage::interface::Interface; -use calimero_storage_macros::{AtomicUnit, Collection}; - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(2)] -struct Child { - #[storage] - storage: Element, -} - -#[derive(Collection, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[children(Child)] -struct Group; - -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[root] -#[type_id(1)] -struct Parent { - group: Group, - #[storage] - storage: Element, -} - -fn main() { - fn child_type_specification() { - let parent: Parent = Parent { - group: Group {}, - storage: Element::new(&Path::new("::root::node").unwrap(), None), - }; - let _: Vec = Interface::children_of(parent.id(), &parent.group).unwrap(); - - // This should fail to compile if the child type is incorrect - let _: Vec = Interface::children_of(parent.id(), &parent.group).unwrap(); - } -} diff --git a/crates/storage-macros/tests/compile_fail/collection.stderr b/crates/storage-macros/tests/compile_fail/collection.stderr deleted file mode 100644 index eeb500f05..000000000 --- a/crates/storage-macros/tests/compile_fail/collection.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[E0308]: mismatched types - --> tests/compile_fail/collection.rs:35:30 - | -35 | let _: Vec = Interface::children_of(parent.id(), &parent.group).unwrap(); - | ----------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Vec`, found `Vec` - | | - | expected due to this - | - = note: expected struct `Vec` - found struct `Vec` diff --git a/crates/storage/src/address.rs b/crates/storage/src/address.rs index b8eb8e7e9..4ad9cc3f2 100644 --- a/crates/storage/src/address.rs +++ b/crates/storage/src/address.rs @@ -13,7 +13,6 @@ use core::fmt::{self, Debug, Display, Formatter}; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; use borsh::{BorshDeserialize, BorshSerialize}; -use calimero_sdk::serde::{Deserialize, Serialize}; use fixedstr::Flexstr; use thiserror::Error as ThisError; @@ -40,15 +39,13 @@ use crate::env::{context_id, random_bytes}; Copy, Clone, Debug, - Deserialize, Eq, PartialEq, Hash, Ord, PartialOrd, - Serialize, + Default, )] -#[serde(crate = "calimero_sdk::serde")] pub struct Id { /// The byte array representation of the ID. bytes: [u8; 32], @@ -106,6 +103,11 @@ impl Id { pub const fn as_bytes(&self) -> &[u8; 32] { &self.bytes } + + /// Checks if the ID is the root. + pub fn is_root(&self) -> bool { + self.bytes == context_id() + } } impl Display for Id { diff --git a/crates/storage/src/collections.rs b/crates/storage/src/collections.rs index 1122bb681..4b79e22a3 100644 --- a/crates/storage/src/collections.rs +++ b/crates/storage/src/collections.rs @@ -1,14 +1,412 @@ //! High-level data structures for storage. -/// This module provides functionality for hashmap data structures. +use core::fmt; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::sync::LazyLock; + +use borsh::{BorshDeserialize, BorshSerialize}; +use indexmap::IndexSet; + pub mod unordered_map; pub use unordered_map::UnorderedMap; -/// This module provides functionality for hashset data structures. pub mod unordered_set; pub use unordered_set::UnorderedSet; -/// This module provides functionality for vector data structures. pub mod vector; pub use vector::Vector; -/// This module provides functionality for handling errors. +mod root; +#[doc(hidden)] +pub use root::Root; pub mod error; pub use error::StoreError; + +// fixme! macro expects `calimero_storage` to be in deps +use crate as calimero_storage; +use crate::address::{Id, Path}; +use crate::entities::{ChildInfo, Data, Element}; +use crate::interface::{Interface, StorageError}; +use crate::store::{MainStorage, StorageAdaptor}; +use crate::{AtomicUnit, Collection}; + +mod compat { + use std::collections::BTreeMap; + + use borsh::{BorshDeserialize, BorshSerialize}; + + use crate::entities::{ChildInfo, Collection, Data, Element}; + + /// Thing. + #[derive(Copy, Clone, Debug)] + pub(super) struct RootHandle; + + #[derive(BorshSerialize, BorshDeserialize)] + pub(super) struct RootChildDud; + + impl Data for RootChildDud { + fn collections(&self) -> BTreeMap> { + unimplemented!() + } + + fn element(&self) -> &Element { + unimplemented!() + } + + fn element_mut(&mut self) -> &mut Element { + unimplemented!() + } + } + + impl Collection for RootHandle { + type Child = RootChildDud; + + fn name(&self) -> &str { + "no collection, remove this nonsense" + } + } +} + +use compat::RootHandle; + +#[derive(BorshSerialize, BorshDeserialize)] +struct Collection { + storage: Element, + + #[borsh(skip)] + children_ids: RefCell>>, + + #[borsh(skip)] + _priv: PhantomData<(T, S)>, +} + +impl Data for Collection { + fn collections(&self) -> BTreeMap> { + BTreeMap::new() + } + + fn element(&self) -> &Element { + &self.storage + } + + fn element_mut(&mut self) -> &mut Element { + &mut self.storage + } +} + +/// A collection of entries in a map. +#[derive(Collection, Copy, Clone, Debug, Eq, PartialEq)] +#[children(Entry)] +struct Entries { + /// Helper to associate the generic types with the collection. + _priv: PhantomData, +} + +/// An entry in a map. +#[derive(AtomicUnit, BorshSerialize, BorshDeserialize, Clone, Debug)] +struct Entry { + /// The item in the entry. + item: T, + /// The storage element for the entry. + #[storage] + storage: Element, +} + +#[expect(unused_qualifications, reason = "AtomicUnit macro is unsanitized")] +type StoreResult = std::result::Result; + +static ROOT_ID: LazyLock = LazyLock::new(|| Id::root()); + +impl Collection { + /// Creates a new collection. + #[expect(clippy::expect_used, reason = "fatal error if it happens")] + fn new(id: Option) -> Self { + let id = id.unwrap_or_else(|| Id::random()); + + let mut this = Self { + children_ids: RefCell::new(None), + storage: Element::new(&Path::new("::unused").expect("valid path"), Some(id)), + _priv: PhantomData, + }; + + if id.is_root() { + let _ignored = >::save(&mut this).expect("save"); + } else { + let _ = + >::add_child_to(*ROOT_ID, &RootHandle, &mut this).expect("add child"); + } + + this + } + + /// Inserts an item into the collection. + fn insert(&mut self, id: Option, item: T) -> StoreResult { + let path = self.path(); + + let mut collection = CollectionMut::new(self); + + let mut entry = Entry { + item, + storage: Element::new(&path, id), + }; + + collection.insert(&mut entry)?; + + Ok(entry.item) + } + + #[inline(never)] + fn get(&self, id: Id) -> StoreResult> { + let entry = >::find_by_id::>(id)?; + + Ok(entry.map(|entry| entry.item)) + } + + fn get_mut(&mut self, id: Id) -> StoreResult>> { + let entry = >::find_by_id::>(id)?; + + Ok(entry.map(|entry| EntryMut { + collection: CollectionMut::new(self), + entry, + })) + } + + fn len(&self) -> StoreResult { + Ok(self.children_cache()?.len()) + } + + fn entries( + &self, + ) -> StoreResult> + DoubleEndedIterator + '_> { + let iter = self.children_cache()?.iter().copied().map(|child| { + let entry = >::find_by_id::>(child)? + .ok_or(StoreError::StorageError(StorageError::NotFound(child)))?; + + Ok(entry.item) + }); + + Ok(iter) + } + + fn nth(&self, index: usize) -> StoreResult> { + Ok(self.children_cache()?.get_index(index).copied()) + } + + fn last(&self) -> StoreResult> { + Ok(self.children_cache()?.last().copied()) + } + + fn clear(&mut self) -> StoreResult<()> { + let mut collection = CollectionMut::new(self); + + collection.clear()?; + + Ok(()) + } + + #[expect( + clippy::unwrap_in_result, + clippy::expect_used, + reason = "fatal error if it happens" + )] + fn children_cache(&self) -> StoreResult<&mut IndexSet> { + let mut cache = self.children_ids.borrow_mut(); + + if cache.is_none() { + let children = >::child_info_for(self.id(), &RootHandle)?; + + let children = children.into_iter().map(|c| c.id()).collect(); + + *cache = Some(children); + } + + let children = cache.as_mut().expect("children"); + + #[expect(unsafe_code, reason = "necessary for caching")] + let children = unsafe { &mut *ptr::from_mut(children) }; + + Ok(children) + } +} + +#[derive(Debug)] +struct EntryMut<'a, T: BorshSerialize + BorshDeserialize, S: StorageAdaptor> { + collection: CollectionMut<'a, T, S>, + entry: Entry, +} + +impl EntryMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn remove(self) -> StoreResult { + let old = self + .collection + .get(self.entry.id())? + .ok_or(StoreError::StorageError(StorageError::NotFound( + self.entry.id(), + )))?; + + let _ = + >::remove_child_from(self.collection.id(), &RootHandle, self.entry.id())?; + + let _ = self + .collection + .children_cache()? + .shift_remove(&self.entry.id()); + + Ok(old) + } +} + +impl Deref for EntryMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.entry.item + } +} + +impl DerefMut for EntryMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.entry.item + } +} + +impl Drop for EntryMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn drop(&mut self) { + self.entry.element_mut().update(); + let _ignored = >::save(&mut self.entry); + } +} + +#[derive(Debug)] +struct CollectionMut<'a, T: BorshSerialize + BorshDeserialize, S: StorageAdaptor> { + collection: &'a mut Collection, +} + +impl<'a, T, S> CollectionMut<'a, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn new(collection: &'a mut Collection) -> Self { + Self { collection } + } + + fn insert(&mut self, item: &mut Entry) -> StoreResult<()> { + let _ = >::add_child_to(self.collection.id(), &RootHandle, item)?; + + let _ignored = self.collection.children_cache()?.insert(item.id()); + + Ok(()) + } + + fn clear(&mut self) -> StoreResult<()> { + let children = self.collection.children_cache()?; + + for child in children.drain(..) { + let _ = >::remove_child_from(self.collection.id(), &RootHandle, child)?; + } + + Ok(()) + } +} + +impl Deref for CollectionMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + type Target = Collection; + + fn deref(&self) -> &Self::Target { + self.collection + } +} + +impl DerefMut for CollectionMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.collection + } +} + +impl Drop for CollectionMut<'_, T, S> +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn drop(&mut self) { + self.collection.element_mut().update(); + } +} + +impl fmt::Debug for Collection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Collection") + .field("element", &self.storage) + .finish() + } +} + +impl Default for Collection +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn default() -> Self { + Self::new(None) + } +} + +impl Eq for Collection {} + +impl PartialEq + for Collection +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn eq(&self, other: &Self) -> bool { + let l = self.entries().unwrap().flatten(); + let r = other.entries().unwrap().flatten(); + + l.eq(r) + } +} + +impl Ord for Collection { + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let l = self.entries().unwrap().flatten(); + let r = other.entries().unwrap().flatten(); + + l.cmp(r) + } +} + +impl PartialOrd + for Collection +{ + fn partial_cmp(&self, other: &Self) -> Option { + let l = self.entries().ok()?.flatten(); + let r = other.entries().ok()?.flatten(); + + l.partial_cmp(r) + } +} diff --git a/crates/storage/src/collections/error.rs b/crates/storage/src/collections/error.rs index bd91c67ff..1181db385 100644 --- a/crates/storage/src/collections/error.rs +++ b/crates/storage/src/collections/error.rs @@ -1,6 +1,7 @@ +//! Error types for storage operations. + use thiserror::Error; -// fixme! macro expects `calimero_storage` to be in deps use crate::address::PathError; use crate::interface::StorageError; diff --git a/crates/storage/src/collections/root.rs b/crates/storage/src/collections/root.rs new file mode 100644 index 000000000..4de3ee951 --- /dev/null +++ b/crates/storage/src/collections/root.rs @@ -0,0 +1,194 @@ +//! A root collection that stores a single value. + +use core::fmt; +use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; +use std::ptr; + +use borsh::{from_slice, BorshDeserialize, BorshSerialize}; + +use super::{Collection, ROOT_ID}; +use crate::address::Id; +use crate::integration::Comparison; +use crate::interface::{Action, Interface, StorageError}; +use crate::store::{MainStorage, StorageAdaptor}; +use crate::sync::{self, SyncArtifact}; + +/// A set collection that stores unqiue values once. +pub struct Root { + inner: Collection, + value: RefCell>, + dirty: bool, +} + +impl fmt::Debug for Root +where + T: BorshSerialize + BorshDeserialize + fmt::Debug, + S: StorageAdaptor, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Root") + .field("inner", &self.inner) + .field("value", &self.value) + .field("dirty", &self.dirty) + .finish() + } +} + +impl Root +where + T: BorshSerialize + BorshDeserialize, +{ + /// Creates a new root collection with the given value. + pub fn new T>(f: F) -> Self { + Self::new_internal(f) + } +} + +impl Root +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + /// Creates a new root collection with the given value. + #[expect(clippy::unwrap_used, reason = "fatal error if it happens")] + pub fn new_internal T>(f: F) -> Self { + let mut inner = Collection::new(Some(*ROOT_ID)); + + let id = Self::entry_id(); + + let value = inner.insert(Some(id), f()).unwrap(); + + Self { + inner, + dirty: false, + value: RefCell::new(Some(value)), + } + } + + fn entry_id() -> Id { + Id::new([118; 32]) + } + + #[expect(clippy::mut_from_ref, reason = "'tis fine")] + #[expect(clippy::unwrap_used, reason = "fatal error if it happens")] + fn get(&self) -> &mut T { + let mut value = self.value.borrow_mut(); + + let id = Self::entry_id(); + + let value = value.get_or_insert_with(|| self.inner.get(id).unwrap().unwrap()); + + #[expect(unsafe_code, reason = "necessary for caching")] + let value = unsafe { &mut *ptr::from_mut(value) }; + + value + } + + /// Fetches the root collection. + #[expect( + clippy::unwrap_used, + clippy::unwrap_in_result, + reason = "fatal error if it happens" + )] + pub fn fetch() -> Option { + let inner = >::root().unwrap()?; + + Some(Self { + inner, + dirty: false, + value: RefCell::new(None), + }) + } + + /// Commits the root collection. + #[expect(clippy::unwrap_used, reason = "fatal error if it happens")] + pub fn commit(mut self) { + if self.dirty { + if let Some(value) = self.value.into_inner() { + if let Some(mut entry) = self.inner.get_mut(Self::entry_id()).unwrap() { + *entry = value; + } + } + } + + >::commit_root(Some(self.inner)).unwrap(); + } + + /// Commits the root collection without an instance of the root state. + #[expect(clippy::unwrap_used, reason = "fatal error if it happens")] + pub fn commit_headless() { + >::commit_root::>(None).unwrap(); + } + + /// Syncs the root collection. + #[expect(clippy::missing_errors_doc, reason = "NO")] + pub fn sync(args: &[u8]) -> Result<(), StorageError> { + let artifact = + from_slice::(args).map_err(StorageError::DeserializationError)?; + + match artifact { + SyncArtifact::Actions(actions) => { + for action in actions { + let _ignored = match action { + Action::Compare { id } => { + sync::push_comparison(Comparison { + data: >::find_by_id_raw(id), + comparison_data: >::generate_comparison_data(Some( + id, + ))?, + }); + } + Action::Add { .. } | Action::Update { .. } | Action::Delete { .. } => { + >::apply_action(action)?; + } + }; + } + } + SyncArtifact::Comparisons(comparisons) => { + if comparisons.is_empty() { + sync::push_comparison(Comparison { + data: >::find_by_id_raw(Id::root()), + comparison_data: >::generate_comparison_data(None)?, + }); + } + + for Comparison { + data, + comparison_data, + } in comparisons + { + >::compare_affective(data, comparison_data)?; + } + } + } + + Self::commit_headless(); + + Ok(()) + } +} + +impl Deref for Root +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl DerefMut for Root +where + T: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty = true; + + self.get() + } +} diff --git a/crates/storage/src/collections/unordered_map.rs b/crates/storage/src/collections/unordered_map.rs index 7eaea3726..d103f0e1b 100644 --- a/crates/storage/src/collections/unordered_map.rs +++ b/crates/storage/src/collections/unordered_map.rs @@ -1,102 +1,59 @@ +//! This module provides functionality for the unordered map data structure. + use core::borrow::Borrow; -use core::marker::PhantomData; +use core::fmt; +use std::mem; use borsh::{BorshDeserialize, BorshSerialize}; +use serde::ser::SerializeMap; +use serde::Serialize; use sha2::{Digest, Sha256}; -// fixme! macro expects `calimero_storage` to be in deps -use crate as calimero_storage; -use crate::address::{Id, Path}; +use super::{Collection, StorageAdaptor}; +use crate::address::Id; use crate::collections::error::StoreError; -use crate::entities::{Data, Element}; -use crate::interface::Interface; -use crate::{AtomicUnit, Collection}; +use crate::entities::Data; +use crate::store::MainStorage; /// A map collection that stores key-value pairs. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(255)] -#[root] -pub struct UnorderedMap { - /// The id used for the map's entries. - id: Id, - /// The entries in the map. - entries: Entries, - /// The storage element for the map. - #[storage] - storage: Element, -} - -/// A collection of entries in a map. -#[derive(Collection, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[children(Entry)] -struct Entries { - /// Helper to associate the generic types with the collection. - _priv: PhantomData<(K, V)>, +#[derive(BorshSerialize, BorshDeserialize)] +pub struct UnorderedMap { + #[borsh(bound(serialize = "", deserialize = ""))] + inner: Collection<(K, V), S>, } -/// An entry in a map. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(254)] -pub struct Entry { - /// The key for the entry. - key: K, - /// The value for the entry. - value: V, - /// The storage element for the entry. - #[storage] - storage: Element, +impl UnorderedMap +where + K: BorshSerialize + BorshDeserialize, + V: BorshSerialize + BorshDeserialize, +{ + /// Create a new map collection. + pub fn new() -> Self { + Self::new_internal() + } } -impl - UnorderedMap +impl UnorderedMap +where + K: BorshSerialize + BorshDeserialize, + V: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, { /// Create a new map collection. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, or a child - /// [`Element`](crate::entities::Element) cannot be found, an error will be - /// returned. - /// - pub fn new() -> Result { - let id = Id::random(); - let mut this = Self { - id: id, - entries: Entries::default(), - storage: Element::new(&Path::new(format!("::unused::map::{id}::path"))?, Some(id)), - }; - - let _ = Interface::save(&mut this)?; - - Ok(this) + fn new_internal() -> Self { + Self { + inner: Collection::new(None), + } } /// Compute the ID for a key. fn compute_id(&self, key: &[u8]) -> Id { let mut hasher = Sha256::new(); - hasher.update(self.id.as_bytes()); + hasher.update(self.inner.id().as_bytes()); hasher.update(key); Id::new(hasher.finalize().into()) } - /// Get the raw entry for a key in the map. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, or a child - /// [`Element`](crate::entities::Element) cannot be found, an error will be - /// returned. - /// - fn get_raw(&self, key: &Q) -> Result>, StoreError> - where - K: Borrow, - Q: PartialEq + AsRef<[u8]> + ?Sized, - { - Ok(Interface::find_by_id::>( - self.compute_id(key.as_ref()), - )?) - } - /// Insert a key-value pair into the map. /// /// # Errors @@ -109,26 +66,15 @@ impl where K: AsRef<[u8]> + PartialEq, { - if let Some(mut entry) = self.get_raw(&key)? { - let ret_value = entry.value; - entry.value = value; - // has to be called to update the entry - entry.element_mut().update(); - let _ = Interface::save(&mut entry)?; - return Ok(Some(ret_value)); + let id = self.compute_id(key.as_ref()); + + if let Some(mut entry) = self.inner.get_mut(id)? { + let (_, v) = &mut *entry; + + return Ok(Some(mem::replace(v, value))); } - let path = self.path(); - let storage = Element::new(&path, Some(self.compute_id(key.as_ref()))); - let _ = Interface::add_child_to( - self.storage.id(), - &mut self.entries, - &mut Entry { - key, - value, - storage, - }, - )?; + let _ignored = self.inner.insert(Some(id), (key, value))?; Ok(None) } @@ -141,10 +87,8 @@ impl /// [`Element`](crate::entities::Element) cannot be found, an error will be /// returned. /// - pub fn entries(&self) -> Result, StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; - - Ok(entries.into_iter().map(|entry| (entry.key, entry.value))) + pub fn entries(&self) -> Result + '_, StoreError> { + Ok(self.inner.entries()?.flatten().fuse()) } /// Get the number of entries in the map. @@ -156,7 +100,7 @@ impl /// returned. /// pub fn len(&self) -> Result { - Ok(Interface::child_info_for(self.id(), &self.entries)?.len()) + self.inner.len() } /// Get the value for a key in the map. @@ -172,10 +116,9 @@ impl K: Borrow, Q: PartialEq + AsRef<[u8]> + ?Sized, { - let entry = Interface::find_by_id::>(self.compute_id(key.as_ref()))?; - let value = entry.map(|e| e.value); + let id = self.compute_id(key.as_ref()); - Ok(value) + Ok(self.inner.get(id)?.map(|(_, v)| v)) } /// Check if the map contains a key. @@ -191,7 +134,7 @@ impl K: Borrow + PartialEq, Q: PartialEq + AsRef<[u8]> + ?Sized, { - Ok(self.get_raw(key)?.is_some()) + self.get(key).map(|v| v.is_some()) } /// Remove a key from the map, returning the value at the key if it previously existed. @@ -202,18 +145,18 @@ impl /// [`Element`](crate::entities::Element) cannot be found, an error will be /// returned. /// - pub fn remove(&mut self, key: &Q) -> Result + pub fn remove(&mut self, key: &Q) -> Result, StoreError> where K: Borrow, Q: PartialEq + AsRef<[u8]> + ?Sized, { - let entry = Element::new(&self.path(), Some(self.compute_id(key.as_ref()))); + let id = self.compute_id(key.as_ref()); - Ok(Interface::remove_child_from( - self.id(), - &mut self.entries, - entry.id(), - )?) + let Some(entry) = self.inner.get_mut(id)? else { + return Ok(None); + }; + + entry.remove().map(|(_, v)| Some(v)) } /// Clear the map, removing all entries. @@ -225,23 +168,120 @@ impl /// returned. /// pub fn clear(&mut self) -> Result<(), StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; + self.inner.clear() + } +} - for entry in entries { - let _ = Interface::remove_child_from(self.id(), &mut self.entries, entry.id())?; +impl Eq for UnorderedMap +where + K: Eq + BorshSerialize + BorshDeserialize, + V: Eq + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ +} + +impl PartialEq for UnorderedMap +where + K: PartialEq + BorshSerialize + BorshDeserialize, + V: PartialEq + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn eq(&self, other: &Self) -> bool { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); + + l.eq(r) + } +} + +impl Ord for UnorderedMap +where + K: Ord + BorshSerialize + BorshDeserialize, + V: Ord + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); + + l.cmp(r) + } +} + +impl PartialOrd for UnorderedMap +where + K: PartialOrd + BorshSerialize + BorshDeserialize, + V: PartialOrd + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn partial_cmp(&self, other: &Self) -> Option { + let l = self.entries().ok()?; + let r = other.entries().ok()?; + + l.partial_cmp(r) + } +} + +impl fmt::Debug for UnorderedMap +where + K: fmt::Debug + BorshSerialize + BorshDeserialize, + V: fmt::Debug + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + #[expect(clippy::unwrap_used, clippy::unwrap_in_result, reason = "'tis fine")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.debug_struct("UnorderedMap") + .field("entries", &self.inner) + .finish() + } else { + f.debug_map().entries(self.entries().unwrap()).finish() } + } +} + +impl Default for UnorderedMap +where + K: Default + BorshSerialize + BorshDeserialize, + V: Default + BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn default() -> Self { + Self::new_internal() + } +} + +impl Serialize for UnorderedMap +where + K: BorshSerialize + BorshDeserialize + Serialize, + V: BorshSerialize + BorshDeserialize + Serialize, + S: StorageAdaptor, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: serde::Serializer, + { + let len = self.len().map_err(serde::ser::Error::custom)?; - Ok(()) + let mut seq = serializer.serialize_map(Some(len))?; + + for (k, v) in self.entries().map_err(serde::ser::Error::custom)? { + seq.serialize_entry(&k, &v)?; + } + + seq.end() } } #[cfg(test)] mod tests { - use crate::collections::unordered_map::UnorderedMap; + use crate::collections::{Root, UnorderedMap}; #[test] fn test_unordered_map_basic_operations() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -249,18 +289,19 @@ mod tests { .is_none()); assert_eq!( - map.get("key").expect("get failed"), - Some("value".to_string()) + map.get("key").expect("get failed").as_deref(), + Some("value") ); assert_ne!( - map.get("key").expect("get failed"), - Some("value2".to_string()) + map.get("key").expect("get failed").as_deref(), + Some("value2") ); assert_eq!( map.insert("key".to_string(), "value2".to_string()) - .expect("insert failed"), - Some("value".to_string()) + .expect("insert failed") + .as_deref(), + Some("value") ); assert!(map .insert("key2".to_string(), "value".to_string()) @@ -268,23 +309,28 @@ mod tests { .is_none()); assert_eq!( - map.get("key").expect("get failed"), - Some("value2".to_string()) + map.get("key").expect("get failed").as_deref(), + Some("value2") ); assert_eq!( - map.get("key2").expect("get failed"), - Some("value".to_string()) + map.get("key2").expect("get failed").as_deref(), + Some("value") ); - assert_eq!(map.remove("key").expect("error while removing key"), true); - assert_eq!(map.remove("key").expect("error while removing key"), false); + assert_eq!( + map.remove("key") + .expect("error while removing key") + .as_deref(), + Some("value2") + ); + assert_eq!(map.remove("key").expect("error while removing key"), None); assert_eq!(map.get("key").expect("get failed"), None); } #[test] fn test_unordered_map_insert_and_get() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) @@ -296,18 +342,18 @@ mod tests { .is_none()); assert_eq!( - map.get("key1").expect("get failed"), - Some("value1".to_string()) + map.get("key1").expect("get failed").as_deref(), + Some("value1") ); assert_eq!( - map.get("key2").expect("get failed"), - Some("value2".to_string()) + map.get("key2").expect("get failed").as_deref(), + Some("value2") ); } #[test] fn test_unordered_map_update_value() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -319,27 +365,30 @@ mod tests { .is_none()); assert_eq!( - map.get("key").expect("get failed"), - Some("new_value".to_string()) + map.get("key").expect("get failed").as_deref(), + Some("new_value") ); } #[test] fn test_remove() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) .expect("insert failed") .is_none()); - assert_eq!(map.remove("key").expect("remove failed"), true); + assert_eq!( + map.remove("key").expect("remove failed").as_deref(), + Some("value") + ); assert_eq!(map.get("key").expect("get failed"), None); } #[test] fn test_clear() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) @@ -358,7 +407,7 @@ mod tests { #[test] fn test_unordered_map_len() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert_eq!(map.len().expect("len failed"), 0); @@ -377,14 +426,17 @@ mod tests { assert_eq!(map.len().expect("len failed"), 2); - assert_eq!(map.remove("key1").expect("remove failed"), true); + assert_eq!( + map.remove("key1").expect("remove failed").as_deref(), + Some("value1") + ); assert_eq!(map.len().expect("len failed"), 1); } #[test] fn test_unordered_map_contains() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -397,7 +449,7 @@ mod tests { #[test] fn test_unordered_map_entries() { - let mut map = UnorderedMap::::new().expect("failed to create map"); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) diff --git a/crates/storage/src/collections/unordered_set.rs b/crates/storage/src/collections/unordered_set.rs index 378ef5bb7..adb92f517 100644 --- a/crates/storage/src/collections/unordered_set.rs +++ b/crates/storage/src/collections/unordered_set.rs @@ -1,76 +1,52 @@ +//! This module provides functionality for the unordered set data structure. + use core::borrow::Borrow; -use core::marker::PhantomData; +use core::fmt; use borsh::{BorshDeserialize, BorshSerialize}; +use serde::ser::SerializeSeq; +use serde::Serialize; use sha2::{Digest, Sha256}; -// fixme! macro expects `calimero_storage` to be in deps -use crate as calimero_storage; -use crate::address::{Id, Path}; +use super::Collection; +use crate::address::Id; use crate::collections::error::StoreError; -use crate::entities::{Data, Element}; -use crate::interface::Interface; -use crate::{AtomicUnit, Collection}; +use crate::entities::Data; +use crate::store::{MainStorage, StorageAdaptor}; /// A set collection that stores unqiue values once. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(253)] -#[root] -pub struct UnorderedSet { - /// The prefix used for the set's entries. - id: Id, - /// The entries in the set. - entries: Entries, - /// The storage element for the set. - #[storage] - storage: Element, -} - -/// A collection of entries in a set. -#[derive(Collection, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[children(Entry)] -struct Entries { - /// Helper to associate the generic types with the collection. - _priv: PhantomData, +#[derive(BorshSerialize, BorshDeserialize)] +pub struct UnorderedSet { + #[borsh(bound(serialize = "", deserialize = ""))] + inner: Collection, } -/// An entry in a set. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(252)] -pub struct Entry { - /// The value for the entry. - value: V, - /// The storage element for the entry. - #[storage] - storage: Element, +impl UnorderedSet +where + V: BorshSerialize + BorshDeserialize, +{ + /// Create a new set collection. + pub fn new() -> Self { + Self::new_internal() + } } -impl UnorderedSet { +impl UnorderedSet +where + V: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ /// Create a new set collection. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, or a child - /// [`Element`](crate::entities::Element) cannot be found, an error will be - /// returned. - /// - pub fn new() -> Result { - let id = Id::random(); - let mut this = Self { - id: id, - entries: Entries::default(), - storage: Element::new(&Path::new(format!("::unused::set::{id}::path"))?, Some(id)), - }; - - let _ = Interface::save(&mut this)?; - - Ok(this) + fn new_internal() -> Self { + Self { + inner: Collection::new(None), + } } /// Compute the ID for a value in the set. fn compute_id(&self, value: &[u8]) -> Id { let mut hasher = Sha256::new(); - hasher.update(self.id.as_bytes()); + hasher.update(self.inner.id().as_bytes()); hasher.update(value); Id::new(hasher.finalize().into()) } @@ -87,18 +63,13 @@ impl UnorderedSet { where V: AsRef<[u8]> + PartialEq, { - let path = self.path(); + let id = self.compute_id(value.as_ref()); - if self.contains(&value)? { + if self.inner.get_mut(id)?.is_some() { return Ok(false); - } + }; - let storage = Element::new(&path, Some(self.compute_id(value.as_ref()))); - let _ = Interface::add_child_to( - self.storage.id(), - &mut self.entries, - &mut Entry { value, storage }, - )?; + let _ignored = self.inner.insert(Some(id), value)?; Ok(true) } @@ -111,10 +82,8 @@ impl UnorderedSet { /// [`Element`](crate::entities::Element) cannot be found, an error will be /// returned. /// - pub fn entries(&self) -> Result, StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; - - Ok(entries.into_iter().map(|entry| entry.value)) + pub fn entries(&self) -> Result + '_, StoreError> { + Ok(self.inner.entries()?.flatten().fuse()) } /// Get the number of entries in the set. @@ -126,7 +95,7 @@ impl UnorderedSet { /// returned. /// pub fn len(&self) -> Result { - Ok(Interface::child_info_for(self.id(), &self.entries)?.len()) + self.inner.len() } /// Get the value for a key in the set. @@ -142,8 +111,9 @@ impl UnorderedSet { V: Borrow, Q: PartialEq + ?Sized + AsRef<[u8]>, { - let entry = Interface::find_by_id::>(self.compute_id(value.as_ref()))?; - Ok(entry.is_some()) + let id = self.compute_id(value.as_ref()); + + Ok(self.inner.get(id)?.is_some()) } /// Remove a key from the set, returning the value at the key if it previously existed. @@ -159,13 +129,15 @@ impl UnorderedSet { V: Borrow, Q: PartialEq + AsRef<[u8]> + ?Sized, { - let entry = Element::new(&self.path(), Some(self.compute_id(value.as_ref()))); + let id = self.compute_id(value.as_ref()); - Ok(Interface::remove_child_from( - self.id(), - &mut self.entries, - entry.id(), - )?) + let Some(entry) = self.inner.get_mut(id)? else { + return Ok(false); + }; + + let _ignored = entry.remove()?; + + Ok(true) } /// Clear the set, removing all entries. @@ -177,23 +149,103 @@ impl UnorderedSet { /// returned. /// pub fn clear(&mut self) -> Result<(), StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; + self.inner.clear() + } +} + +impl Eq for UnorderedSet where V: Eq + BorshSerialize + BorshDeserialize {} + +impl PartialEq for UnorderedSet +where + V: PartialEq + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn eq(&self, other: &Self) -> bool { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); + + l.eq(r) + } +} + +impl Ord for UnorderedSet +where + V: Ord + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); + + l.cmp(r) + } +} + +impl PartialOrd for UnorderedSet +where + V: PartialOrd + BorshSerialize + BorshDeserialize, +{ + fn partial_cmp(&self, other: &Self) -> Option { + let l = self.entries().ok()?; + let r = other.entries().ok()?; + + l.partial_cmp(r) + } +} + +impl fmt::Debug for UnorderedSet +where + V: fmt::Debug + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, clippy::unwrap_in_result, reason = "'tis fine")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.debug_struct("UnorderedSet") + .field("entries", &self.inner) + .finish() + } else { + f.debug_set().entries(self.entries().unwrap()).finish() + } + } +} + +impl Default for UnorderedSet +where + V: BorshSerialize + BorshDeserialize, +{ + fn default() -> Self { + Self::new_internal() + } +} + +impl Serialize for UnorderedSet +where + V: BorshSerialize + BorshDeserialize + Serialize, + S: StorageAdaptor, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: serde::Serializer, + { + let len = self.len().map_err(serde::ser::Error::custom)?; + + let mut seq = serializer.serialize_seq(Some(len))?; - for entry in entries { - let _ = Interface::remove_child_from(self.id(), &mut self.entries, entry.id())?; + for v in self.entries().map_err(serde::ser::Error::custom)? { + seq.serialize_element(&v)?; } - Ok(()) + seq.end() } } #[cfg(test)] mod tests { - use crate::collections::UnorderedSet; + use crate::collections::{Root, UnorderedSet}; #[test] fn test_unordered_set_operations() { - let mut set = UnorderedSet::::new().expect("failed to create set"); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); @@ -221,7 +273,7 @@ mod tests { #[test] fn test_unordered_set_len() { - let mut set = UnorderedSet::::new().expect("failed to create set"); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); @@ -236,7 +288,7 @@ mod tests { #[test] fn test_unordered_set_clear() { - let mut set = UnorderedSet::::new().expect("failed to create set"); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); @@ -252,7 +304,7 @@ mod tests { #[test] fn test_unordered_set_entries() { - let mut set = UnorderedSet::::new().expect("failed to create set"); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); diff --git a/crates/storage/src/collections/vector.rs b/crates/storage/src/collections/vector.rs index 4454d337b..a1d57ae74 100644 --- a/crates/storage/src/collections/vector.rs +++ b/crates/storage/src/collections/vector.rs @@ -1,68 +1,45 @@ +//! This module provides functionality for the vector data structure. + use core::borrow::Borrow; -use core::marker::PhantomData; +use core::fmt; +use std::mem; use borsh::{BorshDeserialize, BorshSerialize}; +use serde::ser::SerializeSeq; +use serde::Serialize; -// fixme! macro expects `calimero_storage` to be in deps -use crate::address::{Id, Path}; +use super::Collection; use crate::collections::error::StoreError; -use crate::entities::{Data, Element}; -use crate::interface::{Interface, StorageError}; -use crate::{self as calimero_storage, AtomicUnit, Collection}; +use crate::store::{MainStorage, StorageAdaptor}; /// A vector collection that stores key-value pairs. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(251)] -#[root] -pub struct Vector { - /// The entries in the vector. - entries: Entries, - /// The storage element for the vector. - #[storage] - storage: Element, -} - -/// A collection of entries in a vector. -#[derive(Collection, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[children(Entry)] -struct Entries { - /// Helper to associate the generic types with the collection. - _priv: PhantomData, +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Vector { + // Borrow/ToOwned + #[borsh(bound(serialize = "", deserialize = ""))] + inner: Collection, } -/// An entry in a vector. -#[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] -#[type_id(250)] -pub struct Entry { - /// The value for the entry. - value: V, - /// The storage element for the entry. - #[storage] - storage: Element, +impl Vector +where + V: BorshSerialize + BorshDeserialize, +{ + /// Create a new vector collection. + pub fn new() -> Self { + Self::new_internal() + } } -impl Vector { +impl Vector +where + V: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ /// Create a new vector collection. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, or a child - /// [`Element`](crate::entities::Element) cannot be found, an error will be - /// returned. - /// - pub fn new() -> Result { - let id = Id::random(); - let mut this = Self { - entries: Entries::default(), - storage: Element::new( - &Path::new(format!("::unused::vector::{id}::path"))?, - Some(id), - ), - }; - - let _ = Interface::save(&mut this)?; - - Ok(this) + fn new_internal() -> Self { + Self { + inner: Collection::new(None), + } } /// Add a value to the end of the vector. @@ -74,18 +51,9 @@ impl Vector { /// returned. /// pub fn push(&mut self, value: V) -> Result<(), StoreError> { - let mut entry = Entry { - value, - storage: Element::new(&self.path(), None), - }; + let _ignored = self.inner.insert(None, value)?; - if Interface::add_child_to(self.id(), &mut self.entries, &mut entry)? { - return Ok(()); - } - - Err(StoreError::StorageError(StorageError::ActionNotAllowed( - "Failed to add child".to_owned(), - ))) + Ok(()) } /// Remove and return the last value from the vector. @@ -97,35 +65,17 @@ impl Vector { /// returned. /// pub fn pop(&mut self) -> Result, StoreError> { - let id = match Interface::child_info_for(self.id(), &self.entries)?.pop() { - Some(info) => info.id(), - None => return Ok(None), + let Some(last) = self.inner.last()? else { + return Ok(None); }; - let entry = Interface::find_by_id::>(id)?; - let _ = Interface::remove_child_from(self.id(), &mut self.entries, id)?; - Ok(entry.map(|e| e.value)) - } - /// Get the raw storage entry at a specific index in the vector. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, or a child - /// [`Element`](crate::entities::Element) cannot be found, an error will be - /// returned. - /// - fn get_raw(&self, index: usize) -> Result>, StoreError> { - let id = match Interface::child_info_for(self.id(), &self.entries)?.get(index) { - Some(info) => info.id(), - None => return Ok(None), + let Some(entry) = self.inner.get_mut(last)? else { + return Ok(None); }; - let entry = match Interface::find_by_id::>(id) { - Ok(entry) => entry, - Err(_) => return Ok(None), - }; + let last = entry.remove()?; - Ok(entry) + Ok(Some(last)) } /// Get the value at a specific index in the vector. @@ -137,7 +87,7 @@ impl Vector { /// returned. /// pub fn get(&self, index: usize) -> Result, StoreError> { - Ok(self.get_raw(index)?.map(|entry| entry.value)) + self.inner.entries()?.nth(index).transpose() } /// Update the value at a specific index in the vector. @@ -148,18 +98,18 @@ impl Vector { /// [`Element`](crate::entities::Element) cannot be found, an error will be /// returned. /// - pub fn update(&mut self, index: usize, value: V) -> Result<(), StoreError> { - let mut entry = self.get_raw(index)?.ok_or(StoreError::StorageError( - StorageError::ActionNotAllowed("error".to_owned()), - ))?; + pub fn update(&mut self, index: usize, value: V) -> Result, StoreError> { + let Some(id) = self.inner.nth(index)? else { + return Ok(None); + }; - // has to be called to update the entry - entry.value = value; - entry.element_mut().update(); + let Some(mut entry) = self.inner.get_mut(id)? else { + return Ok(None); + }; - let _ = Interface::save::>(&mut entry)?; + let old = mem::replace(&mut *entry, value); - Ok(()) + Ok(Some(old)) } /// Get an iterator over the entries in the vector. @@ -170,10 +120,24 @@ impl Vector { /// [`Element`](crate::entities::Element) cannot be found, an error will be /// returned. /// - pub fn entries(&self) -> Result, StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; + pub fn entries(&self) -> Result + '_, StoreError> { + Ok(self.inner.entries()?.flatten().fuse()) + } + + /// Get the last value in the vector. + /// + /// # Errors + /// + /// If an error occurs when interacting with the storage system, or a child + /// [`Element`](crate::entities::Element) cannot be found, an error will be + /// returned. + /// + pub fn last(&self) -> Result, StoreError> { + let Some(last) = self.inner.last()? else { + return Ok(None); + }; - Ok(entries.into_iter().map(|entry| (entry.value))) + self.inner.get(last) } /// Get the number of entries in the vector. @@ -186,7 +150,7 @@ impl Vector { /// #[expect(clippy::len_without_is_empty, reason = "TODO: will be implemented")] pub fn len(&self) -> Result { - Ok(Interface::child_info_for(self.id(), &self.entries)?.len()) + self.inner.len() } /// Get the value for a key in the vector. @@ -200,10 +164,10 @@ impl Vector { pub fn contains(&self, value: &Q) -> Result where V: Borrow, - Q: PartialEq + ?Sized, + Q: PartialEq, { for entry in self.entries()? { - if value.borrow() == &entry { + if value == entry.borrow() { return Ok(true); } } @@ -220,30 +184,105 @@ impl Vector { /// returned. /// pub fn clear(&mut self) -> Result<(), StoreError> { - let entries = Interface::children_of(self.id(), &self.entries)?; + self.inner.clear() + } +} + +impl Eq for Vector where V: Eq + BorshSerialize + BorshDeserialize {} + +impl PartialEq for Vector +where + V: PartialEq + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn eq(&self, other: &Self) -> bool { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); + + l.eq(r) + } +} + +impl Ord for Vector +where + V: Ord + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, reason = "'tis fine")] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let l = self.entries().unwrap(); + let r = other.entries().unwrap(); - for entry in entries { - let _ = Interface::remove_child_from(self.id(), &mut self.entries, entry.id())?; + l.cmp(r) + } +} + +impl PartialOrd for Vector +where + V: PartialOrd + BorshSerialize + BorshDeserialize, +{ + fn partial_cmp(&self, other: &Self) -> Option { + let l = self.entries().ok()?; + let r = other.entries().ok()?; + + l.partial_cmp(r) + } +} + +impl fmt::Debug for Vector +where + V: fmt::Debug + BorshSerialize + BorshDeserialize, +{ + #[expect(clippy::unwrap_used, clippy::unwrap_in_result, reason = "'tis fine")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + f.debug_struct("Vector") + .field("entries", &self.inner) + .finish() + } else { + f.debug_list().entries(self.entries().unwrap()).finish() } + } +} - Ok(()) +impl Default for Vector +where + V: BorshSerialize + BorshDeserialize, + S: StorageAdaptor, +{ + fn default() -> Self { + Self::new_internal() } } -#[cfg(test)] -mod tests { - use crate::collections::error::StoreError; - use crate::collections::vector::Vector; +impl Serialize for Vector +where + V: BorshSerialize + BorshDeserialize + Serialize, + S: StorageAdaptor, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: serde::Serializer, + { + let len = self.len().map_err(serde::ser::Error::custom)?; - #[test] - fn test_vector_new() { - let vector: Result, StoreError> = Vector::new(); - assert!(vector.is_ok()); + let mut seq = serializer.serialize_seq(Some(len))?; + + for entry in self.entries().map_err(serde::ser::Error::custom)? { + seq.serialize_element(&entry)?; + } + + seq.end() } +} + +#[cfg(test)] +mod tests { + use crate::collections::{Root, Vector}; #[test] fn test_vector_push() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value = "test_data".to_string(); let result = vector.push(value.clone()); assert!(result.is_ok()); @@ -252,7 +291,8 @@ mod tests { #[test] fn test_vector_get() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); let retrieved_value = vector.get(0).unwrap(); @@ -261,18 +301,21 @@ mod tests { #[test] fn test_vector_update() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value1 = "test_data1".to_string(); let value2 = "test_data2".to_string(); let _ = vector.push(value1.clone()).unwrap(); - let _ = vector.update(0, value2.clone()).unwrap(); + let old = vector.update(0, value2.clone()).unwrap(); let retrieved_value = vector.get(0).unwrap(); assert_eq!(retrieved_value, Some(value2)); + assert_eq!(old, Some(value1)); } #[test] fn test_vector_get_non_existent() { - let vector: Vector = Vector::new().unwrap(); + let vector = Root::new(|| Vector::::new()); + match vector.get(0) { Ok(retrieved_value) => assert_eq!(retrieved_value, None), Err(e) => panic!("Error occurred: {:?}", e), @@ -281,7 +324,8 @@ mod tests { #[test] fn test_vector_pop() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); let popped_value = vector.pop().unwrap(); @@ -291,7 +335,8 @@ mod tests { #[test] fn test_vector_entries() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value1 = "test_data1".to_string(); let value2 = "test_data2".to_string(); let _ = vector.push(value1.clone()).unwrap(); @@ -302,7 +347,8 @@ mod tests { #[test] fn test_vector_contains() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); assert!(vector.contains(&value).unwrap()); @@ -312,7 +358,8 @@ mod tests { #[test] fn test_vector_clear() { - let mut vector: Vector = Vector::new().unwrap(); + let mut vector = Root::new(|| Vector::new()); + let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); vector.clear().unwrap(); diff --git a/crates/storage/src/entities.rs b/crates/storage/src/entities.rs index 5de099efa..78e047ad0 100644 --- a/crates/storage/src/entities.rs +++ b/crates/storage/src/entities.rs @@ -214,13 +214,12 @@ mod tests; use core::fmt::{self, Debug, Display, Formatter}; use std::collections::BTreeMap; +use std::ops::{Deref, DerefMut}; use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; use crate::address::{Id, Path}; use crate::env::time_now; -use crate::interface::StorageError; /// Represents an atomic unit in the storage system. /// @@ -233,10 +232,11 @@ use crate::interface::StorageError; /// # Examples /// /// ``` +/// use borsh::{BorshSerialize, BorshDeserialize}; /// use calimero_storage::entities::Element; /// use calimero_storage_macros::AtomicUnit; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// #[type_id(43)] /// struct Page { /// title: String, @@ -258,10 +258,11 @@ pub trait AtomicUnit: Data {} /// # Examples /// /// ``` +/// use borsh::{BorshSerialize, BorshDeserialize}; /// use calimero_storage_macros::{AtomicUnit, Collection}; /// use calimero_storage::entities::{ChildInfo, Data, Element}; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// #[type_id(42)] /// struct Book { /// title: String, @@ -274,7 +275,7 @@ pub trait AtomicUnit: Data {} /// #[children(Page)] /// struct Pages; /// -/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd)] +/// #[derive(AtomicUnit, Clone, Debug, Eq, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] /// #[type_id(43)] /// struct Page { /// content: String, @@ -314,56 +315,6 @@ pub trait Collection { /// contentious methods are included, to keep the interface simple and focused. /// pub trait Data: BorshDeserialize + BorshSerialize { - /// Calculates the Merkle hash of the [`Element`]. - /// - /// This method calculates the Merkle hash of the [`Data`] for the - /// [`Element`], which should be based on any regular fields, but ignore - /// skipped fields, private fields, collections, and the storage field (but - /// include the metadata). - /// - /// **IMPORTANT NOTE**: Collection fields do need to be included in the hash - /// calculation, but that is a job for the caller to combine, and this - /// method therefore only calculates the hash of available data (as hashing - /// the children would involve recursive lookups). - /// - /// # Errors - /// - /// This method will return an error if there is a problem calculating the - /// hash. - /// - /// # See also - /// - /// * [`calculate_merkle_hash_for_child()`](Data::calculate_merkle_hash_for_child()) - /// - fn calculate_merkle_hash(&self) -> Result<[u8; 32], StorageError>; - - /// Calculates the Merkle hash of a child of the [`Element`]. - /// - /// This method calculates the Merkle hash of the specified child of the - /// [`Element`]. - /// - /// # Parameters - /// - /// * `collection` - The name of the collection to calculate the hash for. - /// * `slice` - The slice of data to calculate the hash for. This will - /// get deserialised into the appropriate type, and the - /// hash will be calculated based on the data. - /// - /// # Errors - /// - /// This method will return an error if there is a problem calculating the - /// hash, or looking up children. - /// - /// # See also - /// - /// * [`calculate_merkle_hash()`](Data::calculate_merkle_hash()) - /// - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - slice: &[u8], - ) -> Result<[u8; 32], StorageError>; - /// Information about the [`Collection`]s present in the [`Data`]. /// /// This method allows details about the subtree structure and children to @@ -409,14 +360,6 @@ pub trait Data: BorshDeserialize + BorshSerialize { self.element().id() } - /// Whether the [`Element`] is a root. - /// - /// This should return `true` for any types that should sit at the top of - /// the hierarchy; and `false` for all other types, i.e. ones that can have - /// parents. - /// - fn is_root() -> bool; - /// The path to the [`Element`] in the hierarchy. /// /// This is a convenience function that passes through to @@ -430,19 +373,6 @@ pub trait Data: BorshDeserialize + BorshSerialize { fn path(&self) -> Path { self.element().path() } - - /// The type identifier of the entity. - /// - /// This is noted so that the entity can be deserialised correctly in the - /// absence of other semantic information. It is intended that the [`Path`] - /// will be used to help with this at some point, but at present paths are - /// not fully utilised. - /// - /// The value returned is arbitrary, and is up to the implementer to decide - /// what it should be. It is recommended that it be unique for each type of - /// entity. - /// - fn type_id() -> u8; } /// Summary information for the child of an [`Element`] in the storage. @@ -453,20 +383,7 @@ pub trait Data: BorshDeserialize + BorshSerialize { /// purpose is to make information such as the Merkle hash trivially available /// and prevent the need for repeated lookups. /// -#[derive( - BorshDeserialize, - BorshSerialize, - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] pub struct ChildInfo { /// The unique identifier for the child [`Element`]. @@ -476,13 +393,34 @@ pub struct ChildInfo { /// of the significant data in the "scope" of the child [`Element`], and is /// used to determine whether the data has changed and is valid. pub(crate) merkle_hash: [u8; 32], + + /// The metadata for the child [`Element`]. + pub(crate) metadata: Metadata, +} + +impl Ord for ChildInfo { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.created_at() + .cmp(&other.created_at()) + .then_with(|| self.id.cmp(&other.id)) + } +} + +impl PartialOrd for ChildInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } impl ChildInfo { /// Creates a new [`ChildInfo`]. #[must_use] - pub const fn new(id: Id, merkle_hash: [u8; 32]) -> Self { - Self { id, merkle_hash } + pub const fn new(id: Id, merkle_hash: [u8; 32], metadata: Metadata) -> Self { + Self { + id, + merkle_hash, + metadata, + } } /// The unique identifier for the child [`Element`]. @@ -502,6 +440,18 @@ impl ChildInfo { pub const fn merkle_hash(&self) -> [u8; 32] { self.merkle_hash } + + /// The timestamp when the child was created. + #[must_use] + pub const fn created_at(&self) -> u64 { + self.metadata.created_at + } + + /// The timestamp when the child was last updated. + #[must_use] + pub fn updated_at(&self) -> u64 { + *self.metadata.updated_at + } } impl Display for ChildInfo { @@ -594,14 +544,15 @@ impl Display for ChildInfo { /// key-value stores. Therefore, this approach and other similar patterns are /// not suitable for our use case. /// -#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[non_exhaustive] pub struct Element { /// The unique identifier for the [`Element`]. - id: Id, + pub(crate) id: Id, /// Whether the [`Element`] is dirty, i.e. has been modified since it was /// last saved. + #[borsh(skip)] pub(crate) is_dirty: bool, /// The Merkle hash of the [`Element`]. This is a cryptographic hash of the @@ -610,11 +561,13 @@ pub struct Element { /// hashing the substantive data in the [`Element`], along with the hashes /// of the children of the [`Element`], thereby representing the state of /// the entire hierarchy below the [`Element`]. + #[borsh(skip)] pub(crate) merkle_hash: [u8; 32], /// The metadata for the [`Element`]. This represents a range of /// system-managed properties that are used to process the [`Element`], but /// are not part of the primary data. + #[borsh(skip)] pub(crate) metadata: Metadata, /// The path to the [`Element`] in the hierarchy of the storage. @@ -660,7 +613,7 @@ impl Element { is_dirty: true, metadata: Metadata { created_at: timestamp, - updated_at: timestamp, + updated_at: timestamp.into(), }, merkle_hash: [0; 32], path: path.clone(), @@ -677,7 +630,7 @@ impl Element { is_dirty: true, metadata: Metadata { created_at: timestamp, - updated_at: timestamp, + updated_at: timestamp.into(), }, merkle_hash: [0; 32], #[expect(clippy::unwrap_used, reason = "This is expected to be valid")] @@ -780,13 +733,13 @@ impl Element { /// pub fn update(&mut self) { self.is_dirty = true; - self.metadata.updated_at = time_now(); + *self.metadata.updated_at = time_now(); } /// The timestamp when the [`Element`] was last updated. #[must_use] - pub const fn updated_at(&self) -> u64 { - self.metadata.updated_at + pub fn updated_at(&self) -> u64 { + *self.metadata.updated_at } } @@ -821,26 +774,50 @@ impl Display for Element { /// Using a [`u64`] timestamp allows for 585 years from the Unix epoch, at /// nanosecond precision. This is more than sufficient for our current needs. /// -#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive( + BorshDeserialize, BorshSerialize, Copy, Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, +)] #[non_exhaustive] pub struct Metadata { /// When the [`Element`] was first created. Note that this is a global /// creation time, and does not reflect the time that the [`Element`] was /// added to the local storage. - created_at: u64, + pub(crate) created_at: u64, /// When the [`Element`] was last updated. This is the time that the /// [`Element`] was last modified in any way, and is used to determine the /// freshness of the data. It is critical for the "last write wins" strategy /// that is used to resolve conflicts. - pub(crate) updated_at: u64, + pub(crate) updated_at: UpdatedAt, } -#[cfg(test)] -impl Metadata { - /// Sets the created timestamp of the [`Element`]. This is **ONLY** for use - /// in tests. - pub fn set_created_at(&mut self, created_at: u64) { - self.created_at = created_at; +/// The timestamp when the [`Element`] was last updated. +#[derive(BorshDeserialize, BorshSerialize, Copy, Clone, Debug, Default, Eq, Ord, PartialOrd)] +pub struct UpdatedAt(u64); + +impl PartialEq for UpdatedAt { + fn eq(&self, _other: &Self) -> bool { + // we don't care + true + } +} + +impl Deref for UpdatedAt { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for UpdatedAt { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for UpdatedAt { + fn from(value: u64) -> Self { + Self(value) } } diff --git a/crates/storage/src/env.rs b/crates/storage/src/env.rs index 44e74874f..1423e57a8 100644 --- a/crates/storage/src/env.rs +++ b/crates/storage/src/env.rs @@ -163,7 +163,7 @@ mod mocked { /// Return the context id. pub(super) const fn context_id() -> [u8; 32] { - [0; 32] + [236; 32] } /// Gets the current time. diff --git a/crates/storage/src/index.rs b/crates/storage/src/index.rs index ed8e431db..dae8a2d01 100644 --- a/crates/storage/src/index.rs +++ b/crates/storage/src/index.rs @@ -5,18 +5,18 @@ mod tests; use core::marker::PhantomData; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use borsh::{to_vec, BorshDeserialize, BorshSerialize}; use sha2::{Digest, Sha256}; use crate::address::Id; -use crate::entities::ChildInfo; +use crate::entities::{ChildInfo, Metadata, UpdatedAt}; use crate::interface::StorageError; use crate::store::{Key, StorageAdaptor}; /// Stored index information for an entity in the storage system. -#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] struct EntityIndex { /// Unique identifier of the entity. id: Id, @@ -35,11 +35,8 @@ struct EntityIndex { /// the hashes of its children to form the full hash. own_hash: [u8; 32], - /// Type identifier of the entity. This is noted so that the entity can be - /// deserialised correctly in the absence of other semantic information. It - /// is intended that the [`Path`](crate::address::Path) will be used to help - /// with this at some point, but at present paths are not fully utilised. - type_id: u8, + /// Metadata about the entity. + metadata: Metadata, } /// Manages the indexing system for efficient tree navigation. @@ -73,7 +70,6 @@ impl Index { parent_id: Id, collection: &str, child: ChildInfo, - type_id: u8, ) -> Result<(), StorageError> { let mut parent_index = Self::get_index(parent_id)?.ok_or(StorageError::IndexNotFound(parent_id))?; @@ -84,7 +80,7 @@ impl Index { children: BTreeMap::new(), full_hash: [0; 32], own_hash: [0; 32], - type_id, + metadata: child.metadata, }); child_index.parent_id = Some(parent_id); child_index.own_hash = child.merkle_hash(); @@ -92,11 +88,21 @@ impl Index { child_index.full_hash = Self::calculate_full_merkle_hash_for(child.id(), false)?; Self::save_index(&child_index)?; - parent_index + let children = parent_index .children .entry(collection.to_owned()) - .or_insert_with(Vec::new) - .push(ChildInfo::new(child.id(), child_index.full_hash)); + .or_insert_with(Vec::new); + + let mut ordered = children.drain(..).collect::>(); + + let _ignored = ordered.replace(ChildInfo::new( + child.id(), + child_index.full_hash, + child.metadata, + )); + + children.extend(ordered.into_iter()); + Self::save_index(&parent_index)?; parent_index.full_hash = Self::calculate_full_merkle_hash_for(parent_id, false)?; Self::save_index(&parent_index)?; @@ -123,14 +129,14 @@ impl Index { /// /// * [`add_child_to()`](Index::add_child_to()) /// - pub(crate) fn add_root(root: ChildInfo, type_id: u8) -> Result<(), StorageError> { + pub(crate) fn add_root(root: ChildInfo) -> Result<(), StorageError> { let mut index = Self::get_index(root.id())?.unwrap_or_else(|| EntityIndex { id: root.id(), parent_id: None, children: BTreeMap::new(), full_hash: [0; 32], own_hash: [0; 32], - type_id, + metadata: root.metadata, }); index.own_hash = root.merkle_hash(); Self::save_index(&index)?; @@ -231,13 +237,20 @@ impl Index { while let Some(parent_id) = Self::get_parent_id(current_id)? { let (parent_full_hash, _) = Self::get_hashes_for(parent_id)?.ok_or(StorageError::IndexNotFound(parent_id))?; - ancestors.push(ChildInfo::new(parent_id, parent_full_hash)); + let metadata = + Self::get_metadata(parent_id)?.ok_or(StorageError::IndexNotFound(parent_id))?; + ancestors.push(ChildInfo::new(parent_id, parent_full_hash, metadata)); current_id = parent_id; } Ok(ancestors) } + /// Retrieves the metadata of a given entity. + pub(crate) fn get_metadata(id: Id) -> Result, StorageError> { + Ok(Self::get_index(id)?.map(|index| index.metadata)) + } + /// Retrieves the children of a given entity. /// /// # Parameters @@ -277,9 +290,8 @@ impl Index { /// pub(crate) fn get_collection_names_for(parent_id: Id) -> Result, StorageError> { Ok(Self::get_index(parent_id)? - .ok_or(StorageError::IndexNotFound(parent_id))? - .children - .keys() + .iter() + .flat_map(|e| e.children.keys()) .cloned() .collect()) } @@ -341,23 +353,6 @@ impl Index { Ok(Self::get_index(child_id)?.and_then(|index| index.parent_id)) } - /// Retrieves the type of the given entity. - /// - /// # Parameters - /// - /// * `id` - The [`Id`] of the entity whose type is to be retrieved. - /// - /// # Errors - /// - /// If there's an issue retrieving or deserialising the index information, - /// an error will be returned. - /// - pub(crate) fn get_type_id(id: Id) -> Result { - Ok(Self::get_index(id)? - .ok_or(StorageError::IndexNotFound(id))? - .type_id) - } - /// Whether the collection has children. /// /// # Parameters @@ -404,7 +399,7 @@ impl Index { if let Some(child) = children.iter_mut().find(|c| c.id() == current_id) { let new_child_hash = Self::calculate_full_merkle_hash_for(current_id, false)?; if child.merkle_hash() != new_child_hash { - *child = ChildInfo::new(current_id, new_child_hash); + *child = ChildInfo::new(current_id, new_child_hash, child.metadata); } break; } @@ -508,12 +503,20 @@ impl Index { /// If there's an issue updating or saving the index, an error will be /// returned. /// - pub(crate) fn update_hash_for(id: Id, merkle_hash: [u8; 32]) -> Result<[u8; 32], StorageError> { + pub(crate) fn update_hash_for( + id: Id, + merkle_hash: [u8; 32], + updated_at: Option, + ) -> Result<[u8; 32], StorageError> { let mut index = Self::get_index(id)?.ok_or(StorageError::IndexNotFound(id))?; index.own_hash = merkle_hash; Self::save_index(&index)?; index.full_hash = Self::calculate_full_merkle_hash_for(id, false)?; + if let Some(updated_at) = updated_at { + index.metadata.updated_at = updated_at; + } Self::save_index(&index)?; + >::recalculate_ancestor_hashes_for(id)?; Ok(index.full_hash) } } diff --git a/crates/storage/src/integration.rs b/crates/storage/src/integration.rs index 0baea1231..cd4818224 100644 --- a/crates/storage/src/integration.rs +++ b/crates/storage/src/integration.rs @@ -1,29 +1,13 @@ //! Types used for integration with the runtime. use borsh::{BorshDeserialize, BorshSerialize}; -use calimero_sdk::serde::{Deserialize, Serialize}; use crate::interface::ComparisonData; /// Comparison data for synchronisation. -#[derive( - BorshDeserialize, - BorshSerialize, - Clone, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[expect(clippy::exhaustive_structs, reason = "Exhaustive")] pub struct Comparison { - /// The type of the entity. - pub type_id: u8, - /// The serialised data of the entity. pub data: Option>, diff --git a/crates/storage/src/interface.rs b/crates/storage/src/interface.rs index cf3f34d13..295668171 100644 --- a/crates/storage/src/interface.rs +++ b/crates/storage/src/interface.rs @@ -218,20 +218,21 @@ use core::marker::PhantomData; use std::collections::BTreeMap; use std::io::Error as IoError; -use borsh::{to_vec, BorshDeserialize, BorshSerialize}; +use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize}; use eyre::Report; use indexmap::IndexMap; -use serde::{Deserialize, Serialize}; +use serde::Serialize; +use sha2::{Digest, Sha256}; use thiserror::Error as ThisError; use crate::address::{Id, Path}; -use crate::entities::{ChildInfo, Collection, Data}; +use crate::entities::{ChildInfo, Collection, Data, Metadata}; use crate::index::Index; use crate::store::{Key, MainStorage, StorageAdaptor}; use crate::sync; /// Convenient type alias for the main storage system. -pub type Interface = MainInterface; +pub type MainInterface = Interface; /// Actions to be taken during synchronisation. /// @@ -262,19 +263,7 @@ pub type Interface = MainInterface; /// Note: This enum contains the entity type, for passing to the guest for /// processing along with the ID and data. /// -#[derive( - BorshDeserialize, - BorshSerialize, - Clone, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[expect(clippy::exhaustive_enums, reason = "Exhaustive")] pub enum Action { /// Add an entity with the given ID, type, and data. @@ -282,14 +271,14 @@ pub enum Action { /// Unique identifier of the entity. id: Id, - /// Type identifier of the entity. - type_id: u8, - /// Serialised data of the entity. data: Vec, /// Details of the ancestors of the entity. ancestors: Vec, + + /// The metadata of the entity. + metadata: Metadata, }, /// Compare the entity with the given ID and type. Note that this results in @@ -316,31 +305,19 @@ pub enum Action { /// Unique identifier of the entity. id: Id, - /// Type identifier of the entity. - type_id: u8, - /// Serialised data of the entity. data: Vec, /// Details of the ancestors of the entity. ancestors: Vec, + + /// The metadata of the entity. + metadata: Metadata, }, } /// Data that is used for comparison between two nodes. -#[derive( - BorshDeserialize, - BorshSerialize, - Clone, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct ComparisonData { /// The unique identifier of the entity being compared. id: Id, @@ -359,16 +336,17 @@ pub struct ComparisonData { /// The list of children of the entity, with their IDs and hashes, /// organised by collection name. children: BTreeMap>, + + /// The metadata of the entity. + metadata: Metadata, } /// The primary interface for the storage system. #[derive(Debug, Default, Clone)] -#[expect(private_bounds, reason = "The StorageAdaptor is for internal use only")] #[non_exhaustive] -pub struct MainInterface(PhantomData); +pub struct Interface(PhantomData); -#[expect(private_bounds, reason = "The StorageAdaptor is for internal use only")] -impl MainInterface { +impl Interface { /// Adds a child to a collection. /// /// # Parameters @@ -385,18 +363,31 @@ impl MainInterface { /// pub fn add_child_to( parent_id: Id, - collection: &mut C, + collection: &C, child: &mut D, ) -> Result { - let own_hash = child.calculate_merkle_hash()?; + if !child.element().is_dirty() { + return Ok(false); + } + + let data = to_vec(child).map_err(|e| StorageError::SerializationError(e.into()))?; + + let own_hash = Sha256::digest(&data).into(); + >::add_child_to( parent_id, collection.name(), - ChildInfo::new(child.id(), own_hash), - D::type_id(), + ChildInfo::new(child.id(), own_hash, child.element().metadata), )?; - child.element_mut().merkle_hash = >::update_hash_for(child.id(), own_hash)?; - Self::save(child) + + let Some(hash) = Self::save_raw(child.id(), data, child.element().metadata)? else { + return Ok(false); + }; + + child.element_mut().is_dirty = false; + child.element_mut().merkle_hash = hash; + + Ok(true) } /// Applies an [`Action`] to the storage system. @@ -427,36 +418,50 @@ impl MainInterface { /// If there is an error when deserialising into the specified type, or when /// applying the [`Action`], an error will be returned. /// - pub fn apply_action(action: Action) -> Result, StorageError> { - let ancestors = match action { + pub fn apply_action(action: Action) -> Result<(), StorageError> { + match action { Action::Add { - data, ancestors, .. + id, + data, + // todo! we only need parent_id + ancestors, + metadata, } | Action::Update { - data, ancestors, .. + id, + data, + ancestors, + metadata, } => { - let mut entity = - D::try_from_slice(&data).map_err(StorageError::DeserializationError)?; - _ = Self::save(&mut entity)?; - ancestors + if let Some(parent) = ancestors.first() { + let own_hash = Sha256::digest(&data).into(); + + >::add_child_to( + parent.id(), + "no collection, remove this nonsense", + ChildInfo::new(id, own_hash, metadata), + )?; + } + + if Self::save_internal(id, &data, metadata)?.is_none() { + // we didn't save anything, so we skip updating the ancestors + return Ok(()); + } + + sync::push_action(Action::Compare { id }); } Action::Compare { .. } => { return Err(StorageError::ActionNotAllowed("Compare".to_owned())) } - Action::Delete { id, ancestors, .. } => { - _ = S::storage_remove(Key::Entry(id)); - ancestors + Action::Delete { + id, ancestors: _, .. + } => { + // todo! remove_child_from here + let _ignored = S::storage_remove(Key::Entry(id)); } }; - for ancestor in &ancestors { - let (current_hash, _) = >::get_hashes_for(ancestor.id())? - .ok_or(StorageError::IndexNotFound(ancestor.id()))?; - if current_hash != ancestor.merkle_hash() { - return Ok(Some(ancestor.id())); - } - } - Ok(None) + Ok(()) } /// The children of the [`Collection`]. @@ -574,34 +579,34 @@ impl MainInterface { /// This function will return an error if there are issues accessing local /// data or if there are problems during the comparison process. /// - pub fn compare_trees( + pub fn compare_trees( foreign_entity_data: Option>, - foreign_index_data: &ComparisonData, + foreign_index_data: ComparisonData, ) -> Result<(Vec, Vec), StorageError> { let mut actions = (vec![], vec![]); - let foreign_entity = foreign_entity_data - .as_ref() - .map(|d| D::try_from_slice(d)) - .transpose() - .map_err(StorageError::DeserializationError)?; + let id = foreign_index_data.id; + + let local_metadata = >::get_metadata(id)?; - let Some(local_entity) = Self::find_by_id::(foreign_index_data.id)? else { + let Some(local_entity) = Self::find_by_id_raw(id) else { if let Some(foreign_entity) = foreign_entity_data { // Local entity doesn't exist, so we need to add it actions.0.push(Action::Add { - id: foreign_index_data.id, - type_id: D::type_id(), + id, data: foreign_entity, - ancestors: foreign_index_data.ancestors.clone(), + ancestors: foreign_index_data.ancestors, + metadata: foreign_index_data.metadata, }); } return Ok(actions); }; - let (local_full_hash, local_own_hash) = >::get_hashes_for(local_entity.id())? - .ok_or(StorageError::IndexNotFound(local_entity.id()))?; + let local_metadata = local_metadata.ok_or(StorageError::IndexNotFound(id))?; + + let (local_full_hash, local_own_hash) = + >::get_hashes_for(id)?.ok_or(StorageError::IndexNotFound(id))?; // Compare full Merkle hashes if local_full_hash == foreign_index_data.full_hash { @@ -610,24 +615,23 @@ impl MainInterface { // Compare own hashes and timestamps if local_own_hash != foreign_index_data.own_hash { - match (foreign_entity, foreign_entity_data) { - (Some(foreign_entity), Some(foreign_entity_data)) - if local_entity.element().updated_at() - <= foreign_entity.element().updated_at() => + match foreign_entity_data { + Some(foreign_entity_data) + if local_metadata.updated_at <= foreign_index_data.metadata.updated_at => { actions.0.push(Action::Update { - id: local_entity.id(), - type_id: D::type_id(), + id, data: foreign_entity_data, - ancestors: foreign_index_data.ancestors.clone(), + ancestors: foreign_index_data.ancestors, + metadata: foreign_index_data.metadata, }); } _ => { actions.1.push(Action::Update { - id: foreign_index_data.id, - type_id: D::type_id(), - data: to_vec(&local_entity).map_err(StorageError::SerializationError)?, - ancestors: >::get_ancestors_of(local_entity.id())?, + id, + data: local_entity, + ancestors: >::get_ancestors_of(id)?, + metadata: local_metadata, }); } } @@ -635,7 +639,16 @@ impl MainInterface { // The list of collections from the type will be the same on both sides, as // the type is the same. - let local_collections = local_entity.collections(); + + let local_collection_names = >::get_collection_names_for(id)?; + + let local_collections = local_collection_names + .into_iter() + .map(|name| { + let children = >::get_children_of(id, &name)?; + Ok((name, children)) + }) + .collect::, StorageError>>()?; // Compare children for (local_coll_name, local_children) in &local_collections { @@ -649,19 +662,22 @@ impl MainInterface { .map(|child| (child.id(), child.merkle_hash())) .collect(); - for (id, local_hash) in &local_child_map { - match foreign_child_map.get(id) { + for (child_id, local_hash) in &local_child_map { + match foreign_child_map.get(child_id) { Some(foreign_hash) if local_hash != foreign_hash => { - actions.0.push(Action::Compare { id: *id }); - actions.1.push(Action::Compare { id: *id }); + actions.0.push(Action::Compare { id: *child_id }); + actions.1.push(Action::Compare { id: *child_id }); } None => { - if let Some(local_child) = Self::find_by_id_raw(*id)? { + if let Some(local_child) = Self::find_by_id_raw(*child_id) { + let metadata = >::get_metadata(*child_id)? + .ok_or(StorageError::IndexNotFound(*child_id))?; + actions.1.push(Action::Add { - id: *id, - type_id: >::get_type_id(*id)?, + id: *child_id, data: local_child, - ancestors: >::get_ancestors_of(local_entity.id())?, + ancestors: >::get_ancestors_of(id)?, + metadata, }); } } @@ -681,12 +697,15 @@ impl MainInterface { } else { // The entire collection is missing from the foreign entity for child in local_children { - if let Some(local_child) = Self::find_by_id_raw(child.id())? { + if let Some(local_child) = Self::find_by_id_raw(child.id()) { + let metadata = >::get_metadata(child.id())? + .ok_or(StorageError::IndexNotFound(child.id()))?; + actions.1.push(Action::Add { id: child.id(), - type_id: >::get_type_id(child.id())?, data: local_child, - ancestors: >::get_ancestors_of(local_entity.id())?, + ancestors: >::get_ancestors_of(child.id())?, + metadata, }); } } @@ -714,14 +733,18 @@ impl MainInterface { /// This function will return an error if there are issues accessing local /// data or if there are problems during the comparison process. /// - pub fn compare_affective( + pub fn compare_affective( data: Option>, comparison_data: ComparisonData, ) -> Result<(), StorageError> { - let (local, remote) = Interface::compare_trees::(data, &comparison_data)?; + let (local, remote) = >::compare_trees(data, comparison_data)?; for action in local { - let _ignored = Interface::apply_action::(action)?; + if let Action::Compare { .. } = &action { + continue; + } + + >::apply_action(action)?; } for action in remote { @@ -750,18 +773,21 @@ impl MainInterface { pub fn find_by_id(id: Id) -> Result, StorageError> { let value = S::storage_read(Key::Entry(id)); - match value { - Some(slice) => { - let mut entity = - D::try_from_slice(&slice).map_err(StorageError::DeserializationError)?; - // TODO: This is needed for now, as the field gets stored. Later we will - // TODO: implement a custom serialiser that will skip this field along with - // TODO: any others that should not be stored. - entity.element_mut().is_dirty = false; - Ok(Some(entity)) - } - None => Ok(None), - } + let Some(slice) = value else { + return Ok(None); + }; + + let mut item = from_slice::(&slice).map_err(StorageError::DeserializationError)?; + + let (full_hash, _) = + >::get_hashes_for(id)?.ok_or(StorageError::IndexNotFound(id))?; + + item.element_mut().merkle_hash = full_hash; + + item.element_mut().metadata = + >::get_metadata(id)?.ok_or(StorageError::IndexNotFound(id))?; + + Ok(Some(item)) } /// Finds an [`Element`](crate::entities::Element) by its unique identifier @@ -779,13 +805,8 @@ impl MainInterface { /// * `id` - The unique identifier of the [`Element`](crate::entities::Element) /// to find. /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, an error - /// will be returned. - /// - pub fn find_by_id_raw(id: Id) -> Result>, StorageError> { - Ok(S::storage_read(Key::Entry(id))) + pub fn find_by_id_raw(id: Id) -> Option> { + S::storage_read(Key::Entry(id)) } /// Finds one or more [`Element`](crate::entities::Element)s by path in the @@ -859,38 +880,32 @@ impl MainInterface { /// If an error occurs when interacting with the storage system, an error /// will be returned. /// - pub fn generate_comparison_data( - entity: Option<&D>, - ) -> Result { - let Some(entity) = entity else { - return Ok(ComparisonData { - id: Id::root(), - own_hash: [0; 32], - full_hash: [0; 32], - ancestors: Vec::new(), - children: BTreeMap::new(), - }); - }; + pub fn generate_comparison_data(id: Option) -> Result { + let id = id.unwrap_or_else(Id::root); - let (full_hash, own_hash) = >::get_hashes_for(entity.id())? - .ok_or(StorageError::IndexNotFound(entity.id()))?; + let (full_hash, own_hash) = >::get_hashes_for(id)?.unwrap_or_default(); - let ancestors = >::get_ancestors_of(entity.id())?; - let children = entity - .collections() - .into_keys() + let metadata = >::get_metadata(id)?.unwrap_or_default(); + + let ancestors = >::get_ancestors_of(id)?; + + let collection_names = >::get_collection_names_for(id)?; + + let children = collection_names + .into_iter() .map(|collection_name| { - >::get_children_of(entity.id(), &collection_name) + >::get_children_of(id, &collection_name) .map(|children| (collection_name.clone(), children)) }) .collect::, _>>()?; Ok(ComparisonData { - id: entity.id(), + id, own_hash, full_hash, ancestors, children, + metadata, }) } @@ -950,7 +965,7 @@ impl MainInterface { /// pub fn remove_child_from( parent_id: Id, - collection: &mut C, + collection: &C, child_id: Id, ) -> Result { let child_exists = >::get_children_of(parent_id, collection.name())? @@ -965,7 +980,9 @@ impl MainInterface { let (parent_full_hash, _) = >::get_hashes_for(parent_id)?.ok_or(StorageError::IndexNotFound(parent_id))?; let mut ancestors = >::get_ancestors_of(parent_id)?; - ancestors.insert(0, ChildInfo::new(parent_id, parent_full_hash)); + let metadata = + >::get_metadata(parent_id)?.ok_or(StorageError::IndexNotFound(parent_id))?; + ancestors.insert(0, ChildInfo::new(parent_id, parent_full_hash, metadata)); _ = S::storage_remove(Key::Entry(child_id)); @@ -1002,19 +1019,28 @@ impl MainInterface { /// This function will return an error if there are issues accessing local /// data or if there are problems during the comparison process. /// - pub fn commit_root(mut root: D) -> Result<(), StorageError> { - if root.id() != Id::root() { - return Err(StorageError::UnexpectedId(root.id())); - } + pub fn commit_root(root: Option) -> Result<(), StorageError> { + let id: Id = Id::root(); - // fixme! mutations (action application) doesn't propagate to the root - // fixme! so, a best-attempt approach is to force a save on the root - // fixme! deeply nested entries have undefined behaviour - root.element_mut().is_dirty = true; + let hash = if let Some(root) = root { + if root.id() != id { + return Err(StorageError::UnexpectedId(root.id())); + } - let _ = Self::save(&mut root)?; + if !root.element().is_dirty() { + return Ok(()); + } - sync::commit_root(&root.element().merkle_hash())?; + let data = to_vec(&root).map_err(|e| StorageError::SerializationError(e.into()))?; + + Self::save_raw(id, data, root.element().metadata)? + } else { + >::get_hashes_for(id)?.map(|(full_hash, _)| full_hash) + }; + + if let Some(hash) = hash { + sync::commit_root(&hash)?; + } Ok(()) } @@ -1083,78 +1109,95 @@ impl MainInterface { /// pub fn save(entity: &mut D) -> Result { if !entity.element().is_dirty() { - return Ok(true); + return Ok(false); } - let id = entity.id(); - if !D::is_root() && >::get_parent_id(id)?.is_none() { - return Err(StorageError::CannotCreateOrphan(id)); - } + let data = to_vec(entity).map_err(|e| StorageError::SerializationError(e.into()))?; - let is_new = Self::find_by_id::(id)?.is_none(); - if !is_new { - if let Some(existing) = Self::find_by_id::(id)? { - if existing.element().metadata.updated_at >= entity.element().metadata.updated_at { - return Ok(false); - } + let Some(hash) = Self::save_raw(entity.id(), data, entity.element().metadata)? else { + return Ok(false); + }; + + entity.element_mut().is_dirty = false; + entity.element_mut().merkle_hash = hash; + + Ok(true) + } + + /// Saves raw data to the storage system. + /// + /// # Errors + /// + /// If an error occurs when serialising data or interacting with the storage + /// system, an error will be returned. + /// + fn save_internal( + id: Id, + data: &[u8], + metadata: Metadata, + ) -> Result, StorageError> { + let last_metadata = >::get_metadata(id)?; + + if let Some(last_metadata) = &last_metadata { + if last_metadata.updated_at > metadata.updated_at { + return Ok(None); } - } else if D::is_root() { - >::add_root(ChildInfo::new(id, [0_u8; 32]), D::type_id())?; + } else if id.is_root() { + >::add_root(ChildInfo::new(id, [0_u8; 32], metadata))?; } - let own_hash = entity.calculate_merkle_hash()?; - entity.element_mut().merkle_hash = >::update_hash_for(id, own_hash)?; + let own_hash = Sha256::digest(data).into(); - _ = S::storage_write( - Key::Entry(id), - &to_vec(entity).map_err(StorageError::SerializationError)?, - ); + let full_hash = >::update_hash_for(id, own_hash, Some(metadata.updated_at))?; - entity.element_mut().is_dirty = false; + _ = S::storage_write(Key::Entry(id), data); + + let is_new = metadata.created_at == *metadata.updated_at; + + Ok(Some((is_new, full_hash))) + } + + /// Saves raw data to the storage system. + /// + /// # Errors + /// + /// If an error occurs when serialising data or interacting with the storage + /// system, an error will be returned. + /// + pub fn save_raw( + id: Id, + data: Vec, + metadata: Metadata, + ) -> Result, StorageError> { + if !id.is_root() && >::get_parent_id(id)?.is_none() { + return Err(StorageError::CannotCreateOrphan(id)); + } + + let Some((is_new, full_hash)) = Self::save_internal(id, &data, metadata)? else { + return Ok(None); + }; + + let ancestors = >::get_ancestors_of(id)?; let action = if is_new { Action::Add { id, - type_id: D::type_id(), - data: to_vec(entity).map_err(StorageError::SerializationError)?, - ancestors: >::get_ancestors_of(id)?, + data, + ancestors, + metadata, } } else { Action::Update { id, - type_id: D::type_id(), - data: to_vec(entity).map_err(StorageError::SerializationError)?, - ancestors: >::get_ancestors_of(id)?, + data, + ancestors, + metadata, } }; sync::push_action(action); - Ok(true) - } - - /// Type identifier of the entity. - /// - /// This is noted so that the entity can be deserialised correctly in the - /// absence of other semantic information. It is intended that the [`Path`] - /// will be used to help with this at some point, but at present paths are - /// not fully utilised. - /// - /// The value returned is arbitrary, and is up to the implementer to decide - /// what it should be. It is recommended that it be unique for each type of - /// entity. - /// - /// # Parameters - /// - /// * `id` - The [`Id`] of the entity whose type is to be retrieved. - /// - /// # Errors - /// - /// If an error occurs when interacting with the storage system, an error - /// will be returned. - /// - pub fn type_of(id: Id) -> Result { - >::get_type_id(id) + Ok(Some(full_hash)) } /// Validates the stored state. diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index bcbed3a05..14a8e4bcb 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -4,16 +4,11 @@ //! system, as a layer on top of the underlying database store. //! -#![forbid( - unreachable_pub, - unsafe_code, - unsafe_op_in_unsafe_fn, - clippy::missing_docs_in_private_items -)] +#![forbid(unreachable_pub, unsafe_op_in_unsafe_fn)] #![deny( + unsafe_code, clippy::expect_used, clippy::missing_errors_doc, - clippy::missing_panics_doc, clippy::panic, clippy::unwrap_in_result, clippy::unwrap_used diff --git a/crates/storage/src/store.rs b/crates/storage/src/store.rs index bcc839472..e54ed5149 100644 --- a/crates/storage/src/store.rs +++ b/crates/storage/src/store.rs @@ -41,7 +41,7 @@ impl Key { /// used for key operations during testing, such as modelling a foreign node's /// data store. /// -pub(crate) trait StorageAdaptor { +pub trait StorageAdaptor { /// Reads data from persistent storage. /// /// # Parameters diff --git a/crates/storage/src/tests/common.rs b/crates/storage/src/tests/common.rs index 41bd46cb2..3d5adc46d 100644 --- a/crates/storage/src/tests/common.rs +++ b/crates/storage/src/tests/common.rs @@ -1,11 +1,10 @@ use std::collections::BTreeMap; -use borsh::{to_vec, BorshDeserialize, BorshSerialize}; -use sha2::{Digest, Sha256}; +use borsh::{BorshDeserialize, BorshSerialize}; use velcro::btree_map; use crate::entities::{AtomicUnit, ChildInfo, Collection, Data, Element}; -use crate::interface::{Interface, StorageError}; +use crate::interface::MainInterface; /// For tests against empty data structs. #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq, PartialOrd)] @@ -14,21 +13,6 @@ pub struct EmptyData { } impl Data for EmptyData { - fn calculate_merkle_hash(&self) -> Result<[u8; 32], StorageError> { - let mut hasher = Sha256::new(); - hasher.update(self.element().id().as_bytes()); - hasher.update(&to_vec(&self.element().metadata).map_err(StorageError::SerializationError)?); - Ok(hasher.finalize().into()) - } - - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - _slice: &[u8], - ) -> Result<[u8; 32], StorageError> { - Err(StorageError::UnknownCollectionType(collection.to_owned())) - } - fn collections(&self) -> BTreeMap> { BTreeMap::new() } @@ -40,14 +24,6 @@ impl Data for EmptyData { fn element_mut(&mut self) -> &mut Element { &mut self.storage } - - fn is_root() -> bool { - true - } - - fn type_id() -> u8 { - 101 - } } /// A simple page with a title, and paragraphs as children. @@ -72,32 +48,9 @@ impl Page { impl AtomicUnit for Page {} impl Data for Page { - fn calculate_merkle_hash(&self) -> Result<[u8; 32], StorageError> { - let mut hasher = Sha256::new(); - hasher.update(self.element().id().as_bytes()); - hasher.update(&to_vec(&self.title).map_err(StorageError::SerializationError)?); - hasher.update(&to_vec(&self.element().metadata).map_err(StorageError::SerializationError)?); - Ok(hasher.finalize().into()) - } - - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - slice: &[u8], - ) -> Result<[u8; 32], StorageError> { - match collection { - "Paragraphs" => { - let child = ::Child::try_from_slice(slice) - .map_err(|e| StorageError::DeserializationError(e))?; - child.calculate_merkle_hash() - } - _ => Err(StorageError::UnknownCollectionType(collection.to_owned())), - } - } - fn collections(&self) -> BTreeMap> { btree_map! { - "Paragraphs".to_owned(): Interface::child_info_for(self.id(), &self.paragraphs).unwrap_or_default(), + "Paragraphs".to_owned(): MainInterface::child_info_for(self.id(), &self.paragraphs).unwrap_or_default(), } } @@ -108,14 +61,6 @@ impl Data for Page { fn element_mut(&mut self) -> &mut Element { &mut self.storage } - - fn is_root() -> bool { - true - } - - fn type_id() -> u8 { - 102 - } } /// A simple paragraph with text. No children. Belongs to a page. @@ -138,22 +83,6 @@ impl Paragraph { impl AtomicUnit for Paragraph {} impl Data for Paragraph { - fn calculate_merkle_hash(&self) -> Result<[u8; 32], StorageError> { - let mut hasher = Sha256::new(); - hasher.update(self.element().id().as_bytes()); - hasher.update(&to_vec(&self.text).map_err(StorageError::SerializationError)?); - hasher.update(&to_vec(&self.element().metadata).map_err(StorageError::SerializationError)?); - Ok(hasher.finalize().into()) - } - - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - _slice: &[u8], - ) -> Result<[u8; 32], StorageError> { - Err(StorageError::UnknownCollectionType(collection.to_owned())) - } - fn collections(&self) -> BTreeMap> { BTreeMap::new() } @@ -165,14 +94,6 @@ impl Data for Paragraph { fn element_mut(&mut self) -> &mut Element { &mut self.storage } - - fn is_root() -> bool { - false - } - - fn type_id() -> u8 { - 103 - } } /// A collection of paragraphs for a page. @@ -203,23 +124,6 @@ pub struct Person { } impl Data for Person { - fn calculate_merkle_hash(&self) -> Result<[u8; 32], StorageError> { - let mut hasher = Sha256::new(); - hasher.update(self.element().id().as_bytes()); - hasher.update(&to_vec(&self.name).map_err(StorageError::SerializationError)?); - hasher.update(&to_vec(&self.age).map_err(StorageError::SerializationError)?); - hasher.update(&to_vec(&self.element().metadata).map_err(StorageError::SerializationError)?); - Ok(hasher.finalize().into()) - } - - fn calculate_merkle_hash_for_child( - &self, - collection: &str, - _slice: &[u8], - ) -> Result<[u8; 32], StorageError> { - Err(StorageError::UnknownCollectionType(collection.to_owned())) - } - fn collections(&self) -> BTreeMap> { BTreeMap::new() } @@ -231,12 +135,4 @@ impl Data for Person { fn element_mut(&mut self) -> &mut Element { &mut self.storage } - - fn is_root() -> bool { - true - } - - fn type_id() -> u8 { - 104 - } } diff --git a/crates/storage/src/tests/entities.rs b/crates/storage/src/tests/entities.rs index 6db091247..63d094364 100644 --- a/crates/storage/src/tests/entities.rs +++ b/crates/storage/src/tests/entities.rs @@ -1,14 +1,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use borsh::to_vec; use claims::{assert_ge, assert_le}; use sha2::{Digest, Sha256}; use velcro::btree_map; use super::*; -use crate::index::Index; -use crate::interface::Interface; -use crate::store::MainStorage; +use crate::interface::MainInterface; use crate::tests::common::{Page, Paragraph, Paragraphs, Person}; #[cfg(test)] @@ -26,81 +23,6 @@ mod collection__public_methods { mod data__public_methods { use super::*; - #[test] - fn calculate_merkle_hash() { - let element = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let person = Person { - name: "Alice".to_owned(), - age: 30, - storage: element.clone(), - }; - - let mut hasher = Sha256::new(); - hasher.update(person.id().as_bytes()); - hasher.update(&to_vec(&person.name).unwrap()); - hasher.update(&to_vec(&person.age).unwrap()); - hasher.update(&to_vec(&person.element().metadata).unwrap()); - let expected_hash: [u8; 32] = hasher.finalize().into(); - - assert_eq!(person.calculate_merkle_hash().unwrap(), expected_hash); - } - - #[test] - fn calculate_merkle_hash_for_child__valid() { - let parent = Element::new(&Path::new("::root::node").unwrap(), None); - let mut page = Page::new_from_element("Node", parent); - let child1 = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let mut para1 = Paragraph::new_from_element("Leaf1", child1); - assert!(Interface::save(&mut page).unwrap()); - - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para1).unwrap()); - let para1_slice = to_vec(¶1).unwrap(); - let para1_hash = page - .calculate_merkle_hash_for_child("Paragraphs", ¶1_slice) - .unwrap(); - let expected_hash1 = para1.calculate_merkle_hash().unwrap(); - assert_eq!(para1_hash, expected_hash1); - - let child2 = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let para2 = Paragraph::new_from_element("Leaf2", child2); - let para2_slice = to_vec(¶2).unwrap(); - let para2_hash = page - .calculate_merkle_hash_for_child("Paragraphs", ¶2_slice) - .unwrap(); - assert_ne!(para2_hash, para1_hash); - } - - #[test] - fn calculate_merkle_hash_for_child__invalid() { - let parent = Element::new(&Path::new("::root::node").unwrap(), None); - let mut page = Page::new_from_element("Node", parent); - let child1 = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let mut para1 = Paragraph::new_from_element("Leaf1", child1); - assert!(Interface::save(&mut page).unwrap()); - - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para1).unwrap()); - let invalid_slice = &[0, 1, 2, 3]; - let result = page.calculate_merkle_hash_for_child("Paragraphs", invalid_slice); - assert!(matches!(result, Err(StorageError::DeserializationError(_)))); - } - - #[test] - fn calculate_merkle_hash_for_child__unknown_collection() { - let parent = Element::new(&Path::new("::root::node").unwrap(), None); - let mut page = Page::new_from_element("Node", parent); - let child = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let mut para = Paragraph::new_from_element("Leaf", child); - assert!(Interface::save(&mut page).unwrap()); - - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para).unwrap()); - let para_slice = to_vec(¶).unwrap(); - let result = page.calculate_merkle_hash_for_child("unknown_collection", ¶_slice); - assert!(matches!( - result, - Err(StorageError::UnknownCollectionType(_)) - )); - } - #[test] fn collections() { let parent = Element::new(&Path::new("::root::node").unwrap(), None); @@ -108,7 +30,7 @@ mod data__public_methods { assert_eq!( page.collections(), btree_map! { - "Paragraphs".to_owned(): Interface::child_info_for(page.id(), &page.paragraphs).unwrap_or_default(), + "Paragraphs".to_owned(): MainInterface::child_info_for(page.id(), &page.paragraphs).unwrap_or_default(), } ); @@ -179,7 +101,7 @@ mod child_info__constructor { fn new() { let id = Id::random(); let hash = Sha256::digest(b"1").into(); - let info = ChildInfo::new(id, hash); + let info = ChildInfo::new(id, hash, Metadata::default()); assert_eq!(info.id, id); assert_eq!(info.merkle_hash, hash); } @@ -191,13 +113,21 @@ mod child_info__public_methods { #[test] fn id() { - let info = ChildInfo::new(Id::random(), Sha256::digest(b"1").into()); + let info = ChildInfo::new( + Id::random(), + Sha256::digest(b"1").into(), + Metadata::default(), + ); assert_eq!(info.id(), info.id); } #[test] fn merkle_hash() { - let info = ChildInfo::new(Id::random(), Sha256::digest(b"1").into()); + let info = ChildInfo::new( + Id::random(), + Sha256::digest(b"1").into(), + Metadata::default(), + ); assert_eq!(info.merkle_hash(), info.merkle_hash); } } @@ -208,7 +138,11 @@ mod child_info__traits { #[test] fn display() { - let info = ChildInfo::new(Id::random(), Sha256::digest(b"1").into()); + let info = ChildInfo::new( + Id::random(), + Sha256::digest(b"1").into(), + Metadata::default(), + ); assert_eq!( format!("{info}"), format!( @@ -247,8 +181,8 @@ mod element__constructor { assert_eq!(element.path, path); assert_ge!(element.metadata.created_at, timestamp1); assert_le!(element.metadata.created_at, timestamp2); - assert_ge!(element.metadata.updated_at, timestamp1); - assert_le!(element.metadata.updated_at, timestamp2); + assert_ge!(*element.metadata.updated_at, timestamp1); + assert_le!(*element.metadata.updated_at, timestamp2); assert!(element.is_dirty); } } @@ -281,7 +215,7 @@ mod element__public_methods { #[test] fn is_dirty() { - let element = Element::new(&Path::new("::root::node::leaf").unwrap(), None); + let element = Element::root(); assert!(element.is_dirty()); let mut person = Person { @@ -289,29 +223,13 @@ mod element__public_methods { age: 30, storage: element, }; - assert!(Interface::save(&mut person).unwrap()); + assert!(MainInterface::save(&mut person).unwrap()); assert!(!person.element().is_dirty()); person.element_mut().update(); assert!(person.element().is_dirty()); } - #[test] - fn merkle_hash() { - let element = Element::new(&Path::new("::root::node::leaf").unwrap(), None); - let mut person = Person { - name: "Steve".to_owned(), - age: 50, - storage: element.clone(), - }; - assert_eq!(person.element().merkle_hash(), [0_u8; 32]); - - assert!(Interface::save(&mut person).unwrap()); - let expected_hash = - >::calculate_full_merkle_hash_for(person.id(), false).unwrap(); - assert_eq!(person.element().merkle_hash(), expected_hash); - } - #[test] #[ignore] fn metadata() { @@ -327,14 +245,14 @@ mod element__public_methods { #[test] fn update() { - let element = Element::new(&Path::new("::root::node::leaf").unwrap(), None); + let element = Element::root(); let updated_at = element.metadata.updated_at; let mut person = Person { name: "Bob".to_owned(), age: 40, storage: element, }; - assert!(Interface::save(&mut person).unwrap()); + assert!(MainInterface::save(&mut person).unwrap()); assert!(!person.element().is_dirty); person.element_mut().update(); diff --git a/crates/storage/src/tests/index.rs b/crates/storage/src/tests/index.rs index 4c4d25995..92b9292b8 100644 --- a/crates/storage/src/tests/index.rs +++ b/crates/storage/src/tests/index.rs @@ -9,7 +9,12 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); @@ -29,8 +34,7 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child_id, child_own_hash), - 2, + ChildInfo::new(child_id, child_own_hash, Metadata::default()), ) .is_ok()); @@ -41,7 +45,7 @@ mod index__public_methods { assert_eq!(updated_root_index.children.len(), 1); assert_eq!( updated_root_index.children[collection_name][0], - ChildInfo::new(child_id, child_full_hash) + ChildInfo::new(child_id, child_full_hash, Metadata::default()) ); let child_index = >::get_index(child_id).unwrap().unwrap(); @@ -56,7 +60,12 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); @@ -73,35 +82,41 @@ mod index__public_methods { let grandchild_collection_name = "Pages"; let greatgrandchild_collection_name = "Paragraphs"; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let child_id = Id::random(); let child_hash = [2_u8; 32]; - let child_info = ChildInfo::new(child_id, child_hash); + let child_info = ChildInfo::new(child_id, child_hash, Metadata::default()); assert!( - >::add_child_to(root_id, child_collection_name, child_info, 2) - .is_ok() + >::add_child_to(root_id, child_collection_name, child_info).is_ok() ); let grandchild_id = Id::random(); let grandchild_hash = [3_u8; 32]; - let grandchild_info = ChildInfo::new(grandchild_id, grandchild_hash); + let grandchild_info = ChildInfo::new(grandchild_id, grandchild_hash, Metadata::default()); assert!(>::add_child_to( child_id, grandchild_collection_name, grandchild_info, - 3, ) .is_ok()); let greatgrandchild_id = Id::random(); let greatgrandchild_hash = [4_u8; 32]; - let greatgrandchild_info = ChildInfo::new(greatgrandchild_id, greatgrandchild_hash); + let greatgrandchild_info = ChildInfo::new( + greatgrandchild_id, + greatgrandchild_hash, + Metadata::default(), + ); assert!(>::add_child_to( grandchild_id, greatgrandchild_collection_name, greatgrandchild_info, - 4, ) .is_ok()); @@ -114,7 +129,8 @@ mod index__public_methods { >::get_hashes_for(grandchild_id) .unwrap() .unwrap() - .0 + .0, + Metadata::default() ) ); assert_eq!( @@ -124,7 +140,8 @@ mod index__public_methods { >::get_hashes_for(child_id) .unwrap() .unwrap() - .0 + .0, + Metadata::default() ) ); assert_eq!( @@ -134,7 +151,8 @@ mod index__public_methods { >::get_hashes_for(root_id) .unwrap() .unwrap() - .0 + .0, + Metadata::default() ) ); } @@ -144,10 +162,15 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let collection_name = "Books"; - let child1_id = Id::random(); + let child1_id = Id::from([2; 32]); let child1_own_hash = [2_u8; 32]; let child1_full_hash: [u8; 32] = hex::decode("75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a") @@ -155,7 +178,7 @@ mod index__public_methods { .try_into() .unwrap(); - let child2_id = Id::random(); + let child2_id = Id::from([3; 32]); let child2_own_hash = [3_u8; 32]; let child2_full_hash: [u8; 32] = hex::decode("648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b") @@ -166,40 +189,49 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child1_id, child1_own_hash), - 2, + ChildInfo::new(child1_id, child1_own_hash, Metadata::default()), ) .is_ok()); assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child2_id, child2_own_hash), - 2, + ChildInfo::new(child2_id, child2_own_hash, Metadata::default()), ) .is_ok()); let children = >::get_children_of(root_id, collection_name).unwrap(); assert_eq!(children.len(), 2); - assert_eq!(children[0], ChildInfo::new(child1_id, child1_full_hash)); - assert_eq!(children[1], ChildInfo::new(child2_id, child2_full_hash)); + assert_eq!( + children[0], + ChildInfo::new(child1_id, child1_full_hash, Metadata::default()) + ); + assert_eq!( + children[1], + ChildInfo::new(child2_id, child2_full_hash, Metadata::default()) + ); } #[test] fn get_children_of__two_collections() { - let root_id = Id::random(); + let root_id = Id::from([1; 32]); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let collection1_name = "Pages"; - let child1_id = Id::random(); + let child1_id = Id::from([2; 32]); let child1_own_hash = [2_u8; 32]; let child1_full_hash: [u8; 32] = hex::decode("75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a") .unwrap() .try_into() .unwrap(); - let child2_id = Id::random(); + let child2_id = Id::from([3; 32]); let child2_own_hash = [3_u8; 32]; let child2_full_hash: [u8; 32] = hex::decode("648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b") @@ -208,7 +240,7 @@ mod index__public_methods { .unwrap(); let collection2_name = "Reviews"; - let child3_id = Id::random(); + let child3_id = Id::from([4; 32]); let child3_own_hash = [4_u8; 32]; let child3_full_hash: [u8; 32] = hex::decode("9f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed") @@ -219,32 +251,38 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection1_name, - ChildInfo::new(child1_id, child1_own_hash), - 2, + ChildInfo::new(child1_id, child1_own_hash, Metadata::default()), ) .is_ok()); assert!(>::add_child_to( root_id, collection1_name, - ChildInfo::new(child2_id, child2_own_hash), - 2, + ChildInfo::new(child2_id, child2_own_hash, Metadata::default()), ) .is_ok()); assert!(>::add_child_to( root_id, collection2_name, - ChildInfo::new(child3_id, child3_own_hash), - 2, + ChildInfo::new(child3_id, child3_own_hash, Metadata::default()), ) .is_ok()); let children1 = >::get_children_of(root_id, collection1_name).unwrap(); assert_eq!(children1.len(), 2); - assert_eq!(children1[0], ChildInfo::new(child1_id, child1_full_hash)); - assert_eq!(children1[1], ChildInfo::new(child2_id, child2_full_hash)); + assert_eq!( + children1[0], + ChildInfo::new(child1_id, child1_full_hash, Metadata::default()) + ); + assert_eq!( + children1[1], + ChildInfo::new(child2_id, child2_full_hash, Metadata::default()) + ); let children2 = >::get_children_of(root_id, collection2_name).unwrap(); assert_eq!(children2.len(), 1); - assert_eq!(children2[0], ChildInfo::new(child3_id, child3_full_hash)); + assert_eq!( + children2[0], + ChildInfo::new(child3_id, child3_full_hash, Metadata::default()) + ); } #[test] @@ -252,7 +290,12 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let collection1_name = "Pages"; let collection2_name = "Chapters"; @@ -266,15 +309,13 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection1_name, - ChildInfo::new(child1_id, child1_own_hash), - 2, + ChildInfo::new(child1_id, child1_own_hash, Metadata::default()), ) .is_ok()); assert!(>::add_child_to( root_id, collection2_name, - ChildInfo::new(child2_id, child2_own_hash), - 2, + ChildInfo::new(child2_id, child2_own_hash, Metadata::default()), ) .is_ok()); @@ -290,7 +331,12 @@ mod index__public_methods { let root_own_hash = [1_u8; 32]; let root_full_hash = [0_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_own_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_own_hash, + Metadata::default() + ),) + .is_ok()); assert_eq!( >::get_hashes_for(root_id) @@ -305,7 +351,12 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); @@ -320,8 +371,7 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child_id, child_own_hash), - 2, + ChildInfo::new(child_id, child_own_hash, Metadata::default()), ) .is_ok()); @@ -332,28 +382,18 @@ mod index__public_methods { assert_eq!(>::get_parent_id(root_id).unwrap(), None); } - #[test] - fn get_type_id() { - let root_id = Id::random(); - let root_hash = [1_u8; 32]; - - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 99).is_ok()); - - let root_index = >::get_index(root_id).unwrap().unwrap(); - assert_eq!(root_index.id, root_id); - assert_eq!(root_index.own_hash, root_hash); - assert_eq!(root_index.type_id, 99); - - assert_eq!(>::get_type_id(root_id).unwrap(), 99,); - } - #[test] fn has_children() { let root_id = Id::random(); let root_hash = [1_u8; 32]; let collection_name = "Books"; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); assert!(!>::has_children(root_id, collection_name).unwrap()); let child_id = Id::random(); @@ -362,8 +402,7 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child_id, child_own_hash), - 2, + ChildInfo::new(child_id, child_own_hash, Metadata::default()), ) .is_ok()); assert!(>::has_children(root_id, collection_name).unwrap()); @@ -374,7 +413,12 @@ mod index__public_methods { let root_id = Id::random(); let root_hash = [1_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); @@ -389,8 +433,7 @@ mod index__public_methods { assert!(>::add_child_to( root_id, collection_name, - ChildInfo::new(child_id, child_own_hash), - 2, + ChildInfo::new(child_id, child_own_hash, Metadata::default()), ) .is_ok()); assert!( @@ -419,7 +462,7 @@ mod index__private_methods { children: BTreeMap::new(), full_hash: hash1, own_hash: hash2, - type_id: 1, + metadata: Metadata::default(), }; >::save_index(&index).unwrap(); @@ -439,7 +482,7 @@ mod index__private_methods { children: BTreeMap::new(), full_hash: hash1, own_hash: hash2, - type_id: 1, + metadata: Metadata::default(), }; >::save_index(&index).unwrap(); assert_eq!(>::get_index(id).unwrap().unwrap(), index); @@ -455,28 +498,27 @@ mod hashing { #[test] fn calculate_full_merkle_hash_for__with_children() { - let root_id = Id::random(); - assert!(>::add_root(ChildInfo::new(root_id, [0_u8; 32]), 1).is_ok()); + let root_id = Id::from([0; 32]); + assert!(>::add_root(ChildInfo::new( + root_id, + [0_u8; 32], + Metadata::default() + ),) + .is_ok()); let collection_name = "Children"; - let child1_id = Id::random(); + let child1_id = Id::from([1; 32]); let child1_hash = [1_u8; 32]; - let child1_info = ChildInfo::new(child1_id, child1_hash); - assert!( - >::add_child_to(root_id, collection_name, child1_info, 2).is_ok() - ); - let child2_id = Id::random(); + let child1_info = ChildInfo::new(child1_id, child1_hash, Metadata::default()); + assert!(>::add_child_to(root_id, collection_name, child1_info).is_ok()); + let child2_id = Id::from([2; 32]); let child2_hash = [2_u8; 32]; - let child2_info = ChildInfo::new(child2_id, child2_hash); - assert!( - >::add_child_to(root_id, collection_name, child2_info, 2).is_ok() - ); - let child3_id = Id::random(); + let child2_info = ChildInfo::new(child2_id, child2_hash, Metadata::default()); + assert!(>::add_child_to(root_id, collection_name, child2_info).is_ok()); + let child3_id = Id::from([3; 32]); let child3_hash = [3_u8; 32]; - let child3_info = ChildInfo::new(child3_id, child3_hash); - assert!( - >::add_child_to(root_id, collection_name, child3_info, 2).is_ok() - ); + let child3_info = ChildInfo::new(child3_id, child3_hash, Metadata::default()); + assert!(>::add_child_to(root_id, collection_name, child3_info).is_ok()); assert_eq!( hex::encode( @@ -512,17 +554,21 @@ mod hashing { let grandchild_collection_name = "Pages"; let greatgrandchild_collection_name = "Paragraphs"; - assert!(>::add_root(ChildInfo::new(root_id, root_hash), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.full_hash, [0_u8; 32]); let child_id = Id::random(); let child_hash = [2_u8; 32]; - let child_info = ChildInfo::new(child_id, child_hash); + let child_info = ChildInfo::new(child_id, child_hash, Metadata::default()); assert!( - >::add_child_to(root_id, child_collection_name, child_info, 2) - .is_ok() + >::add_child_to(root_id, child_collection_name, child_info).is_ok() ); let root_index_with_child = >::get_index(root_id).unwrap().unwrap(); @@ -538,12 +584,11 @@ mod hashing { let grandchild_id = Id::random(); let grandchild_hash = [3_u8; 32]; - let grandchild_info = ChildInfo::new(grandchild_id, grandchild_hash); + let grandchild_info = ChildInfo::new(grandchild_id, grandchild_hash, Metadata::default()); assert!(>::add_child_to( child_id, grandchild_collection_name, grandchild_info, - 3, ) .is_ok()); @@ -568,12 +613,15 @@ mod hashing { let greatgrandchild_id = Id::random(); let greatgrandchild_hash = [4_u8; 32]; - let greatgrandchild_info = ChildInfo::new(greatgrandchild_id, greatgrandchild_hash); + let greatgrandchild_info = ChildInfo::new( + greatgrandchild_id, + greatgrandchild_hash, + Metadata::default(), + ); assert!(>::add_child_to( grandchild_id, greatgrandchild_collection_name, greatgrandchild_info, - 4, ) .is_ok()); @@ -691,13 +739,18 @@ mod hashing { .try_into() .unwrap(); - assert!(>::add_root(ChildInfo::new(root_id, root_hash1), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash1, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); assert_eq!(root_index.full_hash, root_hash0); - assert!(>::update_hash_for(root_id, root_hash2).is_ok()); + assert!(>::update_hash_for(root_id, root_hash2, None).is_ok()); let updated_root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(updated_root_index.id, root_id); assert_eq!(updated_root_index.full_hash, root_full_hash); @@ -709,13 +762,18 @@ mod hashing { let root_hash1 = [1_u8; 32]; let root_hash2 = [2_u8; 32]; - assert!(>::add_root(ChildInfo::new(root_id, root_hash1), 1).is_ok()); + assert!(>::add_root(ChildInfo::new( + root_id, + root_hash1, + Metadata::default() + ),) + .is_ok()); let root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(root_index.id, root_id); assert_eq!(root_index.own_hash, root_hash1); - assert!(>::update_hash_for(root_id, root_hash2).is_ok()); + assert!(>::update_hash_for(root_id, root_hash2, None).is_ok()); let updated_root_index = >::get_index(root_id).unwrap().unwrap(); assert_eq!(updated_root_index.id, root_id); assert_eq!(updated_root_index.own_hash, root_hash2); diff --git a/crates/storage/src/tests/interface.rs b/crates/storage/src/tests/interface.rs index 3a44d1484..219cb8ade 100644 --- a/crates/storage/src/tests/interface.rs +++ b/crates/storage/src/tests/interface.rs @@ -14,11 +14,11 @@ mod interface__public_methods { #[test] fn children_of() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut page = Page::new_from_element("Node", element); - assert!(Interface::save(&mut page).unwrap()); + assert!(MainInterface::save(&mut page).unwrap()); assert_eq!( - Interface::children_of(page.id(), &page.paragraphs).unwrap(), + MainInterface::children_of(page.id(), &page.paragraphs).unwrap(), vec![] ); @@ -28,29 +28,31 @@ mod interface__public_methods { let mut para1 = Paragraph::new_from_element("Leaf1", child1); let mut para2 = Paragraph::new_from_element("Leaf2", child2); let mut para3 = Paragraph::new_from_element("Leaf3", child3); - assert!(Interface::save(&mut page).unwrap()); - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para1).unwrap()); - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para2).unwrap()); - assert!(Interface::add_child_to(page.id(), &mut page.paragraphs, &mut para3).unwrap()); + + assert!(!MainInterface::save(&mut page).unwrap()); + + assert!(MainInterface::add_child_to(page.id(), &mut page.paragraphs, &mut para1).unwrap()); + assert!(MainInterface::add_child_to(page.id(), &mut page.paragraphs, &mut para2).unwrap()); + assert!(MainInterface::add_child_to(page.id(), &mut page.paragraphs, &mut para3).unwrap()); assert_eq!( - Interface::children_of(page.id(), &page.paragraphs).unwrap(), + MainInterface::children_of(page.id(), &page.paragraphs).unwrap(), vec![para1, para2, para3] ); } #[test] fn find_by_id__existent() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut page = Page::new_from_element("Leaf", element); let id = page.id(); - assert!(Interface::save(&mut page).unwrap()); + assert!(MainInterface::save(&mut page).unwrap()); - assert_eq!(Interface::find_by_id(id).unwrap(), Some(page)); + assert_eq!(MainInterface::find_by_id(id).unwrap(), Some(page)); } #[test] fn find_by_id__non_existent() { - assert_none!(Interface::find_by_id::(Id::random()).unwrap()); + assert_none!(MainInterface::find_by_id::(Id::random()).unwrap()); } #[test] @@ -73,60 +75,49 @@ mod interface__public_methods { #[test] fn save__basic() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut page = Page::new_from_element("Node", element); - assert_ok!(Interface::save(&mut page)); - } - - #[test] - fn save__multiple() { - let element1 = Element::new(&Path::new("::root::node1").unwrap(), None); - let element2 = Element::new(&Path::new("::root::node2").unwrap(), None); - let mut page1 = Page::new_from_element("Node1", element1); - let mut page2 = Page::new_from_element("Node2", element2); - - assert!(Interface::save(&mut page1).unwrap()); - assert!(Interface::save(&mut page2).unwrap()); - assert_eq!(Interface::find_by_id(page1.id()).unwrap(), Some(page1)); - assert_eq!(Interface::find_by_id(page2.id()).unwrap(), Some(page2)); + assert_ok!(MainInterface::save(&mut page)); } #[test] fn save__not_dirty() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut page = Page::new_from_element("Node", element); - assert!(Interface::save(&mut page).unwrap()); + assert!(MainInterface::save(&mut page).unwrap()); page.element_mut().update(); - assert!(Interface::save(&mut page).unwrap()); + assert!(MainInterface::save(&mut page).unwrap()); } #[test] fn save__too_old() { - let element1 = Element::new(&Path::new("::root::node").unwrap(), None); + let element1 = Element::root(); let mut page1 = Page::new_from_element("Node", element1); let mut page2 = page1.clone(); - assert!(Interface::save(&mut page1).unwrap()); + assert!(MainInterface::save(&mut page1).unwrap()); page2.element_mut().update(); - sleep(Duration::from_millis(1)); + sleep(Duration::from_millis(2)); page1.element_mut().update(); - assert!(Interface::save(&mut page1).unwrap()); - assert!(!Interface::save(&mut page2).unwrap()); + assert!(MainInterface::save(&mut page1).unwrap()); + assert!(!MainInterface::save(&mut page2).unwrap()); } #[test] fn save__update_existing() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut page = Page::new_from_element("Node", element); let id = page.id(); - assert!(Interface::save(&mut page).unwrap()); + assert!(MainInterface::save(&mut page).unwrap()); + + page.storage.update(); // TODO: Modify the element's data and check it changed - assert!(Interface::save(&mut page).unwrap()); - assert_eq!(Interface::find_by_id(id).unwrap(), Some(page)); + assert!(MainInterface::save(&mut page).unwrap()); + assert_eq!(MainInterface::find_by_id(id).unwrap(), Some(page)); } #[test] @@ -163,120 +154,89 @@ mod interface__apply_actions { #[test] fn apply_action__add() { - let page = Page::new_from_element( - "Test Page", - Element::new(&Path::new("::test").unwrap(), None), - ); + let page = Page::new_from_element("Test Page", Element::root()); let serialized = to_vec(&page).unwrap(); let action = Action::Add { id: page.id(), - type_id: 102, data: serialized, ancestors: vec![], + metadata: page.element().metadata, }; - assert!(Interface::apply_action::(action).is_ok()); + assert!(MainInterface::apply_action(action).is_ok()); // Verify the page was added - let retrieved_page = Interface::find_by_id::(page.id()).unwrap(); + let retrieved_page = MainInterface::find_by_id::(page.id()).unwrap(); assert!(retrieved_page.is_some()); assert_eq!(retrieved_page.unwrap().title, "Test Page"); } #[test] fn apply_action__update() { - let mut page = Page::new_from_element( - "Old Title", - Element::new(&Path::new("::test").unwrap(), None), - ); - assert!(Interface::save(&mut page).unwrap()); + let mut page = Page::new_from_element("Old Title", Element::root()); + assert!(MainInterface::save(&mut page).unwrap()); page.title = "New Title".to_owned(); page.element_mut().update(); let serialized = to_vec(&page).unwrap(); let action = Action::Update { id: page.id(), - type_id: 102, data: serialized, ancestors: vec![], + metadata: page.element().metadata, }; - assert!(Interface::apply_action::(action).is_ok()); + assert!(MainInterface::apply_action(action).is_ok()); // Verify the page was updated - let retrieved_page = Interface::find_by_id::(page.id()).unwrap().unwrap(); + let retrieved_page = MainInterface::find_by_id::(page.id()) + .unwrap() + .unwrap(); assert_eq!(retrieved_page.title, "New Title"); } #[test] fn apply_action__delete() { - let mut page = Page::new_from_element( - "Test Page", - Element::new(&Path::new("::test").unwrap(), None), - ); - assert!(Interface::save(&mut page).unwrap()); + let mut page = Page::new_from_element("Test Page", Element::root()); + assert!(MainInterface::save(&mut page).unwrap()); let action = Action::Delete { id: page.id(), ancestors: vec![], }; - assert!(Interface::apply_action::(action).is_ok()); + assert!(MainInterface::apply_action(action).is_ok()); // Verify the page was deleted - let retrieved_page = Interface::find_by_id::(page.id()).unwrap(); + let retrieved_page = MainInterface::find_by_id::(page.id()).unwrap(); assert!(retrieved_page.is_none()); } #[test] fn apply_action__compare() { - let page = Page::new_from_element( - "Test Page", - Element::new(&Path::new("::test").unwrap(), None), - ); + let page = Page::new_from_element("Test Page", Element::root()); let action = Action::Compare { id: page.id() }; // Compare should fail - assert!(Interface::apply_action::(action).is_err()); - } - - #[test] - fn apply_action__wrong_type() { - let page = Page::new_from_element( - "Test Page", - Element::new(&Path::new("::test").unwrap(), None), - ); - let serialized = to_vec(&page).unwrap(); - let action = Action::Add { - id: page.id(), - type_id: 102, - data: serialized, - ancestors: vec![], - }; - - // Trying to apply a Page action as if it were a Paragraph should fail - assert!(Interface::apply_action::(action).is_err()); + assert!(MainInterface::apply_action(action).is_err()); } #[test] fn apply_action__non_existent_update() { - let page = Page::new_from_element( - "Test Page", - Element::new(&Path::new("::test").unwrap(), None), - ); + let page = Page::new_from_element("Test Page", Element::root()); let serialized = to_vec(&page).unwrap(); let action = Action::Update { id: page.id(), - type_id: 102, data: serialized, ancestors: vec![], + metadata: page.element().metadata, }; // Updating a non-existent page should still succeed (it will be added) - assert!(Interface::apply_action::(action).is_ok()); + assert!(MainInterface::apply_action(action).is_ok()); // Verify the page was added - let retrieved_page = Interface::find_by_id::(page.id()).unwrap(); + let retrieved_page = MainInterface::find_by_id::(page.id()).unwrap(); assert!(retrieved_page.is_some()); assert_eq!(retrieved_page.unwrap().title, "Test Page"); } @@ -286,13 +246,13 @@ mod interface__apply_actions { mod interface__comparison { use super::*; - type ForeignInterface = MainInterface>; + type ForeignInterface = Interface>; fn compare_trees( foreign: Option<&D>, - comparison_data: &ComparisonData, + comparison_data: ComparisonData, ) -> Result<(Vec, Vec), StorageError> { - Interface::compare_trees::( + MainInterface::compare_trees( foreign .map(to_vec) .transpose() @@ -303,11 +263,11 @@ mod interface__comparison { #[test] fn compare_trees__identical() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut local = Page::new_from_element("Test Page", element); let mut foreign = local.clone(); - assert!(Interface::save(&mut local).unwrap()); + assert!(MainInterface::save(&mut local).unwrap()); assert!(ForeignInterface::save(&mut foreign).unwrap()); assert_eq!( local.element().merkle_hash(), @@ -316,7 +276,7 @@ mod interface__comparison { let result = compare_trees( Some(&foreign), - &ForeignInterface::generate_comparison_data(Some(&foreign)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign.id())).unwrap(), ) .unwrap(); assert_eq!(result, (vec![], vec![])); @@ -324,7 +284,7 @@ mod interface__comparison { #[test] fn compare_trees__local_newer() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut local = Page::new_from_element("Test Page", element.clone()); let mut foreign = Page::new_from_element("Old Test Page", element); @@ -333,11 +293,11 @@ mod interface__comparison { // Make local newer sleep(Duration::from_millis(10)); local.element_mut().update(); - assert!(Interface::save(&mut local).unwrap()); + assert!(MainInterface::save(&mut local).unwrap()); let result = compare_trees( Some(&foreign), - &ForeignInterface::generate_comparison_data(Some(&foreign)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign.id())).unwrap(), ) .unwrap(); assert_eq!( @@ -346,9 +306,9 @@ mod interface__comparison { vec![], vec![Action::Update { id: local.id(), - type_id: 102, data: to_vec(&local).unwrap(), - ancestors: vec![] + ancestors: vec![], + metadata: local.element().metadata, }] ) ); @@ -356,11 +316,11 @@ mod interface__comparison { #[test] fn compare_trees__foreign_newer() { - let element = Element::new(&Path::new("::root::node").unwrap(), None); + let element = Element::root(); let mut local = Page::new_from_element("Old Test Page", element.clone()); let mut foreign = Page::new_from_element("Test Page", element); - assert!(Interface::save(&mut local).unwrap()); + assert!(MainInterface::save(&mut local).unwrap()); // Make foreign newer sleep(Duration::from_millis(10)); @@ -369,7 +329,7 @@ mod interface__comparison { let result = compare_trees( Some(&foreign), - &ForeignInterface::generate_comparison_data(Some(&foreign)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign.id())).unwrap(), ) .unwrap(); assert_eq!( @@ -377,9 +337,9 @@ mod interface__comparison { ( vec![Action::Update { id: foreign.id(), - type_id: 102, data: to_vec(&foreign).unwrap(), - ancestors: vec![] + ancestors: vec![], + metadata: foreign.element().metadata, }], vec![] ) @@ -388,7 +348,7 @@ mod interface__comparison { #[test] fn compare_trees__with_collections() { - let page_element = Element::new(&Path::new("::root::node").unwrap(), None); + let page_element = Element::root(); let para1_element = Element::new(&Path::new("::root::node::leaf1").unwrap(), None); let para2_element = Element::new(&Path::new("::root::node::leaf2").unwrap(), None); let para3_element = Element::new(&Path::new("::root::node::leaf3").unwrap(), None); @@ -402,14 +362,14 @@ mod interface__comparison { let mut foreign_para1 = Paragraph::new_from_element("Updated Paragraph 1", para1_element); let mut foreign_para3 = Paragraph::new_from_element("Foreign Paragraph 3", para3_element); - assert!(Interface::save(&mut local_page).unwrap()); - assert!(Interface::add_child_to( + assert!(MainInterface::save(&mut local_page).unwrap()); + assert!(MainInterface::add_child_to( local_page.id(), &mut local_page.paragraphs, &mut local_para1 ) .unwrap()); - assert!(Interface::add_child_to( + assert!(MainInterface::add_child_to( local_page.id(), &mut local_page.paragraphs, &mut local_para2 @@ -432,7 +392,7 @@ mod interface__comparison { let (local_actions, foreign_actions) = compare_trees( Some(&foreign_page), - &ForeignInterface::generate_comparison_data(Some(&foreign_page)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign_page.id())).unwrap(), ) .unwrap(); @@ -442,9 +402,9 @@ mod interface__comparison { // Page needs update due to different child structure Action::Update { id: foreign_page.id(), - type_id: 102, data: to_vec(&foreign_page).unwrap(), - ancestors: vec![] + ancestors: vec![], + metadata: foreign_page.element().metadata, }, // Para1 needs comparison due to different hash Action::Compare { @@ -463,9 +423,9 @@ mod interface__comparison { // Para2 needs to be added to foreign Action::Add { id: local_para2.id(), - type_id: 103, data: to_vec(&local_para2).unwrap(), - ancestors: vec![] + ancestors: vec![], + metadata: local_para2.element().metadata, }, // Para3 needs to be added locally, but we don't have the data, so we compare Action::Compare { @@ -477,7 +437,7 @@ mod interface__comparison { // Compare the updated para1 let (local_para1_actions, foreign_para1_actions) = compare_trees( Some(&foreign_para1), - &ForeignInterface::generate_comparison_data(Some(&foreign_para1)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign_para1.id())).unwrap(), ) .unwrap(); @@ -498,9 +458,13 @@ mod interface__comparison { local_para1_actions, vec![Action::Update { id: foreign_para1.id(), - type_id: 103, data: to_vec(&foreign_para1).unwrap(), - ancestors: vec![ChildInfo::new(foreign_page.id(), local_para1_ancestor_hash,)], + ancestors: vec![ChildInfo::new( + foreign_page.id(), + local_para1_ancestor_hash, + local_page.element().metadata + )], + metadata: foreign_para1.element().metadata, }] ); assert_eq!(foreign_para1_actions, vec![]); @@ -508,7 +472,7 @@ mod interface__comparison { // Compare para3 which doesn't exist locally let (local_para3_actions, foreign_para3_actions) = compare_trees( Some(&foreign_para3), - &ForeignInterface::generate_comparison_data(Some(&foreign_para3)).unwrap(), + ForeignInterface::generate_comparison_data(Some(foreign_para3.id())).unwrap(), ) .unwrap(); @@ -529,9 +493,13 @@ mod interface__comparison { local_para3_actions, vec![Action::Add { id: foreign_para3.id(), - type_id: 103, data: to_vec(&foreign_para3).unwrap(), - ancestors: vec![ChildInfo::new(foreign_page.id(), local_para3_ancestor_hash,)], + ancestors: vec![ChildInfo::new( + foreign_page.id(), + local_para3_ancestor_hash, + foreign_page.element().metadata + )], + metadata: foreign_para3.element().metadata, }] ); assert_eq!(foreign_para3_actions, vec![]); diff --git a/scripts/build-all-apps.sh b/scripts/build-all-apps.sh index 827c97e05..685d38acc 100755 --- a/scripts/build-all-apps.sh +++ b/scripts/build-all-apps.sh @@ -7,9 +7,8 @@ cd "$(dirname $0)" BUILD_SCRIPTS=( "../apps/kv-store/build.sh" - # todo! update to use CRDTs - # "../apps/gen-ext/build.sh" - # "../apps/only-peers/build.sh" + "../apps/gen-ext/build.sh" + "../apps/only-peers/build.sh" ) run_script() {