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

Native contract executor #2156

Merged
merged 1 commit into from
Sep 23, 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
13 changes: 8 additions & 5 deletions vm/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions vm/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ edition = "2021"
[dependencies]
serde = "1.0.171"
serde_json = { version = "1.0.96", features = ["raw_value"] }
blockifier = { git = "https://github.com/NethermindEth/sequencer", branch = "native2.8.x" }
starknet_api = { git = "https://github.com/NethermindEth/sequencer", branch = "native2.8.x" }
blockifier = { git = "https://github.com/NethermindEth/sequencer", rev = "68cd327a71062ea4ad03f930b11d1f6a0b019c17" }
starknet_api = { git = "https://github.com/NethermindEth/sequencer", rev = "68cd327a71062ea4ad03f930b11d1f6a0b019c17" }
cairo-lang-sierra = "2.8.0"
cairo-lang-starknet = "2.8.0"
cairo-lang-starknet-classes = "2.8.0"
Expand All @@ -18,7 +18,7 @@ starknet-types-core = { version = "0.1.5", features = [
"prime-bigint",
"serde",
] }
cairo-native = { git = "https://github.com/lambdaclass/cairo_native", rev = "4355357697e9ab57ab88ae3a4282aac61455619e" }
cairo-native = { git = "https://github.com/lambdaclass/cairo_native", rev = "a478e89b749bf0b596a7e63afd14e834c08a84e3" }
cairo-vm = "1.0.0"
indexmap = "2.1.0"
cached = "0.46.1"
Expand All @@ -30,7 +30,7 @@ libloading = "0.8.5"
thiserror = "1.0.63"
ciborium = "0.2.2"

# Trace block dependencies
# Trace block dependencies
starknet-core = "0.11.1"
starknet-providers = "0.11.0"
tokio = { version = "1.38.1", features = ["rt", "macros"] }
Expand Down
106 changes: 11 additions & 95 deletions vm/rust/src/juno_state_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ use blockifier::{
state::state_api::{StateReader, StateResult},
};
use cached::{Cached, SizedCache};
use cairo_lang_sierra::{program::Program, program_registry::ProgramRegistry};
use cairo_native::{
context::NativeContext, error::Error as NativeError, executor::AotNativeExecutor,
metadata::gas::GasMetadata, module::NativeModule,
};
use libloading::Library;
use cairo_native::executor::contract::ContractExecutor;
use cairo_native::OptLevel;
use once_cell::sync::Lazy;
use serde::Deserialize;
use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce};
use starknet_api::state::StorageKey;
use starknet_types_core::felt::Felt;
use std::cell::RefCell;
use std::sync::Arc;
use std::{
ffi::{c_char, c_uchar, c_void, CStr},
fs,
Expand Down Expand Up @@ -185,6 +182,7 @@ impl StateReader for JunoStateReader {
/// Returns the contract class of the given class hash.
fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult<ContractClass> {
println!("Juno State Reader(Rust): calling `get_compiled_contract_class` with class hash: {class_hash}");

if let Some(cached_class) = CLASS_CACHE.lock().unwrap().cache_get(&class_hash) {
// skip the cache if it comes from a height higher than ours. Class might be undefined on the height
// that we are reading from right now.
Expand Down Expand Up @@ -311,68 +309,16 @@ fn native_try_from_json_string(
raw_contract_class: &str,
library_output_path: &PathBuf,
) -> Result<NativeContractClassV1, Box<dyn std::error::Error>> {
fn compile_and_load(
sierra_program: Program,
library_output_path: &PathBuf,
) -> Result<AotNativeExecutor, Box<dyn std::error::Error>> {
let native_context = NativeContext::new();
let native_module = native_context.compile(&sierra_program)?;

persist_from_native_module(native_module, &sierra_program, library_output_path)
}

let sierra_contract_class: cairo_lang_starknet_classes::contract_class::ContractClass =
serde_json::from_str(raw_contract_class)?;

// todo(rodro): we are having two instances of a sierra program, one it's object form
// and another in its felt encoded form. This can be avoided by either:
// 1. Having access to the encoding/decoding functions
// 2. Refactoring the code on the Cairo mono-repo

let sierra_program = sierra_contract_class.extract_sierra_program()?;

// todo(xrvdg) lift this match out of the function once we do not need sierra_program anymore
let executor = match load_compiled_contract(&sierra_program, library_output_path) {
Some(executor) => {
executor.or_else(|_err| compile_and_load(sierra_program, library_output_path))
}
None => compile_and_load(sierra_program, library_output_path),
}?;

Ok(NativeContractClassV1::new(executor, sierra_contract_class)?)
}

/// Load a contract that is already compiled.
///
/// Returns None if the contract does not exist at the output_path.
///
/// To compile and load a contract use [persist_from_native_module] instead.
fn load_compiled_contract(
sierra_program: &Program,
library_output_path: &PathBuf,
) -> Option<Result<AotNativeExecutor, Box<dyn std::error::Error>>> {
fn load(
sierra_program: &Program,
library_output_path: &PathBuf,
) -> Result<AotNativeExecutor, Box<dyn std::error::Error>> {
let has_gas_builtin = sierra_program
.type_declarations
.iter()
.any(|decl| decl.long_id.generic_id.0.as_str() == "GasBuiltin");
let config = has_gas_builtin.then_some(Default::default());
let gas_metadata = GasMetadata::new(sierra_program, config)?;
let program_registry = ProgramRegistry::new(sierra_program)?;
let library = unsafe { Library::new(library_output_path)? };
Ok(AotNativeExecutor::new(
library,
program_registry,
gas_metadata,
))
}

library_output_path
.is_file()
.then_some(load(sierra_program, library_output_path))
let executor = ContractExecutor::load(library_output_path).or_else(|_| {
let executor = ContractExecutor::new(&sierra_program, OptLevel::Default)?;
executor.save(library_output_path)?;
Ok::<ContractExecutor, Box<dyn std::error::Error>>(executor)
})?;
let contract_executor = NativeContractClassV1::new(Arc::new(executor), sierra_contract_class)?;
Ok(contract_executor)
}

// todo(xrvdg) once [class_info_from_json_str] is part of JunoStateReader
Expand Down Expand Up @@ -403,33 +349,3 @@ fn generate_library_path(class_hash: ClassHash) -> PathBuf {
path.push(class_hash.to_string().trim_start_matches("0x"));
path
}

/// Compiles and load contract
///
/// Modelled after [AotNativeExecutor::from_native_module].
/// Needs a sierra_program to workaround limitations of NativeModule
fn persist_from_native_module(
mut native_module: NativeModule,
sierra_program: &Program,
library_output_path: &PathBuf,
) -> Result<AotNativeExecutor, Box<dyn std::error::Error>> {
let object_data = cairo_native::module_to_object(native_module.module(), Default::default())
.map_err(|err| NativeError::LLVMCompileError(err.to_string()))?; // cairo native didn't include a from instance

cairo_native::object_to_shared_lib(&object_data, library_output_path)?;

let gas_metadata = native_module
.remove_metadata()
.expect("native_module should have set gas_metadata");

// Recreate the program registry as it can't be moved out of native module.
let program_registry = ProgramRegistry::new(sierra_program)?;

let library = unsafe { Library::new(library_output_path)? };

Ok(AotNativeExecutor::new(
library,
program_registry,
gas_metadata,
))
}
4 changes: 2 additions & 2 deletions vm/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ fn cairo_vm_execute(
};

if let Some(path) = JUNO_RECORD_DIR.clone() {
let mut args_path: PathBuf = path.into();
let mut args_path: PathBuf = path;
args_path.push(format!("{}.args.cbor", block_info.block_number));

let file_args = std::fs::File::create(args_path).unwrap();
Expand Down Expand Up @@ -401,7 +401,7 @@ fn cairo_vm_execute(
}

if let Some(path) = JUNO_RECORD_DIR.clone() {
let mut state_path: PathBuf = path.into();
let mut state_path: PathBuf = path;
state_path.push(format!("{}.state.cbor", block_info.block_number));

let state_file = File::create(state_path).unwrap();
Expand Down
Loading