diff --git a/Cargo.lock b/Cargo.lock index b86008ca66c..4ef206618e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2288,8 +2288,11 @@ dependencies = [ name = "apollo_proof_manager" version = "0.0.0" dependencies = [ + "hex", "starknet-types-core", "starknet_api", + "tempfile", + "thiserror 1.0.69", ] [[package]] diff --git a/crates/apollo_proof_manager/Cargo.toml b/crates/apollo_proof_manager/Cargo.toml index 93d81cea94b..5270845445d 100644 --- a/crates/apollo_proof_manager/Cargo.toml +++ b/crates/apollo_proof_manager/Cargo.toml @@ -12,8 +12,11 @@ testing = [] workspace = true [dependencies] +hex.workspace = true starknet-types-core = { workspace = true, features = ["hash"] } starknet_api.workspace = true +tempfile.workspace = true +thiserror.workspace = true [dev-dependencies] diff --git a/crates/apollo_proof_manager/src/proof_storage.rs b/crates/apollo_proof_manager/src/proof_storage.rs index 05fce9e7ed7..34f947ac1fc 100644 --- a/crates/apollo_proof_manager/src/proof_storage.rs +++ b/crates/apollo_proof_manager/src/proof_storage.rs @@ -1,7 +1,9 @@ use std::error::Error; +use std::path::PathBuf; use starknet_api::transaction::fields::Proof; use starknet_types_core::felt::Felt; +use thiserror::Error; pub trait ProofStorage: Send + Sync { type Error: Error; @@ -9,3 +11,77 @@ pub trait ProofStorage: Send + Sync { fn get_proof(&self, facts_hash: Felt) -> Result, Self::Error>; fn contains_proof(&self, facts_hash: Felt) -> Result; } + +#[derive(Debug, Error)] +pub enum FsProofStorageError { + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error("Proof for facts_hash {facts_hash} not found.")] + ProofNotFound { facts_hash: Felt }, +} + +type FsProofStorageResult = Result; + +pub struct FsProofStorage { + persistent_root: PathBuf, +} + +impl FsProofStorage { + // TODO(Einat): consider code sharing with class storage. + pub fn new(persistent_root: PathBuf) -> Result { + std::fs::create_dir_all(&persistent_root)?; + Ok(Self { persistent_root }) + } + + /// Returns the directory that will hold the proof of a certain proof facts hash. + /// For a proof facts hash: 0xa1b2c3d4... (rest of hash), the structure is: + /// a1/ + /// └── b2/ + /// └── a1b2c3d4.../ + #[allow(dead_code)] + fn get_proof_dir(&self, facts_hash: Felt) -> PathBuf { + let facts_hash = hex::encode(facts_hash.to_bytes_be()); + let (first_msb_byte, second_msb_byte, _rest_of_bytes) = + (&facts_hash[..2], &facts_hash[2..4], &facts_hash[4..]); + PathBuf::from(first_msb_byte).join(second_msb_byte).join(facts_hash) + } + + #[allow(dead_code)] + fn get_persistent_dir(&self, facts_hash: Felt) -> PathBuf { + self.persistent_root.join(self.get_proof_dir(facts_hash)) + } + + #[allow(dead_code)] + fn get_persistent_dir_with_create(&self, facts_hash: Felt) -> FsProofStorageResult { + let path = self.get_persistent_dir(facts_hash); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + + Ok(path) + } + + #[allow(dead_code)] + fn create_tmp_dir( + &self, + facts_hash: Felt, + ) -> FsProofStorageResult<(tempfile::TempDir, PathBuf)> { + // Compute the final persistent directory for this `facts_hash` + let persistent_dir = self.get_persistent_dir(facts_hash); + let parent_dir = persistent_dir + .parent() + .expect("Proof persistent dir should have a parent") + .to_path_buf(); + std::fs::create_dir_all(&parent_dir)?; + // Create a temporary directory under the parent of the final persistent directory to ensure + // `rename` will be atomic. + let tmp_root = tempfile::tempdir_in(&parent_dir)?; + // Get the leaf directory name of the final persistent directory. + let leaf = persistent_dir.file_name().expect("Proof dir leaf should exist"); + // Create the temporary directory under the temporary root. + let tmp_dir = tmp_root.path().join(leaf); + // Returning `TempDir` since without it the handle would drop immediately and the temp + // directory would be removed before writes/rename. + Ok((tmp_root, tmp_dir)) + } +}