diff --git a/crates/blockifier/src/block_execution.rs b/crates/blockifier/src/block_execution.rs index 7621e812c0..2f5ae57cb5 100644 --- a/crates/blockifier/src/block_execution.rs +++ b/crates/blockifier/src/block_execution.rs @@ -3,7 +3,9 @@ use starknet_api::core::ContractAddress; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; -use crate::abi::constants; +use crate::abi::constants::{self, STORED_BLOCK_HASH_BUFFER}; +use crate::block_context::{BlockContext, BlockContextArgs, BlockInfo, ChainInfo}; +use crate::state::errors::StateError; use crate::state::state_api::{State, StateResult}; #[cfg(test)] @@ -24,21 +26,49 @@ impl BlockNumberHashPair { // Block pre-processing. // Writes the hash of the (current_block_number - N) block under its block number in the dedicated // contract state, where N=STORED_BLOCK_HASH_BUFFER. +// NOTE: This function must remain idempotent since full nodes can call it for an already updated +// block hash table. pub fn pre_process_block( state: &mut dyn State, old_block_number_and_hash: Option, -) -> StateResult<()> { - if let Some(BlockNumberHashPair { number: block_number, hash: block_hash }) = - old_block_number_and_hash - { - state.set_storage_at( - ContractAddress::try_from(StarkFelt::from(constants::BLOCK_HASH_CONTRACT_ADDRESS)) - .expect("Failed to convert `BLOCK_HASH_CONTRACT_ADDRESS` to ContractAddress."), - StorageKey::try_from(StarkFelt::from(block_number.0)) - .expect("Failed to convert BlockNumber to StorageKey."), - block_hash.0, - )?; + block_context_args: BlockContextArgs, +) -> StateResult { + match old_block_number_and_hash { + Some(BlockNumberHashPair { number: block_number, hash: block_hash }) => { + state.set_storage_at( + ContractAddress::try_from(StarkFelt::from(constants::BLOCK_HASH_CONTRACT_ADDRESS)) + .expect("Failed to convert `BLOCK_HASH_CONTRACT_ADDRESS` to ContractAddress."), + StorageKey::try_from(StarkFelt::from(block_number.0)) + .expect("Failed to convert BlockNumber to StorageKey."), + block_hash.0, + )?; + } + None if block_context_args.block_number >= BlockNumber(STORED_BLOCK_HASH_BUFFER) => { + // We allow None value for block_number < STORED_BLOCK_HASH_BUFFER because we update + // the hash table in STORED_BLOCK_HASH_BUFFER blocks delay. This is done since the + // hash computation has a delay. + return Err(StateError::OldBlockHashNotProvided); + } + None => {} } - Ok(()) + let block_context = BlockContext { + block_info: BlockInfo { + block_number: block_context_args.block_number, + block_timestamp: block_context_args.block_timestamp, + sequencer_address: block_context_args.sequencer_address, + vm_resource_fee_cost: block_context_args.vm_resource_fee_cost, + use_kzg_da: block_context_args.use_kzg_da, + gas_prices: block_context_args.gas_prices, + invoke_tx_max_n_steps: block_context_args.invoke_tx_max_n_steps, + validate_max_n_steps: block_context_args.validate_max_n_steps, + max_recursion_depth: block_context_args.max_recursion_depth, + }, + chain_info: ChainInfo { + chain_id: block_context_args.chain_id, + fee_token_addresses: block_context_args.fee_token_addresses, + }, + }; + + Ok(block_context) } diff --git a/crates/blockifier/src/block_execution_test.rs b/crates/blockifier/src/block_execution_test.rs index 5edc129a60..3c392561b7 100644 --- a/crates/blockifier/src/block_execution_test.rs +++ b/crates/blockifier/src/block_execution_test.rs @@ -1,8 +1,10 @@ +use starknet_api::block::BlockNumber; use starknet_api::core::ContractAddress; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; -use crate::abi::constants; +use crate::abi::constants::{self, STORED_BLOCK_HASH_BUFFER}; +use crate::block_context::BlockContextArgs; use crate::block_execution::{pre_process_block, BlockNumberHashPair}; use crate::state::state_api::StateReader; use crate::test_utils::cached_state::create_test_state; @@ -13,12 +15,29 @@ fn test_pre_process_block() { let block_number: u64 = 10; let block_hash = StarkFelt::from(20_u8); - pre_process_block(&mut state, Some(BlockNumberHashPair::new(block_number, block_hash))) - .unwrap(); + pre_process_block( + &mut state, + Some(BlockNumberHashPair::new(block_number, block_hash)), + BlockContextArgs::default(), + ) + .unwrap(); let written_hash = state.get_storage_at( ContractAddress::try_from(StarkFelt::from(constants::BLOCK_HASH_CONTRACT_ADDRESS)).unwrap(), StorageKey::try_from(StarkFelt::from(block_number)).unwrap(), ); assert_eq!(written_hash.unwrap(), block_hash); + + // Test that the function returns ok when block_number < 10 and an error when + // block_number >= 10 for old_block_number_and_hash = None + let block_context_args = BlockContextArgs { + block_number: BlockNumber(STORED_BLOCK_HASH_BUFFER - 1), + ..Default::default() + }; + assert!(pre_process_block(&mut state, None, block_context_args).is_ok()); + let block_context_args = BlockContextArgs { + block_number: BlockNumber(STORED_BLOCK_HASH_BUFFER), + ..Default::default() + }; + assert!(pre_process_block(&mut state, None, block_context_args).is_err()); } diff --git a/crates/blockifier/src/state/errors.rs b/crates/blockifier/src/state/errors.rs index 25a6f9c5ba..2bd812b3e4 100644 --- a/crates/blockifier/src/state/errors.rs +++ b/crates/blockifier/src/state/errors.rs @@ -3,6 +3,8 @@ use starknet_api::core::{ClassHash, ContractAddress}; use starknet_api::StarknetApiError; use thiserror::Error; +use crate::abi::constants::STORED_BLOCK_HASH_BUFFER; + #[derive(Debug, Error)] pub enum StateError { #[error("Cannot deploy contract at address 0.")] @@ -18,4 +20,6 @@ pub enum StateError { /// Represents all unexpected errors that may occur while reading from state. #[error("Failed to read from state: {0}.")] StateReadError(String), + #[error("A block hash must be provided for block number > {STORED_BLOCK_HASH_BUFFER}.")] + OldBlockHashNotProvided, } diff --git a/crates/native_blockifier/src/py_block_executor.rs b/crates/native_blockifier/src/py_block_executor.rs index 5436ce5e11..e2fb160d44 100644 --- a/crates/native_blockifier/src/py_block_executor.rs +++ b/crates/native_blockifier/src/py_block_executor.rs @@ -2,10 +2,13 @@ use std::collections::HashMap; use std::sync::Arc; use blockifier::block_context::{ - BlockContextArgs, ChainInfo, FeeTokenAddresses, GasPrices, + BlockContext, BlockContextArgs, ChainInfo, FeeTokenAddresses, GasPrices, }; -use blockifier::state::cached_state::GlobalContractCache; - +use blockifier::block_execution::{ + pre_process_block as pre_process_block_blockifier, BlockNumberHashPair, +}; +use blockifier::state::cached_state::{CachedState, GlobalContractCache}; +use blockifier::state::state_api::State; use pyo3::prelude::*; use starknet_api::block::{BlockNumber, BlockTimestamp}; use starknet_api::core::{ChainId, ContractAddress}; @@ -59,20 +62,23 @@ impl PyBlockExecutor { // Transaction Execution API. /// Initializes the transaction executor for the given block. - #[pyo3(signature = (next_block_info))] + #[pyo3(signature = (next_block_info, old_block_number_and_hash))] fn setup_block_execution( &mut self, next_block_info: PyBlockInfo, + old_block_number_and_hash: Option<(u64, PyFelt)>, ) -> NativeBlockifierResult<()> { let papyrus_reader = self.get_aligned_reader(next_block_info.block_number); - - let tx_executor = TransactionExecutor::new( - papyrus_reader, + let global_contract_cache = self.global_contract_cache.clone(); + let mut state = CachedState::new(papyrus_reader, global_contract_cache); + let block_context = pre_process_block( + &mut state, + old_block_number_and_hash, &self.general_config, next_block_info, self.max_recursion_depth, - self.global_contract_cache.clone(), )?; + let tx_executor = TransactionExecutor::new(state, block_context)?; self.tx_executor = Some(tx_executor); Ok(()) @@ -102,14 +108,6 @@ impl PyBlockExecutor { finalized_state } - #[pyo3(signature = (old_block_number_and_hash))] - pub fn pre_process_block( - &mut self, - old_block_number_and_hash: Option<(u64, PyFelt)>, - ) -> NativeBlockifierResult<()> { - self.tx_executor().pre_process_block(old_block_number_and_hash) - } - pub fn commit_tx(&mut self) { self.tx_executor().commit() } @@ -323,3 +321,22 @@ pub fn into_block_context_args( Ok(block_context) } + +// Block pre-processing; see `block_execution::pre_process_block` documentation. +fn pre_process_block( + state: &mut dyn State, + old_block_number_and_hash: Option<(u64, PyFelt)>, + general_config: &PyGeneralConfig, + block_info: PyBlockInfo, + max_recursion_depth: usize, +) -> NativeBlockifierResult { + let old_block_number_and_hash = old_block_number_and_hash + .map(|(block_number, block_hash)| BlockNumberHashPair::new(block_number, block_hash.0)); + + let block_context_args = + into_block_context_args(general_config, block_info, max_recursion_depth)?; + let block_context = + pre_process_block_blockifier(state, old_block_number_and_hash, block_context_args)?; + + Ok(block_context) +} diff --git a/crates/native_blockifier/src/py_block_executor_test.rs b/crates/native_blockifier/src/py_block_executor_test.rs index 2bbb2f2c95..d51c1b552a 100644 --- a/crates/native_blockifier/src/py_block_executor_test.rs +++ b/crates/native_blockifier/src/py_block_executor_test.rs @@ -19,7 +19,7 @@ fn global_contract_cache_update() { let temp_storage_path = tempfile::tempdir().unwrap().into_path(); let mut block_executor = PyBlockExecutor::create_for_testing(PyGeneralConfig::default(), temp_storage_path); - block_executor.setup_block_execution(PyBlockInfo::default()).unwrap(); + block_executor.setup_block_execution(PyBlockInfo::default(), None).unwrap(); let class_hash = class_hash!(TEST_CLASS_HASH); let contract_class = get_test_contract_class(); @@ -36,7 +36,7 @@ fn global_contract_cache_update() { block_executor.teardown_block_execution(); // Finalizing a non-pending block does update the global cache. - block_executor.setup_block_execution(PyBlockInfo::default()).unwrap(); + block_executor.setup_block_execution(PyBlockInfo::default(), None).unwrap(); block_executor.tx_executor().state.set_contract_class(class_hash, contract_class).unwrap(); let is_pending_block = false; block_executor.finalize(is_pending_block); diff --git a/crates/native_blockifier/src/py_validator.rs b/crates/native_blockifier/src/py_validator.rs index 9a3c088292..12c28d73ed 100644 --- a/crates/native_blockifier/src/py_validator.rs +++ b/crates/native_blockifier/src/py_validator.rs @@ -1,7 +1,8 @@ +use blockifier::block_context::BlockContext; use blockifier::execution::call_info::CallInfo; use blockifier::fee::actual_cost::ActualCost; use blockifier::fee::fee_checks::PostValidationReport; -use blockifier::state::cached_state::GlobalContractCache; +use blockifier::state::cached_state::{CachedState, GlobalContractCache}; use blockifier::state::state_api::StateReader; use blockifier::transaction::account_transaction::AccountTransaction; use blockifier::transaction::objects::{AccountTransactionContext, TransactionExecutionResult}; @@ -11,7 +12,7 @@ use starknet_api::core::Nonce; use starknet_api::hash::StarkFelt; use crate::errors::NativeBlockifierResult; -use crate::py_block_executor::PyGeneralConfig; +use crate::py_block_executor::{into_block_context_args, PyGeneralConfig}; use crate::py_state_diff::PyBlockInfo; use crate::py_transaction::py_account_tx; use crate::py_transaction_execution_info::{PyBouncerInfo, PyTransactionExecutionInfo}; @@ -39,13 +40,14 @@ impl PyValidator { max_recursion_depth: usize, max_nonce_for_validation_skip: PyFelt, ) -> NativeBlockifierResult { - let tx_executor = TransactionExecutor::new( - PyStateReader::new(state_reader_proxy), - &general_config, - next_block_info, - max_recursion_depth, - GlobalContractCache::default(), - )?; + let global_contract_cache = GlobalContractCache::default(); + let state_reader = PyStateReader::new(state_reader_proxy); + let state = CachedState::new(state_reader, global_contract_cache); + let block_context_args = + into_block_context_args(&general_config, next_block_info, max_recursion_depth)?; + let block_context = BlockContext::new_unchecked(block_context_args); + // TODO(Yael 24/01/24): calc block_context using pre_process_block + let tx_executor = TransactionExecutor::new(state, block_context)?; let validator = Self { general_config, max_recursion_depth, @@ -110,13 +112,14 @@ impl PyValidator { next_block_info: PyBlockInfo, max_recursion_depth: usize, ) -> NativeBlockifierResult { - let tx_executor = TransactionExecutor::new( - PyStateReader::new(state_reader_proxy), - &general_config, - next_block_info, - max_recursion_depth, - GlobalContractCache::default(), - )?; + let state_reader = PyStateReader::new(state_reader_proxy); + let global_contract_cache = GlobalContractCache::default(); + let state = CachedState::new(state_reader, global_contract_cache); + let block_context_args = + into_block_context_args(&general_config, next_block_info, max_recursion_depth)?; + let block_context = BlockContext::new_unchecked(block_context_args); + // TODO(Yael 24/01/24): calc block_context using pre_process_block + let tx_executor = TransactionExecutor::new(state, block_context)?; Ok(Self { general_config, max_recursion_depth: 50, diff --git a/crates/native_blockifier/src/transaction_executor.rs b/crates/native_blockifier/src/transaction_executor.rs index cefce6a5aa..ad4b0c5ba1 100644 --- a/crates/native_blockifier/src/transaction_executor.rs +++ b/crates/native_blockifier/src/transaction_executor.rs @@ -1,12 +1,11 @@ use std::collections::{HashMap, HashSet}; use blockifier::block_context::BlockContext; -use blockifier::block_execution::{pre_process_block, BlockNumberHashPair}; use blockifier::execution::call_info::CallInfo; use blockifier::execution::entry_point::ExecutionResources; use blockifier::fee::actual_cost::ActualCost; use blockifier::state::cached_state::{ - CachedState, GlobalContractCache, StagedTransactionalState, StorageEntry, TransactionalState, + CachedState, StagedTransactionalState, StorageEntry, TransactionalState, }; use blockifier::state::state_api::{State, StateReader}; use blockifier::transaction::account_transaction::AccountTransaction; @@ -18,8 +17,7 @@ use pyo3::prelude::*; use starknet_api::core::ClassHash; use crate::errors::{NativeBlockifierError, NativeBlockifierResult}; -use crate::py_block_executor::{into_block_context_args, PyGeneralConfig}; -use crate::py_state_diff::{PyBlockInfo, PyStateDiff}; +use crate::py_state_diff::PyStateDiff; use crate::py_transaction::py_tx; use crate::py_transaction_execution_info::{ PyBouncerInfo, PyTransactionExecutionInfo, PyVmExecutionResources, @@ -43,21 +41,13 @@ pub struct TransactionExecutor { } impl TransactionExecutor { - pub fn new( - state_reader: S, - general_config: &PyGeneralConfig, - block_info: PyBlockInfo, - max_recursion_depth: usize, - global_contract_cache: GlobalContractCache, - ) -> NativeBlockifierResult { + pub fn new(state: CachedState, block_context: BlockContext) -> NativeBlockifierResult { log::debug!("Initializing Transaction Executor..."); - let block_context_args = - into_block_context_args(general_config, block_info, max_recursion_depth)?; let tx_executor = Self { - block_context: BlockContext::new_unchecked(block_context_args), + block_context, executed_class_hashes: HashSet::::new(), visited_storage_entries: HashSet::::new(), - state: CachedState::new(state_reader, global_contract_cache), + state, staged_for_commit_state: None, }; log::debug!("Initialized Transaction Executor."); @@ -177,19 +167,6 @@ impl TransactionExecutor { (PyStateDiff::from(self.state.to_state_diff()), visited_pcs) } - // Block pre-processing; see `block_execution::pre_process_block` documentation. - pub fn pre_process_block( - &mut self, - old_block_number_and_hash: Option<(u64, PyFelt)>, - ) -> NativeBlockifierResult<()> { - let old_block_number_and_hash = old_block_number_and_hash - .map(|(block_number, block_hash)| BlockNumberHashPair::new(block_number, block_hash.0)); - - pre_process_block(&mut self.state, old_block_number_and_hash)?; - - Ok(()) - } - pub fn commit(&mut self) { let Some(finalized_transactional_state) = self.staged_for_commit_state.take() else { panic!("commit called without a transactional state")