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