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

Add support to Pathfinder v0_8 #456

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/bin/output_segment/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ struct Args {
/// RPC endpoint to use for fact fetching
#[arg(long = "rpc-provider", default_value = "http://localhost:9545")]
rpc_provider: String,

/// RPC version to use for fact fetching
#[arg(long = "rpc-version", default_value = "v0_7")]
rpc_version: String,
}

fn init_logging() {
Expand Down Expand Up @@ -48,7 +52,7 @@ async fn main() {
log::info!("Runnin SNOS for block number: {}", block_number);

let (snos_pie, _snos_output) =
prove_block(DEFAULT_COMPILED_OS, block_number, &endpoint, LayoutName::all_cairo, true)
prove_block(DEFAULT_COMPILED_OS, block_number, &endpoint, &args.rpc_version, LayoutName::all_cairo, true)
.await
.map_err(debug_prove_error)
.expect("OS generate Cairo PIE");
Expand Down
12 changes: 9 additions & 3 deletions crates/bin/prove_block/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;

use blockifier::state::cached_state::CachedState;
use cairo_vm::types::layout_name::LayoutName;
Expand All @@ -8,6 +9,7 @@ use cairo_vm::vm::errors::cairo_run_errors::CairoRunError;
use cairo_vm::vm::runners::cairo_pie::CairoPie;
use cairo_vm::Felt252;
use reexecute::{reexecute_transactions_with_blockifier, ProverPerContractStorage};
use rpc_client::client::{ClientError, ClientVersion};
use rpc_client::pathfinder::proofs::{PathfinderClassProof, ProofVerificationError};
use rpc_client::RpcClient;
use rpc_replay::block_context::build_block_context;
Expand Down Expand Up @@ -68,6 +70,8 @@ pub enum ProveBlockError {
ToBlockifierError(#[from] ToBlockifierError),
#[error("Felt Conversion Error: {0}")]
FeltConversionError(#[from] FeltConversionError),
#[error("Client Error: {0}")]
ClientError(#[from] ClientError),
}

fn compute_class_commitment(
Expand Down Expand Up @@ -116,13 +120,15 @@ pub async fn prove_block(
compiled_os: &[u8],
block_number: u64,
rpc_provider: &str,
rpc_version: &str,
layout: LayoutName,
full_output: bool,
) -> Result<(CairoPie, StarknetOsOutput), ProveBlockError> {
let block_id = BlockId::Number(block_number);
let previous_block_id = BlockId::Number(block_number - 1);

let rpc_client = RpcClient::new(rpc_provider);
let client_version = ClientVersion::from_str(rpc_version)?;
let rpc_client = RpcClient::new(rpc_provider, client_version);

// Step 1: build the block context
let chain_id = chain_id_from_felt(rpc_client.starknet_rpc().chain_id().await?);
Expand Down Expand Up @@ -166,7 +172,8 @@ pub async fn prove_block(
let transactions: Vec<_> =
block_with_txs.transactions.clone().into_iter().map(starknet_rs_tx_to_internal_tx).collect();

let (processed_state_update, traces) = get_formatted_state_update(&rpc_client, previous_block_id, block_id).await?;
let (processed_state_update, traces) =
get_formatted_state_update(&rpc_client, previous_block_id, block_id, block_number).await?;

let class_hash_to_compiled_class_hash = processed_state_update.class_hash_to_compiled_class_hash;

Expand Down Expand Up @@ -209,7 +216,6 @@ pub async fn prove_block(
let mut contract_storages = ContractStorageMap::new();
let mut contract_address_to_class_hash = HashMap::new();

// TODO: remove this clone()
for (contract_address, storage_proof) in storage_proofs.clone() {
let previous_storage_proof =
previous_storage_proofs.get(&contract_address).expect("failed to find previous storage proof");
Expand Down
14 changes: 13 additions & 1 deletion crates/bin/prove_block/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ struct Args {
/// RPC endpoint to use for fact fetching
#[arg(long = "rpc-provider", default_value = "http://localhost:9545")]
rpc_provider: String,

/// RPC version to use for fact fetching
#[arg(long = "rpc-version", default_value = "v0_7")]
rpc_version: String,
}

fn init_logging() {
Expand All @@ -32,7 +36,15 @@ async fn main() {
let block_number = args.block_number;
let layout = LayoutName::all_cairo;

let result = prove_block::prove_block(DEFAULT_COMPILED_OS, block_number, &args.rpc_provider, layout, true).await;
let result = prove_block::prove_block(
DEFAULT_COMPILED_OS,
block_number,
&args.rpc_provider,
&args.rpc_version,
layout,
true,
)
.await;
let (pie, _snos_output) = result.map_err(debug_prove_error).expect("Block proven");
pie.run_validity_checks().expect("Valid PIE");
}
1 change: 0 additions & 1 deletion crates/bin/prove_block/src/reexecute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ impl PerContractStorage for ProverPerContractStorage {
None => &ContractData::default(),
Some(data) => data,
};

let updated_root = contract_data.root;

let commitment_facts = format_commitment_facts::<PedersenHash>(&contract_data.storage_proofs);
Expand Down
131 changes: 60 additions & 71 deletions crates/bin/prove_block/src/rpc_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use blockifier::transaction::objects::TransactionExecutionInfo;
use cairo_vm::Felt252;
use num_bigint::BigInt;
use rpc_client::pathfinder::client::ClientError;
use rpc_client::client::ClientError;
use rpc_client::pathfinder::proofs::{
ContractData, EdgePath, PathfinderClassProof, PathfinderProof, ProofVerificationError, TrieNode,
};
Expand All @@ -18,6 +18,59 @@ use starknet_types_core::felt::Felt;

use crate::utils::get_all_accessed_keys;

/// Fetches the storage proof for the specified contract and storage keys.
/// This function can fetch additional keys if required to fill gaps in the storage trie
/// that must be filled to get the OS to function. See `get_key_following_edge` for more details.
pub(crate) async fn get_storage_proofs(
client: &RpcClient,
block_number: u64,
tx_execution_infos: &[TransactionExecutionInfo],
old_block_number: Felt,
) -> Result<HashMap<Felt, PathfinderProof>, ClientError> {
let accessed_keys_by_address = {
let mut keys = get_all_accessed_keys(tx_execution_infos);
// We need to fetch the storage proof for the block hash contract
keys.entry(contract_address!("0x1")).or_default().insert(old_block_number.try_into().unwrap());
// Include extra keys for contracts that trigger get_block_hash_syscall
insert_extra_storage_reads_keys(old_block_number, &mut keys);
keys
};

let mut storage_proofs = HashMap::new();

log::info!("Contracts we're fetching proofs for:");
for (contract_address, storage_keys) in accessed_keys_by_address {
log::info!(" Fetching proof for {}", contract_address.to_string());
let contract_address_felt = *contract_address.key();
let keys: Vec<_> = storage_keys.into_iter().map(|storage_key| *storage_key.key()).collect();

let mut storage_proof =
fetch_storage_proof_for_contract(client, contract_address_felt, &keys, block_number).await?;

match &storage_proof.contract_data {
None => {
storage_proofs.insert(contract_address_felt, storage_proof);
}
Some(contract_data) => {
let additional_keys = verify_storage_proof(contract_data, &keys);

// Fetch additional proofs required to fill gaps in the storage trie that could make
// the OS crash otherwise.
if !additional_keys.is_empty() {
let additional_proof =
fetch_storage_proof_for_contract(client, contract_address_felt, &additional_keys, block_number)
.await?;

storage_proof = merge_storage_proofs(vec![storage_proof, additional_proof]);
}
storage_proofs.insert(contract_address_felt, storage_proof);
}
};
}

Ok(storage_proofs)
}

/// Fetches the state + storage proof for a single contract for all the specified keys.
/// This function handles the chunking of requests imposed by the RPC API and merges
/// the proofs returned from multiple calls into one.
Expand All @@ -28,56 +81,22 @@ async fn fetch_storage_proof_for_contract(
block_number: u64,
) -> Result<PathfinderProof, ClientError> {
let storage_proof = if keys.is_empty() {
rpc_client.pathfinder_rpc().get_proof(block_number, contract_address, &[]).await?
rpc_client.pathfinder_rpc().get_contract_proof(block_number, contract_address, &[]).await?
} else {
// The endpoint is limited to 100 keys at most per call
const MAX_KEYS: usize = 100;
// The endpoint is limited to 100 keys at most per call, including the contract address
const MAX_KEYS: usize = 99;
let mut chunked_storage_proofs = Vec::new();
for keys_chunk in keys.chunks(MAX_KEYS) {
chunked_storage_proofs
.push(rpc_client.pathfinder_rpc().get_proof(block_number, contract_address, keys_chunk).await?);
chunked_storage_proofs.push(
rpc_client.pathfinder_rpc().get_contract_proof(block_number, contract_address, keys_chunk).await?,
);
}
merge_storage_proofs(chunked_storage_proofs)
};

Ok(storage_proof)
}

/// Fetches the storage proof for the specified contract and storage keys.
/// This function can fetch additional keys if required to fill gaps in the storage trie
/// that must be filled to get the OS to function. See `get_key_following_edge` for more details.
async fn get_storage_proof_for_contract<KeyIter: Iterator<Item = StorageKey>>(
rpc_client: &RpcClient,
contract_address: ContractAddress,
storage_keys: KeyIter,
block_number: u64,
) -> Result<PathfinderProof, ClientError> {
let contract_address_felt = *contract_address.key();
let keys: Vec<_> = storage_keys.map(|storage_key| *storage_key.key()).collect();

let mut storage_proof =
fetch_storage_proof_for_contract(rpc_client, contract_address_felt, &keys, block_number).await?;

let contract_data = match &storage_proof.contract_data {
None => {
return Ok(storage_proof);
}
Some(contract_data) => contract_data,
};
let additional_keys = verify_storage_proof(contract_data, &keys);

// Fetch additional proofs required to fill gaps in the storage trie that could make
// the OS crash otherwise.
if !additional_keys.is_empty() {
let additional_proof =
fetch_storage_proof_for_contract(rpc_client, contract_address_felt, &additional_keys, block_number).await?;

storage_proof = merge_storage_proofs(vec![storage_proof, additional_proof]);
}

Ok(storage_proof)
}

/// Verify the storage proofs and handle errors.
/// Returns a list of additional keys to fetch to fill gaps in the tree that will make the OS
/// crash otherwise.
Expand Down Expand Up @@ -141,36 +160,6 @@ fn insert_extra_storage_reads_keys(
}
}

pub(crate) async fn get_storage_proofs(
client: &RpcClient,
block_number: u64,
tx_execution_infos: &[TransactionExecutionInfo],
old_block_number: Felt,
) -> Result<HashMap<Felt, PathfinderProof>, ClientError> {
let accessed_keys_by_address = {
let mut keys = get_all_accessed_keys(tx_execution_infos);
// We need to fetch the storage proof for the block hash contract
keys.entry(contract_address!("0x1")).or_default().insert(old_block_number.try_into().unwrap());
// Include extra keys for contracts that trigger get_block_hash_syscall
insert_extra_storage_reads_keys(old_block_number, &mut keys);
keys
};

let mut storage_proofs = HashMap::new();

log::info!("Contracts we're fetching proofs for:");
for (contract_address, storage_keys) in accessed_keys_by_address {
log::info!(" Fetching proof for {}", contract_address.to_string());
let contract_address_felt = *contract_address.key();
let storage_proof =
get_storage_proof_for_contract(client, contract_address, storage_keys.into_iter(), block_number).await?;

storage_proofs.insert(contract_address_felt, storage_proof);
}

Ok(storage_proofs)
}

/// Returns a modified key that follows the specified edge path.
/// This function is used to work around an issue where the OS fails if it encounters a
/// write to 0 and the last node in the storage proof is an edge node of length 1.
Expand Down
3 changes: 2 additions & 1 deletion crates/bin/prove_block/src/state_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(crate) async fn get_formatted_state_update(
rpc_client: &RpcClient,
previous_block_id: BlockId,
block_id: BlockId,
block_number: u64,
) -> Result<(FormattedStateUpdate, Vec<TransactionTraceWithHash>), ProveBlockError> {
let state_update =
match rpc_client.starknet_rpc().get_state_update(block_id).await.expect("Failed to get state update") {
Expand All @@ -45,7 +46,7 @@ pub(crate) async fn get_formatted_state_update(
// Extract other contracts used in our block from the block trace
// We need this to get all the class hashes used and correctly feed address_to_class_hash
let traces =
rpc_client.starknet_rpc().trace_block_transactions(block_id).await.expect("Failed to get block tx traces");
rpc_client.pathfinder_rpc().get_block_traces(block_number).await.expect("Failed to get block tx traces");
let (accessed_addresses, accessed_classes) = get_subcalled_contracts_from_tx_traces(&traces);

let declared_classes: HashSet<_> =
Expand Down
12 changes: 9 additions & 3 deletions crates/bin/prove_block/tests/hash_tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::str::FromStr;

use rpc_client::client::ClientVersion;
use rpc_client::pathfinder::proofs::ProofVerificationError;
use rpc_client::RpcClient;
use rstest::rstest;
Expand All @@ -19,7 +22,8 @@ async fn test_recompute_class_hash(#[case] class_hash_str: String, #[case] block
let class_hash = Felt::from_hex(&class_hash_str).unwrap();
let block_id = BlockId::Number(block_number);

let rpc_client = RpcClient::new(&endpoint);
let rpc_version = ClientVersion::from_str("v0_7").unwrap();
let rpc_client = RpcClient::new(&endpoint, rpc_version);
let contract_class = rpc_client.starknet_rpc().get_class(block_id, class_hash).await.unwrap();

let compiled_class = if let starknet::core::types::ContractClass::Legacy(legacy_cc) = contract_class {
Expand Down Expand Up @@ -48,7 +52,8 @@ async fn test_recompute_class_hash(#[case] class_hash_str: String, #[case] block
async fn test_class_proof_verification_non_inclusion(#[case] class_hash_str: String, #[case] block_number: u64) {
let endpoint = std::env::var("PATHFINDER_RPC_URL").expect("Missing PATHFINDER_RPC_URL in env");
let class_hash = Felt::from_hex(&class_hash_str).unwrap();
let rpc_client = RpcClient::new(&endpoint);
let rpc_version = ClientVersion::from_str("v0_7").unwrap();
let rpc_client = RpcClient::new(&endpoint, rpc_version);

let class_proof = rpc_client.pathfinder_rpc().get_class_proof(block_number, &class_hash).await.unwrap();
let result = class_proof.verify(class_hash);
Expand All @@ -72,7 +77,8 @@ async fn test_class_proof_verification_non_inclusion(#[case] class_hash_str: Str
async fn test_class_proof_verification_ok(#[case] class_hash_str: String, #[case] block_number: u64) {
let endpoint = std::env::var("PATHFINDER_RPC_URL").expect("Missing PATHFINDER_RPC_URL in env");
let class_hash = Felt::from_hex(&class_hash_str).unwrap();
let rpc_client = RpcClient::new(&endpoint);
let rpc_version = ClientVersion::from_str("v0_7").unwrap();
let rpc_client = RpcClient::new(&endpoint, rpc_version);

let class_proof = rpc_client.pathfinder_rpc().get_class_proof(block_number, &class_hash).await.unwrap();
assert!(class_proof.verify(class_hash).is_ok());
Expand Down
Loading
Loading