Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blockifier): add non-recoverable error handling for native syscall handler #1948

Merged
merged 3 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/blockifier/src/execution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use thiserror::Error;

use crate::execution::entry_point::ConstructorContext;
use crate::execution::stack_trace::Cairo1RevertStack;
#[cfg(feature = "cairo_native")]
use crate::execution::syscalls::hint_processor::SyscallExecutionError;
use crate::state::errors::StateError;

// TODO(AlonH, 21/12/2022): Implement Display for all types that appear in errors.
Expand Down Expand Up @@ -91,6 +93,9 @@ pub enum EntryPointExecutionError {
#[cfg(feature = "cairo_native")]
#[error(transparent)]
NativeUnexpectedError(#[from] NativeError),
#[cfg(feature = "cairo_native")]
#[error(transparent)]
NativeUnrecoverableError(#[from] Box<SyscallExecutionError>),
#[error(transparent)]
PostExecutionError(#[from] PostExecutionError),
#[error(transparent)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ pub fn execute_entry_point_call(
);

let call_result = execution_result.map_err(EntryPointExecutionError::NativeUnexpectedError)?;

if let Some(error) = syscall_handler.unrecoverable_error {
return Err(EntryPointExecutionError::NativeUnrecoverableError(Box::new(error)));
}

create_callinfo(call_result, syscall_handler)
}

#[allow(unreachable_code)]
fn create_callinfo(
call_result: ContractExecutionResult,
syscall_handler: NativeSyscallHandler<'_>,
Expand Down
58 changes: 44 additions & 14 deletions crates/blockifier/src/execution/native/syscall_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use cairo_native::starknet::{
SyscallResult,
U256,
};
use cairo_native::starknet_stub::encode_str_as_felts;
use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use starknet_api::core::EthAddress;
use starknet_api::state::StorageKey;
Expand All @@ -25,6 +24,7 @@ use crate::execution::call_info::{
Retdata,
};
use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext};
use crate::execution::errors::EntryPointExecutionError;
use crate::execution::syscalls::hint_processor::{
SyscallExecutionError,
INVALID_INPUT_LENGTH_ERROR,
Expand All @@ -47,6 +47,9 @@ pub struct NativeSyscallHandler<'state> {
// Additional information gathered during execution.
pub read_values: Vec<Felt>,
pub accessed_keys: HashSet<StorageKey, RandomState>,

// It is set if an unrecoverable error happens during syscall execution
pub unrecoverable_error: Option<SyscallExecutionError>,
}

impl<'state> NativeSyscallHandler<'state> {
Expand All @@ -66,6 +69,7 @@ impl<'state> NativeSyscallHandler<'state> {
inner_calls: Vec::new(),
read_values: Vec::new(),
accessed_keys: HashSet::new(),
unrecoverable_error: None,
}
}

Expand Down Expand Up @@ -96,25 +100,18 @@ impl<'state> NativeSyscallHandler<'state> {
Ok(retdata)
}

fn handle_error(
&mut self,
_remaining_gas: &mut u128,
error: SyscallExecutionError,
) -> Vec<Felt> {
match error {
SyscallExecutionError::SyscallError { error_data } => error_data,
// unrecoverable errors are yet to be implemented
_ => encode_str_as_felts(&error.to_string()),
}
}

/// Handles all gas-related logics. In native,
/// Handles all gas-related logics and perform additional checks. In native,
/// we need to explicitly call this method at the beginning of each syscall.
fn pre_execute_syscall(
&mut self,
remaining_gas: &mut u128,
syscall_gas_cost: u64,
) -> SyscallResult<()> {
if self.unrecoverable_error.is_some() {
// An unrecoverable error was found in a previous syscall, we return immediatly to
// accelerate the end of the execution. The returned data is not important
return Err(vec![]);
}
// Refund `SYSCALL_BASE_GAS_COST` as it was pre-charged.
let required_gas =
u128::from(syscall_gas_cost - self.context.gas_costs().syscall_base_gas_cost);
Expand All @@ -131,6 +128,39 @@ impl<'state> NativeSyscallHandler<'state> {

Ok(())
}

fn handle_error(
&mut self,
remaining_gas: &mut u128,
error: SyscallExecutionError,
) -> Vec<Felt> {
// In case of more than one inner call and because each inner call has their own
// syscall handler, if there is an unrecoverable error at call `n` it will create a
// `NativeExecutionError`. When rolling back, each call from `n-1` to `1` will also
// store the result of a previous `NativeExecutionError` in a `NativeExecutionError`
// creating multiple wraps around the same error. This function is meant to prevent that.
fn unwrap_native_error(error: SyscallExecutionError) -> SyscallExecutionError {
match error {
SyscallExecutionError::EntryPointExecutionError(
EntryPointExecutionError::NativeUnrecoverableError(e),
) => *e,
_ => error,
}
}

match error {
SyscallExecutionError::SyscallError { error_data } => error_data,
error => {
assert!(
self.unrecoverable_error.is_none(),
"Trying to set an unrecoverable error twice in Native Syscall Handler"
);
self.unrecoverable_error = Some(unwrap_native_error(error));
*remaining_gas = 0;
vec![]
}
}
}
}

impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> {
Expand Down
Loading