Skip to content

Commit 676e751

Browse files
apollo_integration_tests: verify Patricia storage proofs of persisted witnesses in flow tests
1 parent 3d19aeb commit 676e751

4 files changed

Lines changed: 91 additions & 0 deletions

File tree

Cargo.lock

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

crates/apollo_integration_tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ serde_json.workspace = true
7777
starknet-types-core.workspace = true
7878
starknet_api.workspace = true
7979
starknet_committer.workspace = true
80+
starknet_patricia = { workspace = true, features = ["testing"] }
8081
starknet_patricia_storage.workspace = true
8182
strum.workspace = true
8283
tempfile.workspace = true

crates/apollo_integration_tests/src/flow_test_setup.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use starknet_api::consensus_transaction::ConsensusTransaction;
5050
use starknet_api::core::{ChainId, ContractAddress};
5151
use starknet_api::execution_resources::GasAmount;
5252
use starknet_api::rpc_transaction::RpcTransaction;
53+
use starknet_api::state::ThinStateDiff;
5354
use starknet_api::transaction::{
5455
L1HandlerTransaction,
5556
TransactionHash,
@@ -425,6 +426,27 @@ impl FlowSequencerSetup {
425426
compressed_infos.decompress().expect("stored state commitment infos decompress")
426427
})
427428
}
429+
430+
pub async fn get_thin_state_diff(&self, block_number: BlockNumber) -> ThinStateDiff {
431+
let response = self
432+
.send_batcher_storage_reader_request(StorageReaderRequest::StateDiffsLocation(
433+
block_number,
434+
))
435+
.await;
436+
let state_diff_location = match response {
437+
StorageReaderResponse::StateDiffsLocation(location) => location,
438+
other => panic!("Expected StateDiffsLocation response, got: {other:?}"),
439+
};
440+
let response = self
441+
.send_batcher_storage_reader_request(StorageReaderRequest::StateDiffsFromLocation(
442+
state_diff_location,
443+
))
444+
.await;
445+
match response {
446+
StorageReaderResponse::StateDiffsFromLocation(thin_state_diff) => thin_state_diff,
447+
other => panic!("Expected StateDiffsFromLocation response, got: {other:?}"),
448+
}
449+
}
428450
}
429451

430452
pub fn create_consensus_manager_configs_and_channels(

crates/apollo_integration_tests/src/utils.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,12 @@ use serde_json::{json, to_value};
120120
use starknet_api::block::BlockNumber;
121121
use starknet_api::core::{ChainId, ContractAddress};
122122
use starknet_api::execution_resources::GasAmount;
123+
use starknet_api::hash::HashOutput;
123124
use starknet_api::rpc_transaction::{RpcInvokeTransaction, RpcTransaction};
124125
use starknet_api::staking::StakingWeight;
125126
use starknet_api::transaction::fields::{ContractAddressSalt, Proof, ProofFacts};
126127
use starknet_api::transaction::{L1HandlerTransaction, TransactionHash, TransactionHasher};
128+
use starknet_committer::block_committer::input::StarknetStorageValue;
127129
use starknet_committer::db::forest_trait::{
128130
ForestMetadata,
129131
ForestMetadataType,
@@ -132,7 +134,11 @@ use starknet_committer::db::forest_trait::{
132134
};
133135
use starknet_committer::db::index_db::IndexDb;
134136
use starknet_committer::db::serde_db_utils::DbBlockNumber;
137+
use starknet_committer::hash_function::hash::TreeHashFunctionImpl;
135138
use starknet_committer::patricia_merkle_tree::types::StateCommitmentInfos;
139+
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{Preimage, PreimageMap};
140+
use starknet_patricia::patricia_merkle_tree::storage_proof_verification::verify_patricia_proof;
141+
use starknet_patricia::patricia_merkle_tree::types::NodeIndex;
136142
use starknet_patricia_storage::storage_trait::{DbOperation, DbValue};
137143
use starknet_types_core::felt::Felt;
138144
use tokio::net::TcpListener;
@@ -1273,9 +1279,70 @@ pub async fn end_to_end_flow(args: EndToEndFlowArgs) {
12731279
);
12741280
verify_block_hash_flow(&sequencers, scenario_timeout).await;
12751281
verify_witnesses_flow(&sequencers).await;
1282+
verify_witness_storage_proofs_flow(&sequencers).await;
12761283
verify_recorder_blobs_flow(&sequencers, scenario_timeout).await;
12771284
}
12781285

1286+
/// Verifies that for every contract with storage writes in a block's state diff, the persisted
1287+
/// witnesses contain valid Patricia paths from the updated storage root to every written leaf.
1288+
async fn verify_witness_storage_proofs_flow(sequencers: &[&FlowSequencerSetup]) {
1289+
for sequencer in sequencers {
1290+
let global_root_height = sequencer.get_global_root_height().await;
1291+
for block_number in (0..global_root_height.0).map(BlockNumber) {
1292+
let Some(state_commitment_infos) =
1293+
sequencer.get_state_commitment_infos(block_number).await
1294+
else {
1295+
// Heights committed without witnesses (e.g. seeded genesis) have nothing to prove.
1296+
continue;
1297+
};
1298+
let thin_state_diff = sequencer.get_thin_state_diff(block_number).await;
1299+
for (contract_address, storage_writes) in &thin_state_diff.storage_diffs {
1300+
let commitment_info = state_commitment_infos
1301+
.storage_tries_commitment_infos
1302+
.get(contract_address)
1303+
.unwrap_or_else(|| {
1304+
panic!(
1305+
"Block {block_number}: contract {contract_address} has storage writes \
1306+
but no storage-trie witnesses."
1307+
)
1308+
});
1309+
let preimages: PreimageMap = commitment_info
1310+
.commitment_facts
1311+
.iter()
1312+
.map(|(fact_hash, raw_preimage)| {
1313+
let preimage = Preimage::try_from(raw_preimage).unwrap_or_else(|err| {
1314+
panic!(
1315+
"Block {block_number}: invalid preimage for fact {fact_hash:?}: \
1316+
{err:?}"
1317+
)
1318+
});
1319+
(*fact_hash, preimage)
1320+
})
1321+
.collect();
1322+
// A zero value is an absent leaf, proved by path structure rather than leaf hash.
1323+
let requested_leaves: HashMap<NodeIndex, HashOutput> = storage_writes
1324+
.iter()
1325+
.filter(|(_, value)| **value != Felt::ZERO)
1326+
.map(|(key, value)| {
1327+
(NodeIndex::from_leaf_felt(key.0.key()), HashOutput(*value))
1328+
})
1329+
.collect();
1330+
verify_patricia_proof::<StarknetStorageValue, TreeHashFunctionImpl>(
1331+
commitment_info.updated_root,
1332+
&preimages,
1333+
&requested_leaves,
1334+
)
1335+
.unwrap_or_else(|err| {
1336+
panic!(
1337+
"Block {block_number}: witness storage proof failed for contract \
1338+
{contract_address}: {err:?}"
1339+
)
1340+
});
1341+
}
1342+
}
1343+
}
1344+
}
1345+
12791346
/// Verifies that the dummy recorders accepted every cende blob and that state commitment infos
12801347
/// were carried in at least one blob.
12811348
async fn verify_recorder_blobs_flow(

0 commit comments

Comments
 (0)