From 68cbe2736474eb5820d97b3482738c626e15908b Mon Sep 17 00:00:00 2001 From: Yael Doweck Date: Sun, 28 Jan 2024 10:16:40 +0200 Subject: [PATCH] refactor(execution)!: pre_process_block refactor for full_nodes simulate transaction --- crates/blockifier/src/block_execution.rs | 55 +++++++++++++++---- crates/blockifier/src/block_execution_test.rs | 23 +++++++- crates/blockifier/src/state/errors.rs | 7 +++ .../src/py_block_executor.rs | 52 +++++++++++++----- .../src/py_block_executor_test.rs | 9 ++- crates/native_blockifier/src/py_validator.rs | 41 ++++++++------ .../src/transaction_executor.rs | 33 ++--------- 7 files changed, 145 insertions(+), 75 deletions(-) diff --git a/crates/blockifier/src/block_execution.rs b/crates/blockifier/src/block_execution.rs index 7621e812c0..b4548774c5 100644 --- a/crates/blockifier/src/block_execution.rs +++ b/crates/blockifier/src/block_execution.rs @@ -4,6 +4,8 @@ use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; use crate::abi::constants; +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,50 @@ 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(constants::STORED_BLOCK_HASH_BUFFER) => + { + // The block hash is not available for the first `constants::STORED_BLOCK_HASH_BUFFER` + // blocks. + 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..598861a825 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::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 block pre-process with block hash None is successful only within the allowed + // block number interval. + let block_context_args = BlockContextArgs { + block_number: BlockNumber(constants::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(constants::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..645cb443db 100644 --- a/crates/blockifier/src/state/errors.rs +++ b/crates/blockifier/src/state/errors.rs @@ -3,8 +3,15 @@ use starknet_api::core::{ClassHash, ContractAddress}; use starknet_api::StarknetApiError; use thiserror::Error; +use crate::abi::constants; + #[derive(Debug, Error)] pub enum StateError { + #[error( + "A block hash must be provided for block number > {}.", + constants::STORED_BLOCK_HASH_BUFFER + )] + OldBlockHashNotProvided, #[error("Cannot deploy contract at address 0.")] OutOfRangeContractAddress, #[error(transparent)] diff --git a/crates/native_blockifier/src/py_block_executor.rs b/crates/native_blockifier/src/py_block_executor.rs index d6878dfeef..f3b94435ab 100644 --- a/crates/native_blockifier/src/py_block_executor.rs +++ b/crates/native_blockifier/src/py_block_executor.rs @@ -1,8 +1,16 @@ use std::collections::HashMap; use std::sync::Arc; -use blockifier::block_context::{BlockContextArgs, ChainInfo, FeeTokenAddresses, GasPrices}; -use blockifier::state::cached_state::{GlobalContractCache, GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST}; +use blockifier::block_context::{ + BlockContext, BlockContextArgs, ChainInfo, FeeTokenAddresses, GasPrices, +}; +use blockifier::block_execution::{ + pre_process_block as pre_process_block_blockifier, BlockNumberHashPair, +}; +use blockifier::state::cached_state::{ + CachedState, GlobalContractCache, GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST, +}; +use blockifier::state::state_api::State; use pyo3::prelude::*; use starknet_api::block::{BlockNumber, BlockTimestamp}; use starknet_api::core::{ChainId, ContractAddress}; @@ -58,20 +66,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(()) @@ -101,14 +112,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() } @@ -322,3 +325,22 @@ pub fn into_block_context_args( Ok(block_context) } + +// Executes 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..dcb7d18656 100644 --- a/crates/native_blockifier/src/py_block_executor_test.rs +++ b/crates/native_blockifier/src/py_block_executor_test.rs @@ -19,7 +19,10 @@ 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(); + let sentinel_block_number_and_hash = None; // Information does not exist for block 0. + block_executor + .setup_block_execution(PyBlockInfo::default(), sentinel_block_number_and_hash) + .unwrap(); let class_hash = class_hash!(TEST_CLASS_HASH); let contract_class = get_test_contract_class(); @@ -36,7 +39,9 @@ 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(), sentinel_block_number_and_hash) + .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 09d05e0ca5..a2d341e58a 100644 --- a/crates/native_blockifier/src/py_validator.rs +++ b/crates/native_blockifier/src/py_validator.rs @@ -1,7 +1,10 @@ +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, GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST}; +use blockifier::state::cached_state::{ + CachedState, GlobalContractCache, GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST, +}; use blockifier::state::state_api::StateReader; use blockifier::transaction::account_transaction::AccountTransaction; use blockifier::transaction::objects::{AccountTransactionContext, TransactionExecutionResult}; @@ -11,7 +14,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}; @@ -40,13 +43,16 @@ impl PyValidator { global_contract_cache_size: 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::new(global_contract_cache_size), - )?; + let global_contract_cache = GlobalContractCache::new(global_contract_cache_size); + 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, @@ -111,13 +117,16 @@ 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::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST), - )?; + let state_reader = PyStateReader::new(state_reader_proxy); + let global_contract_cache = GlobalContractCache::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST); + 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 4db04c5bbc..6fab42dc7d 100644 --- a/crates/native_blockifier/src/transaction_executor.rs +++ b/crates/native_blockifier/src/transaction_executor.rs @@ -2,12 +2,11 @@ use std::collections::{HashMap, HashSet}; use std::vec::IntoIter; use blockifier::block_context::BlockContext; -use blockifier::block_execution::{pre_process_block, BlockNumberHashPair}; use blockifier::execution::call_info::{CallInfo, MessageL1CostInfo}; 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; @@ -20,8 +19,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}; use crate::py_utils::PyFelt; @@ -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."); @@ -193,19 +183,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")