Skip to content

Commit

Permalink
Refactor error handling and add new error types
Browse files Browse the repository at this point in the history
- Introduced `ExecutionError` enum to encapsulate different error types, including `TransactionExecutionError` and `CustomError`.
- Added `SimulationError` enum to handle specific simulation-related errors such as `OutOfGas` and `ExecutionError`.
- Implemented `fmt::Display` and `From` traits for `ExecutionError` and `SimulationError` to provide meaningful error messages and conversions.
- Replaced the use of `anyhow::anyhow!` with structured error handling using the new error types.
- Updated `execute_transaction` to use the new `ExecutionError` type.
- Refactored `is_l2_gas_accounting_enabled`, `get_gas_vector_computation_mode`, `simulate_execution`, `set_l2_gas_limit`, and `extract_l2_gas_limit` functions to handle errors using the new error types.
  • Loading branch information
AnkushinDaniil committed Feb 26, 2025
1 parent 6f21aa8 commit d4fffa4
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 70 deletions.
164 changes: 99 additions & 65 deletions vm/rust/src/execution.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::juno_state_reader::JunoStateReader;
use blockifier::execution::contract_class::TrackedResource;
use blockifier::fee::fee_checks::FeeCheckError;
use blockifier::state::state_api::{StateReader, StateResult, UpdatableState};
use blockifier::transaction::transaction_execution::Transaction;
use blockifier::transaction::transactions::ExecutableTransaction;
Expand All @@ -14,29 +13,79 @@ use starknet_api::executable_transaction::AccountTransaction;
use starknet_api::execution_resources::GasAmount;
use starknet_api::transaction::fields::{GasVectorComputationMode, ValidResourceBounds};
use starknet_api::transaction::{DeclareTransaction, DeployAccountTransaction, InvokeTransaction};
use std::fmt;

#[derive(Debug)]
pub enum ExecutionError {
TransactionExecutionError(TransactionExecutionError),
CustomError(anyhow::Error),
}

impl fmt::Display for ExecutionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExecutionError::TransactionExecutionError(err) => {
write!(f, "Transaction execution error: {}", err)
}
ExecutionError::CustomError(err) => write!(f, "Custom error: {}", err),
}
}
}

impl From<TransactionExecutionError> for ExecutionError {
fn from(err: TransactionExecutionError) -> Self {
ExecutionError::TransactionExecutionError(err)
}
}

impl From<anyhow::Error> for ExecutionError {
fn from(err: anyhow::Error) -> Self {
ExecutionError::CustomError(err)
}
}

#[derive(Debug)]
enum SimulationError {
OutOfGas(GasAmount),
ExecutionError(TransactionExecutionError),
}

impl fmt::Display for SimulationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SimulationError::OutOfGas(gas) => write!(f, "Out of gas: {}", gas),
SimulationError::ExecutionError(err) => write!(f, "Execution error: {}", err),
}
}
}

impl From<TransactionExecutionError> for SimulationError {
fn from(err: TransactionExecutionError) -> Self {
SimulationError::ExecutionError(err)
}
}

pub fn execute_transaction(
txn: &mut Transaction,
txn_state: &mut TransactionalState<'_, CachedState<JunoStateReader>>,
block_context: &BlockContext,
) -> Result<TransactionExecutionInfo, TransactionExecutionError> {
) -> Result<TransactionExecutionInfo, ExecutionError> {
match is_l2_gas_accounting_enabled(
txn,
txn_state,
block_context,
&determine_gas_vector_mode(txn),
) {
Ok(true) => get_gas_vector_computation_mode(txn, txn_state, block_context),
Ok(false) => txn.execute(txn_state, block_context),
Err(error) => Err(TransactionExecutionError::StateError(error)),
Ok(false) => txn
.execute(txn_state, block_context)
.map_err(ExecutionError::from),
Err(error) => Err(ExecutionError::TransactionExecutionError(
TransactionExecutionError::StateError(error),
)),
}
}

/// Determines whether L2 gas accounting should be enabled for fee estimation.
///
/// Starknet 0.13.4 introduced runtime L2 gas accounting, which is only enabled
/// if both the caller and the callee contract classes were compiled as Sierra 1.7.
/// This function checks whether the sender contract meets this requirement.
pub fn is_l2_gas_accounting_enabled(
transaction: &Transaction,
state: &mut TransactionalState<'_, CachedState<JunoStateReader>>,
Expand All @@ -45,7 +94,6 @@ pub fn is_l2_gas_accounting_enabled(
) -> StateResult<bool> {
let sender_class_hash = state.get_class_hash_at(transaction.sender_address())?;

// L2 gas accounting is disabled if the sender contract is uninitialized.
if sender_class_hash == ClassHash::default() {
return Ok(false);
}
Expand All @@ -57,9 +105,6 @@ pub fn is_l2_gas_accounting_enabled(
.get_compiled_class(sender_class_hash)?
.tracked_resource(min_sierra_version, None);

// L2 gas accounting is enabled if:
// 1. The gas computation mode requires all gas vectors.
// 2. The sender contract's tracked resource is Sierra gas.
Ok(
matches!(gas_computation_mode, GasVectorComputationMode::All)
&& sender_tracked_resource == TrackedResource::SierraGas,
Expand Down Expand Up @@ -88,58 +133,41 @@ fn determine_gas_vector_mode(transaction: &Transaction) -> GasVectorComputationM
}
}

enum SimulationError {
OutOfGas(GasAmount),
ExecutionError(TransactionExecutionError),
}

/// The margin used in binary search for finding the minimal L2 gas limit.
const L2_GAS_SEARCH_MARGIN: GasAmount = GasAmount(1_000_000);

/// Determines the optimal L2 gas limit required for a transaction to execute successfully.
/// If the required gas exceeds the initial limit, the transaction is reverted.
fn get_gas_vector_computation_mode<S>(
transaction: &mut Transaction,
state: &mut S,
block_context: &blockifier::context::BlockContext,
) -> Result<TransactionExecutionInfo, TransactionExecutionError>
) -> Result<TransactionExecutionInfo, ExecutionError>
where
S: UpdatableState,
{
let initial_gas_limit = extract_l2_gas_limit(transaction);
let initial_gas_limit = extract_l2_gas_limit(transaction)?;
let mut original_transaction = transaction.clone();

// Simulate transaction execution with maximum possible gas to get actual gas usage.
set_l2_gas_limit(transaction, GasAmount::MAX);
// TODO: Consider getting the upper bound from the balance and not changing the execution flags
match transaction {
Transaction::Account(account_transaction) => {
account_transaction.execution_flags.charge_fee = false;
account_transaction.execution_flags.validate = false;
}
_ => {}
};
let (simulation_result, _) = match simulate_execution(transaction, state, block_context) {
Ok(info) => info,
set_l2_gas_limit(transaction, GasAmount::MAX)?;
if let Transaction::Account(account_transaction) = transaction {
account_transaction.execution_flags.charge_fee = false;
account_transaction.execution_flags.validate = false;
}

let simulation_result = match simulate_execution(transaction, state, block_context) {
Ok((tx_info, _)) => tx_info,
Err(SimulationError::ExecutionError(error)) => {
return Err(error);
return Err(ExecutionError::TransactionExecutionError(error))
}
Err(SimulationError::OutOfGas(gas)) => {
return Err(TransactionExecutionError::FeeCheckError(
FeeCheckError::MaxGasAmountExceeded {
resource: starknet_api::transaction::fields::Resource::L2Gas,
max_amount: GasAmount::MAX,
actual_amount: gas,
},
));
return Err(ExecutionError::CustomError(anyhow::anyhow!(
"Transaction ran out of gas during simulation: {}",
gas
)));
}
};

let GasAmount(gas_used) = simulation_result.receipt.gas.l2_gas;

// Add a 10% buffer to the actual gas usage to prevent underestimation.
let l2_gas_adjusted = GasAmount(gas_used.saturating_add(gas_used / 10));
set_l2_gas_limit(transaction, l2_gas_adjusted);
set_l2_gas_limit(transaction, l2_gas_adjusted)?;

let (l2_gas_limit, _, tx_state) = match simulate_execution(transaction, state, block_context) {
Ok((tx_info, tx_state)) => {
Expand All @@ -155,7 +183,7 @@ where
// Run a binary search to find the minimal gas limit that still allows the
// transaction to execute without running out of L2 gas.
let (tx_info, tx_state) = loop {
set_l2_gas_limit(transaction, current_l2_gas_limit);
set_l2_gas_limit(transaction, current_l2_gas_limit)?;

// Special case where the search would get stuck if `current_l2_gas_limit ==
// lower_bound` but the required amount is equal to the upper bound.
Expand All @@ -180,25 +208,28 @@ where
lower_bound = current_l2_gas_limit;
current_l2_gas_limit = calculate_midpoint(lower_bound, upper_bound);
}
Err(SimulationError::ExecutionError(error)) => return Err(error),
Err(SimulationError::ExecutionError(error)) => {
return Err(ExecutionError::TransactionExecutionError(error))
}
}
};

(current_l2_gas_limit, tx_info, tx_state)
}
Err(SimulationError::ExecutionError(error)) => return Err(error),
Err(SimulationError::ExecutionError(error)) => {
return Err(ExecutionError::TransactionExecutionError(error))
}
};
tx_state.abort();

// If the computed gas limit exceeds the initial limit, revert the transaction.
// The L2 gas limit is set to zero to prevent the transaction execution from succeeding
// in the case where the user defined gas limit is less than the required gas limit
if l2_gas_limit > initial_gas_limit {
set_l2_gas_limit(&mut original_transaction, GasAmount(0));
return original_transaction.execute(state, block_context);
set_l2_gas_limit(&mut original_transaction, GasAmount(0))?;
return original_transaction
.execute(state, block_context)
.map_err(ExecutionError::from);
}

set_l2_gas_limit(&mut original_transaction, initial_gas_limit);
set_l2_gas_limit(&mut original_transaction, initial_gas_limit)?;
let mut exec_info = original_transaction.execute(state, block_context)?;

// Execute the transaction with the determined gas limit and update the estimate.
Expand Down Expand Up @@ -237,7 +268,10 @@ where
}
}

fn set_l2_gas_limit(transaction: &mut Transaction, gas_limit: GasAmount) {
fn set_l2_gas_limit(
transaction: &mut Transaction,
gas_limit: GasAmount,
) -> Result<(), anyhow::Error> {
if let Transaction::Account(ref mut account_transaction) = transaction {
match &mut account_transaction.tx {
AccountTransaction::Declare(ref mut tx) => {
Expand All @@ -246,7 +280,7 @@ fn set_l2_gas_limit(transaction: &mut Transaction, gas_limit: GasAmount) {
&mut tx.resource_bounds
{
all_resource_bounds.l2_gas.max_amount = gas_limit;
return;
return Ok(());
}
}
}
Expand All @@ -256,7 +290,7 @@ fn set_l2_gas_limit(transaction: &mut Transaction, gas_limit: GasAmount) {
&mut tx.resource_bounds
{
all_resource_bounds.l2_gas.max_amount = gas_limit;
return;
return Ok(());
}
}
}
Expand All @@ -266,24 +300,24 @@ fn set_l2_gas_limit(transaction: &mut Transaction, gas_limit: GasAmount) {
&mut tx.resource_bounds
{
all_resource_bounds.l2_gas.max_amount = gas_limit;
return;
return Ok(());
}
}
}
}
}
unreachable!();
Err(anyhow::anyhow!("Failed to set L2 gas limit"))
}

fn extract_l2_gas_limit(transaction: &Transaction) -> GasAmount {
fn extract_l2_gas_limit(transaction: &Transaction) -> Result<GasAmount, anyhow::Error> {
if let Transaction::Account(account_transaction) = transaction {
match &account_transaction.tx {
AccountTransaction::Declare(tx) => {
if let DeclareTransaction::V3(tx) = &tx.tx {
if let ValidResourceBounds::AllResources(all_resource_bounds) =
&tx.resource_bounds
{
return all_resource_bounds.l2_gas.max_amount;
return Ok(all_resource_bounds.l2_gas.max_amount);
}
}
}
Expand All @@ -292,7 +326,7 @@ fn extract_l2_gas_limit(transaction: &Transaction) -> GasAmount {
if let ValidResourceBounds::AllResources(all_resource_bounds) =
&tx.resource_bounds
{
return all_resource_bounds.l2_gas.max_amount;
return Ok(all_resource_bounds.l2_gas.max_amount);
}
}
}
Expand All @@ -301,13 +335,13 @@ fn extract_l2_gas_limit(transaction: &Transaction) -> GasAmount {
if let ValidResourceBounds::AllResources(all_resource_bounds) =
&tx.resource_bounds
{
return all_resource_bounds.l2_gas.max_amount;
return Ok(all_resource_bounds.l2_gas.max_amount);
}
}
}
}
}
unreachable!();
Err(anyhow::anyhow!("Failed to extract L2 gas limit"))
}

fn is_out_of_gas(execution_info: &TransactionExecutionInfo) -> bool {
Expand Down
14 changes: 9 additions & 5 deletions vm/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,11 +346,15 @@ pub extern "C" fn cairoVMExecute(
match execute_transaction(&mut txn, &mut txn_state, &block_context) {
Err(error) => {
let err_string = match &error {
ContractConstructorExecutionFailed(e) => format!("{error} {e}"),
ExecutionError { error: e, .. } | ValidateTransactionError { error: e, .. } => {
format!("{error} {e}")
}
other => other.to_string(),
execution::ExecutionError::TransactionExecutionError(
transaction_execution_error,
) => match transaction_execution_error {
ContractConstructorExecutionFailed(e) => format!("{:?}", e),
ExecutionError { error: e, .. } => format!("{:?}", e),
ValidateTransactionError { error: e, .. } => format!("{:?}", e),
other => other.to_string(),
},
execution::ExecutionError::CustomError(error) => error.to_string(),
};
report_error(
reader_handle,
Expand Down

0 comments on commit d4fffa4

Please sign in to comment.