From 8663eee6f0a7805545c1ab7e7c36e9c48b2fb2fb Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Mon, 7 Oct 2024 11:24:45 +0200 Subject: [PATCH 01/10] feat(tree): Add struct for MembershipProof --- crates/common/src/tree.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index 9a4afc7b..1cff8850 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -187,6 +187,22 @@ pub enum Proof { Insert(InsertProof), } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MembershipProof { + pub root: Digest, + pub proof: SparseMerkleProof, + pub key: KeyHash, + pub value: Hashchain, +} + +impl MembershipProof { + pub fn verify(&self) -> Result<()> { + let value = bincode::serialize(&self.value)?; + self.proof + .verify_existence(self.root.into(), self.key, value) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NonMembershipProof { pub root: Digest, From 6c9df038f5c81f02e354ee08b19de005ff4a6b51 Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Mon, 7 Oct 2024 11:35:03 +0200 Subject: [PATCH 02/10] feat(tree): Return membership proof when getting tree values Before this, only a non-membership-proof was returned when the value could not be found in the tree. Now, a proof is returned alongside the value when it can be found. --- crates/common/src/test_utils.rs | 7 +- crates/common/src/tree.rs | 127 +++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 47 deletions(-) diff --git a/crates/common/src/test_utils.rs b/crates/common/src/test_utils.rs index 8ad707c6..42221771 100644 --- a/crates/common/src/test_utils.rs +++ b/crates/common/src/test_utils.rs @@ -1,7 +1,7 @@ use crate::{ hashchain::Hashchain, operation::{Operation, ServiceChallenge, SigningKey, VerifyingKey}, - tree::{InsertProof, KeyDirectoryTree, Proof, SnarkableTree, UpdateProof}, + tree::{HashchainResponse, InsertProof, KeyDirectoryTree, Proof, SnarkableTree, UpdateProof}, }; use anyhow::{anyhow, Result}; #[cfg(not(feature = "secp256k1"))] @@ -170,7 +170,10 @@ pub fn create_random_update(state: &mut TestTreeState, rng: &mut StdRng) -> Upda .iter() .nth(rng.gen_range(0..state.inserted_keys.len())) .unwrap(); - let mut hc = state.tree.get(key).unwrap().unwrap(); + + let HashchainResponse::Found(mut hc, _) = state.tree.get(key).unwrap() else { + panic!("No response found for key. Cannot perform update."); + }; let signing_key = create_mock_signing_key(); let verifying_key = signing_key.verifying_key(); diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index 1cff8850..299317ef 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -284,11 +284,17 @@ impl UpdateProof { } } +#[derive(Debug)] +pub enum HashchainResponse { + Found(Hashchain, MembershipProof), + NotFound(NonMembershipProof), +} + pub trait SnarkableTree { fn process_operation(&mut self, operation: &Operation) -> Result; fn insert(&mut self, key: KeyHash, value: Hashchain) -> Result; fn update(&mut self, key: KeyHash, value: HashchainEntry) -> Result; - fn get(&self, key: KeyHash) -> Result>; + fn get(&self, key: KeyHash) -> Result; } pub struct KeyDirectoryTree @@ -367,16 +373,19 @@ where let hashed_id = Digest::hash(id); let key_hash = KeyHash::with::(hashed_id); - let mut current_chain = self - .get(key_hash)? - .map_err(|_| anyhow!("Failed to get hashchain for ID {}", id))?; + match self.get(key_hash)? { + HashchainResponse::Found(mut current_chain, _) => { + let new_entry = current_chain.perform_operation(operation.clone())?; - let new_entry = current_chain.perform_operation(operation.clone())?; + debug!("updating hashchain for user id {}", id.clone()); + let proof = self.update(key_hash, new_entry.clone())?; - debug!("updating hashchain for user id {}", id.clone()); - let proof = self.update(key_hash, new_entry.clone())?; - - Ok(Proof::Update(proof)) + Ok(Proof::Update(proof)) + } + HashchainResponse::NotFound(_) => { + bail!("Failed to get hashchain for ID {}", id) + } + } } Operation::CreateAccount(CreateAccountArgs { id, @@ -389,7 +398,7 @@ where let account_key_hash = KeyHash::with::(hashed_id); // Verify that the account doesn't already exist - if self.get(account_key_hash)?.is_ok() { + if matches!(self.get(account_key_hash)?, HashchainResponse::Found(_, _)) { bail!(DatabaseError::NotFoundError(format!( "Account already exists for ID {}", id @@ -397,9 +406,11 @@ where } let service_key_hash = KeyHash::with::(Digest::hash(service_id.as_bytes())); - let service_hashchain = self.get(service_key_hash)?.map_err(|_| { - anyhow!("Failed to get hashchain for service ID {}", service_id) - })?; + + let HashchainResponse::Found(service_hashchain, _) = self.get(service_key_hash)? + else { + bail!("Failed to get hashchain for service ID {}", service_id); + }; let service_last_entry = service_hashchain.last().ok_or(anyhow!( "Service hashchain is empty, could not retrieve challenge key" @@ -452,12 +463,12 @@ where let key_hash = KeyHash::with::(hashed_id); // hashchain should not already exist - if self.get(key_hash)?.is_ok() { + let HashchainResponse::NotFound(_) = self.get(key_hash)? else { bail!(DatabaseError::NotFoundError(format!( "empty slot for ID {}", id ))); - } + }; debug!("creating new hashchain for service id {}", id); let chain = Hashchain::register_service(id.clone(), creation_gate.clone())?; @@ -534,19 +545,31 @@ where }) } - fn get(&self, key: KeyHash) -> Result> { + fn get(&self, key: KeyHash) -> Result { let (value, proof) = self.jmt.get_with_proof(key, self.epoch)?; match value { Some(serialized_value) => { let deserialized_value = Self::deserialize_value(&serialized_value)?; - Ok(Ok(deserialized_value)) + let membership_proof = MembershipProof { + root: self.get_current_root()?.into(), + proof, + key, + value: deserialized_value.clone(), + }; + Ok(HashchainResponse::Found( + deserialized_value, + membership_proof, + )) + } + None => { + let non_membership_proof = NonMembershipProof { + root: self.get_current_root()?.into(), + proof, + key, + }; + Ok(HashchainResponse::NotFound(non_membership_proof)) } - None => Ok(Err(NonMembershipProof { - root: self.get_current_root()?.into(), - proof, - key, - })), } } } @@ -570,8 +593,10 @@ mod tests { let insert_proof = tree_state.insert_account(account.clone()).unwrap(); assert!(insert_proof.verify().is_ok()); - let get_result = tree_state.tree.get(account.key_hash).unwrap().unwrap(); - assert_eq!(get_result, account.hashchain); + let get_result = tree_state.tree.get(account.key_hash).unwrap(); + assert!( + matches!(get_result, HashchainResponse::Found(hashchain, _) if hashchain == account.hashchain) + ); } #[test] @@ -638,8 +663,10 @@ mod tests { let update_proof = tree_state.update_account(account.clone()).unwrap(); assert!(update_proof.verify().is_ok()); - let get_result = tree_state.tree.get(account.key_hash).unwrap().unwrap(); - assert_eq!(get_result, account.hashchain); + let get_result = tree_state.tree.get(account.key_hash).unwrap(); + assert!( + matches!(get_result, HashchainResponse::Found(hashchain, _) if hashchain == account.hashchain) + ); } #[test] @@ -661,11 +688,12 @@ mod tests { let key = KeyHash::with::(b"non_existing_key"); let result = tree_state.tree.get(key).unwrap(); - assert!(result.is_err()); - if let Err(non_membership_proof) = result { - assert!(non_membership_proof.verify().is_ok()); - } + let HashchainResponse::NotFound(non_membership_proof) = result else { + panic!("Hashchain found for key while it was expected to be missing"); + }; + + assert!(non_membership_proof.verify().is_ok()); } #[test] @@ -688,11 +716,14 @@ mod tests { tree_state.update_account(account1.clone()).unwrap(); tree_state.update_account(account2.clone()).unwrap(); - let tree_hashchain1 = tree_state.tree.get(account1.key_hash).unwrap().unwrap(); - let tree_hashchain2 = tree_state.tree.get(account2.key_hash).unwrap().unwrap(); - - assert_eq!(tree_hashchain1, account1.hashchain); - assert_eq!(tree_hashchain2, account2.hashchain); + assert!(matches!( + tree_state.tree.get(account1.key_hash).unwrap(), + HashchainResponse::Found(h, _) if h == account1.hashchain + )); + assert!(matches!( + tree_state.tree.get(account2.key_hash).unwrap(), + HashchainResponse::Found(h, _) if h == account2.hashchain + )); } #[test] @@ -718,14 +749,14 @@ mod tests { // Update account_2 using the correct key index let last_proof = test_tree.update_account(account_2.clone()).unwrap(); - assert_eq!( - test_tree.tree.get(account_1.key_hash).unwrap().unwrap(), - account_1.hashchain - ); - assert_eq!( - test_tree.tree.get(account_2.key_hash).unwrap().unwrap(), - account_2.hashchain - ); + assert!(matches!( + test_tree.tree.get(account_1.key_hash).unwrap(), + HashchainResponse::Found(h, _) if h == account_1.hashchain + )); + assert!(matches!( + test_tree.tree.get(account_2.key_hash).unwrap(), + HashchainResponse::Found(h, _) if h == account_2.hashchain + )); assert_eq!( last_proof.new_root, test_tree.tree.get_current_root().unwrap() @@ -794,7 +825,13 @@ mod tests { println!("Final get result for key1: {:?}", get_result1); println!("Final get result for key2: {:?}", get_result2); - assert_eq!(get_result1.unwrap().unwrap(), account1.hashchain); - assert_eq!(get_result2.unwrap().unwrap(), account2.hashchain); + assert!(matches!( + get_result1.unwrap(), + HashchainResponse::Found(h, _) if h == account1.hashchain + )); + assert!(matches!( + get_result2.unwrap(), + HashchainResponse::Found(h, _) if h == account2.hashchain + )); } } From f2681ef40c96f76fd91f35408f0a7b668946e587 Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Mon, 7 Oct 2024 11:36:59 +0200 Subject: [PATCH 03/10] feat(sequencer): Adapt to new tree get signature --- crates/prism/src/node_types/sequencer.rs | 28 ++++++++++-------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/prism/src/node_types/sequencer.rs b/crates/prism/src/node_types/sequencer.rs index c8a26349..5ddc50c1 100644 --- a/crates/prism/src/node_types/sequencer.rs +++ b/crates/prism/src/node_types/sequencer.rs @@ -1,10 +1,9 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use ed25519_dalek::SigningKey; use jmt::KeyHash; -use prism_common::{ - hashchain::Hashchain, - tree::{Batch, Digest, Hasher, KeyDirectoryTree, NonMembershipProof, Proof, SnarkableTree}, +use prism_common::tree::{ + Batch, Digest, HashchainResponse, Hasher, KeyDirectoryTree, Proof, SnarkableTree, }; use prism_errors::DataAvailabilityError; use std::{self, collections::VecDeque, sync::Arc}; @@ -363,10 +362,7 @@ impl Sequencer { tree.get_commitment().context("Failed to get commitment") } - pub async fn get_hashchain( - &self, - id: &String, - ) -> Result> { + pub async fn get_hashchain(&self, id: &String) -> Result { let tree = self.tree.read().await; let hashed_id = Digest::hash(id); let key_hash = KeyHash::with::(hashed_id); @@ -393,15 +389,13 @@ impl Sequencer { Operation::RegisterService(_) => (), Operation::CreateAccount(_) => (), Operation::AddKey(_) | Operation::RevokeKey(_) => { - let hc = self.get_hashchain(&incoming_operation.id()).await?; - if let Ok(mut hc) = hc { - hc.perform_operation(incoming_operation.clone())?; - } else { - return Err(anyhow!( - "Hashchain not found for id: {}", - incoming_operation.id() - )); - } + let hcr = self.get_hashchain(&incoming_operation.id()).await?; + + let HashchainResponse::Found(mut hc, _) = hcr else { + bail!("Hashchain not found for id: {}", incoming_operation.id()) + }; + + hc.perform_operation(incoming_operation.clone())?; } }; From 1840de645c5a7e025edaee9c95a809b9ab429a42 Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Mon, 7 Oct 2024 11:41:36 +0200 Subject: [PATCH 04/10] feat(webserver): Return membership-proof for /get-hashchain --- crates/prism/src/webserver.rs | 47 +++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/crates/prism/src/webserver.rs b/crates/prism/src/webserver.rs index 65bc2b0b..68a968a7 100644 --- a/crates/prism/src/webserver.rs +++ b/crates/prism/src/webserver.rs @@ -11,7 +11,12 @@ use indexed_merkle_tree::{ tree::{Proof, UpdateProof}, Hash as TreeHash, }; -use prism_common::{hashchain::Hashchain, operation::Operation}; +use jmt::proof::SparseMerkleProof; +use prism_common::{ + hashchain::Hashchain, + operation::Operation, + tree::{HashchainResponse, Hasher}, +}; use serde::{Deserialize, Serialize}; use std::{self, sync::Arc}; use tower_http::cors::CorsLayer; @@ -46,11 +51,10 @@ pub struct UserKeyRequest { pub id: String, } -// TODO: Retrieve Merkle proof of current epoch #[derive(Serialize, Deserialize, ToSchema)] pub struct UserKeyResponse { - pub hashchain: Hashchain, - // pub proof: MerkleProof + pub hashchain: Option, + pub proof: SparseMerkleProof, } #[derive(OpenApi)] @@ -142,16 +146,33 @@ async fn get_hashchain( State(session): State>, Json(request): Json, ) -> impl IntoResponse { - match session.get_hashchain(&request.id).await { - Ok(hashchain_or_proof) => match hashchain_or_proof { - Ok(hashchain) => (StatusCode::OK, Json(UserKeyResponse { hashchain })).into_response(), - Err(non_inclusion_proof) => { - (StatusCode::BAD_REQUEST, Json(non_inclusion_proof)).into_response() - } - }, - Err(err) => ( + let get_hashchain_result = session.get_hashchain(&request.id).await; + let Ok(hashchain_response) = get_hashchain_result else { + return ( + StatusCode::BAD_REQUEST, + format!( + "Couldn't get hashchain: {}", + get_hashchain_result.unwrap_err() + ), + ) + .into_response(); + }; + + match hashchain_response { + HashchainResponse::Found(hashchain, membership_proof) => ( + StatusCode::OK, + Json(UserKeyResponse { + hashchain: Some(hashchain), + proof: membership_proof.proof, + }), + ) + .into_response(), + HashchainResponse::NotFound(non_membership_proof) => ( StatusCode::BAD_REQUEST, - format!("Couldn't get hashchain: {}", err), + Json(UserKeyResponse { + hashchain: None, + proof: non_membership_proof.proof, + }), ) .into_response(), } From 7060ba9f434bf3002d66914e97b8c3314808f45a Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Tue, 8 Oct 2024 13:50:48 +0200 Subject: [PATCH 05/10] fix: return OK when no value within /get-hashchain When no value is found within /get-hashchain, there is still a valid non-membership-proof response. In order not to confuse clients that may think a non-success code means a failed request, we switch to a success code here. Clients have to parse the hashchain property to see if there is a valid value. --- crates/prism/src/webserver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/prism/src/webserver.rs b/crates/prism/src/webserver.rs index 68a968a7..d6c1f77a 100644 --- a/crates/prism/src/webserver.rs +++ b/crates/prism/src/webserver.rs @@ -168,7 +168,7 @@ async fn get_hashchain( ) .into_response(), HashchainResponse::NotFound(non_membership_proof) => ( - StatusCode::BAD_REQUEST, + StatusCode::OK, Json(UserKeyResponse { hashchain: None, proof: non_membership_proof.proof, From 62c9be1071f1ee19279255f725d2430b24b9c45a Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Tue, 8 Oct 2024 13:51:37 +0200 Subject: [PATCH 06/10] fix: return 500 instead of 400 for internal error --- crates/prism/src/webserver.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/prism/src/webserver.rs b/crates/prism/src/webserver.rs index d6c1f77a..83466131 100644 --- a/crates/prism/src/webserver.rs +++ b/crates/prism/src/webserver.rs @@ -149,9 +149,9 @@ async fn get_hashchain( let get_hashchain_result = session.get_hashchain(&request.id).await; let Ok(hashchain_response) = get_hashchain_result else { return ( - StatusCode::BAD_REQUEST, + StatusCode::INTERNAL_SERVER_ERROR, format!( - "Couldn't get hashchain: {}", + "Failed to retrieve hashchain or non-membership-proof: {}", get_hashchain_result.unwrap_err() ), ) From 60f9349db8906b17b5efd96e90b2bd1697a8a88d Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Tue, 8 Oct 2024 13:53:16 +0200 Subject: [PATCH 07/10] docs: Add docs for HashchainResponse enum --- crates/common/src/tree.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index 299317ef..5bdeeffc 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -284,9 +284,13 @@ impl UpdateProof { } } +/// Enumerates possible responses when fetching tree values #[derive(Debug)] pub enum HashchainResponse { + /// When a hashchain was found, provides the value and its corresponding membership-proof Found(Hashchain, MembershipProof), + + /// When no hashchain was found for a specific key, provides the corresponding non-membership-proof NotFound(NonMembershipProof), } From d1e1454670a89e1c1831216e1c67c270db508b3d Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Tue, 8 Oct 2024 13:57:45 +0200 Subject: [PATCH 08/10] refac: Bring HashchainResponse enum members into scope That way if-let constructs for ensuring some HashchainResponse value are way more readable --- crates/common/src/test_utils.rs | 6 ++- crates/common/src/tree.rs | 64 +++++++++--------------- crates/prism/src/node_types/sequencer.rs | 7 +-- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/crates/common/src/test_utils.rs b/crates/common/src/test_utils.rs index 42221771..3c6ec559 100644 --- a/crates/common/src/test_utils.rs +++ b/crates/common/src/test_utils.rs @@ -1,7 +1,9 @@ use crate::{ hashchain::Hashchain, operation::{Operation, ServiceChallenge, SigningKey, VerifyingKey}, - tree::{HashchainResponse, InsertProof, KeyDirectoryTree, Proof, SnarkableTree, UpdateProof}, + tree::{ + HashchainResponse::*, InsertProof, KeyDirectoryTree, Proof, SnarkableTree, UpdateProof, + }, }; use anyhow::{anyhow, Result}; #[cfg(not(feature = "secp256k1"))] @@ -171,7 +173,7 @@ pub fn create_random_update(state: &mut TestTreeState, rng: &mut StdRng) -> Upda .nth(rng.gen_range(0..state.inserted_keys.len())) .unwrap(); - let HashchainResponse::Found(mut hc, _) = state.tree.get(key).unwrap() else { + let Found(mut hc, _) = state.tree.get(key).unwrap() else { panic!("No response found for key. Cannot perform update."); }; diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index 5bdeeffc..5dd6748d 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -21,6 +21,8 @@ use crate::{ }, }; +use HashchainResponse::*; + pub const SPARSE_MERKLE_PLACEHOLDER_HASH: Digest = Digest::new(*b"SPARSE_MERKLE_PLACEHOLDER_HASH__"); @@ -378,7 +380,7 @@ where let key_hash = KeyHash::with::(hashed_id); match self.get(key_hash)? { - HashchainResponse::Found(mut current_chain, _) => { + Found(mut current_chain, _) => { let new_entry = current_chain.perform_operation(operation.clone())?; debug!("updating hashchain for user id {}", id.clone()); @@ -386,7 +388,7 @@ where Ok(Proof::Update(proof)) } - HashchainResponse::NotFound(_) => { + NotFound(_) => { bail!("Failed to get hashchain for ID {}", id) } } @@ -402,7 +404,7 @@ where let account_key_hash = KeyHash::with::(hashed_id); // Verify that the account doesn't already exist - if matches!(self.get(account_key_hash)?, HashchainResponse::Found(_, _)) { + if matches!(self.get(account_key_hash)?, Found(_, _)) { bail!(DatabaseError::NotFoundError(format!( "Account already exists for ID {}", id @@ -411,8 +413,7 @@ where let service_key_hash = KeyHash::with::(Digest::hash(service_id.as_bytes())); - let HashchainResponse::Found(service_hashchain, _) = self.get(service_key_hash)? - else { + let Found(service_hashchain, _) = self.get(service_key_hash)? else { bail!("Failed to get hashchain for service ID {}", service_id); }; @@ -467,7 +468,7 @@ where let key_hash = KeyHash::with::(hashed_id); // hashchain should not already exist - let HashchainResponse::NotFound(_) = self.get(key_hash)? else { + let NotFound(_) = self.get(key_hash)? else { bail!(DatabaseError::NotFoundError(format!( "empty slot for ID {}", id @@ -561,10 +562,7 @@ where key, value: deserialized_value.clone(), }; - Ok(HashchainResponse::Found( - deserialized_value, - membership_proof, - )) + Ok(Found(deserialized_value, membership_proof)) } None => { let non_membership_proof = NonMembershipProof { @@ -572,7 +570,7 @@ where proof, key, }; - Ok(HashchainResponse::NotFound(non_membership_proof)) + Ok(NotFound(non_membership_proof)) } } } @@ -667,10 +665,8 @@ mod tests { let update_proof = tree_state.update_account(account.clone()).unwrap(); assert!(update_proof.verify().is_ok()); - let get_result = tree_state.tree.get(account.key_hash).unwrap(); - assert!( - matches!(get_result, HashchainResponse::Found(hashchain, _) if hashchain == account.hashchain) - ); + let get_result = tree_state.tree.get(account.key_hash); + assert!(matches!(get_result.unwrap(), Found(hc, _) if hc == account.hashchain)); } #[test] @@ -693,7 +689,7 @@ mod tests { let result = tree_state.tree.get(key).unwrap(); - let HashchainResponse::NotFound(non_membership_proof) = result else { + let NotFound(non_membership_proof) = result else { panic!("Hashchain found for key while it was expected to be missing"); }; @@ -720,14 +716,11 @@ mod tests { tree_state.update_account(account1.clone()).unwrap(); tree_state.update_account(account2.clone()).unwrap(); - assert!(matches!( - tree_state.tree.get(account1.key_hash).unwrap(), - HashchainResponse::Found(h, _) if h == account1.hashchain - )); - assert!(matches!( - tree_state.tree.get(account2.key_hash).unwrap(), - HashchainResponse::Found(h, _) if h == account2.hashchain - )); + let get_result1 = tree_state.tree.get(account1.key_hash); + let get_result2 = tree_state.tree.get(account2.key_hash); + + assert!(matches!(get_result1.unwrap(), Found(hc, _) if hc == account1.hashchain)); + assert!(matches!(get_result2.unwrap(), Found(hc, _) if hc == account2.hashchain)); } #[test] @@ -753,14 +746,11 @@ mod tests { // Update account_2 using the correct key index let last_proof = test_tree.update_account(account_2.clone()).unwrap(); - assert!(matches!( - test_tree.tree.get(account_1.key_hash).unwrap(), - HashchainResponse::Found(h, _) if h == account_1.hashchain - )); - assert!(matches!( - test_tree.tree.get(account_2.key_hash).unwrap(), - HashchainResponse::Found(h, _) if h == account_2.hashchain - )); + let get_result1 = test_tree.tree.get(account_1.key_hash); + let get_result2 = test_tree.tree.get(account_2.key_hash); + + assert!(matches!(get_result1.unwrap(), Found(hc, _) if hc == account_1.hashchain)); + assert!(matches!(get_result2.unwrap(), Found(hc, _) if hc == account_2.hashchain)); assert_eq!( last_proof.new_root, test_tree.tree.get_current_root().unwrap() @@ -829,13 +819,7 @@ mod tests { println!("Final get result for key1: {:?}", get_result1); println!("Final get result for key2: {:?}", get_result2); - assert!(matches!( - get_result1.unwrap(), - HashchainResponse::Found(h, _) if h == account1.hashchain - )); - assert!(matches!( - get_result2.unwrap(), - HashchainResponse::Found(h, _) if h == account2.hashchain - )); + assert!(matches!(get_result1.unwrap(), Found(hc, _) if hc == account1.hashchain)); + assert!(matches!(get_result2.unwrap(), Found(hc, _) if hc == account2.hashchain)); } } diff --git a/crates/prism/src/node_types/sequencer.rs b/crates/prism/src/node_types/sequencer.rs index 5ddc50c1..2e4c90e7 100644 --- a/crates/prism/src/node_types/sequencer.rs +++ b/crates/prism/src/node_types/sequencer.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use ed25519_dalek::SigningKey; use jmt::KeyHash; use prism_common::tree::{ - Batch, Digest, HashchainResponse, Hasher, KeyDirectoryTree, Proof, SnarkableTree, + Batch, Digest, HashchainResponse, HashchainResponse::*, Hasher, KeyDirectoryTree, Proof, + SnarkableTree, }; use prism_errors::DataAvailabilityError; use std::{self, collections::VecDeque, sync::Arc}; @@ -389,9 +390,9 @@ impl Sequencer { Operation::RegisterService(_) => (), Operation::CreateAccount(_) => (), Operation::AddKey(_) | Operation::RevokeKey(_) => { - let hcr = self.get_hashchain(&incoming_operation.id()).await?; + let hc_response = self.get_hashchain(&incoming_operation.id()).await?; - let HashchainResponse::Found(mut hc, _) = hcr else { + let Found(mut hc, _) = hc_response else { bail!("Hashchain not found for id: {}", incoming_operation.id()) }; From 59ebbd3d187864ecfda5cfbf880330e78271c49d Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Tue, 8 Oct 2024 14:20:53 +0200 Subject: [PATCH 09/10] refac: Pull out root variable --- crates/common/src/tree.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index 5dd6748d..d075e595 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -551,13 +551,14 @@ where } fn get(&self, key: KeyHash) -> Result { + let root = self.get_current_root()?.into(); let (value, proof) = self.jmt.get_with_proof(key, self.epoch)?; match value { Some(serialized_value) => { let deserialized_value = Self::deserialize_value(&serialized_value)?; let membership_proof = MembershipProof { - root: self.get_current_root()?.into(), + root, proof, key, value: deserialized_value.clone(), @@ -565,11 +566,7 @@ where Ok(Found(deserialized_value, membership_proof)) } None => { - let non_membership_proof = NonMembershipProof { - root: self.get_current_root()?.into(), - proof, - key, - }; + let non_membership_proof = NonMembershipProof { root, proof, key }; Ok(NotFound(non_membership_proof)) } } From ae4cce0804640ec881739bd0054271c13c30d657 Mon Sep 17 00:00:00 2001 From: Jonas Pusch Date: Wed, 9 Oct 2024 09:19:54 +0200 Subject: [PATCH 10/10] test(tree): Verify membership proof when testing get --- crates/common/src/tree.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/common/src/tree.rs b/crates/common/src/tree.rs index d075e595..4a7bd514 100644 --- a/crates/common/src/tree.rs +++ b/crates/common/src/tree.rs @@ -592,10 +592,13 @@ mod tests { let insert_proof = tree_state.insert_account(account.clone()).unwrap(); assert!(insert_proof.verify().is_ok()); - let get_result = tree_state.tree.get(account.key_hash).unwrap(); - assert!( - matches!(get_result, HashchainResponse::Found(hashchain, _) if hashchain == account.hashchain) - ); + let Found(hashchain, membership_proof) = tree_state.tree.get(account.key_hash).unwrap() + else { + panic!("Expected hashchain to be found, but was not found.") + }; + + assert_eq!(hashchain, account.hashchain); + assert!(membership_proof.verify().is_ok()); } #[test]