@@ -3,6 +3,12 @@ use std::error::Error;
33use std:: path:: PathBuf ;
44
55use 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+ } ;
612use 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 ;
3040use starknet_committer:: db:: forest_trait:: {
3141 EmptyInitialReadContext ,
3242 ForestMetadataType ,
3343 ForestStorageWithEmptyReadContext ,
3444} ;
3545use starknet_committer:: db:: index_db:: IndexDb ;
46+ #[ cfg( feature = "os_input" ) ]
47+ use starknet_committer:: db:: serde_db_utils:: accessed_keys_digest;
3648use starknet_committer:: db:: serde_db_utils:: {
3749 deserialize_felt_no_packing,
3850 serialize_felt_no_packing,
3951 DbBlockNumber ,
4052} ;
4153use starknet_committer:: forest:: deleted_nodes:: DeletedNodes ;
4254use 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 ;
4359use starknet_patricia_storage:: map_storage:: CachedStorage ;
4460use starknet_patricia_storage:: rocksdb_storage:: RocksDbStorage ;
61+ #[ cfg( feature = "os_input" ) ]
62+ use starknet_patricia_storage:: storage_trait:: ImmutableReadOnlyStorage ;
4563use starknet_patricia_storage:: storage_trait:: { DbValue , Storage } ;
4664use 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]
457631impl ComponentStarter for ApolloCommitter {
458632 async fn start ( & mut self ) {
0 commit comments