diff --git a/CHANGELOG.md b/CHANGELOG.md index b40e2a213..a14095e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next release +- fix(block_production): dynamic block closing now adds special address with prev block hash - fix(rpc): call, simulate, estimate rpcs executed on top of the block, not at the start of it - fix(compilation): crate-level compilation - chore: Move crates under a madara subdir diff --git a/crates/madara/client/block_production/src/lib.rs b/crates/madara/client/block_production/src/lib.rs index 314f598b6..e7355e1d4 100644 --- a/crates/madara/client/block_production/src/lib.rs +++ b/crates/madara/client/block_production/src/lib.rs @@ -359,6 +359,50 @@ impl BlockProductionTask { Ok(()) } + /// Updates the state diff to store a block hash at the special address 0x1, which serves as + /// Starknet's block hash registry. + /// + /// # Purpose + /// Address 0x1 in Starknet is a special contract address that maintains a mapping of block numbers + /// to their corresponding block hashes. This storage is used by the `get_block_hash` system call + /// and is essential for block hash verification within the Starknet protocol. + /// + /// # Storage Structure at Address 0x1 + /// - Keys: Block numbers + /// - Values: Corresponding block hashes + /// - Default: 0 for all other block numbers + /// + /// # Implementation Details + /// For each block N ≥ 10, this function stores the hash of block (N-10) at address 0x1 + /// with the block number as the key. + /// + /// For more details, see the [official Starknet documentation on special addresses] + /// (https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-state/#address_0x1) + /// + /// It is also required by SNOS for PIEs creation of the block. + fn update_block_hash_registry(&self, state_diff: &mut StateDiff, block_n: u64) -> Result<(), Error> { + if block_n >= 10 { + let prev_block_number = block_n - 10; + let prev_block_hash = self + .backend + .get_block_hash(&BlockId::Number(prev_block_number)) + .map_err(|err| { + Error::Unexpected( + format!("Error fetching block hash for block {prev_block_number}: {err:#}").into(), + ) + })? + .ok_or_else(|| { + Error::Unexpected(format!("No block hash found for block number {prev_block_number}").into()) + })?; + + state_diff.storage_diffs.push(ContractStorageDiffItem { + address: Felt::ONE, // Address 0x1 + storage_entries: vec![StorageEntry { key: Felt::from(prev_block_number), value: prev_block_hash }], + }); + } + Ok(()) + } + #[tracing::instrument(skip(self), fields(module = "BlockProductionTask"))] pub async fn on_pending_time_tick(&mut self) -> Result { let current_pending_tick = self.current_pending_tick; @@ -366,13 +410,15 @@ impl BlockProductionTask { return Ok(false); } - // Use full bouncer capacity - let bouncer_cap = self.backend.chain_config().bouncer_config.block_max_capacity; - let start_time = Instant::now(); - let ContinueBlockResult { state_diff, visited_segments, bouncer_weights, stats, block_now_full } = - self.continue_block(bouncer_cap)?; + let ContinueBlockResult { + state_diff: mut new_state_diff, + visited_segments, + bouncer_weights, + stats, + block_now_full, + } = self.continue_block(self.backend.chain_config().bouncer_config.block_max_capacity)?; if stats.n_added_to_block > 0 { tracing::info!( @@ -385,8 +431,11 @@ impl BlockProductionTask { // Check if block is full if block_now_full { + let block_n = self.block_n(); + self.update_block_hash_registry(&mut new_state_diff, block_n)?; + tracing::info!("Resource limits reached, closing block early"); - self.close_and_prepare_next_block(state_diff, visited_segments, start_time).await?; + self.close_and_prepare_next_block(new_state_diff, visited_segments, start_time).await?; return Ok(true); } @@ -394,7 +443,7 @@ impl BlockProductionTask { // todo, prefer using the block import pipeline? self.backend.store_block( self.block.clone().into(), - state_diff, + new_state_diff, self.declared_classes.clone(), Some(visited_segments), Some(bouncer_weights), @@ -421,27 +470,7 @@ impl BlockProductionTask { block_now_full: _block_now_full, } = self.continue_block(self.backend.chain_config().bouncer_config.block_max_capacity)?; - // SNOS requirement: For blocks >= 10, the hash of the block 10 blocks prior - // at address 0x1 with the block number as the key - if block_n >= 10 { - let prev_block_number = block_n - 10; - let prev_block_hash = self - .backend - .get_block_hash(&BlockId::Number(prev_block_number)) - .map_err(|err| { - Error::Unexpected( - format!("Error fetching block hash for block {prev_block_number}: {err:#}").into(), - ) - })? - .ok_or_else(|| { - Error::Unexpected(format!("No block hash found for block number {prev_block_number}").into()) - })?; - - new_state_diff.storage_diffs.push(ContractStorageDiffItem { - address: Felt::ONE, - storage_entries: vec![StorageEntry { key: Felt::from(prev_block_number), value: prev_block_hash }], - }); - } + self.update_block_hash_registry(&mut new_state_diff, block_n)?; self.close_and_prepare_next_block(new_state_diff, visited_segments, start_time).await }