Skip to content

Commit cddd859

Browse files
committed
starknet_committer: underlying logic of the new read witnesses and commit endpoint
1 parent 5c08172 commit cddd859

4 files changed

Lines changed: 182 additions & 3 deletions

File tree

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: 174 additions & 0 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,
@@ -27,21 +33,33 @@ use starknet_committer::block_committer::measurements_util::{
2733
MeasurementsTrait,
2834
SingleBlockMeasurements,
2935
};
36+
#[cfg(feature = "os_input")]
37+
use starknet_committer::db::forest_trait::ForestStorageWithWitnesses;
38+
#[cfg(feature = "os_input")]
39+
use starknet_committer::db::forest_trait::PatriciaProofsUpdates;
3040
use starknet_committer::db::forest_trait::{
3141
EmptyInitialReadContext,
3242
ForestMetadataType,
3343
ForestStorageWithEmptyReadContext,
3444
};
3545
use starknet_committer::db::index_db::IndexDb;
46+
#[cfg(feature = "os_input")]
47+
use starknet_committer::db::serde_db_utils::accessed_keys_digest;
3648
use starknet_committer::db::serde_db_utils::{
3749
deserialize_felt_no_packing,
3850
serialize_felt_no_packing,
3951
DbBlockNumber,
4052
};
4153
use starknet_committer::forest::deleted_nodes::DeletedNodes;
4254
use starknet_committer::forest::filled_forest::FilledForest;
55+
#[cfg(feature = "os_input")]
56+
use starknet_committer::patricia_merkle_tree::tree::{LeavesRequest, SortedLeavesRequest};
57+
#[cfg(feature = "os_input")]
58+
use starknet_patricia_storage::errors::SerializationError;
4359
use starknet_patricia_storage::map_storage::CachedStorage;
4460
use starknet_patricia_storage::rocksdb_storage::RocksDbStorage;
61+
#[cfg(feature = "os_input")]
62+
use starknet_patricia_storage::storage_trait::ImmutableReadOnlyStorage;
4563
use starknet_patricia_storage::storage_trait::{DbValue, Storage};
4664
use tracing::{debug, error, info, warn};
4765

@@ -453,6 +471,162 @@ where
453471
}
454472
}
455473

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

crates/apollo_committer/src/metrics.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use apollo_committer_types::communication::COMMITTER_REQUEST_LABELS;
22
use apollo_infra::metrics::{
3-
InfraMetrics, LocalClientMetrics, LocalServerMetrics, RemoteClientMetrics, RemoteServerMetrics,
3+
InfraMetrics,
4+
LocalClientMetrics,
5+
LocalServerMetrics,
6+
RemoteClientMetrics,
7+
RemoteServerMetrics,
48
};
59
use apollo_metrics::{define_infra_metrics, define_metrics};
610
use starknet_api::block::BlockNumber;

crates/starknet_committer/src/patricia_merkle_tree/types.rs

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

65-
#[derive(Debug, Clone, PartialEq)]
65+
#[derive(Clone, Debug, PartialEq)]
6666
pub struct ContractsTrieProof {
6767
pub nodes: PreimageMap,
6868
pub leaves: HashMap<ContractAddress, ContractState>,
6969
}
7070

71-
#[derive(Debug, Clone, PartialEq)]
71+
#[derive(Clone, Debug, PartialEq)]
7272
pub struct StarknetForestProofs {
7373
pub classes_trie_proof: PreimageMap,
7474
pub contracts_trie_proof: ContractsTrieProof,

0 commit comments

Comments
 (0)