@@ -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 ,
@@ -14,11 +20,15 @@ use apollo_infra::component_definitions::{default_component_start_fn, ComponentS
1420use async_trait:: async_trait;
1521use starknet_api:: block:: BlockNumber ;
1622use starknet_api:: block_hash:: state_diff_hash:: calculate_state_diff_hash;
23+ #[ cfg( feature = "os_input" ) ]
24+ use starknet_api:: core:: ContractAddress ;
1725use starknet_api:: core:: { GlobalRoot , StateDiffCommitment } ;
1826use starknet_api:: hash:: PoseidonHash ;
1927use starknet_api:: state:: ThinStateDiff ;
2028use starknet_committer:: block_committer:: commit:: commit_block;
2129use starknet_committer:: block_committer:: input:: Input ;
30+ #[ cfg( feature = "os_input" ) ]
31+ use starknet_committer:: block_committer:: input:: StarknetStorageKey ;
2232use 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+ } ;
3046use starknet_committer:: db:: forest_trait:: {
3147 EmptyInitialReadContext ,
3248 ForestMetadataType ,
3349 ForestStorageWithEmptyReadContext ,
3450} ;
3551use starknet_committer:: db:: index_db:: IndexDb ;
52+ #[ cfg( feature = "os_input" ) ]
53+ use starknet_committer:: db:: serde_db_utils:: accessed_keys_digest;
3654use starknet_committer:: db:: serde_db_utils:: {
3755 deserialize_felt_no_packing,
3856 serialize_felt_no_packing,
3957 DbBlockNumber ,
4058} ;
4159use starknet_committer:: forest:: deleted_nodes:: DeletedNodes ;
4260use 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 ;
4365use starknet_patricia_storage:: map_storage:: CachedStorage ;
4466use starknet_patricia_storage:: rocksdb_storage:: RocksDbStorage ;
67+ #[ cfg( feature = "os_input" ) ]
68+ use starknet_patricia_storage:: storage_trait:: ImmutableReadOnlyStorage ;
4569use starknet_patricia_storage:: storage_trait:: { DbValue , Storage } ;
4670use 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
0 commit comments