From e8f34ba3867fd6d62790122317224d8c65cdf887 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Sat, 13 Jul 2024 13:20:33 +0530 Subject: [PATCH 01/13] feat : added pathfinder_getClassProof method --- .gitignore | 5 +- crates/merkle-tree/src/class.rs | 173 +++++++++++++++++- crates/merkle-tree/src/lib.rs | 2 +- crates/rpc/src/pathfinder.rs | 1 + crates/rpc/src/pathfinder/methods.rs | 1 + .../rpc/src/pathfinder/methods/get_proof.rs | 111 ++++++++++- 6 files changed, 283 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index c44283c5da..050cdb9ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ pathfinder-var.env .vscode/ # mdbook compilation -**/book/ \ No newline at end of file +**/book/ + +# Intellij IDE generated files +.idea \ No newline at end of file diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 273060c9f3..1131a2f770 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,16 +1,19 @@ +use crate::merkle_node::InternalNode; use anyhow::Context; -use pathfinder_common::hash::PoseidonHash; +use bitvec::order::Msb0; +use bitvec::prelude::BitSlice; +use bitvec::view::BitView; +use pathfinder_common::hash::{PedersenHash, PoseidonHash}; +use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, - ClassCommitment, - ClassCommitmentLeafHash, - ClassHash, - SierraHash, + BlockNumber, CasmHash, ClassCommitment, ClassCommitmentLeafHash, ClassHash, ContractAddress, + ContractRoot, SierraHash, StorageAddress, StorageValue, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; +use std::ops::ControlFlow; -use crate::tree::MerkleTree; +use crate::tree::{MerkleTree, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -71,6 +74,162 @@ impl<'tx> ClassCommitmentTree<'tx> { let commitment = ClassCommitment(update.root_commitment); Ok((commitment, update)) } + + /// Generates a proof for a given `key` + pub fn get_proof( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + class_hash: ClassHash, + ) -> anyhow::Result>> { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(None); + }; + + let storage = ClassTrieStorage { + tx, + block: Some(block), + }; + + let casm = tx + .casm_hash_at(block.into(), class_hash) + .context("Querying CASM hash")?; + + let Some(casm) = casm else { + return Ok(None); + }; + + MerkleTree::::get_proof(root, &storage, casm.view_bits()) + } +} + +/// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to +/// Starknet's Sierra classes. +/// +/// It maps a class's [SierraHash] to its [ClassCommitmentLeafHash] +/// +/// Tree data is persisted by a sqlite table 'tree_class'. + +pub struct ClassStorageTree<'tx> { + tree: MerkleTree, + storage: ClassStorage<'tx>, +} + +impl<'tx> ClassStorageTree<'tx> { + pub fn empty(tx: &'tx Transaction<'tx>) -> Self { + let storage = ClassStorage { tx, block: None }; + let tree = MerkleTree::empty(); + + Self { tree, storage } + } + + pub fn load(tx: &'tx Transaction<'tx>, block: BlockNumber) -> anyhow::Result { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(Self::empty(tx)); + }; + + let storage = ClassStorage { + tx, + block: Some(block), + }; + + let tree = MerkleTree::new(root); + + Ok(Self { tree, storage }) + } + + pub fn with_verify_hashes(mut self, verify_hashes: bool) -> Self { + self.tree = self.tree.with_verify_hashes(verify_hashes); + self + } + + /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. + pub fn get_proof( + tx: &'tx Transaction<'tx>, + block: BlockNumber, + key: &BitSlice, + ) -> anyhow::Result>> { + let root = tx + .class_root_index(block) + .context("Querying class root index")?; + + let Some(root) = root else { + return Ok(None); + }; + + let storage = ClassStorage { + tx, + block: Some(block), + }; + + MerkleTree::::get_proof(root, &storage, key) + } + + pub fn set(&mut self, address: StorageAddress, value: StorageValue) -> anyhow::Result<()> { + let key = address.view_bits().to_owned(); + self.tree.set(&self.storage, key, value.0) + } + + /// Commits the changes and calculates the new node hashes. Returns the new + /// commitment and any potentially newly created nodes. + pub fn commit(self) -> anyhow::Result<(CasmHash, TrieUpdate)> { + let update = self.tree.commit(&self.storage)?; + let commitment = CasmHash(update.root_commitment); + Ok((commitment, update)) + } + + /// See [`MerkleTree::dfs`] + pub fn dfs) -> ControlFlow>( + &mut self, + f: &mut F, + ) -> anyhow::Result> { + self.tree.dfs(&self.storage, f) + } +} + +struct ClassTrieStorage<'tx> { + tx: &'tx Transaction<'tx>, + block: Option, +} + +impl crate::storage::Storage for ClassTrieStorage<'_> { + fn get(&self, index: u64) -> anyhow::Result> { + self.tx.storage_trie_node(index) + } + + fn hash(&self, index: u64) -> anyhow::Result> { + self.tx.storage_trie_node_hash(index) + } + + fn leaf(&self, path: &BitSlice) -> anyhow::Result> { + assert!(path.len() == 251); + + let Some(block) = self.block else { + return Ok(None); + }; + + let sierra = + ClassHash(Felt::from_bits(path).context("Mapping leaf path to contract address")?); + + let casm = self + .tx + .casm_hash_at(block.into(), sierra) + .context("Querying CASM hash")?; + let Some(casm) = casm else { + return Ok(None); + }; + + let value = self.tx.class_commitment_leaf(block, &casm)?.map(|x| x.0); + + Ok(value) + } } struct ClassStorage<'tx> { diff --git a/crates/merkle-tree/src/lib.rs b/crates/merkle-tree/src/lib.rs index 07ef9424f1..0bf2bbcdce 100644 --- a/crates/merkle-tree/src/lib.rs +++ b/crates/merkle-tree/src/lib.rs @@ -3,7 +3,7 @@ pub mod merkle_node; pub mod storage; pub mod tree; -mod class; +pub mod class; mod contract; mod transaction; diff --git a/crates/rpc/src/pathfinder.rs b/crates/rpc/src/pathfinder.rs index 4b0ca8ec4a..f491080f6e 100644 --- a/crates/rpc/src/pathfinder.rs +++ b/crates/rpc/src/pathfinder.rs @@ -8,4 +8,5 @@ pub fn register_routes() -> RpcRouterBuilder { .register("pathfinder_version", || { pathfinder_common::consts::VERGEN_GIT_DESCRIBE }) .register("pathfinder_getProof", methods::get_proof) .register("pathfinder_getTransactionStatus", methods::get_transaction_status) + .register("pathfinder_getClassProof", methods::get_proof_class) } diff --git a/crates/rpc/src/pathfinder/methods.rs b/crates/rpc/src/pathfinder/methods.rs index eab78fb2bd..a0e8bd5a7f 100644 --- a/crates/rpc/src/pathfinder/methods.rs +++ b/crates/rpc/src/pathfinder/methods.rs @@ -2,4 +2,5 @@ mod get_proof; mod get_transaction_status; pub(crate) use get_proof::get_proof; +pub(crate) use get_proof::get_proof_class; pub(crate) use get_transaction_status::get_transaction_status; diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 3b5118f09d..99cf00d3ce 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,8 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::{ContractsStorageTree, StorageCommitmentTree}; +use pathfinder_merkle_tree::class::ClassStorageTree; +use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -16,6 +17,13 @@ pub struct GetProofInput { pub keys: Vec, } +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct GetProofInputClass { + pub block_id: BlockId, + pub class_hash: ClassHash, + pub keys: Vec, +} + // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. // This may not actually be possible though. #[derive(Debug)] @@ -151,6 +159,18 @@ pub struct GetProofOutput { contract_data: Option, } +#[derive(Debug, Serialize)] +#[skip_serializing_none] +pub struct GetProofOutputClass { + /// Required to verify that the hash of the class commitment and the root of + /// the [contract_proof](GetProofOutput::contract_proof) matches the + /// [state_commitment](Self#state_commitment). Present only for Starknet + /// blocks 0.11.0 onwards. + class_commitment: Option, + /// Membership / Non-membership proof for the queried contract classes + class_proof: ProofNodes, +} + /// Returns all the necessary data to trustlessly verify storage slots for a /// particular contract. pub async fn get_proof( @@ -278,6 +298,95 @@ pub async fn get_proof( jh.await.context("Database read panic or shutting down")? } +/// Returns all the necessary data to trustlessly verify class changes for a +/// particular contract. +pub async fn get_proof_class( + context: RpcContext, + input: GetProofInputClass, +) -> Result { + const MAX_KEYS: usize = 100; + if input.keys.len() > MAX_KEYS { + return Err(GetProofError::ProofLimitExceeded { + limit: MAX_KEYS as u32, + requested: input.keys.len() as u32, + }); + } + + let block_id = match input.block_id { + BlockId::Pending => { + return Err(GetProofError::Internal(anyhow!( + "'pending' is not currently supported by this method!" + ))) + } + other => other.try_into().expect("Only pending cast should fail"), + }; + + let storage = context.storage.clone(); + let span = tracing::Span::current(); + + let jh = tokio::task::spawn_blocking(move || { + let _g = span.enter(); + let mut db = storage + .connection() + .context("Opening database connection")?; + + let tx = db.transaction().context("Creating database transaction")?; + + // Use internal error to indicate that the process of querying for a particular + // block failed, which is not the same as being sure that the block is + // not in the db. + let header = tx + .block_header(block_id) + .context("Fetching block header")? + .ok_or(GetProofError::BlockNotFound)?; + + let class_commitment = match header.class_commitment { + ClassCommitment::ZERO => None, + other => Some(other), + }; + + // Generate a proof for this class. If the class does not exist, this will + // be a "non membership" proof. + let class_proof = ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash) + .context("Creating contract proof")? + .ok_or(GetProofError::ProofMissing)?; + let class_proof = ProofNodes(class_proof); + + let class_root_exists = tx + .class_root_exists(header.number) + .context("Fetching class root existence")?; + + if !class_root_exists { + return Ok(GetProofOutputClass { + class_commitment, + class_proof, + }); + }; + + let mut class_proofs = Vec::new(); + for k in &input.keys { + let proof = ClassStorageTree::get_proof(&tx, header.number, k.view_bits()) + .context("Get proof from class tree")? + .ok_or_else(|| { + let e = anyhow!( + "Storage proof missing for key {:?}, but should be present", + k + ); + tracing::warn!("{e}"); + e + })?; + class_proofs.push(ProofNodes(proof)); + } + + Ok(GetProofOutputClass { + class_commitment, + class_proof, + }) + }); + + jh.await.context("Database read panic or shutting down")? +} + #[cfg(test)] mod tests { use pathfinder_common::macro_prelude::*; From 64daf4cf579efd692abf2042f5e82693e9c7df01 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Mon, 15 Jul 2024 17:02:25 +0530 Subject: [PATCH 02/13] feat : added poseidon hash for class tree function and fixed lint tests --- .gitignore | 3 --- .idea/.gitignore | 5 ++++ .idea/modules.xml | 8 ++++++ .idea/pathfinder.iml | 37 ++++++++++++++++++++++++++++ .idea/vcs.xml | 6 +++++ crates/merkle-tree/src/class.rs | 20 +++++++++------ crates/rpc/src/pathfinder/methods.rs | 3 +-- 7 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/pathfinder.iml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 050cdb9ab0..ed158cfecf 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,3 @@ pathfinder-var.env # mdbook compilation **/book/ - -# Intellij IDE generated files -.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..b58b603fea --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..921c18fcf8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml new file mode 100644 index 0000000000..e4c15ed1a5 --- /dev/null +++ b/.idea/pathfinder.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 1131a2f770..e211ccc469 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,18 +1,24 @@ -use crate::merkle_node::InternalNode; +use std::ops::ControlFlow; + use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use bitvec::view::BitView; use pathfinder_common::hash::{PedersenHash, PoseidonHash}; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, CasmHash, ClassCommitment, ClassCommitmentLeafHash, ClassHash, ContractAddress, - ContractRoot, SierraHash, StorageAddress, StorageValue, + BlockNumber, + CasmHash, + ClassCommitment, + ClassCommitmentLeafHash, + ClassHash, + SierraHash, + StorageAddress, + StorageValue, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use std::ops::ControlFlow; +use crate::merkle_node::InternalNode; use crate::tree::{MerkleTree, Visit}; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to @@ -102,7 +108,7 @@ impl<'tx> ClassCommitmentTree<'tx> { return Ok(None); }; - MerkleTree::::get_proof(root, &storage, casm.view_bits()) + MerkleTree::::get_proof(root, &storage, casm.view_bits()) } } @@ -114,7 +120,7 @@ impl<'tx> ClassCommitmentTree<'tx> { /// Tree data is persisted by a sqlite table 'tree_class'. pub struct ClassStorageTree<'tx> { - tree: MerkleTree, + tree: MerkleTree, storage: ClassStorage<'tx>, } diff --git a/crates/rpc/src/pathfinder/methods.rs b/crates/rpc/src/pathfinder/methods.rs index a0e8bd5a7f..d2c6f85074 100644 --- a/crates/rpc/src/pathfinder/methods.rs +++ b/crates/rpc/src/pathfinder/methods.rs @@ -1,6 +1,5 @@ mod get_proof; mod get_transaction_status; -pub(crate) use get_proof::get_proof; -pub(crate) use get_proof::get_proof_class; +pub(crate) use get_proof::{get_proof, get_proof_class}; pub(crate) use get_transaction_status::get_transaction_status; From 8f91005ff2e7be0eaf1f05d09e991b87eaac6095 Mon Sep 17 00:00:00 2001 From: Arun Jangra Date: Mon, 15 Jul 2024 17:02:41 +0530 Subject: [PATCH 03/13] feat : added poseidon hash for class tree function and fixed lint tests --- .idea/.gitignore | 5 ----- .idea/modules.xml | 8 -------- .idea/pathfinder.iml | 37 ------------------------------------- .idea/vcs.xml | 6 ------ 4 files changed, 56 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/modules.xml delete mode 100644 .idea/pathfinder.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603fea..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 921c18fcf8..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/pathfinder.iml b/.idea/pathfinder.iml deleted file mode 100644 index e4c15ed1a5..0000000000 --- a/.idea/pathfinder.iml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbb..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 4d90d6f2359afcbffb0143a9665eefcba6eb97bc Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Mon, 12 Aug 2024 16:58:48 +0200 Subject: [PATCH 04/13] Fix: remove keys and use correct Merkle tree table --- crates/merkle-tree/src/class.rs | 112 ++---------------- .../rpc/src/pathfinder/methods/get_proof.rs | 37 +----- 2 files changed, 17 insertions(+), 132 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index e211ccc469..54c5cf5ffd 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,25 +1,23 @@ -use std::ops::ControlFlow; - use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use pathfinder_common::hash::{PedersenHash, PoseidonHash}; -use pathfinder_common::trie::TrieNode; + use pathfinder_common::{ - BlockNumber, - CasmHash, + BlockNumber + , ClassCommitment, ClassCommitmentLeafHash, ClassHash, - SierraHash, - StorageAddress, - StorageValue, + SierraHash + + , }; +use pathfinder_common::hash::PoseidonHash; +use pathfinder_common::trie::TrieNode; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; -use crate::merkle_node::InternalNode; -use crate::tree::{MerkleTree, Visit}; +use crate::tree::MerkleTree; /// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to /// Starknet's Sierra classes. @@ -112,94 +110,6 @@ impl<'tx> ClassCommitmentTree<'tx> { } } -/// A [Patricia Merkle tree](MerkleTree) used to calculate commitments to -/// Starknet's Sierra classes. -/// -/// It maps a class's [SierraHash] to its [ClassCommitmentLeafHash] -/// -/// Tree data is persisted by a sqlite table 'tree_class'. - -pub struct ClassStorageTree<'tx> { - tree: MerkleTree, - storage: ClassStorage<'tx>, -} - -impl<'tx> ClassStorageTree<'tx> { - pub fn empty(tx: &'tx Transaction<'tx>) -> Self { - let storage = ClassStorage { tx, block: None }; - let tree = MerkleTree::empty(); - - Self { tree, storage } - } - - pub fn load(tx: &'tx Transaction<'tx>, block: BlockNumber) -> anyhow::Result { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(Self::empty(tx)); - }; - - let storage = ClassStorage { - tx, - block: Some(block), - }; - - let tree = MerkleTree::new(root); - - Ok(Self { tree, storage }) - } - - pub fn with_verify_hashes(mut self, verify_hashes: bool) -> Self { - self.tree = self.tree.with_verify_hashes(verify_hashes); - self - } - - /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. - pub fn get_proof( - tx: &'tx Transaction<'tx>, - block: BlockNumber, - key: &BitSlice, - ) -> anyhow::Result>> { - let root = tx - .class_root_index(block) - .context("Querying class root index")?; - - let Some(root) = root else { - return Ok(None); - }; - - let storage = ClassStorage { - tx, - block: Some(block), - }; - - MerkleTree::::get_proof(root, &storage, key) - } - - pub fn set(&mut self, address: StorageAddress, value: StorageValue) -> anyhow::Result<()> { - let key = address.view_bits().to_owned(); - self.tree.set(&self.storage, key, value.0) - } - - /// Commits the changes and calculates the new node hashes. Returns the new - /// commitment and any potentially newly created nodes. - pub fn commit(self) -> anyhow::Result<(CasmHash, TrieUpdate)> { - let update = self.tree.commit(&self.storage)?; - let commitment = CasmHash(update.root_commitment); - Ok((commitment, update)) - } - - /// See [`MerkleTree::dfs`] - pub fn dfs) -> ControlFlow>( - &mut self, - f: &mut F, - ) -> anyhow::Result> { - self.tree.dfs(&self.storage, f) - } -} - struct ClassTrieStorage<'tx> { tx: &'tx Transaction<'tx>, block: Option, @@ -207,11 +117,11 @@ struct ClassTrieStorage<'tx> { impl crate::storage::Storage for ClassTrieStorage<'_> { fn get(&self, index: u64) -> anyhow::Result> { - self.tx.storage_trie_node(index) + self.tx.class_trie_node(index) } fn hash(&self, index: u64) -> anyhow::Result> { - self.tx.storage_trie_node_hash(index) + self.tx.class_trie_node_hash(index) } fn leaf(&self, path: &BitSlice) -> anyhow::Result> { diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index 99cf00d3ce..a37508d267 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -3,7 +3,6 @@ use pathfinder_common::prelude::*; use pathfinder_common::trie::TrieNode; use pathfinder_common::BlockId; use pathfinder_crypto::Felt; -use pathfinder_merkle_tree::class::ClassStorageTree; use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -18,10 +17,9 @@ pub struct GetProofInput { } #[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct GetProofInputClass { +pub struct GetClassProofInput { pub block_id: BlockId, pub class_hash: ClassHash, - pub keys: Vec, } // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. @@ -161,7 +159,7 @@ pub struct GetProofOutput { #[derive(Debug, Serialize)] #[skip_serializing_none] -pub struct GetProofOutputClass { +pub struct GetClassProofOutput { /// Required to verify that the hash of the class commitment and the root of /// the [contract_proof](GetProofOutput::contract_proof) matches the /// [state_commitment](Self#state_commitment). Present only for Starknet @@ -302,16 +300,8 @@ pub async fn get_proof( /// particular contract. pub async fn get_proof_class( context: RpcContext, - input: GetProofInputClass, -) -> Result { - const MAX_KEYS: usize = 100; - if input.keys.len() > MAX_KEYS { - return Err(GetProofError::ProofLimitExceeded { - limit: MAX_KEYS as u32, - requested: input.keys.len() as u32, - }); - } - + input: GetClassProofInput, +) -> Result { let block_id = match input.block_id { BlockId::Pending => { return Err(GetProofError::Internal(anyhow!( @@ -357,28 +347,13 @@ pub async fn get_proof_class( .context("Fetching class root existence")?; if !class_root_exists { - return Ok(GetProofOutputClass { + return Ok(GetClassProofOutput { class_commitment, class_proof, }); }; - let mut class_proofs = Vec::new(); - for k in &input.keys { - let proof = ClassStorageTree::get_proof(&tx, header.number, k.view_bits()) - .context("Get proof from class tree")? - .ok_or_else(|| { - let e = anyhow!( - "Storage proof missing for key {:?}, but should be present", - k - ); - tracing::warn!("{e}"); - e - })?; - class_proofs.push(ProofNodes(proof)); - } - - Ok(GetProofOutputClass { + Ok(GetClassProofOutput { class_commitment, class_proof, }) From a894a11a504e93847bb75584a7c438e746f72937 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 00:55:40 +0200 Subject: [PATCH 05/13] fix: use class hash instead of casm class hash when generating class proof --- crates/merkle-tree/src/class.rs | 10 +--------- crates/rpc/src/pathfinder/methods/get_proof.rs | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 54c5cf5ffd..42abe82ce8 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -98,15 +98,7 @@ impl<'tx> ClassCommitmentTree<'tx> { block: Some(block), }; - let casm = tx - .casm_hash_at(block.into(), class_hash) - .context("Querying CASM hash")?; - - let Some(casm) = casm else { - return Ok(None); - }; - - MerkleTree::::get_proof(root, &storage, casm.view_bits()) + MerkleTree::::get_proof(root, &storage, class_hash.0.view_bits()) } } diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index a37508d267..ccf417b7d0 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -338,7 +338,7 @@ pub async fn get_proof_class( // Generate a proof for this class. If the class does not exist, this will // be a "non membership" proof. let class_proof = ClassCommitmentTree::get_proof(&tx, header.number, input.class_hash) - .context("Creating contract proof")? + .context("Creating class proof")? .ok_or(GetProofError::ProofMissing)?; let class_proof = ProofNodes(class_proof); From 1bd43fa7ab1078e303714c98c761218d55305ace Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 11:53:16 +0200 Subject: [PATCH 06/13] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a60d5d80..bb5a2d502f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pathfinder JSON-RPC extension methods are now also exposed on the `/rpc/pathfinder/v0_1` endpoint. - `--sync.l1-poll-interval` CLI option has been added to set the poll interval for L1 state. Defaults to 30s. +- Added the `pathfinder_getClassProof` endpoint to retrieve the Merkle proof of any class hash in the class trie. ## [0.14.1] - 2024-07-29 From f57246ec2a7f19fa438512dbfff3813ac0b68010 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:38:34 +0200 Subject: [PATCH 07/13] revert whitespace change in .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed158cfecf..c44283c5da 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ pathfinder-var.env .vscode/ # mdbook compilation -**/book/ +**/book/ \ No newline at end of file From 2c659a23ea593f4a25d803a9448db9e3636562f7 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:39:36 +0200 Subject: [PATCH 08/13] rustfmt --- crates/merkle-tree/src/class.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 42abe82ce8..d657bec59f 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -2,18 +2,11 @@ use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; -use pathfinder_common::{ - BlockNumber - , - ClassCommitment, - ClassCommitmentLeafHash, - ClassHash, - SierraHash - - , -}; use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; +use pathfinder_common::{ + BlockNumber, ClassCommitment, ClassCommitmentLeafHash, ClassHash, SierraHash, +}; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; From 8f8667f81621387d1ad050a42c0801782b1ab678 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:42:05 +0200 Subject: [PATCH 09/13] remove useless condition --- crates/rpc/src/pathfinder.rs | 2 +- crates/rpc/src/pathfinder/methods/get_proof.rs | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/rpc/src/pathfinder.rs b/crates/rpc/src/pathfinder.rs index f491080f6e..256e9a0f99 100644 --- a/crates/rpc/src/pathfinder.rs +++ b/crates/rpc/src/pathfinder.rs @@ -7,6 +7,6 @@ pub fn register_routes() -> RpcRouterBuilder { RpcRouter::builder(crate::RpcVersion::PathfinderV01) .register("pathfinder_version", || { pathfinder_common::consts::VERGEN_GIT_DESCRIBE }) .register("pathfinder_getProof", methods::get_proof) - .register("pathfinder_getTransactionStatus", methods::get_transaction_status) .register("pathfinder_getClassProof", methods::get_proof_class) + .register("pathfinder_getTransactionStatus", methods::get_transaction_status) } diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index ccf417b7d0..e4b9fad237 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -342,17 +342,6 @@ pub async fn get_proof_class( .ok_or(GetProofError::ProofMissing)?; let class_proof = ProofNodes(class_proof); - let class_root_exists = tx - .class_root_exists(header.number) - .context("Fetching class root existence")?; - - if !class_root_exists { - return Ok(GetClassProofOutput { - class_commitment, - class_proof, - }); - }; - Ok(GetClassProofOutput { class_commitment, class_proof, From 5753824414a5264e2c24f5e548c242803bc9d584 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 21 Aug 2024 13:48:50 +0200 Subject: [PATCH 10/13] rustfmt --- crates/merkle-tree/src/class.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index d657bec59f..b3cd3b4783 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,11 +1,14 @@ use anyhow::Context; use bitvec::order::Msb0; use bitvec::prelude::BitSlice; - use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{ - BlockNumber, ClassCommitment, ClassCommitmentLeafHash, ClassHash, SierraHash, + BlockNumber, + ClassCommitment, + ClassCommitmentLeafHash, + ClassHash, + SierraHash, }; use pathfinder_crypto::Felt; use pathfinder_storage::{Transaction, TrieUpdate}; From d3a36ed313aca75c34918435e4cbe6fdb32352c7 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Thu, 26 Sep 2024 10:57:12 -0700 Subject: [PATCH 11/13] remove ClassTrieStorage as was a duplication of ClassStorage --- crates/merkle-tree/src/class.rs | 40 +-------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index b3cd3b4783..265ae4fedd 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -89,7 +89,7 @@ impl<'tx> ClassCommitmentTree<'tx> { return Ok(None); }; - let storage = ClassTrieStorage { + let storage = ClassStorage { tx, block: Some(block), }; @@ -98,44 +98,6 @@ impl<'tx> ClassCommitmentTree<'tx> { } } -struct ClassTrieStorage<'tx> { - tx: &'tx Transaction<'tx>, - block: Option, -} - -impl crate::storage::Storage for ClassTrieStorage<'_> { - fn get(&self, index: u64) -> anyhow::Result> { - self.tx.class_trie_node(index) - } - - fn hash(&self, index: u64) -> anyhow::Result> { - self.tx.class_trie_node_hash(index) - } - - fn leaf(&self, path: &BitSlice) -> anyhow::Result> { - assert!(path.len() == 251); - - let Some(block) = self.block else { - return Ok(None); - }; - - let sierra = - ClassHash(Felt::from_bits(path).context("Mapping leaf path to contract address")?); - - let casm = self - .tx - .casm_hash_at(block.into(), sierra) - .context("Querying CASM hash")?; - let Some(casm) = casm else { - return Ok(None); - }; - - let value = self.tx.class_commitment_leaf(block, &casm)?.map(|x| x.0); - - Ok(value) - } -} - struct ClassStorage<'tx> { tx: &'tx Transaction<'tx>, block: Option, From 045cd7e81ed8b388112c4d5029c4c5b671fa4858 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Thu, 26 Sep 2024 11:38:57 -0700 Subject: [PATCH 12/13] implement DeserializeForVersion for GetClassProofInput --- crates/rpc/src/pathfinder/methods/get_proof.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/rpc/src/pathfinder/methods/get_proof.rs b/crates/rpc/src/pathfinder/methods/get_proof.rs index e05df4c6b7..96295d3fb6 100644 --- a/crates/rpc/src/pathfinder/methods/get_proof.rs +++ b/crates/rpc/src/pathfinder/methods/get_proof.rs @@ -35,6 +35,17 @@ impl crate::dto::DeserializeForVersion for GetProofInput { } } +impl crate::dto::DeserializeForVersion for GetClassProofInput { + fn deserialize(value: crate::dto::Value) -> Result { + value.deserialize_map(|value| { + Ok(Self { + block_id: value.deserialize("block_id")?, + class_hash: ClassHash(value.deserialize("class_hash")?), + }) + }) + } +} + // FIXME: allow `generate_rpc_error_subset!` to work with enum struct variants. // This may not actually be possible though. #[derive(Debug)] From 7968ba788b781799cbe00e6561d245a49871ed88 Mon Sep 17 00:00:00 2001 From: Herman Obst Demaestri Date: Wed, 2 Oct 2024 11:14:41 -0700 Subject: [PATCH 13/13] fix clippy --- crates/merkle-tree/src/class.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/merkle-tree/src/class.rs b/crates/merkle-tree/src/class.rs index 265ae4fedd..5aaf7966f5 100644 --- a/crates/merkle-tree/src/class.rs +++ b/crates/merkle-tree/src/class.rs @@ -1,6 +1,4 @@ use anyhow::Context; -use bitvec::order::Msb0; -use bitvec::prelude::BitSlice; use pathfinder_common::hash::PoseidonHash; use pathfinder_common::trie::TrieNode; use pathfinder_common::{