Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: re-add class_commitment to pathfinder_getProof output #2491

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 88 additions & 8 deletions crates/rpc/src/pathfinder/methods/get_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,12 @@ impl crate::dto::SerializeForVersion for ProofNodes {
let mut s = serializer.serialize_struct()?;
match self.0 {
TrieNode::Binary { left, right } => {
s.serialize_field("type", &"binary")?;
s.serialize_field("left", left)?;
s.serialize_field("right", right)?;
let mut inner = serializer.serialize_struct()?;
inner.serialize_field("left", left)?;
inner.serialize_field("right", right)?;
let inner = inner.end()?;

s.serialize_field("binary", &inner)?;
}
TrieNode::Edge { child, path } => {
let value = Felt::from_bits(path).unwrap();
Expand All @@ -143,9 +146,12 @@ impl crate::dto::SerializeForVersion for ProofNodes {
len: path.len(),
};

s.serialize_field("type", &"edge")?;
s.serialize_field("path", &path)?;
s.serialize_field("child", child)?;
let mut inner = serializer.serialize_struct()?;
inner.serialize_field("path", &path)?;
inner.serialize_field("child", child)?;
let inner = inner.end()?;

s.serialize_field("edge", &inner)?;
}
}
s.end()
Expand Down Expand Up @@ -208,6 +214,11 @@ pub struct GetProofOutput {
/// [contract_proof](GetProofOutput#contract_proof) is the global state
/// commitment.
state_commitment: Option<StateCommitment>,
/// 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
contract_proof: ProofNodes,
Expand All @@ -222,7 +233,8 @@ impl crate::dto::SerializeForVersion for GetProofOutput {
serializer: crate::dto::Serializer,
) -> Result<crate::dto::Ok, crate::dto::Error> {
let mut serializer = serializer.serialize_struct()?;
serializer.serialize_optional("state_commitment", self.state_commitment)?;
serializer.serialize_optional_with_null("state_commitment", self.state_commitment)?;
serializer.serialize_optional_with_null("class_commitment", self.class_commitment)?;
serializer.serialize_field("contract_proof", &self.contract_proof)?;
serializer.serialize_optional("contract_data", self.contract_data.clone())?;
serializer.end()
Expand All @@ -231,6 +243,11 @@ impl crate::dto::SerializeForVersion for GetProofOutput {

#[derive(Debug, PartialEq)]
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,
}
Expand All @@ -241,6 +258,7 @@ impl crate::dto::SerializeForVersion for GetClassProofOutput {
serializer: crate::dto::Serializer,
) -> Result<crate::dto::Ok, crate::dto::Error> {
let mut serializer = serializer.serialize_struct()?;
serializer.serialize_optional_with_null("class_commitment", self.class_commitment)?;
serializer.serialize_field("class_proof", &self.class_proof)?;
serializer.end()
}
Expand Down Expand Up @@ -295,6 +313,9 @@ pub async fn get_proof(
let storage_root_idx = tx
.storage_root_index(header.number)
.context("Querying storage root index")?;
let class_commitment = tx
.class_root(header.number)
.context("Querying class commitment")?;

let Some(storage_root_idx) = storage_root_idx else {
if tx.trie_pruning_enabled() {
Expand All @@ -306,6 +327,7 @@ pub async fn get_proof(
// An empty proof is then a proof of non-membership in an empty block.
return Ok(GetProofOutput {
state_commitment,
class_commitment,
contract_proof: ProofNodes(vec![]),
contract_data: None,
});
Expand Down Expand Up @@ -333,6 +355,7 @@ pub async fn get_proof(
if contract_state_hash.is_none() {
return Ok(GetProofOutput {
state_commitment,
class_commitment,
contract_proof,
contract_data: None,
});
Expand Down Expand Up @@ -388,6 +411,7 @@ pub async fn get_proof(

Ok(GetProofOutput {
state_commitment,
class_commitment,
contract_proof,
contract_data: Some(contract_data),
})
Expand Down Expand Up @@ -442,11 +466,17 @@ pub async fn get_class_proof(
// - or all leaves were removed resulting in an empty trie
// An empty proof is then a proof of non-membership in an empty block.
return Ok(GetClassProofOutput {
class_commitment: None,
class_proof: ProofNodes(vec![]),
});
}
};

let class_commitment = tx
.class_trie_node_hash(class_root_idx)
.context("Querying class trie root")?
.map(ClassCommitment);

// Generate a proof for this class. If the class does not exist, this will
// be a "non membership" proof.
let class_proof =
Expand All @@ -457,7 +487,10 @@ pub async fn get_class_proof(

let class_proof = ProofNodes(class_proof);

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

jh.await.context("Database read panic or shutting down")?
Expand All @@ -472,6 +505,52 @@ mod tests {

use super::*;

mod serialization {
use bitvec::prelude::*;

use super::*;
use crate::dto::SerializeForVersion;

#[test]
fn serialize_proof_nodes() {
let nodes = ProofNodes(vec![
TrieNode::Binary {
left: Felt::from_u64(0),
right: Felt::from_u64(1),
},
TrieNode::Edge {
child: Felt::from_u64(2),
path: bitvec::bitvec![u8, Msb0; 1, 1],
},
]);
let actual = nodes
.serialize(crate::dto::Serializer {
version: crate::RpcVersion::default(),
})
.unwrap();
let expected = serde_json::json!(
[
{
"binary": {
"left": "0x0",
"right": "0x1",
}
},
{
"edge": {
"path": {
"value": "0x3",
"len": 2,
},
"child": "0x2",
}
},
]
);
assert_eq!(actual, expected);
}
}

mod get_proof {
use super::*;

Expand Down Expand Up @@ -678,6 +757,7 @@ mod tests {
assert_eq!(
output,
GetClassProofOutput {
class_commitment: None,
class_proof: ProofNodes(vec![])
}
);
Expand Down
14 changes: 14 additions & 0 deletions crates/storage/src/connection/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ impl Transaction<'_> {
.map_err(Into::into)
}

pub fn class_root(&self, block_number: BlockNumber) -> anyhow::Result<Option<ClassCommitment>> {
self.inner()
.query_row(
r"SELECT hash FROM trie_class WHERE idx = (
SELECT root_index FROM class_roots WHERE block_number <= ? ORDER BY block_number DESC LIMIT 1
)",
params![&block_number],
|row| row.get_optional_class_commitment(0),
)
.optional()
.map(|x| x.flatten())
.map_err(Into::into)
}

pub fn class_root_exists(&self, block_number: BlockNumber) -> anyhow::Result<bool> {
self.inner()
.query_row(
Expand Down
7 changes: 7 additions & 0 deletions crates/storage/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ pub trait RowExt {
Ok(self.get_optional_felt(index)?.map(StateCommitment))
}

fn get_optional_class_commitment<Index: RowIndex>(
&self,
index: Index,
) -> rusqlite::Result<Option<ClassCommitment>> {
Ok(self.get_optional_felt(index)?.map(ClassCommitment))
}

fn get_block_number<Index: RowIndex>(&self, index: Index) -> rusqlite::Result<BlockNumber> {
let num = self.get_i64(index)?;
// Always safe since we are fetching an i64
Expand Down
Loading