Skip to content

Commit f012c83

Browse files
yoavGrsclaude
andauthored
apollo_storage,starknet_api: add helper to build declared class component hashes for a block (#14460)
Add `get_declared_class_hash_to_component_hashes`, mapping each Cairo 1 class freshly declared in a block to its contract class component hashes (matching the OS block input). Classes whose compiled class hash was merely migrated in the block (declared in an earlier block) are excluded. The helper takes `impl StateStorageReader + ClassStorageReader` rather than a concrete transaction. Derive `Eq`/`PartialEq` for `ContractClassComponentHashes` so the test can assert on the returned map directly. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6fdd21e commit f012c83

3 files changed

Lines changed: 112 additions & 4 deletions

File tree

crates/apollo_storage/src/class.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,23 @@
6868
#[path = "class_test.rs"]
6969
mod class_test;
7070

71+
use std::collections::HashMap;
72+
7173
use apollo_proc_macros::latency_histogram;
7274
use starknet_api::block::BlockNumber;
7375
use starknet_api::core::ClassHash;
7476
use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass;
75-
use starknet_api::state::SierraContractClass;
77+
use starknet_api::state::{ContractClassComponentHashes, SierraContractClass};
7678

7779
use crate::db::table_types::Table;
7880
use crate::db::RW;
7981
use crate::mmap_file::LocationInFile;
80-
use crate::state::{DeclaredClassesTable, DeprecatedDeclaredClassesTable, FileOffsetTable};
82+
use crate::state::{
83+
DeclaredClassesTable,
84+
DeprecatedDeclaredClassesTable,
85+
FileOffsetTable,
86+
StateStorageReader,
87+
};
8188
use crate::{
8289
DbTransaction,
8390
FileHandlers,
@@ -87,6 +94,7 @@ use crate::{
8794
StorageError,
8895
StorageResult,
8996
StorageTransaction,
97+
TransactionKind,
9098
};
9199

92100
/// Interface for reading data related to classes or deprecated classes.
@@ -205,6 +213,45 @@ impl<T: StorageTransaction> ClassStorageReader for T {
205213
}
206214
}
207215

216+
/// Builds the map from each Cairo 1 class hash declared in `block_number` to its contract class
217+
/// component hashes, matching the `declared_class_hash_to_component_hashes` field of the OS block
218+
/// input. This excludes classes whose compiled class hash was merely migrated in `block_number`.
219+
pub fn get_declared_class_hash_to_component_hashes<Mode: TransactionKind>(
220+
reader: &(impl StateStorageReader<Mode> + ClassStorageReader),
221+
block_number: BlockNumber,
222+
) -> StorageResult<HashMap<ClassHash, ContractClassComponentHashes>> {
223+
let state_diff = reader.get_state_diff(block_number)?.ok_or(StorageError::MissingObject {
224+
object_name: "state diff".to_string(),
225+
height: block_number,
226+
})?;
227+
let state_reader = reader.get_state_reader()?;
228+
229+
let mut declared_sierra_classes = Vec::new();
230+
// Read all Sierra classes declared in this block from storage.
231+
for class_hash in state_diff.class_hash_to_compiled_class_hash.keys() {
232+
// `class_hash_to_compiled_class_hash` merges fresh declarations with migrated classes,
233+
// while `declared_classes_block` records each class's original declaration block.
234+
// Only classes first declared in this block contribute component hashes.
235+
if state_reader.get_class_definition_block_number(class_hash)? != Some(block_number) {
236+
// A migrated class that was declared in an earlier block.
237+
continue;
238+
}
239+
let sierra_class = reader.get_class(class_hash)?.ok_or(StorageError::DBInconsistency {
240+
msg: format!(
241+
"Class {class_hash} is declared in block {} but its Sierra class is missing from \
242+
storage.",
243+
block_number.0
244+
),
245+
})?;
246+
declared_sierra_classes.push((*class_hash, sierra_class));
247+
}
248+
249+
Ok(declared_sierra_classes
250+
.into_iter()
251+
.map(|(class_hash, sierra_class)| (class_hash, sierra_class.get_component_hashes()))
252+
.collect())
253+
}
254+
208255
impl<T: StorageTransaction<Mode = RW>> ClassStorageWriter for T {
209256
#[latency_histogram("storage_append_classes_latency_seconds", false)]
210257
fn append_classes(

crates/apollo_storage/src/class_test.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
use assert_matches::assert_matches;
24
use indexmap::indexmap;
35
use pretty_assertions::assert_eq;
@@ -9,7 +11,7 @@ use starknet_api::hash::StarkHash;
911
use starknet_api::state::{SierraContractClass, StateNumber, ThinStateDiff};
1012
use starknet_api::test_utils::read_json_file;
1113

12-
use super::{ClassStorageReader, ClassStorageWriter};
14+
use super::{get_declared_class_hash_to_component_hashes, ClassStorageReader, ClassStorageWriter};
1315
use crate::state::{StateStorageReader, StateStorageWriter};
1416
use crate::test_utils::get_test_storage;
1517
use crate::{MarkerKind, StorageError};
@@ -119,3 +121,62 @@ fn append_deprecated_class_not_in_state_diff() {
119121
expected_deprecated_class
120122
);
121123
}
124+
125+
/// Verifies that `get_declared_class_hash_to_component_hashes` returns the component hashes of
126+
/// classes freshly declared in a block.
127+
#[test]
128+
fn test_declared_class_hash_to_component_hashes() {
129+
let class_a = SierraContractClass::default();
130+
let class_b: SierraContractClass = read_json_file("class.json");
131+
let class_hash_a = ClassHash::default();
132+
let class_hash_b = ClassHash(StarkHash::ONE);
133+
134+
let ((reader, mut writer), _temp_dir) = get_test_storage();
135+
136+
writer
137+
.begin_rw_txn()
138+
.unwrap()
139+
// Block 0: class A is freshly declared.
140+
.append_state_diff(
141+
BlockNumber(0),
142+
ThinStateDiff {
143+
class_hash_to_compiled_class_hash: indexmap! {
144+
class_hash_a => compiled_class_hash!(1_u8),
145+
},
146+
..Default::default()
147+
},
148+
)
149+
.unwrap()
150+
.append_classes(BlockNumber(0), &[(class_hash_a, &class_a)], &[])
151+
.unwrap()
152+
// Block 1: class B is freshly declared, while class A's compiled class hash is migrated.
153+
.append_state_diff(
154+
BlockNumber(1),
155+
ThinStateDiff {
156+
class_hash_to_compiled_class_hash: indexmap! {
157+
class_hash_a => compiled_class_hash!(2_u8),
158+
class_hash_b => compiled_class_hash!(3_u8),
159+
},
160+
..Default::default()
161+
},
162+
)
163+
.unwrap()
164+
.append_classes(BlockNumber(1), &[(class_hash_b, &class_b)], &[])
165+
.unwrap()
166+
.commit()
167+
.unwrap();
168+
169+
let txn = reader.begin_ro_txn().unwrap();
170+
171+
// Block 0: only the freshly declared class A.
172+
assert_eq!(
173+
get_declared_class_hash_to_component_hashes(&txn, BlockNumber(0)).unwrap(),
174+
HashMap::from([(class_hash_a, class_a.get_component_hashes())]),
175+
);
176+
177+
// Block 1: only the freshly declared class B; the migrated class A is excluded.
178+
assert_eq!(
179+
get_declared_class_hash_to_component_hashes(&txn, BlockNumber(1)).unwrap(),
180+
HashMap::from([(class_hash_b, class_b.get_component_hashes())]),
181+
);
182+
}

crates/starknet_api/src/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ impl From<FlattenedSierraClass> for SierraContractClass {
290290
}
291291
}
292292

293-
#[derive(Clone, Debug, Deserialize)]
293+
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
294294
pub struct ContractClassComponentHashes {
295295
pub contract_class_version: Felt,
296296
pub external_functions_hash: PoseidonHash,

0 commit comments

Comments
 (0)