Skip to content

Commit 48a282a

Browse files
committed
starknet_committer: underlying logic of the new read witnesses and commit endpoint
1 parent 1aff4e2 commit 48a282a

5 files changed

Lines changed: 224 additions & 4 deletions

File tree

.github/workflows/apollo_storage_os_input_ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
- ".github/workflows/apollo_storage_os_input_ci.yml"
1313
- "Cargo.lock"
1414
- "Cargo.toml"
15+
- "crates/apollo_committer/**"
1516
- "crates/apollo_committer_types/**"
1617
- "crates/apollo_storage/**"
1718
- "crates/apollo_config/**"
@@ -61,6 +62,7 @@ jobs:
6162
github_token: ${{ secrets.GITHUB_TOKEN }}
6263
- run: cargo test -p starknet_committer --features os_input
6364
- run: cargo test -p apollo_committer_types --features os_input
65+
- run: cargo test -p apollo_committer --features os_input
6466
- run: cargo build -p apollo_batcher --features os_input
6567
- run: cargo test -p apollo_batcher --features os_input
6668
- run: cargo test -p apollo_reverts --features os_input

crates/apollo_committer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ description = "State root commitment computation component for the Starknet sequ
88

99
[features]
1010
testing = []
11+
os_input = ["apollo_committer_types/os_input", "starknet_committer/os_input"]
1112

1213
[dependencies]
1314
apollo_committer_config.workspace = true

crates/apollo_committer/src/committer.rs

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ use std::error::Error;
33
use std::path::PathBuf;
44

55
use apollo_committer_config::config::{ApolloStorage, CommitterConfig};
6+
#[cfg(feature = "os_input")]
7+
use apollo_committer_types::committer_types::{
8+
AccessedKeys,
9+
ReadPathsAndCommitBlockRequest,
10+
ReadPathsAndCommitBlockResponse,
11+
};
612
use apollo_committer_types::committer_types::{
713
CommitBlockRequest,
814
CommitBlockResponse,
@@ -14,11 +20,15 @@ use apollo_infra::component_definitions::{default_component_start_fn, ComponentS
1420
use async_trait::async_trait;
1521
use starknet_api::block::BlockNumber;
1622
use starknet_api::block_hash::state_diff_hash::calculate_state_diff_hash;
23+
#[cfg(feature = "os_input")]
24+
use starknet_api::core::ContractAddress;
1725
use starknet_api::core::{GlobalRoot, StateDiffCommitment};
1826
use starknet_api::hash::PoseidonHash;
1927
use starknet_api::state::ThinStateDiff;
2028
use starknet_committer::block_committer::commit::commit_block;
2129
use starknet_committer::block_committer::input::Input;
30+
#[cfg(feature = "os_input")]
31+
use starknet_committer::block_committer::input::StarknetStorageKey;
2232
use starknet_committer::block_committer::measurements_util::{
2333
Action,
2434
BlockDurations,
@@ -27,21 +37,35 @@ use starknet_committer::block_committer::measurements_util::{
2737
MeasurementsTrait,
2838
SingleBlockMeasurements,
2939
};
40+
#[cfg(feature = "os_input")]
41+
use starknet_committer::db::forest_trait::forest_trait_witnesses::{
42+
ForestStorageWithWitnesses,
43+
PatriciaProofsUpdate,
44+
PatriciaProofsWrite,
45+
};
3046
use starknet_committer::db::forest_trait::{
3147
EmptyInitialReadContext,
3248
ForestMetadataType,
3349
ForestStorageWithEmptyReadContext,
3450
};
3551
use starknet_committer::db::index_db::IndexDb;
52+
#[cfg(feature = "os_input")]
53+
use starknet_committer::db::serde_db_utils::accessed_keys_digest;
3654
use starknet_committer::db::serde_db_utils::{
3755
deserialize_felt_no_packing,
3856
serialize_felt_no_packing,
3957
DbBlockNumber,
4058
};
4159
use starknet_committer::forest::deleted_nodes::DeletedNodes;
4260
use starknet_committer::forest::filled_forest::FilledForest;
61+
#[cfg(feature = "os_input")]
62+
use starknet_committer::patricia_merkle_tree::tree::{LeavesRequest, SortedLeavesRequest};
63+
#[cfg(feature = "os_input")]
64+
use starknet_patricia_storage::errors::SerializationError;
4365
use starknet_patricia_storage::map_storage::CachedStorage;
4466
use starknet_patricia_storage::rocksdb_storage::RocksDbStorage;
67+
#[cfg(feature = "os_input")]
68+
use starknet_patricia_storage::storage_trait::ImmutableReadOnlyStorage;
4569
use starknet_patricia_storage::storage_trait::{DbValue, Storage};
4670
use tracing::{debug, error, info, warn};
4771

@@ -447,9 +471,192 @@ where
447471
}
448472

449473
fn map_internal_error<E: Error>(&self, err: E) -> CommitterError {
474+
self.map_internal_error_at_height(self.offset, err)
475+
}
476+
477+
fn map_internal_error_at_height<E: Error>(
478+
&self,
479+
height: BlockNumber,
480+
err: E,
481+
) -> CommitterError {
450482
let error_message = format!("{err:?}: {err}");
451-
error!("Error committing block number {0}. {error_message}.", self.offset);
452-
CommitterError::Internal { height: self.offset, message: error_message }
483+
error!("Error committing block number {height}. {error_message}.");
484+
CommitterError::Internal { height, message: error_message }
485+
}
486+
}
487+
488+
#[cfg(feature = "os_input")]
489+
impl<S, ForestDB> Committer<S, ForestDB>
490+
where
491+
S: StorageConstructor + ImmutableReadOnlyStorage + 'static,
492+
ForestDB: ForestStorageWithWitnesses<Storage = S>,
493+
{
494+
/// Commits the next block and returns merged Patricia witness facts for OS input, persisting
495+
/// digest + payload for idempotent replay.
496+
pub async fn read_paths_and_commit_block(
497+
&mut self,
498+
ReadPathsAndCommitBlockRequest {
499+
commit: CommitBlockRequest { state_diff, state_diff_commitment, height },
500+
accessed_keys: AccessedKeys { storage_keys, accessed_contracts, accessed_class_hashes },
501+
}: ReadPathsAndCommitBlockRequest,
502+
) -> CommitterResult<ReadPathsAndCommitBlockResponse> {
503+
let class_hashes: Vec<_> = accessed_class_hashes.iter().copied().collect();
504+
let contract_addresses: Vec<_> = accessed_contracts.iter().copied().collect();
505+
let contract_storage_keys = storage_keys.iter().fold(
506+
HashMap::<ContractAddress, Vec<StarknetStorageKey>>::new(),
507+
|mut accumulator, (address, key)| {
508+
accumulator.entry(*address).or_default().push(StarknetStorageKey(*key));
509+
accumulator
510+
},
511+
);
512+
let mut leaves_request = LeavesRequest::from_accessed_leaves(
513+
&class_hashes,
514+
&contract_addresses,
515+
&contract_storage_keys,
516+
);
517+
info!(
518+
"read_paths_and_commit_block: height {height}, accessed keys len {}, state diff len {}",
519+
leaves_request.total_leaf_count(),
520+
state_diff.len(),
521+
);
522+
let sorted_leaves: SortedLeavesRequest<'_> = (&mut leaves_request).into();
523+
let digest = accessed_keys_digest(&sorted_leaves);
524+
525+
match self.commit_or_load(&state_diff, state_diff_commitment, height).await? {
526+
CommitBlockHeightPlan::Historical { global_root } => {
527+
let stored_digest = self.load_witnesses_digest(height).await?;
528+
if stored_digest != Some(digest) {
529+
return Err(CommitterError::AccessedKeysDigestMismatch {
530+
height,
531+
stored: stored_digest,
532+
expected: digest,
533+
});
534+
}
535+
let proofs = self
536+
.forest_storage
537+
.read_witnesses(height)
538+
.await
539+
.map_err(|error| self.map_internal_error_at_height(height, error))?;
540+
let proofs = proofs.ok_or(CommitterError::MissingPatriciaPaths { height })?;
541+
Ok(ReadPathsAndCommitBlockResponse { global_root, patricia_proofs: proofs })
542+
}
543+
// Flow overview:
544+
// 1. Fetch patricia paths for the accessed keys.
545+
// 2. Compute the updates from the state diff (commit) but avoid updating the underlying
546+
// DB in order to guarantee atomicity.
547+
// 3. Fetch patricia paths for the post-commit tries, via running step 1 against a two
548+
// layer storage composed from the underlying storage and the modifications from 2.
549+
// 4. Merge the two sets of patricia paths and write the result to the storage.
550+
// 5. Update the commitment offset and return the global root and the patricia proofs.
551+
CommitBlockHeightPlan::CommitTip { state_diff_commitment } => {
552+
let pre_roots = self
553+
.forest_storage
554+
.read_roots(ForestDB::InitialReadContext::create_empty())
555+
.await
556+
.map_err(|e| self.map_internal_error(e))?;
557+
let mut patricia_proofs = self
558+
.forest_storage
559+
.fetch_patricia_witnesses(
560+
pre_roots.classes_trie_root_hash,
561+
pre_roots.contracts_trie_root_hash,
562+
sorted_leaves.class_sorted,
563+
sorted_leaves.contract_sorted,
564+
&sorted_leaves.storage_sorted,
565+
None,
566+
)
567+
.await
568+
.map_err(|e| CommitterError::PatriciaPathsCollectionFailed {
569+
height,
570+
message: format!("pre-commit witness paths: {e:?}"),
571+
})?;
572+
573+
let mut block_measurements = SingleBlockMeasurements::default();
574+
block_measurements.start_measurement(Action::EndToEnd);
575+
let CommitStateDiffOutput { filled_forest, global_root, deleted_nodes } =
576+
self.commit_state_diff(state_diff, &mut block_measurements).await?;
577+
let post_roots = filled_forest.state_roots();
578+
579+
let forest_updates = ForestDB::serialize_forest(&filled_forest)
580+
.map_err(|e| self.map_internal_error(e))?;
581+
582+
let proof_after = self
583+
.forest_storage
584+
.fetch_patricia_witnesses(
585+
post_roots.classes_trie_root_hash,
586+
post_roots.contracts_trie_root_hash,
587+
sorted_leaves.class_sorted,
588+
sorted_leaves.contract_sorted,
589+
&sorted_leaves.storage_sorted,
590+
Some(forest_updates),
591+
)
592+
.await
593+
.map_err(|e| CommitterError::PatriciaPathsCollectionFailed {
594+
height,
595+
message: format!("post-commit witness paths: {e:?}"),
596+
})?;
597+
598+
patricia_proofs.extend(proof_after);
599+
600+
let (metadata, next_offset) =
601+
commit_tip_metadata_bundle(height, global_root, state_diff_commitment);
602+
let witness_node_count = patricia_proofs.classes_trie_proof.len()
603+
+ patricia_proofs.contracts_trie_proof.nodes.len()
604+
+ patricia_proofs.contracts_trie_proof.leaves.len()
605+
+ patricia_proofs
606+
.contracts_trie_storage_proofs
607+
.values()
608+
.map(|proof| proof.len())
609+
.sum::<usize>();
610+
info!(
611+
"For block number {height}, writing filled forest and {witness_node_count} \
612+
witness nodes to storage with metadata: {metadata:?}, delete {} nodes",
613+
deleted_nodes.len()
614+
);
615+
block_measurements.start_measurement(Action::Write);
616+
let n_write_entries = self
617+
.forest_storage
618+
.write_with_metadata_and_witnesses(
619+
&filled_forest,
620+
metadata,
621+
deleted_nodes,
622+
PatriciaProofsUpdate::Write(PatriciaProofsWrite {
623+
block_number: height,
624+
keys_digest: digest,
625+
witnesses: patricia_proofs.clone(),
626+
}),
627+
)
628+
.await
629+
.map_err(|e: SerializationError| self.map_internal_error(e))?;
630+
block_measurements.attempt_to_stop_measurement(Action::Write, n_write_entries).ok();
631+
block_measurements.attempt_to_stop_measurement(Action::EndToEnd, 0).ok();
632+
update_metrics(height, &block_measurements.block_measurement);
633+
self.update_offset(next_offset);
634+
Ok(ReadPathsAndCommitBlockResponse { global_root, patricia_proofs })
635+
}
636+
}
637+
}
638+
639+
async fn load_witnesses_digest(
640+
&mut self,
641+
block_number: BlockNumber,
642+
) -> CommitterResult<Option<[u8; 32]>> {
643+
let digest_raw = self
644+
.forest_storage
645+
.read_metadata(ForestMetadataType::AccessedKeysDigest(DbBlockNumber(block_number)))
646+
.await
647+
.map_err(|error| self.map_internal_error_at_height(block_number, error))?;
648+
649+
digest_raw
650+
.map(|digest_raw| {
651+
digest_raw.0.as_slice().try_into().map_err(|_| CommitterError::Internal {
652+
height: block_number,
653+
message: format!(
654+
"Invalid OS witnesses digest length {} (expected 32)",
655+
digest_raw.0.len()
656+
),
657+
})
658+
})
659+
.transpose()
453660
}
454661
}
455662

crates/starknet_committer/src/patricia_merkle_tree/tree.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ impl LeavesRequest {
9191
contract_storage_leaf_indices,
9292
}
9393
}
94+
95+
/// Total number of trie leaves requested (classes, contracts, and storage slots).
96+
pub fn total_leaf_count(&self) -> usize {
97+
self.class_leaf_indices.len()
98+
+ self.contract_leaf_indices.len()
99+
+ self
100+
.contract_storage_leaf_indices
101+
.values()
102+
.fold(0, |count, leaf_indices| count + leaf_indices.len())
103+
}
94104
}
95105

96106
impl<'a> From<&'a mut LeavesRequest> for SortedLeavesRequest<'a> {

crates/starknet_committer/src/patricia_merkle_tree/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ pub type ClassesTrie = FilledTreeImpl<CompiledClassHash>;
4343
pub type ContractsTrie = FilledTreeImpl<ContractState>;
4444
pub type StorageTrieMap = HashMap<ContractAddress, StorageTrie>;
4545

46-
#[derive(Debug, Clone, PartialEq)]
46+
#[derive(Clone, Debug, PartialEq)]
4747
pub struct ContractsTrieProof {
4848
pub nodes: PreimageMap,
4949
pub leaves: HashMap<ContractAddress, ContractState>,
5050
}
5151

52-
#[derive(Debug, Clone, PartialEq)]
52+
#[derive(Clone, Debug, PartialEq)]
5353
pub struct StarknetForestProofs {
5454
pub classes_trie_proof: PreimageMap,
5555
pub contracts_trie_proof: ContractsTrieProof,

0 commit comments

Comments
 (0)