Skip to content

Commit

Permalink
feat(cairo_native): apply resource limits to compilation processes (s…
Browse files Browse the repository at this point in the history
  • Loading branch information
avi-starkware authored Jan 5, 2025
1 parent 61d6140 commit 2cf1a8b
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 26 deletions.
8 changes: 4 additions & 4 deletions config/sequencer/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@
"batcher_config.contract_class_manager_config.native_compiler_config.max_memory_usage": {
"description": "Limitation of compilation process's virtual memory (bytes).",
"privacy": "Public",
"value": 1258291200
"value": 5368709120
},
"batcher_config.contract_class_manager_config.native_compiler_config.max_native_bytecode_size": {
"description": "Limitation of compiled native bytecode size.",
"privacy": "Public",
"value": 20971520
"value": 15728640
},
"batcher_config.contract_class_manager_config.native_compiler_config.sierra_to_native_compiler_path": {
"description": "The path to the Sierra-to-Native compiler binary.",
Expand Down Expand Up @@ -282,12 +282,12 @@
"compiler_config.max_memory_usage": {
"description": "Limitation of compilation process's virtual memory (bytes).",
"privacy": "Public",
"value": 1258291200
"value": 5368709120
},
"compiler_config.max_native_bytecode_size": {
"description": "Limitation of compiled native bytecode size.",
"privacy": "Public",
"value": 20971520
"value": 15728640
},
"compiler_config.sierra_to_native_compiler_path": {
"description": "The path to the Sierra-to-Native compiler binary.",
Expand Down
2 changes: 1 addition & 1 deletion crates/native_blockifier/src/py_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ impl From<PyConcurrencyConfig> for ConcurrencyConfig {
pub struct PySierraCompilationConfig {
pub sierra_to_native_compiler_path: String,
pub libcairo_native_runtime_path: String,
pub max_native_bytecode_size: usize,
pub max_native_bytecode_size: u64,
pub max_cpu_time: u64,
pub max_memory_usage: u64,
}
Expand Down
9 changes: 6 additions & 3 deletions crates/starknet_gateway/src/compilation_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ fn test_compile_contract_class_bytecode_size_validation(declare_tx_v3: RpcDeclar
let result = gateway_compiler.process_declare_tx(&RpcDeclareTransaction::V3(declare_tx_v3));
assert_matches!(result.unwrap_err(), GatewaySpecError::CompilationFailed);
let expected_compilation_error = CompilationUtilError::CompilationError(
"Error: Compilation failed.\n\nCaused by:\n Code size limit exceeded.\n".to_owned(),
"Exit status: exit status: 1\n Stderr: Error: Compilation failed.\n\nCaused by:\n Code \
size limit exceeded.\n"
.to_owned(),
);
assert!(logs_contain(format!("Compilation failed: {:?}", expected_compilation_error).as_str()));
}
Expand All @@ -60,8 +62,9 @@ fn test_compile_contract_class_bad_sierra(
let err = gateway_compiler.process_declare_tx(&declare_tx).unwrap_err();
assert_eq!(err, GatewaySpecError::CompilationFailed);

let expected_compilation_error =
CompilationUtilError::CompilationError("Error: Invalid Sierra program.\n".to_owned());
let expected_compilation_error = CompilationUtilError::CompilationError(
"Exit status: exit status: 1\n Stderr: Error: Invalid Sierra program.\n".to_owned(),
);
assert!(logs_contain(format!("Compilation failed: {:?}", expected_compilation_error).as_str()));
}

Expand Down
40 changes: 32 additions & 8 deletions crates/starknet_sierra_compile/src/command_line_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::constants::CAIRO_LANG_BINARY_NAME;
use crate::constants::CAIRO_NATIVE_BINARY_NAME;
use crate::errors::CompilationUtilError;
use crate::paths::binary_path;
use crate::resource_limits::ResourceLimits;
use crate::SierraToCasmCompiler;
#[cfg(feature = "cairo_native")]
use crate::SierraToNativeCompiler;
Expand Down Expand Up @@ -55,9 +56,15 @@ impl SierraToCasmCompiler for CommandLineCompiler {
&self.config.max_casm_bytecode_size.to_string(),
];
let env_vars = vec![];

let stdout =
compile_with_args(compiler_binary_path, contract_class, additional_args, env_vars)?;
let resource_limits = ResourceLimits::new(None, None, None);

let stdout = compile_with_args(
compiler_binary_path,
contract_class,
additional_args,
env_vars,
resource_limits,
)?;
Ok(serde_json::from_slice::<CasmContractClass>(&stdout)?)
}
}
Expand All @@ -74,8 +81,7 @@ impl SierraToNativeCompiler for CommandLineCompiler {
let output_file_path = output_file.path().to_str().ok_or(
CompilationUtilError::UnexpectedError("Failed to get output file path".to_owned()),
)?;
let additional_args = &[output_file_path];

let additional_args = [output_file_path];
let mut env_vars = vec![];
// Overrides the cairo native runtime library environment variable defined in config.toml.
if let Some(path) = &self.config.libcairo_native_runtime_path {
Expand All @@ -86,8 +92,18 @@ impl SierraToNativeCompiler for CommandLineCompiler {
));
};

let _stdout =
compile_with_args(compiler_binary_path, contract_class, additional_args, env_vars)?;
let resource_limits = ResourceLimits::new(
Some(self.config.max_cpu_time),
Some(self.config.max_native_bytecode_size),
Some(self.config.max_memory_usage),
);
let _stdout = compile_with_args(
compiler_binary_path,
contract_class,
&additional_args,
env_vars,
resource_limits,
)?;

Ok(AotContractExecutor::load(Path::new(&output_file_path))?)
}
Expand All @@ -98,6 +114,7 @@ fn compile_with_args(
contract_class: ContractClass,
additional_args: &[&str],
env_vars: Vec<(&str, &str)>,
resource_limits: ResourceLimits,
) -> Result<Vec<u8>, CompilationUtilError> {
// Create a temporary file to store the Sierra contract class.
let serialized_contract_class = serde_json::to_string(&contract_class)?;
Expand All @@ -116,13 +133,20 @@ fn compile_with_args(
command.env(name, value);
}

// Apply the resource limits to the command.
resource_limits.apply(&mut command);

// Run the compile process.
let compile_output = command.output()?;

if !compile_output.status.success() {
let stderr_output = String::from_utf8(compile_output.stderr)
.unwrap_or("Failed to get stderr output".into());
return Err(CompilationUtilError::CompilationError(stderr_output));
// TODO(Avi, 28/2/2025): Make the error messages more readable.
return Err(CompilationUtilError::CompilationError(format!(
"Exit status: {}\n Stderr: {}",
compile_output.status, stderr_output
)));
};
Ok(compile_output.stdout)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/starknet_sierra_compile/src/compile_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::SierraToCasmCompiler;
#[cfg(feature = "cairo_native")]
use crate::SierraToNativeCompiler;

const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraCompilationConfig = SierraCompilationConfig {
const SIERRA_COMPILATION_CONFIG: SierraCompilationConfig = SierraCompilationConfig {
max_casm_bytecode_size: DEFAULT_MAX_CASM_BYTECODE_SIZE,
sierra_to_native_compiler_path: None,
libcairo_native_runtime_path: None,
Expand All @@ -31,7 +31,7 @@ const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraCompilationConfig = SierraCompila
};

fn command_line_compiler() -> CommandLineCompiler {
CommandLineCompiler::new(SIERRA_TO_CASM_COMPILATION_CONFIG)
CommandLineCompiler::new(SIERRA_COMPILATION_CONFIG)
}
fn get_test_contract() -> ContractClass {
env::set_current_dir(resolve_project_relative_path(TEST_FILES_FOLDER).unwrap())
Expand Down
8 changes: 4 additions & 4 deletions crates/starknet_sierra_compile/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam};
use serde::{Deserialize, Serialize};
use validator::Validate;

pub const DEFAULT_MAX_CASM_BYTECODE_SIZE: usize = 81920;
pub const DEFAULT_MAX_CASM_BYTECODE_SIZE: usize = 80 * 1024;
// TODO(Noa): Reconsider the default values.
pub const DEFAULT_MAX_NATIVE_BYTECODE_SIZE: usize = 20 * 1024 * 1024;
pub const DEFAULT_MAX_NATIVE_BYTECODE_SIZE: u64 = 15 * 1024 * 1024;
pub const DEFAULT_MAX_CPU_TIME: u64 = 15;
pub const DEFAULT_MAX_MEMORY_USAGE: u64 = 1200 * 1024 * 1024;
pub const DEFAULT_MAX_MEMORY_USAGE: u64 = 5 * 1024 * 1024 * 1024;

#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)]
pub struct SierraCompilationConfig {
/// CASM bytecode size limit (in felts).
pub max_casm_bytecode_size: usize,
/// Native bytecode size limit (in bytes).
pub max_native_bytecode_size: usize,
pub max_native_bytecode_size: u64,
/// Compilation CPU time limit (in seconds).
pub max_cpu_time: u64,
/// Compilation process’s virtual memory (address space) byte limit.
Expand Down
11 changes: 7 additions & 4 deletions crates/starknet_sierra_compile/src/resource_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl RLimit {

/// A struct to hold resource limits for a process.
/// Each limit is optional and can be set to `None` if not needed.
struct ResourceLimits {
pub struct ResourceLimits {
/// A limit (in seconds) on the amount of CPU time that the process can consume.
cpu_time: Option<RLimit>,
/// The maximum size (in bytes) of files that the process may create.
Expand All @@ -47,7 +47,7 @@ struct ResourceLimits {
}

impl ResourceLimits {
fn new(
pub fn new(
cpu_time: Option<u64>,
file_size: Option<u64>,
memory_size: Option<u64>,
Expand Down Expand Up @@ -75,7 +75,7 @@ impl ResourceLimits {
}

/// Set all defined resource limits for the current process. Limits set to `None` are ignored.
fn set(&self) -> io::Result<()> {
pub fn set(&self) -> io::Result<()> {
[self.cpu_time.as_ref(), self.file_size.as_ref(), self.memory_size.as_ref()]
.iter()
.flatten()
Expand All @@ -86,7 +86,10 @@ impl ResourceLimits {
/// struct into a closure that is held by the given command. The closure is executed in the
/// child process spawned by the command, right before it invokes the `exec` system call.
/// NOTE: This method is only implemented for Unix-like systems.
fn apply(self, command: &mut Command) -> &mut Command {
pub fn apply(self, command: &mut Command) -> &mut Command {
if self.cpu_time.is_none() && self.file_size.is_none() && self.memory_size.is_none() {
return command;
}
#[cfg(unix)]
unsafe {
// The `pre_exec` method runs a given closure after the parent process has been forked
Expand Down

0 comments on commit 2cf1a8b

Please sign in to comment.