diff --git a/crates/blockifier/src/execution/native/entry_point_execution.rs b/crates/blockifier/src/execution/native/entry_point_execution.rs index ee713aa263..187aa9941e 100644 --- a/crates/blockifier/src/execution/native/entry_point_execution.rs +++ b/crates/blockifier/src/execution/native/entry_point_execution.rs @@ -1,6 +1,7 @@ use cairo_native::execution_result::ContractExecutionResult; use cairo_native::utils::BuiltinCosts; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use num_rational::Ratio; use stacker; use crate::execution::call_info::{CallExecution, CallInfo, ChargedResources, Retdata}; @@ -15,6 +16,7 @@ use crate::execution::errors::{EntryPointExecutionError, PostExecutionError}; use crate::execution::native::contract_class::NativeCompiledClassV1; use crate::execution::native::syscall_handler::NativeSyscallHandler; use crate::state::state_api::State; +use crate::versioned_constants::CairoNativeStackConfig; // todo(rodrigo): add an `entry point not found` test for Native pub fn execute_entry_point_call( @@ -53,20 +55,36 @@ pub fn execute_entry_point_call( // The gas upper bound is MAX_POSSIBLE_SIERRA_GAS, and sequencers must not raise it without // adjusting the stack size. // This also limits multi-threading, since each thread has its own stack. - // TODO(Aviv/Yonatan): add these numbers to overridable VC. - let stack_size_red_zone = 160 * 1024 * 1024; - let target_stack_size = stack_size_red_zone + 10 * 1024 * 1024; + // If the the free stack size is in the red zone, We will grow the stack to the + // target size, relative to reaming gas. + let stack_config = CairoNativeStackConfig { + // TODO(Aviv): Take it from VC. + gas_to_stack_ratio: Ratio::new(1, 20), + max_stack_size: 200 * 1024 * 1024, + min_stack_red_zone: 2 * 1024 * 1024, + buffer_size: 5 * 1024 * 1024, + }; + let stack_size_red_zone = + stack_config.get_stack_size_red_zone(syscall_handler.base.call.initial_gas); + let target_stack_size = + usize::try_from(stack_config.get_target_stack_size(stack_size_red_zone)) + .unwrap_or_else(|e| panic!("Failed to convert target stack size to usize: {}", e)); // Use `maybe_grow` and not `grow` for performance, since in happy flows, only the main call // should trigger the growth. - let execution_result = stacker::maybe_grow(stack_size_red_zone, target_stack_size, || { - compiled_class.executor.run( - entry_point.selector.0, - &syscall_handler.base.call.calldata.0.clone(), - syscall_handler.base.call.initial_gas, - Some(builtin_costs), - &mut syscall_handler, - ) - }); + let execution_result = stacker::maybe_grow( + usize::try_from(stack_size_red_zone) + .unwrap_or_else(|e| panic!("Failed to convert stack size red zone to usize: {}", e)), + target_stack_size, + || { + compiled_class.executor.run( + entry_point.selector.0, + &syscall_handler.base.call.calldata.0.clone(), + syscall_handler.base.call.initial_gas, + Some(builtin_costs), + &mut syscall_handler, + ) + }, + ); syscall_handler.finalize(); let call_result = execution_result.map_err(EntryPointExecutionError::NativeUnexpectedError)?; diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs index e93e26c6be..05e3a1e80c 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs @@ -50,14 +50,24 @@ fn test_total_tx_limits_less_than_max_sierra_gas() { } #[cfg(feature = "cairo_native")] -#[test] +#[rstest::rstest] +#[case(MAX_POSSIBLE_SIERRA_GAS, MAX_POSSIBLE_SIERRA_GAS - 6590)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 10, 349991430)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 100, 34992990)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 1000, 3498420)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 10000, 342810)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 100000, 26370)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 1000000, 0)] +#[case(350, 0)] +#[case(35, 0)] +#[case(0, 0)] /// Tests that Native can handle deep recursion calls without overflowing the stack. /// Note that the recursive function must be complicated, since the compiler might transform /// simple recursions into loops. The tested function was manually tested with higher gas and /// reached stack overflow. /// /// Also, there is no need to test the VM here since it doesn't use the stack. -fn test_stack_overflow() { +fn test_stack_overflow(#[case] initial_gas: u64, #[case] gas_consumed: u64) { let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Native)); let mut state = test_state(&ChainInfo::create_for_testing(), BALANCE, &[(test_contract, 1)]); @@ -65,7 +75,7 @@ fn test_stack_overflow() { let entry_point_call = CallEntryPoint { calldata: calldata![depth], entry_point_selector: selector_from_name("test_stack_overflow"), - initial_gas: MAX_POSSIBLE_SIERRA_GAS, + initial_gas, ..trivial_external_entry_point_new(test_contract) }; let call_info = entry_point_call.execute_directly(&mut state).unwrap(); @@ -74,7 +84,7 @@ fn test_stack_overflow() { CallExecution { // 'Out of gas' retdata: retdata![felt!["0x4f7574206f6620676173"]], - gas_consumed: MAX_POSSIBLE_SIERRA_GAS - 6590, + gas_consumed, failed: true, ..Default::default() } diff --git a/crates/blockifier/src/versioned_constants.rs b/crates/blockifier/src/versioned_constants.rs index 928c9b6940..829a13b297 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -483,6 +483,30 @@ pub struct ArchivalDataGasCosts { pub gas_per_code_byte: ResourceCost, } +pub struct CairoNativeStackConfig { + pub gas_to_stack_ratio: Ratio, + pub max_stack_size: u64, + pub min_stack_red_zone: u64, + pub buffer_size: u64, +} + +impl CairoNativeStackConfig { + /// Returns the size of the stack red zone, relative to the remaining gas. + pub fn get_stack_size_red_zone(&self, remaining_gas: u64) -> u64 { + std::cmp::max( + self.max_stack_size, + std::cmp::min( + (self.gas_to_stack_ratio * Ratio::new(remaining_gas, 1)).to_integer(), + self.min_stack_red_zone, + ), + ) + } + + pub fn get_target_stack_size(&self, red_zone: u64) -> u64 { + red_zone + self.buffer_size + } +} + #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)] pub struct EventLimits { pub max_data_length: usize,