Skip to content

Commit 15affd5

Browse files
yoavGrsclaude
andcommitted
blockifier,apollo_batcher: produce OS initial reads behind os_input
Add an os_input feature on blockifier and, behind it, compute the pre-block OS read values (CachedState::get_os_initial_reads) in finalize_block, expose them on BlockExecutionSummary/BlockExecutionArtifacts, and carry them into the batcher's CentralObjects decision-reached response. Not yet consumed downstream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2ef220c commit 15affd5

10 files changed

Lines changed: 85 additions & 6 deletions

File tree

.github/workflows/apollo_storage_os_input_ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
- run: cargo test -p starknet_committer --features os_input
6767
- run: cargo test -p apollo_committer_types --features os_input
6868
- run: cargo test -p apollo_committer --features os_input
69+
- run: cargo build -p blockifier --features os_input
6970
- run: cargo build -p apollo_batcher --features os_input
7071
- run: cargo test -p apollo_batcher --features os_input
7172
- run: cargo test -p apollo_consensus_orchestrator --features os_input,testing

crates/apollo_batcher/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ os_input = [
1313
"apollo_committer_types/os_input",
1414
"apollo_reverts/os_input",
1515
"apollo_storage/os_input",
16+
"blockifier/os_input",
1617
"dep:starknet_committer",
1718
]
1819
testing = []

crates/apollo_batcher/src/batcher.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,8 @@ impl Batcher {
10251025
parent_proposal_commitment,
10261026
#[cfg(feature = "os_input")]
10271027
accessed_keys,
1028+
#[cfg(feature = "os_input")]
1029+
initial_reads: block_execution_artifacts.initial_reads,
10281030
},
10291031
})
10301032
}

crates/apollo_batcher/src/block_builder.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ use blockifier::blockifier_versioned_constants::VersionedConstants;
3030
use blockifier::bouncer::{BouncerWeights, CasmHashComputationData};
3131
use blockifier::concurrency::worker_pool::WorkerPool;
3232
use blockifier::context::BlockContext;
33+
#[cfg(feature = "os_input")]
34+
use blockifier::state::cached_state::StateMaps;
3335
use blockifier::state::cached_state::{CachedState, CommitmentStateDiff};
3436
use blockifier::state::contract_class_manager::ContractClassManager;
3537
use blockifier::state::errors::StateError;
@@ -129,6 +131,8 @@ pub struct BlockExecutionArtifacts {
129131
pub execution_data: BlockTransactionExecutionData,
130132
pub commitment_state_diff: CommitmentStateDiff,
131133
pub compressed_state_diff: Option<CommitmentStateDiff>,
134+
#[cfg(feature = "os_input")]
135+
pub initial_reads: StateMaps,
132136
pub bouncer_weights: BouncerWeights,
133137
pub l2_gas_used: GasAmount,
134138
pub casm_hash_computation_data_sierra_gas: CasmHashComputationData,
@@ -142,18 +146,23 @@ pub struct BlockExecutionArtifacts {
142146

143147
impl BlockExecutionArtifacts {
144148
pub async fn new(
145-
BlockExecutionSummary {
149+
block_summary: BlockExecutionSummary,
150+
execution_data: BlockTransactionExecutionData,
151+
final_n_executed_txs: usize,
152+
) -> Self {
153+
#[cfg(feature = "os_input")]
154+
let initial_reads = block_summary.initial_reads;
155+
let BlockExecutionSummary {
146156
state_diff: commitment_state_diff,
147157
compressed_state_diff,
148158
bouncer_weights,
149159
casm_hash_computation_data_sierra_gas,
150160
casm_hash_computation_data_proving_gas,
151161
compiled_class_hashes_for_migration,
152162
block_info,
153-
}: BlockExecutionSummary,
154-
execution_data: BlockTransactionExecutionData,
155-
final_n_executed_txs: usize,
156-
) -> Self {
163+
// TODO(Yoav): Remove the ".." when the os_input feature is removed.
164+
..
165+
} = block_summary;
157166
let l1_da_mode = L1DataAvailabilityMode::from_use_kzg_da(block_info.use_kzg_da);
158167
let transactions_data =
159168
prepare_txs_hashing_data(&execution_data.execution_infos_and_signatures);
@@ -173,6 +182,8 @@ impl BlockExecutionArtifacts {
173182
execution_data,
174183
commitment_state_diff,
175184
compressed_state_diff,
185+
#[cfg(feature = "os_input")]
186+
initial_reads,
176187
bouncer_weights,
177188
l2_gas_used,
178189
casm_hash_computation_data_sierra_gas,

crates/apollo_batcher/src/block_builder_test.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ use starknet_api::transaction::fields::{
4545
VIRTUAL_SNOS,
4646
};
4747
use starknet_api::transaction::TransactionHash;
48+
#[cfg(feature = "os_input")]
49+
use starknet_api::{contract_address, felt, nonce, proof_facts, storage_key, tx_hash};
50+
#[cfg(not(feature = "os_input"))]
4851
use starknet_api::{proof_facts, tx_hash};
4952
use starknet_types_core::felt::Felt;
5053
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
@@ -100,6 +103,8 @@ async fn block_execution_artifacts(
100103
let block_summary = BlockExecutionSummary {
101104
state_diff: Default::default(),
102105
compressed_state_diff: Default::default(),
106+
#[cfg(feature = "os_input")]
107+
initial_reads: test_initial_reads(),
103108
bouncer_weights: BouncerWeights { l1_gas: 100, ..BouncerWeights::empty() },
104109
casm_hash_computation_data_sierra_gas: CasmHashComputationData::default(),
105110
casm_hash_computation_data_proving_gas: CasmHashComputationData::default(),
@@ -135,6 +140,14 @@ fn execution_info() -> TransactionExecutionInfo {
135140
}
136141
}
137142

143+
#[cfg(feature = "os_input")]
144+
fn test_initial_reads() -> StateMaps {
145+
let mut initial_reads = StateMaps::default();
146+
initial_reads.nonces.insert(contract_address!("0x1"), nonce!(7_u64));
147+
initial_reads.storage.insert((contract_address!("0x1"), storage_key!("0x2")), felt!(8_u8));
148+
initial_reads
149+
}
150+
138151
async fn one_chunk_test_expectations() -> TestExpectations {
139152
let input_txs = test_txs(0..3);
140153
let block_size = input_txs.len();
@@ -445,6 +458,8 @@ async fn transaction_failed_test_expectations() -> TestExpectations {
445458
Ok(BlockExecutionSummary {
446459
state_diff: expected_block_artifacts_copy.commitment_state_diff,
447460
compressed_state_diff: None,
461+
#[cfg(feature = "os_input")]
462+
initial_reads: test_initial_reads(),
448463
bouncer_weights: expected_block_artifacts_copy.bouncer_weights,
449464
casm_hash_computation_data_sierra_gas: expected_block_artifacts_copy
450465
.casm_hash_computation_data_sierra_gas,
@@ -543,6 +558,8 @@ async fn set_close_block_expectations(
543558
Ok(BlockExecutionSummary {
544559
state_diff: output_block_artifacts.commitment_state_diff,
545560
compressed_state_diff: None,
561+
#[cfg(feature = "os_input")]
562+
initial_reads: test_initial_reads(),
546563
bouncer_weights: output_block_artifacts.bouncer_weights,
547564
casm_hash_computation_data_sierra_gas: output_block_artifacts
548565
.casm_hash_computation_data_sierra_gas,
@@ -1080,6 +1097,8 @@ async fn failed_l1_handler_transaction_consumed() {
10801097
Ok(BlockExecutionSummary {
10811098
state_diff: Default::default(),
10821099
compressed_state_diff: None,
1100+
#[cfg(feature = "os_input")]
1101+
initial_reads: test_initial_reads(),
10831102
bouncer_weights: BouncerWeights::empty(),
10841103
casm_hash_computation_data_sierra_gas: CasmHashComputationData::default(),
10851104
casm_hash_computation_data_proving_gas: CasmHashComputationData::default(),
@@ -1141,6 +1160,8 @@ async fn partial_chunk_execution_proposer() {
11411160
Ok(BlockExecutionSummary {
11421161
state_diff: expected_block_artifacts.commitment_state_diff,
11431162
compressed_state_diff: None,
1163+
#[cfg(feature = "os_input")]
1164+
initial_reads: test_initial_reads(),
11441165
bouncer_weights: expected_block_artifacts.bouncer_weights,
11451166
casm_hash_computation_data_sierra_gas: expected_block_artifacts
11461167
.casm_hash_computation_data_sierra_gas,

crates/apollo_batcher/src/test_utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use blockifier::blockifier::transaction_executor::BlockExecutionSummary;
2323
use blockifier::bouncer::{BouncerWeights, CasmHashComputationData};
2424
use blockifier::fee::receipt::TransactionReceipt;
2525
use blockifier::state::cached_state::CommitmentStateDiff;
26+
#[cfg(feature = "os_input")]
27+
use blockifier::state::cached_state::StateMaps;
2628
use blockifier::transaction::objects::TransactionExecutionInfo;
2729
use indexmap::{indexmap, IndexMap};
2830
use mockall::predicate::eq;
@@ -213,6 +215,8 @@ impl BlockExecutionArtifacts {
213215
address_to_nonce: IndexMap::from_iter([(contract_address!("0x7"), nonce!(1_u64))]),
214216
},
215217
compressed_state_diff: Default::default(),
218+
#[cfg(feature = "os_input")]
219+
initial_reads: StateMaps::default(),
216220
bouncer_weights: BouncerWeights::empty(),
217221
casm_hash_computation_data_sierra_gas: CasmHashComputationData::empty(),
218222
casm_hash_computation_data_proving_gas: CasmHashComputationData::empty(),

crates/apollo_batcher_types/src/batcher_types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use blockifier::bouncer::{BouncerWeights, CasmHashComputationData};
66
#[cfg(feature = "os_input")]
77
use blockifier::state::accessed_keys::AccessedKeys;
88
use blockifier::state::cached_state::CommitmentStateDiff;
9+
#[cfg(feature = "os_input")]
10+
use blockifier::state::cached_state::StateMaps;
911
use blockifier::transaction::objects::TransactionExecutionInfo;
1012
use chrono::prelude::*;
1113
use indexmap::IndexMap;
@@ -162,6 +164,9 @@ pub struct CentralObjects {
162164
pub parent_proposal_commitment: Option<ProposalCommitment>,
163165
#[cfg(feature = "os_input")]
164166
pub accessed_keys: AccessedKeys,
167+
/// Pre-block read values the OS needs to replay the block.
168+
#[cfg(feature = "os_input")]
169+
pub initial_reads: StateMaps,
165170
}
166171

167172
#[derive(Debug, Serialize, Deserialize, PartialEq)]

crates/blockifier/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mocks = []
2121
native_blockifier = []
2222
node_api = []
2323
only-native = ["cairo_native"]
24+
os_input = []
2425
reexecution = ["transaction_serde"]
2526
testing = [
2627
"blockifier_test_utils",

crates/blockifier/src/blockifier/transaction_executor.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub type CompiledClassHashesForMigration = Vec<CompiledClassHashV2ToV1>;
5656
pub struct BlockExecutionSummary {
5757
pub state_diff: CommitmentStateDiff,
5858
pub compressed_state_diff: Option<CommitmentStateDiff>,
59+
#[cfg(feature = "os_input")]
60+
pub initial_reads: StateMaps,
5961
pub bouncer_weights: BouncerWeights,
6062
pub casm_hash_computation_data_sierra_gas: CasmHashComputationData,
6163
pub casm_hash_computation_data_proving_gas: CasmHashComputationData,
@@ -272,6 +274,9 @@ pub(crate) fn finalize_block<S: StateReader>(
272274

273275
let state_diff = block_state.to_state_diff()?.state_maps;
274276

277+
#[cfg(feature = "os_input")]
278+
let initial_reads = block_state.get_os_initial_reads()?;
279+
275280
let compressed_state_diff = if block_context.versioned_constants.enable_stateful_compression {
276281
Some(compress(&state_diff, block_state, alias_contract_address)?.into())
277282
} else {
@@ -299,6 +304,8 @@ pub(crate) fn finalize_block<S: StateReader>(
299304
Ok(BlockExecutionSummary {
300305
state_diff: state_diff.into(),
301306
compressed_state_diff,
307+
#[cfg(feature = "os_input")]
308+
initial_reads,
302309
bouncer_weights: *bouncer.get_bouncer_weights(),
303310
casm_hash_computation_data_sierra_gas,
304311
casm_hash_computation_data_proving_gas,

crates/blockifier/src/state/cached_state.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,13 +288,39 @@ impl Default for CachedState<crate::test_utils::dict_state_reader::DictStateRead
288288
}
289289
}
290290

291-
#[cfg(feature = "reexecution")]
291+
#[cfg(any(feature = "reexecution", feature = "os_input"))]
292292
impl<S: StateReader> CachedState<S> {
293293
pub fn get_initial_reads(&self) -> StateResult<StateMaps> {
294294
Ok(self.cache.borrow().initial_reads.clone())
295295
}
296296
}
297297

298+
#[cfg(feature = "os_input")]
299+
impl<S: StateReader> CachedState<S> {
300+
/// Returns the pre-block values the OS needs to replay the block: the values read during
301+
/// execution, extended with the class hash and nonce of every accessed contract and the
302+
/// compiled class hash of every accessed class (the OS reads these trie leaves even when
303+
/// execution itself did not). `declared_contracts` is cleared, as the OS does not consume it.
304+
pub fn get_os_initial_reads(&mut self) -> StateResult<StateMaps> {
305+
// Back-fill write-only storage cells so their pre-block values are part of the initial
306+
// reads.
307+
self.update_initial_values_of_write_only_access()?;
308+
let raw_initial_reads = self.get_initial_reads()?;
309+
// Force-read the contract-trie and class-trie leaves so their pre-block values are cached
310+
// in the initial reads.
311+
for contract_address in raw_initial_reads.get_contract_addresses() {
312+
self.get_class_hash_at(contract_address)?;
313+
self.get_nonce_at(contract_address)?;
314+
}
315+
for class_hash in raw_initial_reads.declared_contracts.keys() {
316+
self.get_compiled_class_hash(*class_hash)?;
317+
}
318+
let mut os_initial_reads = self.get_initial_reads()?;
319+
os_initial_reads.declared_contracts.clear();
320+
Ok(os_initial_reads)
321+
}
322+
}
323+
298324
pub type StorageEntry = (ContractAddress, StorageKey);
299325

300326
#[derive(Debug, Default, derive_more::IntoIterator)]

0 commit comments

Comments
 (0)