Skip to content

Commit

Permalink
feat(blockifier): refactor native stack config
Browse files Browse the repository at this point in the history
  • Loading branch information
AvivYossef-starkware committed Dec 30, 2024
1 parent b875cc0 commit 5d2bcf8
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 16 deletions.
42 changes: 30 additions & 12 deletions crates/blockifier/src/execution/native/entry_point_execution.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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(
Expand Down Expand Up @@ -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)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,32 @@ 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)]);

let depth = felt!(1000000_u128);
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();
Expand All @@ -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()
}
Expand Down
24 changes: 24 additions & 0 deletions crates/blockifier/src/versioned_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,30 @@ pub struct ArchivalDataGasCosts {
pub gas_per_code_byte: ResourceCost,
}

pub struct CairoNativeStackConfig {
pub gas_to_stack_ratio: Ratio<u64>,
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,
Expand Down

0 comments on commit 5d2bcf8

Please sign in to comment.