Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
refactor(execution)!: pre_process_block refactor for full_nodes simul…
Browse files Browse the repository at this point in the history
…ate transaction
  • Loading branch information
Yael-Starkware committed Jan 28, 2024
1 parent 37b31c0 commit 6c8794c
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 77 deletions.
56 changes: 43 additions & 13 deletions crates/blockifier/src/block_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<BlockNumberHashPair>,
) -> 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<BlockContext> {
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)
}
25 changes: 22 additions & 3 deletions crates/blockifier/src/block_execution_test.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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());
}
4 changes: 4 additions & 0 deletions crates/blockifier/src/state/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand All @@ -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,
}
50 changes: 35 additions & 15 deletions crates/native_blockifier/src/py_block_executor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use std::collections::HashMap;
use std::sync::Arc;

use blockifier::block_context::{BlockContextArgs, ChainInfo, FeeTokenAddresses, GasPrices};
use blockifier::state::cached_state::GlobalContractCache;
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};
use blockifier::state::state_api::State;
use pyo3::prelude::*;
use starknet_api::block::{BlockNumber, BlockTimestamp};
use starknet_api::core::{ChainId, ContractAddress};
Expand Down Expand Up @@ -56,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(())
Expand Down Expand Up @@ -99,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()
}
Expand Down Expand Up @@ -320,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<BlockContext> {
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)
}
4 changes: 2 additions & 2 deletions crates/native_blockifier/src/py_block_executor_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
35 changes: 19 additions & 16 deletions crates/native_blockifier/src/py_validator.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -39,13 +40,14 @@ impl PyValidator {
max_recursion_depth: usize,
max_nonce_for_validation_skip: PyFelt,
) -> NativeBlockifierResult<Self> {
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,
Expand Down Expand Up @@ -110,13 +112,14 @@ impl PyValidator {
next_block_info: PyBlockInfo,
max_recursion_depth: usize,
) -> NativeBlockifierResult<Self> {
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,
Expand Down
33 changes: 5 additions & 28 deletions crates/native_blockifier/src/transaction_executor.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -43,21 +41,13 @@ pub struct TransactionExecutor<S: StateReader> {
}

impl<S: StateReader> TransactionExecutor<S> {
pub fn new(
state_reader: S,
general_config: &PyGeneralConfig,
block_info: PyBlockInfo,
max_recursion_depth: usize,
global_contract_cache: GlobalContractCache,
) -> NativeBlockifierResult<Self> {
pub fn new(state: CachedState<S>, block_context: BlockContext) -> NativeBlockifierResult<Self> {
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::<ClassHash>::new(),
visited_storage_entries: HashSet::<StorageEntry>::new(),
state: CachedState::new(state_reader, global_contract_cache),
state,
staged_for_commit_state: None,
};
log::debug!("Initialized Transaction Executor.");
Expand Down Expand Up @@ -177,19 +167,6 @@ impl<S: StateReader> TransactionExecutor<S> {
(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")
Expand Down

0 comments on commit 6c8794c

Please sign in to comment.