Skip to content

Commit a86ce6e

Browse files
starknet_os_runner: storage proof provider trait
1 parent b7bc6a3 commit a86ce6e

File tree

5 files changed

+195
-47
lines changed

5 files changed

+195
-47
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/starknet_os_runner/Cargo.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ description = "Runs transactions through the Starknet OS and returns Cairo PIE a
1010
blockifier.workspace = true
1111
blockifier_reexecution.workspace = true
1212
indexmap.workspace = true
13-
starknet_api.workspace = true
14-
starknet_os.workspace = true
15-
starknet_patricia.workspace = true
1613
starknet-rust.workspace = true
1714
starknet-rust-core.workspace = true
1815
starknet-types-core.workspace = true
16+
starknet_api.workspace = true
17+
starknet_os.workspace = true
18+
starknet_patricia.workspace = true
1919
thiserror.workspace = true
20+
tokio.workspace = true
2021
url.workspace = true
2122

23+
[dev-dependencies]
24+
blockifier = { workspace = true, features = ["testing"] }
25+
rstest.workspace = true
26+
2227
[lints]
2328
workspace = true

crates/starknet_os_runner/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ pub mod errors;
22
pub mod storage_proofs;
33
pub mod virtual_block_executor;
44

5+
#[cfg(test)]
6+
mod storage_proofs_test;
57
#[cfg(test)]
68
mod virtual_block_executor_test;

crates/starknet_os_runner/src/storage_proofs.rs

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use blockifier::state::cached_state::StateMaps;
44
use starknet_api::block::BlockNumber;
55
use starknet_api::core::{ClassHash, ContractAddress, Nonce};
66
use starknet_api::hash::HashOutput;
7-
use starknet_api::state::StorageKey;
8-
use starknet_os::io::os_input::{CachedStateInput, CommitmentInfo, CommitmentInfos};
7+
use starknet_os::io::os_input::{CommitmentInfo, StateCommitmentInfos};
98
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
109
flatten_preimages,
1110
Preimage,
@@ -20,9 +19,26 @@ use starknet_rust_core::types::{
2019
Felt,
2120
StorageProof as RpcStorageProof,
2221
};
23-
use starknet_types_core::felt::Felt as TypesFelt;
2422

2523
use crate::errors::ProofProviderError;
24+
use crate::virtual_block_executor::VirtualBlockExecutionData;
25+
26+
/// Provides Patricia Merkle proofs for the initial state used in transaction execution.
27+
///
28+
/// This trait abstracts the retrieval of storage proofs, which are essential for OS input
29+
/// generation. The proofs allow the OS to verify that the initial state values (read during
30+
/// execution) are consistent with the global state commitment (Patricia root).
31+
///
32+
/// The returned `StorageProofs` contains:
33+
/// - `proof_state`: The ambient state values (nonces, class hashes) discovered in the proof.
34+
/// - `commitment_infos`: The Patricia Merkle proof nodes for contracts, classes, and storage tries.
35+
pub trait StorageProofProvider {
36+
fn get_storage_proofs(
37+
&self,
38+
block_number: BlockNumber,
39+
execution_data: &VirtualBlockExecutionData,
40+
) -> Result<StorageProofs, ProofProviderError>;
41+
}
2642

2743
/// Query parameters for fetching storage proofs from RPC.
2844
pub struct RpcStorageProofsQuery {
@@ -33,8 +49,11 @@ pub struct RpcStorageProofsQuery {
3349

3450
/// Complete OS input data built from RPC proofs.
3551
pub struct StorageProofs {
36-
pub cached_state_input: CachedStateInput,
37-
pub commitment_infos: CommitmentInfos,
52+
/// State information discovered in the Patricia proof (nonces, class hashes)
53+
/// that might not have been explicitly read during transaction execution.
54+
/// This data is required by the OS to verify the contract state leaves.
55+
pub proof_state: StateMaps,
56+
pub commitment_infos: StateCommitmentInfos,
3857
}
3958

4059
/// Converts RPC merkle nodes (hash → MerkleNode mapping) to a PreimageMap.
@@ -55,12 +74,11 @@ impl RpcStorageProofsProvider {
5574
}
5675

5776
/// Extract query parameters from StateMaps.
58-
pub fn prepare_query(
59-
initial_reads: &StateMaps,
60-
executed_class_hashes: &HashSet<ClassHash>,
61-
) -> RpcStorageProofsQuery {
62-
let class_hashes: Vec<Felt> = executed_class_hashes.iter().map(|ch| ch.0).collect();
77+
pub fn prepare_query(execution_data: &VirtualBlockExecutionData) -> RpcStorageProofsQuery {
78+
let class_hashes: Vec<Felt> =
79+
execution_data.executed_class_hashes.iter().map(|ch| ch.0).collect();
6380

81+
let initial_reads = &execution_data.initial_reads;
6482
let contract_addresses: Vec<ContractAddress> =
6583
initial_reads.get_contract_addresses().into_iter().collect();
6684

@@ -107,49 +125,26 @@ impl RpcStorageProofsProvider {
107125
/// Converts an RPC storage proof response to OS input format.
108126
pub fn to_storage_proofs(
109127
rpc_proof: &RpcStorageProof,
110-
initial_reads: &StateMaps,
111128
contract_addresses: &[ContractAddress],
112129
) -> StorageProofs {
113-
let cached_state_input =
114-
Self::build_cached_state_input(rpc_proof, initial_reads, contract_addresses);
130+
let mut proof_state = StateMaps::default();
115131
let commitment_infos = Self::build_commitment_infos(rpc_proof, contract_addresses);
116132

117-
StorageProofs { cached_state_input, commitment_infos }
118-
}
119-
120-
fn build_cached_state_input(
121-
rpc_proof: &RpcStorageProof,
122-
initial_reads: &StateMaps,
123-
contract_addresses: &[ContractAddress],
124-
) -> CachedStateInput {
125-
let (address_to_class_hash, address_to_nonce) = rpc_proof
126-
.contracts_proof
127-
.contract_leaves_data
128-
.iter()
129-
.zip(contract_addresses)
130-
.map(|(leaf, addr)| ((*addr, ClassHash(leaf.class_hash)), (*addr, Nonce(leaf.nonce))))
131-
.unzip();
132-
133-
let storage = initial_reads.storage.iter().fold(
134-
HashMap::<ContractAddress, HashMap<StorageKey, TypesFelt>>::new(),
135-
|mut acc, ((addr, key), val)| {
136-
acc.entry(*addr).or_default().insert(*key, *val);
137-
acc
138-
},
139-
);
140-
141-
CachedStateInput {
142-
storage,
143-
address_to_class_hash,
144-
address_to_nonce,
145-
class_hash_to_compiled_class_hash: initial_reads.compiled_class_hashes.clone(),
133+
// Update proof_state with class hashes and nonces from the proof.
134+
for (leaf, addr) in
135+
rpc_proof.contracts_proof.contract_leaves_data.iter().zip(contract_addresses)
136+
{
137+
proof_state.class_hashes.insert(*addr, ClassHash(leaf.class_hash));
138+
proof_state.nonces.insert(*addr, Nonce(leaf.nonce));
146139
}
140+
141+
StorageProofs { proof_state, commitment_infos }
147142
}
148143

149144
fn build_commitment_infos(
150145
rpc_proof: &RpcStorageProof,
151146
contract_addresses: &[ContractAddress],
152-
) -> CommitmentInfos {
147+
) -> StateCommitmentInfos {
153148
let contracts_tree_root = HashOutput(rpc_proof.global_roots.contracts_tree_root);
154149
let classes_tree_root = HashOutput(rpc_proof.global_roots.classes_tree_root);
155150

@@ -174,7 +169,7 @@ impl RpcStorageProofsProvider {
174169
let storage_tries_commitment_infos =
175170
Self::build_storage_commitment_infos(rpc_proof, contract_addresses);
176171

177-
CommitmentInfos {
172+
StateCommitmentInfos {
178173
contracts_trie_commitment_info,
179174
classes_trie_commitment_info,
180175
storage_tries_commitment_infos,
@@ -219,3 +214,19 @@ impl RpcStorageProofsProvider {
219214
.collect()
220215
}
221216
}
217+
218+
impl StorageProofProvider for RpcStorageProofsProvider {
219+
fn get_storage_proofs(
220+
&self,
221+
block_number: BlockNumber,
222+
execution_data: &VirtualBlockExecutionData,
223+
) -> Result<StorageProofs, ProofProviderError> {
224+
let query = Self::prepare_query(execution_data);
225+
let contract_addresses = query.contract_addresses.clone();
226+
227+
let runtime = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
228+
let rpc_proof = runtime.block_on(self.fetch_proofs(block_number, &query))?;
229+
230+
Ok(Self::to_storage_proofs(&rpc_proof, &contract_addresses))
231+
}
232+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use std::collections::HashSet;
2+
use std::env;
3+
4+
use blockifier::context::BlockContext;
5+
use blockifier::state::cached_state::StateMaps;
6+
use rstest::{fixture, rstest};
7+
use starknet_api::block::BlockNumber;
8+
use starknet_api::core::ContractAddress;
9+
use starknet_api::state::StorageKey;
10+
use starknet_rust::providers::Provider;
11+
use starknet_types_core::felt::Felt;
12+
use url::Url;
13+
14+
use crate::storage_proofs::{RpcStorageProofsProvider, StorageProofProvider};
15+
use crate::virtual_block_executor::VirtualBlockExecutionData;
16+
17+
/// Mainnet STRK token contract address.
18+
const STRK_CONTRACT_ADDRESS: Felt =
19+
Felt::from_hex_unchecked("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d");
20+
21+
/// Fixture: Creates initial reads with the STRK contract and storage slot 0.
22+
#[fixture]
23+
fn initial_reads() -> (StateMaps, ContractAddress, StorageKey) {
24+
let mut state_maps = StateMaps::default();
25+
let contract_address = ContractAddress::try_from(STRK_CONTRACT_ADDRESS).unwrap();
26+
27+
// Add a storage read for slot 0 (commonly used for total_supply or similar).
28+
let storage_key = StorageKey::from(0u32);
29+
state_maps.storage.insert((contract_address, storage_key), Felt::ZERO);
30+
31+
(state_maps, contract_address, storage_key)
32+
}
33+
34+
/// Fixture: Creates an RPC provider from the RPC_URL environment variable.
35+
#[fixture]
36+
fn rpc_provider() -> RpcStorageProofsProvider {
37+
let rpc_url_str = env::var("NODE_URL").expect("NODE_URL environment variable must be set");
38+
let rpc_url = Url::parse(&rpc_url_str).expect("Invalid RPC URL");
39+
RpcStorageProofsProvider::new(rpc_url)
40+
}
41+
42+
/// Sanity test that verifies storage proof fetching works with a real RPC endpoint.
43+
///
44+
/// This test is ignored by default because it requires a running RPC node.
45+
/// Run with: `RPC_URL=<your_rpc_url> cargo test -p starknet_os_runner -- --ignored`
46+
#[rstest]
47+
#[ignore]
48+
fn test_get_storage_proofs_from_rpc(
49+
rpc_provider: RpcStorageProofsProvider,
50+
initial_reads: (StateMaps, ContractAddress, StorageKey),
51+
) {
52+
let (state_maps, contract_address, storage_key) = initial_reads;
53+
54+
// Fetch latest block number.
55+
let runtime = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
56+
let block_number = runtime.block_on(async { rpc_provider.0.block_number().await }).unwrap();
57+
58+
let execution_data = VirtualBlockExecutionData {
59+
execution_outputs: vec![],
60+
block_context: BlockContext::create_for_account_testing(),
61+
initial_reads: state_maps,
62+
executed_class_hashes: HashSet::new(),
63+
};
64+
65+
let result = rpc_provider.get_storage_proofs(BlockNumber(block_number), &execution_data);
66+
assert!(result.is_ok(), "Failed to get storage proofs: {:?}", result.err());
67+
68+
let storage_proofs = result.unwrap();
69+
70+
// Verify contracts tree root is non-zero.
71+
assert!(
72+
storage_proofs.commitment_infos.contracts_trie_commitment_info.previous_root.0
73+
!= Felt::ZERO,
74+
"Expected non-zero contracts tree root"
75+
);
76+
77+
// Verify contracts tree commitment facts are not empty.
78+
assert!(
79+
!storage_proofs.commitment_infos.contracts_trie_commitment_info.commitment_facts.is_empty(),
80+
"Expected non-empty contracts tree commitment facts"
81+
);
82+
83+
// Verify the queried contract is in proof_state.
84+
assert!(
85+
storage_proofs.proof_state.class_hashes.contains_key(&contract_address),
86+
"Expected contract address {:?} in class_hashes",
87+
contract_address
88+
);
89+
assert!(
90+
storage_proofs.proof_state.nonces.contains_key(&contract_address),
91+
"Expected contract address {:?} in nonces",
92+
contract_address
93+
);
94+
95+
// Verify the queried storage is in the original execution_data (not proof_state, which only has
96+
// nonces/hashes)
97+
assert!(
98+
execution_data.initial_reads.storage.contains_key(&(contract_address, storage_key)),
99+
"Expected storage key {:?} in contract's storage",
100+
storage_key
101+
);
102+
103+
// Verify the contract has a storage trie commitment info.
104+
assert!(
105+
storage_proofs
106+
.commitment_infos
107+
.storage_tries_commitment_infos
108+
.contains_key(&contract_address),
109+
"Expected contract address {:?} in storage_tries_commitment_infos",
110+
contract_address
111+
);
112+
113+
// Verify the storage trie commitment facts are not empty.
114+
let storage_commitment =
115+
&storage_proofs.commitment_infos.storage_tries_commitment_infos[&contract_address];
116+
assert!(
117+
!storage_commitment.commitment_facts.is_empty(),
118+
"Expected non-empty storage trie commitment facts for contract {:?}",
119+
contract_address
120+
);
121+
122+
// Verify the storage root is non-zero.
123+
assert!(
124+
storage_commitment.previous_root.0 != Felt::ZERO,
125+
"Expected non-zero storage root for contract {:?}",
126+
contract_address
127+
);
128+
}

0 commit comments

Comments
 (0)