Skip to content

Commit 8f9e24b

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

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