Skip to content

Commit 1c2ef14

Browse files
apollo_storage: add state_commitment_infos table
1 parent 62c30ea commit 1c2ef14

8 files changed

Lines changed: 229 additions & 3 deletions

File tree

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/apollo_storage/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license-file.workspace = true
77
description = "A storage implementation for a Starknet node."
88

99
[features]
10-
os_input = ["dep:blockifier"]
10+
os_input = ["dep:blockifier", "dep:starknet_committer"]
1111
storage_cli = ["clap", "reqwest"]
1212
storage_validation = ["clap"]
1313
testing = ["reqwest", "starknet_api/testing", "tempfile", "tower"]
@@ -18,6 +18,7 @@ apollo_metrics.workspace = true
1818
apollo_proc_macros.workspace = true
1919
async-trait.workspace = true
2020
axum.workspace = true
21+
bincode.workspace = true
2122
blockifier = { workspace = true, features = ["transaction_serde"], optional = true }
2223
byteorder.workspace = true
2324
cairo-lang-casm = { workspace = true, features = ["parity-scale-codec"] }
@@ -41,6 +42,7 @@ serde = { workspace = true, features = ["derive"] }
4142
serde_json = { workspace = true, features = ["arbitrary_precision"] }
4243
starknet-types-core = { workspace = true, features = ["papyrus-serialization"] }
4344
starknet_api.workspace = true
45+
starknet_committer = { workspace = true, optional = true }
4446
tempfile = { workspace = true, optional = true }
4547
thiserror.workspace = true
4648
tokio = { workspace = true, features = ["rt-multi-thread"] }
@@ -70,6 +72,7 @@ rstest.workspace = true
7072
schemars.workspace = true
7173
simple_logger.workspace = true
7274
starknet_api = { workspace = true, features = ["testing"] }
75+
starknet_committer = { workspace = true, features = ["testing"] }
7376
tempfile = { workspace = true }
7477
test-case.workspace = true
7578
test-log.workspace = true

crates/apollo_storage/src/db/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::db::table_types::TableType;
4646
#[cfg(not(feature = "os_input"))]
4747
const MAX_DBS: u64 = 25;
4848
#[cfg(feature = "os_input")]
49-
const MAX_DBS: u64 = 26;
49+
const MAX_DBS: u64 = 27;
5050

5151
// Note that NO_TLS mode is used by default.
5252
type EnvironmentKind = WriteMap;

crates/apollo_storage/src/db/serialization.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ pub enum StorageSerdeError {
166166
Io(#[from] std::io::Error),
167167
#[error(transparent)]
168168
Serde(#[from] serde_json::Error),
169+
#[error(transparent)]
170+
Bincode(#[from] bincode::Error),
169171
/// An error occurred during migration.
170172
#[error("Failed to migrate value")]
171173
Migration,

crates/apollo_storage/src/lib.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ pub mod global_root_marker;
9191
#[allow(missing_docs)]
9292
pub mod metrics;
9393
pub mod partial_block_hash;
94+
#[cfg(feature = "os_input")]
95+
pub mod state_commitment_infos;
9496
pub mod storage_metrics;
9597
// TODO(yair): Make the compression_utils module pub(crate) or extract it from the crate.
9698
#[doc(hidden)]
@@ -186,6 +188,8 @@ use crate::header::StorageBlockHeader;
186188
use crate::metrics::{register_metrics, STORAGE_COMMIT_LATENCY};
187189
use crate::mmap_file::MMapFileStats;
188190
use crate::state::data::IndexedDeprecatedContractClass;
191+
#[cfg(feature = "os_input")]
192+
use crate::state_commitment_infos::StateCommitmentInfos;
189193
use crate::storage_reader_server::{
190194
create_storage_reader_server,
191195
ServerConfig,
@@ -280,6 +284,8 @@ fn open_storage_internal(
280284
.create_simple_table("stateless_compiled_class_hash_v2")?,
281285
#[cfg(feature = "os_input")]
282286
accessed_keys: db_writer.create_simple_table("accessed_keys")?,
287+
#[cfg(feature = "os_input")]
288+
state_commitment_infos: db_writer.create_simple_table("state_commitment_infos")?,
283289
});
284290
let (file_writers, file_readers) = open_storage_files(
285291
&storage_config.db_config,
@@ -993,7 +999,9 @@ struct_field_names! {
993999
stateless_compiled_class_hash_v2: TableIdentifier<ClassHash, NoVersionValueWrapper<CompiledClassHash>, SimpleTable>,
9941000

9951001
#[cfg(feature = "os_input")]
996-
accessed_keys: TableIdentifier<BlockNumber, VersionZeroWrapper<LocationInFile>, SimpleTable>
1002+
accessed_keys: TableIdentifier<BlockNumber, VersionZeroWrapper<LocationInFile>, SimpleTable>,
1003+
#[cfg(feature = "os_input")]
1004+
state_commitment_infos: TableIdentifier<BlockNumber, VersionZeroWrapper<LocationInFile>, SimpleTable>
9971005
}
9981006
}
9991007

@@ -1164,6 +1172,8 @@ struct FileHandlers<Mode: TransactionKind> {
11641172
transaction: FileHandler<VersionZeroWrapper<Transaction>, Mode>,
11651173
#[cfg(feature = "os_input")]
11661174
accessed_keys: FileHandler<VersionZeroWrapper<AccessedKeys>, Mode>,
1175+
#[cfg(feature = "os_input")]
1176+
state_commitment_infos: FileHandler<VersionZeroWrapper<StateCommitmentInfos>, Mode>,
11671177
}
11681178

11691179
impl FileHandlers<RW> {
@@ -1206,6 +1216,14 @@ impl FileHandlers<RW> {
12061216
self.clone().accessed_keys.append(accessed_keys)
12071217
}
12081218

1219+
#[cfg(feature = "os_input")]
1220+
fn append_state_commitment_infos(
1221+
&self,
1222+
state_commitment_infos: &StateCommitmentInfos,
1223+
) -> LocationInFile {
1224+
self.clone().state_commitment_infos.append(state_commitment_infos)
1225+
}
1226+
12091227
// TODO(dan): Consider 1. flushing only the relevant files, 2. flushing concurrently.
12101228
#[latency_histogram("storage_file_handler_flush_latency_seconds", false)]
12111229
fn flush(&self) {
@@ -1218,6 +1236,8 @@ impl FileHandlers<RW> {
12181236
self.transaction.flush();
12191237
#[cfg(feature = "os_input")]
12201238
self.accessed_keys.flush();
1239+
#[cfg(feature = "os_input")]
1240+
self.state_commitment_infos.flush();
12211241
}
12221242
}
12231243

@@ -1233,6 +1253,8 @@ impl<Mode: TransactionKind> FileHandlers<Mode> {
12331253
("transaction".to_string(), self.transaction.stats()),
12341254
#[cfg(feature = "os_input")]
12351255
("accessed_keys".to_string(), self.accessed_keys.stats()),
1256+
#[cfg(feature = "os_input")]
1257+
("state_commitment_infos".to_string(), self.state_commitment_infos.stats()),
12361258
])
12371259
}
12381260

@@ -1302,6 +1324,17 @@ impl<Mode: TransactionKind> FileHandlers<Mode> {
13021324
msg: format!("AccessedKeys at location {location:?} not found."),
13031325
})
13041326
}
1327+
1328+
#[cfg(feature = "os_input")]
1329+
// Returns the commitment infos at the given location or an error in case they don't exist.
1330+
pub(crate) fn get_state_commitment_infos_unchecked(
1331+
&self,
1332+
location: LocationInFile,
1333+
) -> StorageResult<StateCommitmentInfos> {
1334+
self.state_commitment_infos.get(location)?.ok_or(StorageError::DBInconsistency {
1335+
msg: format!("StateCommitmentInfos at location {location:?} not found."),
1336+
})
1337+
}
13051338
}
13061339

13071340
fn open_storage_files(
@@ -1340,6 +1373,9 @@ fn open_storage_files(
13401373
#[cfg(feature = "os_input")]
13411374
let (accessed_keys_writer, accessed_keys_reader) =
13421375
open_storage_file!("accessed_keys", AccessedKeys)?;
1376+
#[cfg(feature = "os_input")]
1377+
let (state_commitment_infos_writer, state_commitment_infos_reader) =
1378+
open_storage_file!("state_commitment_infos", StateCommitmentInfos)?;
13431379

13441380
Ok((
13451381
FileHandlers {
@@ -1351,6 +1387,8 @@ fn open_storage_files(
13511387
transaction: transaction_writer,
13521388
#[cfg(feature = "os_input")]
13531389
accessed_keys: accessed_keys_writer,
1390+
#[cfg(feature = "os_input")]
1391+
state_commitment_infos: state_commitment_infos_writer,
13541392
},
13551393
FileHandlers {
13561394
thin_state_diff: thin_state_diff_reader,
@@ -1361,6 +1399,8 @@ fn open_storage_files(
13611399
transaction: transaction_reader,
13621400
#[cfg(feature = "os_input")]
13631401
accessed_keys: accessed_keys_reader,
1402+
#[cfg(feature = "os_input")]
1403+
state_commitment_infos: state_commitment_infos_reader,
13641404
},
13651405
))
13661406
}
@@ -1383,4 +1423,7 @@ pub enum OffsetKind {
13831423
/// An accessed-keys file.
13841424
#[cfg(feature = "os_input")]
13851425
AccessedKeys,
1426+
/// A state-commitment-infos file.
1427+
#[cfg(feature = "os_input")]
1428+
StateCommitmentInfos,
13861429
}

crates/apollo_storage/src/serialization/serializers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ auto_storage_serde! {
372372
Transaction = 5,
373373
#[cfg(feature = "os_input")]
374374
AccessedKeys = 6,
375+
#[cfg(feature = "os_input")]
376+
StateCommitmentInfos = 7,
375377
}
376378
pub struct PartialBlockHashComponents {
377379
pub header_commitments: BlockHeaderCommitments,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! Storage for the per-block OS-input commitment infos (state-trie commitment data for the OS).
2+
3+
use starknet_api::block::BlockNumber;
4+
pub use starknet_committer::patricia_merkle_tree::types::StateCommitmentInfos;
5+
6+
#[cfg(test)]
7+
#[path = "state_commitment_infos_test.rs"]
8+
mod state_commitment_infos_test;
9+
10+
use crate::compression_utils::{compress, decompress};
11+
use crate::db::serialization::{StorageSerde, StorageSerdeError};
12+
use crate::db::table_types::Table;
13+
use crate::db::{TransactionKind, RW};
14+
use crate::{OffsetKind, StorageResult, StorageTransaction};
15+
16+
// Encoded with bincode (not serde_json): this is hash/`Felt`-heavy data, where bincode's fixed
17+
// binary encoding is markedly more compact than JSON's hex-string encoding. The trade-off is that
18+
// bincode is positional and not schema-evolution tolerant, which is acceptable here.
19+
impl StorageSerde for StateCommitmentInfos {
20+
fn serialize_into(&self, res: &mut impl std::io::Write) -> Result<(), StorageSerdeError> {
21+
let bytes = bincode::serialize(self)?;
22+
let compressed = compress(bytes.as_slice())?;
23+
compressed.serialize_into(res)
24+
}
25+
26+
fn deserialize_from(bytes: &mut impl std::io::Read) -> Option<Self> {
27+
let compressed = Vec::<u8>::deserialize_from(bytes)?;
28+
let data = decompress(compressed.as_slice()).ok()?;
29+
bincode::deserialize(&data).ok()
30+
}
31+
}
32+
33+
/// Interface for reading the OS-input commitment infos from storage.
34+
pub trait StateCommitmentInfosStorageReader<Mode: TransactionKind> {
35+
/// Returns the commitment infos for the given block, or `None` if not stored.
36+
fn get_state_commitment_infos(
37+
&self,
38+
block_number: BlockNumber,
39+
) -> StorageResult<Option<StateCommitmentInfos>>;
40+
}
41+
42+
/// Interface for writing the OS-input commitment infos to storage.
43+
pub trait StateCommitmentInfosStorageWriter
44+
where
45+
Self: Sized,
46+
{
47+
/// Appends the commitment infos for the given block to storage.
48+
fn append_state_commitment_infos(
49+
self,
50+
block_number: BlockNumber,
51+
state_commitment_infos: &StateCommitmentInfos,
52+
) -> StorageResult<Self>;
53+
54+
/// Removes the commitment infos for the given block from storage.
55+
/// If no entry exists for the block, returns without error.
56+
fn revert_state_commitment_infos(self, block_number: BlockNumber) -> StorageResult<Self>;
57+
}
58+
59+
impl<T: StorageTransaction> StateCommitmentInfosStorageReader<<T as StorageTransaction>::Mode>
60+
for T
61+
{
62+
fn get_state_commitment_infos(
63+
&self,
64+
block_number: BlockNumber,
65+
) -> StorageResult<Option<StateCommitmentInfos>> {
66+
let table = self.open_table(&self.tables().state_commitment_infos)?;
67+
let Some(location) = table.get(self.txn(), &block_number)? else {
68+
return Ok(None);
69+
};
70+
Ok(Some(self.file_handlers().get_state_commitment_infos_unchecked(location)?))
71+
}
72+
}
73+
74+
impl<T: StorageTransaction<Mode = RW>> StateCommitmentInfosStorageWriter for T {
75+
fn append_state_commitment_infos(
76+
self,
77+
block_number: BlockNumber,
78+
state_commitment_infos: &StateCommitmentInfos,
79+
) -> StorageResult<Self> {
80+
let file_offset_table = self.open_table(&self.tables().file_offsets)?;
81+
let state_commitment_infos_table =
82+
self.open_table(&self.tables().state_commitment_infos)?;
83+
84+
let location = self.file_handlers().append_state_commitment_infos(state_commitment_infos);
85+
state_commitment_infos_table.upsert(self.txn(), &block_number, &location)?;
86+
file_offset_table.upsert(
87+
self.txn(),
88+
&OffsetKind::StateCommitmentInfos,
89+
&location.next_offset(),
90+
)?;
91+
92+
Ok(self)
93+
}
94+
95+
fn revert_state_commitment_infos(self, block_number: BlockNumber) -> StorageResult<Self> {
96+
let state_commitment_infos_table =
97+
self.open_table(&self.tables().state_commitment_infos)?;
98+
state_commitment_infos_table.delete(self.txn(), &block_number)?;
99+
Ok(self)
100+
}
101+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use std::collections::HashMap;
2+
3+
use starknet_api::block::BlockNumber;
4+
use starknet_committer::patricia_merkle_tree::types::{CommitmentInfo, StateCommitmentInfos};
5+
6+
use crate::state_commitment_infos::{
7+
StateCommitmentInfosStorageReader,
8+
StateCommitmentInfosStorageWriter,
9+
};
10+
use crate::test_utils::get_test_storage;
11+
12+
fn sample_state_commitment_infos() -> StateCommitmentInfos {
13+
StateCommitmentInfos {
14+
contracts_trie_commitment_info: CommitmentInfo::default(),
15+
classes_trie_commitment_info: CommitmentInfo::default(),
16+
storage_tries_commitment_infos: HashMap::new(),
17+
}
18+
}
19+
20+
#[test]
21+
fn append_and_get_state_commitment_infos() {
22+
let (reader, mut writer) = get_test_storage().0;
23+
let height = BlockNumber(5);
24+
let state_commitment_infos = sample_state_commitment_infos();
25+
26+
// No infos stored for the height yet.
27+
assert_eq!(reader.begin_ro_txn().unwrap().get_state_commitment_infos(height).unwrap(), None);
28+
29+
writer
30+
.begin_rw_txn()
31+
.unwrap()
32+
.append_state_commitment_infos(height, &state_commitment_infos)
33+
.unwrap()
34+
.commit()
35+
.unwrap();
36+
37+
assert_eq!(
38+
reader.begin_ro_txn().unwrap().get_state_commitment_infos(height).unwrap(),
39+
Some(state_commitment_infos)
40+
);
41+
// A different height is still empty.
42+
assert_eq!(
43+
reader.begin_ro_txn().unwrap().get_state_commitment_infos(BlockNumber(6)).unwrap(),
44+
None
45+
);
46+
}
47+
48+
#[test]
49+
fn revert_state_commitment_infos() {
50+
let (reader, mut writer) = get_test_storage().0;
51+
let height = BlockNumber(5);
52+
53+
writer
54+
.begin_rw_txn()
55+
.unwrap()
56+
.append_state_commitment_infos(height, &sample_state_commitment_infos())
57+
.unwrap()
58+
.revert_state_commitment_infos(height)
59+
.unwrap()
60+
.commit()
61+
.unwrap();
62+
63+
assert_eq!(reader.begin_ro_txn().unwrap().get_state_commitment_infos(height).unwrap(), None);
64+
65+
// Reverting a height with no stored infos is a no-op.
66+
writer
67+
.begin_rw_txn()
68+
.unwrap()
69+
.revert_state_commitment_infos(BlockNumber(99))
70+
.unwrap()
71+
.commit()
72+
.unwrap();
73+
}

0 commit comments

Comments
 (0)