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

Open
wants to merge 11 commits into
base: native2.8.x-blockifier
Choose a base branch
from
17 changes: 10 additions & 7 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 = "7e775e93a74ddb863d7810d0a854b97bbb1ff2c8" }
starknet_api = { git = "https://github.com/NethermindEth/sequencer", rev = "7e775e93a74ddb863d7810d0a854b97bbb1ff2c8" }
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
147 changes: 21 additions & 126 deletions vm/rust/src/juno_state_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ 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::OptLevel;
use cairo_native::
executor::contract::ContractExecutor
;
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 @@ -65,11 +64,7 @@ pub struct JunoStateReader {

impl JunoStateReader {
pub fn new(handle: usize, height: u64) -> Self {
Self {
handle,
height,
serdes: Default::default(),
}
Self { handle, height, serdes: Default::default() }
}
}

Expand Down Expand Up @@ -102,10 +97,7 @@ impl StateReader for JunoStateReader {

// Note [Replay Invariant]
assert_eq!(
self.serdes
.borrow_mut()
.storage
.insert((contract_address, key), felt_val),
self.serdes.borrow_mut().storage.insert((contract_address, key), felt_val),
None,
"Overwritten storage"
);
Expand All @@ -132,10 +124,7 @@ impl StateReader for JunoStateReader {

// Note [Replay Invariant]
assert_eq!(
self.serdes
.borrow_mut()
.nonce
.insert(contract_address, nonce),
self.serdes.borrow_mut().nonce.insert(contract_address, nonce),
None,
"Overwriting nonce"
);
Expand All @@ -147,10 +136,7 @@ impl StateReader for JunoStateReader {
/// Returns the class hash of the contract class at the given contract instance.
/// Default: 0 (uninitialized class hash) for an uninitialized contract address.
fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult<ClassHash> {
println!(
"Juno State Reader(Rust): calling `get_class_hash_at` {0}",
contract_address
);
println!("Juno State Reader(Rust): calling `get_class_hash_at` {0}", contract_address);
let addr = contract_address.0.key().to_bytes_be();
let ptr = unsafe { JunoStateGetClassHashAt(self.handle, addr.as_ptr()) };
if ptr.is_null() {
Expand All @@ -171,10 +157,7 @@ impl StateReader for JunoStateReader {

// Note [Replay Invariant]
assert_eq!(
self.serdes
.borrow_mut()
.class_hash
.insert(contract_address, class_hash),
self.serdes.borrow_mut().class_hash.insert(contract_address, class_hash),
None,
"Overwritten class_hash"
);
Expand All @@ -185,6 +168,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 @@ -215,10 +199,7 @@ impl StateReader for JunoStateReader {

// Note [Replay Invariant]
assert_eq!(
self.serdes
.borrow_mut()
.contract_class
.insert(class_hash, json_str.to_string()), // Can't serialize the Contract Class due to AotNativeExecutor therefore we store the string
self.serdes.borrow_mut().contract_class.insert(class_hash, json_str.to_string()), // Can't serialize the Contract Class due to AotNativeExecutor therefore we store the string
None,
"Overwritten compiled contract_class"
);
Expand Down Expand Up @@ -292,12 +273,8 @@ pub fn class_info_from_json_str(
return Err("not a valid contract class".to_string());
};

BlockifierClassInfo::new(
&class,
class_info.sierra_program_length,
class_info.abi_length,
)
.map_err(|err| err.to_string())
BlockifierClassInfo::new(&class, class_info.sierra_program_length, class_info.abi_length)
.map_err(|err| err.to_string())
}

/// Compiled Native contracts
Expand All @@ -311,68 +288,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).unwrap_or({
let executor = ContractExecutor::new(&sierra_program, OptLevel::Default)?;
executor.save(&library_output_path)?;
executor
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always compile and save the contract as the argument to unwrap_or is eagerly evaluated. Use unwrap_or_else instead.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, load and save needlessly borrow the library_output_path.

Copy link
Author

@PearsonWhite PearsonWhite Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always compile and save the contract as the argument to unwrap_or is eagerly evaluated. Use unwrap_or_else instead.

Changed to or_else so I could still use "?" operator. Had to specify the type though since it couldn't figure it out.

Furthermore, load and save needlessly borrow the library_output_path.

Fixed.

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 +328,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,
))
}
Loading
Loading