From cb0632f0bd1decc2cc5f35ade057086ff2749321 Mon Sep 17 00:00:00 2001 From: Mohammad Nassar Date: Sun, 7 Jan 2024 12:12:11 +0200 Subject: [PATCH] perf(native_blockifier): transaction execution info add serialize --- Cargo.lock | 1 + crates/blockifier/src/execution/call_info.rs | 25 +++++++++---- .../blockifier/src/execution/entry_point.rs | 5 +-- crates/blockifier/src/transaction/objects.rs | 5 +-- .../src/transaction/transaction_types.rs | 4 +-- crates/native_blockifier/Cargo.toml | 1 + .../src/py_block_executor.rs | 6 ++-- crates/native_blockifier/src/py_validator.rs | 9 ++--- .../src/transaction_executor.rs | 36 ++++++++++++++++--- 9 files changed, 67 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0ce74c831..861b1940f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2112,6 +2112,7 @@ dependencies = [ "pretty_assertions", "pyo3", "pyo3-log", + "serde", "serde_json", "starknet_api", "tempfile", diff --git a/crates/blockifier/src/execution/call_info.rs b/crates/blockifier/src/execution/call_info.rs index e723cb1735..3e5b9f21b3 100644 --- a/crates/blockifier/src/execution/call_info.rs +++ b/crates/blockifier/src/execution/call_info.rs @@ -1,6 +1,7 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use cairo_vm::vm::runners::cairo_runner::ExecutionResources as VmExecutionResources; +use serde::{Deserialize, Serialize}; use starknet_api::core::{ClassHash, EthAddress}; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; @@ -12,7 +13,7 @@ use crate::state::cached_state::StorageEntry; use crate::transaction::errors::TransactionExecutionError; use crate::transaction::objects::TransactionExecutionResult; -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct Retdata(pub Vec); #[macro_export] @@ -23,7 +24,7 @@ macro_rules! retdata { } #[cfg_attr(test, derive(Clone))] -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct OrderedEvent { pub order: usize, pub event: EventContent, @@ -53,14 +54,14 @@ impl MessageL1CostInfo { } #[cfg_attr(test, derive(Clone))] -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct MessageToL1 { pub to_address: EthAddress, pub payload: L2ToL1Payload, } #[cfg_attr(test, derive(Clone))] -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct OrderedL2ToL1Message { pub order: usize, pub message: MessageToL1, @@ -68,7 +69,7 @@ pub struct OrderedL2ToL1Message { /// Represents the effects of executing a single entry point. #[cfg_attr(test, derive(Clone))] -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct CallExecution { pub retdata: Retdata, pub events: Vec, @@ -77,11 +78,21 @@ pub struct CallExecution { pub gas_consumed: u64, } +// This struct is used to implement `serde` functionality in a remote `VmExecutionResources` Struct. +#[derive(Debug, Default, Deserialize, derive_more::From, Eq, PartialEq, Serialize)] +#[serde(remote = "VmExecutionResources")] +struct VmExecutionResourcesDef { + n_steps: usize, + n_memory_holes: usize, + builtin_instance_counter: HashMap, +} + /// Represents the full effects of executing an entry point, including the inner calls it invoked. -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct CallInfo { pub call: CallEntryPoint, pub execution: CallExecution, + #[serde(with = "VmExecutionResourcesDef")] pub vm_resources: VmExecutionResources, pub inner_calls: Vec, diff --git a/crates/blockifier/src/execution/entry_point.rs b/crates/blockifier/src/execution/entry_point.rs index d862142875..12501e0568 100644 --- a/crates/blockifier/src/execution/entry_point.rs +++ b/crates/blockifier/src/execution/entry_point.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use cairo_vm::vm::runners::cairo_runner::{ ExecutionResources as VmExecutionResources, ResourceTracker, RunResources, }; +use serde::Serialize; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; use starknet_api::deprecated_contract_class::EntryPointType; use starknet_api::hash::StarkFelt; @@ -35,14 +36,14 @@ pub const FAULTY_CLASS_HASH: &str = pub type EntryPointExecutionResult = Result; /// Represents a the type of the call (used for debugging). -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)] pub enum CallType { #[default] Call = 0, Delegate = 1, } /// Represents a call to an entry point of a Starknet contract. -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct CallEntryPoint { // The class hash is not given if it can be deduced from the storage address. pub class_hash: Option, diff --git a/crates/blockifier/src/transaction/objects.rs b/crates/blockifier/src/transaction/objects.rs index 2930f94c87..076b0e1707 100644 --- a/crates/blockifier/src/transaction/objects.rs +++ b/crates/blockifier/src/transaction/objects.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use cairo_felt::Felt252; use itertools::concat; use num_traits::Pow; +use serde::Serialize; use starknet_api::core::{ClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::transaction::{ @@ -146,7 +147,7 @@ pub struct CommonAccountFields { } /// Contains the information gathered by the execution of a transaction. -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct TransactionExecutionInfo { /// Transaction validation call info; [None] for `L1Handler`. pub validate_call_info: Option, @@ -194,7 +195,7 @@ impl TransactionExecutionInfo { /// A mapping from a transaction execution resource to its actual usage. #[cfg_attr(test, derive(Clone))] -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Serialize)] pub struct ResourcesMapping(pub HashMap); impl ResourcesMapping { diff --git a/crates/blockifier/src/transaction/transaction_types.rs b/crates/blockifier/src/transaction/transaction_types.rs index f535076e4d..3df0670c5a 100644 --- a/crates/blockifier/src/transaction/transaction_types.rs +++ b/crates/blockifier/src/transaction/transaction_types.rs @@ -1,11 +1,11 @@ use std::str::FromStr; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; use crate::transaction::errors::ParseError; -#[derive(Clone, Copy, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq, Serialize)] pub enum TransactionType { Declare, DeployAccount, diff --git a/crates/native_blockifier/Cargo.toml b/crates/native_blockifier/Cargo.toml index 4d5a9109ba..6315d250d1 100644 --- a/crates/native_blockifier/Cargo.toml +++ b/crates/native_blockifier/Cargo.toml @@ -35,6 +35,7 @@ num-bigint.workspace = true papyrus_storage = { workspace = true, features = ["testing"] } pyo3 = { workspace = true, features = ["num-bigint", "hashbrown"] } pyo3-log.workspace = true +serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } starknet_api = { workspace = true, features = ["testing"] } thiserror.workspace = true diff --git a/crates/native_blockifier/src/py_block_executor.rs b/crates/native_blockifier/src/py_block_executor.rs index 4c5546a890..f0bbd4be77 100644 --- a/crates/native_blockifier/src/py_block_executor.rs +++ b/crates/native_blockifier/src/py_block_executor.rs @@ -11,11 +11,11 @@ use starknet_api::hash::StarkFelt; use crate::errors::{NativeBlockifierError, NativeBlockifierResult}; use crate::py_state_diff::{PyBlockInfo, PyStateDiff}; -use crate::py_transaction_execution_info::{PyBouncerInfo, PyTransactionExecutionInfo}; +use crate::py_transaction_execution_info::PyBouncerInfo; use crate::py_utils::{int_to_chain_id, py_attr, versioned_constants_with_overrides, PyFelt}; use crate::state_readers::papyrus_state::PapyrusReader; use crate::storage::{PapyrusStorage, Storage, StorageConfig}; -use crate::transaction_executor::TransactionExecutor; +use crate::transaction_executor::{RawTransactionExecutionInfo, TransactionExecutor}; #[cfg(test)] #[path = "py_block_executor_test.rs"] @@ -87,7 +87,7 @@ impl PyBlockExecutor { &mut self, tx: &PyAny, raw_contract_class: Option<&str>, - ) -> NativeBlockifierResult<(PyTransactionExecutionInfo, PyBouncerInfo)> { + ) -> NativeBlockifierResult<(RawTransactionExecutionInfo, PyBouncerInfo)> { let charge_fee = true; self.tx_executor().execute(tx, raw_contract_class, charge_fee) } diff --git a/crates/native_blockifier/src/py_validator.rs b/crates/native_blockifier/src/py_validator.rs index 6383924569..54ca0cc4d7 100644 --- a/crates/native_blockifier/src/py_validator.rs +++ b/crates/native_blockifier/src/py_validator.rs @@ -15,10 +15,10 @@ use crate::errors::NativeBlockifierResult; use crate::py_block_executor::PyGeneralConfig; use crate::py_state_diff::PyBlockInfo; use crate::py_transaction::py_account_tx; -use crate::py_transaction_execution_info::{PyBouncerInfo, PyTransactionExecutionInfo}; +use crate::py_transaction_execution_info::PyBouncerInfo; use crate::py_utils::{versioned_constants_with_overrides, PyFelt}; use crate::state_readers::py_state_reader::PyStateReader; -use crate::transaction_executor::TransactionExecutor; +use crate::transaction_executor::{RawTransactionExecutionInfo, TransactionExecutor}; /// Manages transaction validation for pre-execution flows. #[pyclass] @@ -72,7 +72,8 @@ impl PyValidator { // before `__validate_deploy__`. The execution already includes all necessary validations, // so they are skipped here. if let AccountTransaction::DeployAccount(_deploy_account_tx) = account_tx { - let (_py_tx_execution_info, _py_bouncer_info) = self.execute(tx, raw_contract_class)?; + let (_raw_tx_execution_info, _py_bouncer_info) = + self.execute(tx, raw_contract_class)?; // TODO(Ayelet, 09/11/2023): Check call succeeded. return Ok(()); @@ -126,7 +127,7 @@ impl PyValidator { &mut self, tx: &PyAny, raw_contract_class: Option<&str>, - ) -> NativeBlockifierResult<(PyTransactionExecutionInfo, PyBouncerInfo)> { + ) -> NativeBlockifierResult<(RawTransactionExecutionInfo, PyBouncerInfo)> { let limit_execution_steps_by_resource_bounds = true; self.tx_executor.execute(tx, raw_contract_class, limit_execution_steps_by_resource_bounds) } diff --git a/crates/native_blockifier/src/transaction_executor.rs b/crates/native_blockifier/src/transaction_executor.rs index f5b1af52fe..cde88a252c 100644 --- a/crates/native_blockifier/src/transaction_executor.rs +++ b/crates/native_blockifier/src/transaction_executor.rs @@ -11,6 +11,7 @@ use blockifier::state::cached_state::{ }; use blockifier::state::state_api::{State, StateReader}; use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::objects::TransactionExecutionInfo; use blockifier::transaction::transaction_execution::Transaction; use blockifier::transaction::transaction_utils::calculate_tx_weights; use blockifier::transaction::transactions::{ExecutableTransaction, ValidatableTransaction}; @@ -18,15 +19,26 @@ use blockifier::versioned_constants::VersionedConstants; use cairo_vm::vm::runners::builtin_runner::HASH_BUILTIN_NAME; use cairo_vm::vm::runners::cairo_runner::ExecutionResources as VmExecutionResources; use pyo3::prelude::*; +use serde::Serialize; use starknet_api::core::ClassHash; use crate::errors::{NativeBlockifierError, NativeBlockifierResult}; use crate::py_block_executor::{into_block_context, PyGeneralConfig}; use crate::py_state_diff::{PyBlockInfo, PyStateDiff}; use crate::py_transaction::py_tx; -use crate::py_transaction_execution_info::{PyBouncerInfo, PyTransactionExecutionInfo}; +use crate::py_transaction_execution_info::PyBouncerInfo; use crate::py_utils::PyFelt; +pub(crate) type RawTransactionExecutionInfo = Vec; + +#[pyclass] +#[derive(Debug, Serialize)] +pub(crate) struct TypedTransactionExecutionInfo { + #[serde(flatten)] + info: TransactionExecutionInfo, + tx_type: String, +} + pub struct TransactionExecutor { pub block_context: BlockContext, @@ -72,7 +84,8 @@ impl TransactionExecutor { tx: &PyAny, raw_contract_class: Option<&str>, charge_fee: bool, - ) -> NativeBlockifierResult<(PyTransactionExecutionInfo, PyBouncerInfo)> { + ) -> NativeBlockifierResult<(RawTransactionExecutionInfo, PyBouncerInfo)> { + let tx_type: &str = tx.getattr("tx_type")?.getattr("name")?.extract()?; let tx: Transaction = py_tx(tx, raw_contract_class)?; let l1_handler_payload_size: usize = if let Transaction::L1HandlerTransaction(l1_handler_tx) = &tx { @@ -91,8 +104,11 @@ impl TransactionExecutor { match tx_execution_result { Ok(tx_execution_info) => { // TODO(Elin, 01/06/2024): consider traversing the calls to collect data once. + // TODO(Elin, 01/06/2024): consider moving Bouncer logic to a function. tx_executed_class_hashes.extend(tx_execution_info.get_executed_class_hashes()); tx_visited_storage_entries.extend(tx_execution_info.get_visited_storage_entries()); + + // Count message to L1 resources. let call_infos: IntoIter<&CallInfo> = [&tx_execution_info.validate_call_info, &tx_execution_info.execute_call_info] .iter() @@ -102,7 +118,7 @@ impl TransactionExecutor { let MessageL1CostInfo { l2_to_l1_payload_lengths: _, message_segment_length } = MessageL1CostInfo::calculate(call_infos, Some(l1_handler_payload_size))?; - // TODO(Elin, 01/06/2024): consider moving Bouncer logic to a function. + // Count additional OS resources. let mut additional_os_resources = get_casm_hash_calculation_resources( &mut transactional_state, &self.executed_class_hashes, @@ -112,7 +128,11 @@ impl TransactionExecutor { &self.visited_storage_entries, &tx_visited_storage_entries, )?; + + // Count blob resources. let state_diff_size = 0; + + // Finalize counting logic. let actual_resources = tx_execution_info.actual_resources.0.clone(); let tx_weights = calculate_tx_weights( additional_os_resources, @@ -124,8 +144,14 @@ impl TransactionExecutor { self.staged_for_commit_state = Some( transactional_state.stage(tx_executed_class_hashes, tx_visited_storage_entries), ); - let py_tx_execution_info = PyTransactionExecutionInfo::from(tx_execution_info); - Ok((py_tx_execution_info, py_bouncer_info)) + + let typed_tx_execution_info = TypedTransactionExecutionInfo { + info: tx_execution_info, + tx_type: tx_type.to_string(), + }; + let raw_tx_execution_info = serde_json::to_vec(&typed_tx_execution_info)?; + + Ok((raw_tx_execution_info, py_bouncer_info)) } Err(error) => { transactional_state.abort();