Skip to content

Commit

Permalink
Merge pull request eqlabs#2179 from Moonsong-Labs/od/rpc/feat/get-cla…
Browse files Browse the repository at this point in the history
…ss-proof

feat(proof): pathfinder_getClassProof endpoint
  • Loading branch information
kkovaacs authored Oct 3, 2024
2 parents f7370dc + 1bdb9dc commit da0a0bf
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Pathfinder now fetches data concurrently from the feeder gateway when catching up. The `--gateway.fetch-concurrency` CLI option can be used to limit how many blocks are fetched concurrently (the default is 8).
- `--disable-version-update-check` CLI option has been added to disable the periodic checking for a new version.
- Add `pathfinder_getClassProof` endpoint to retrieve the Merkle proof of any class hash in the class trie.
- add `process_start_time_seconds` metric showing the unix timestamp when the process started.

### Changed
Expand Down
23 changes: 23 additions & 0 deletions crates/merkle-tree/src/class.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Context;
use pathfinder_common::hash::PoseidonHash;
use pathfinder_common::trie::TrieNode;
use pathfinder_common::{
BlockNumber,
ClassCommitment,
Expand Down Expand Up @@ -71,6 +72,28 @@ 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<Option<Vec<TrieNode>>> {
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::<PoseidonHash, 251>::get_proof(root, &storage, class_hash.0.view_bits())
}
}

struct ClassStorage<'tx> {
Expand Down
2 changes: 1 addition & 1 deletion crates/merkle-tree/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod merkle_node;
pub mod storage;
pub mod tree;

mod class;
pub mod class;
mod contract;
mod transaction;

Expand Down
1 change: 1 addition & 0 deletions crates/rpc/src/pathfinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +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_getClassProof", methods::get_proof_class)
.register("pathfinder_getTransactionStatus", methods::get_transaction_status)
}
2 changes: 1 addition & 1 deletion crates/rpc/src/pathfinder/methods.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod get_proof;
mod get_transaction_status;

pub(crate) use get_proof::get_proof;
pub(crate) use get_proof::{get_proof, get_proof_class};
pub(crate) use get_transaction_status::get_transaction_status;
88 changes: 86 additions & 2 deletions crates/rpc/src/pathfinder/methods/get_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +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 serde::Serialize;
use pathfinder_merkle_tree::{ClassCommitmentTree, ContractsStorageTree, StorageCommitmentTree};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;

use crate::context::RpcContext;
Expand All @@ -16,6 +16,12 @@ pub struct GetProofInput {
pub keys: Vec<StorageAddress>,
}

#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct GetClassProofInput {
pub block_id: BlockId,
pub class_hash: ClassHash,
}

impl crate::dto::DeserializeForVersion for GetProofInput {
fn deserialize(value: crate::dto::Value) -> Result<Self, serde_json::Error> {
value.deserialize_map(|value| {
Expand All @@ -29,6 +35,17 @@ impl crate::dto::DeserializeForVersion for GetProofInput {
}
}

impl crate::dto::DeserializeForVersion for GetClassProofInput {
fn deserialize(value: crate::dto::Value) -> Result<Self, serde_json::Error> {
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)]
Expand Down Expand Up @@ -164,6 +181,18 @@ pub struct GetProofOutput {
contract_data: Option<ContractData>,
}

#[derive(Debug, Serialize)]
#[skip_serializing_none]
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
/// blocks 0.11.0 onwards.
class_commitment: Option<ClassCommitment>,
/// 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(
Expand Down Expand Up @@ -291,6 +320,61 @@ 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: GetClassProofInput,
) -> Result<GetClassProofOutput, GetProofError> {
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 class proof")?
.ok_or(GetProofError::ProofMissing)?;
let class_proof = ProofNodes(class_proof);

Ok(GetClassProofOutput {
class_commitment,
class_proof,
})
});

jh.await.context("Database read panic or shutting down")?
}

#[cfg(test)]
mod tests {
use pathfinder_common::macro_prelude::*;
Expand Down

0 comments on commit da0a0bf

Please sign in to comment.