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

feat(blockifier): add native execution engine #429

Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ef8318b
refactor: split syscalls into separate files
varex83 Aug 6, 2024
956639e
fix: apply cargo-fmt
varex83 Aug 6, 2024
47f3557
fix: address review comments
varex83 Aug 7, 2024
2d1a082
Merge remote-tracking branch 'origin/main' into native/split-syscalls
varex83 Aug 8, 2024
47e6969
fix: conflicts after merge
varex83 Aug 8, 2024
86df83b
fix: clippy warnings
varex83 Aug 8, 2024
e70684e
feat: add ability of native execution
varex83 Aug 8, 2024
379a9d9
fix: make comments capitalized
varex83 Aug 8, 2024
1a5d04e
fix: apply fmt
varex83 Aug 8, 2024
584dc88
fix: small fixes in comments and matching
varex83 Aug 8, 2024
71c8757
fix: bring back old `.toml` formatting
varex83 Aug 8, 2024
5a1bcc2
fix: remove unused yet utility functions
varex83 Aug 8, 2024
83d1e2d
Merge remote-tracking branch 'origin/main' into native/split-syscalls
varex83 Aug 12, 2024
32c4400
feat: add syscall failure format
varex83 Aug 12, 2024
07d3d2f
Merge branch 'native/split-syscalls' into native/add-native-execution…
varex83 Aug 13, 2024
a2ba95b
chore: update Cargo.lock
varex83 Aug 16, 2024
ef1331a
Merge remote-tracking branch 'origin/main' into native/add-native-exe…
varex83 Aug 16, 2024
938c552
feat: update Cargo.lock
varex83 Aug 19, 2024
f236548
fix: address review
varex83 Aug 20, 2024
00d5ca7
refactor: address `get_entry_point` review requests
varex83 Aug 20, 2024
9b7a80a
Merge remote-tracking branch 'origin/main' into native/add-native-exe…
varex83 Aug 20, 2024
bb23d4a
refactor: move out methods that won't be reviewed in this PR
varex83 Aug 20, 2024
3403746
fix: rust fmt
varex83 Aug 20, 2024
79a2872
fix: apply some of the CI fixes
varex83 Aug 20, 2024
20db622
fix: comment in Cargo.toml
varex83 Aug 21, 2024
b73639a
fix: address some of the review comments
varex83 Aug 27, 2024
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
2,093 changes: 1,355 additions & 738 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ byteorder = "1.4.3"
bytes = "1"
cached = "0.44.0"
cairo-felt = "0.9.1"
# This is a temporary dependency, will be removed once the new version of cairo-native is released to main.
cairo-native = { git = "https://github.com/lambdaclass/cairo_native", branch = "cairo-lang2.7.0-rc.3" }
cairo-lang-casm = "2.7.0"
cairo-lang-runner = "2.7.0"
cairo-lang-sierra = "=2.7.0"
Expand Down
2 changes: 2 additions & 0 deletions crates/blockifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ ark-ff.workspace = true
ark-secp256k1.workspace = true
ark-secp256r1.workspace = true
cached.workspace = true
cairo-native.workspace = true
cairo-lang-casm = { workspace = true, features = ["parity-scale-codec"] }
cairo-lang-runner.workspace = true
cairo-lang-starknet-classes.workspace = true
cairo-lang-sierra.workspace = true
cairo-lang-utils.workspace = true
cairo-vm.workspace = true
derive_more.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/blockifier/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ pub mod entry_point_execution;
pub mod errors;
pub mod execution_utils;
pub mod hint_code;
pub mod native;
pub mod stack_trace;
pub mod syscalls;
14 changes: 5 additions & 9 deletions crates/blockifier/src/execution/call_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ macro_rules! retdata {
};
}

#[cfg_attr(test, derive(Clone))]
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct OrderedEvent {
pub order: usize,
pub event: EventContent,
Expand Down Expand Up @@ -54,15 +53,13 @@ impl MessageL1CostInfo {
}
}

#[cfg_attr(test, derive(Clone))]
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct MessageToL1 {
pub to_address: EthAddress,
pub payload: L2ToL1Payload,
}

#[cfg_attr(test, derive(Clone))]
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct OrderedL2ToL1Message {
pub order: usize,
pub message: MessageToL1,
Expand All @@ -73,8 +70,7 @@ pub fn get_payload_lengths(l2_to_l1_messages: &[OrderedL2ToL1Message]) -> Vec<us
}

/// Represents the effects of executing a single entry point.
#[cfg_attr(test, derive(Clone))]
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct CallExecution {
pub retdata: Retdata,
pub events: Vec<OrderedEvent>,
Expand Down Expand Up @@ -162,7 +158,7 @@ impl TestExecutionSummary {
}

/// Represents the full effects of executing an entry point, including the inner calls it invoked.
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct CallInfo {
pub call: CallEntryPoint,
pub execution: CallExecution,
Expand Down
232 changes: 224 additions & 8 deletions crates/blockifier/src/execution/contract_class.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::ops::{Deref, Index};
use std::sync::Arc;

use cairo_lang_casm;
use cairo_lang_casm::hints::Hint;
use cairo_lang_starknet_classes::casm_contract_class::{CasmContractClass, CasmContractEntryPoint};
use cairo_lang_sierra::ids::FunctionId;
use cairo_lang_starknet_classes::casm_contract_class::{
CasmContractClass,
CasmContractEntryPoint,
StarknetSierraCompilationError,
};
use cairo_lang_starknet_classes::contract_class::{
ContractClass as SierraContractClass,
ContractEntryPoint,
ContractEntryPoints as SierraContractEntryPoints,
};
use cairo_lang_starknet_classes::NestedIntList;
use cairo_lang_utils::bigint::BigUintAsHex;
use cairo_native::executor::AotNativeExecutor;
use cairo_vm::serde::deserialize_program::{
ApTracking,
FlowTrackingData,
Expand All @@ -30,12 +42,12 @@ use starknet_api::deprecated_contract_class::{
};
use starknet_types_core::felt::Felt;

use super::execution_utils::poseidon_hash_many_cost;
use crate::abi::abi_utils::selector_from_name;
use crate::abi::constants::{self, CONSTRUCTOR_ENTRY_POINT_NAME};
use crate::execution::entry_point::CallEntryPoint;
use crate::execution::errors::{ContractClassError, PreExecutionError};
use crate::execution::execution_utils::sn_api_to_cairo_vm_program;
use crate::execution::execution_utils::{poseidon_hash_many_cost, sn_api_to_cairo_vm_program};
use crate::execution::native::utils::contract_entrypoint_to_entrypoint_selector;
use crate::fee::eth_gas_constants;
use crate::transaction::errors::TransactionExecutionError;

Expand All @@ -46,10 +58,11 @@ pub mod test;
pub type ContractClassResult<T> = Result<T, ContractClassError>;

/// Represents a runnable Starknet contract class (meaning, the program is runnable by the VM).
#[derive(Clone, Debug, Eq, PartialEq, derive_more::From)]
#[derive(Clone, Debug, PartialEq, derive_more::From)]
pub enum ContractClass {
V0(ContractClassV0),
V1(ContractClassV1),
V1Native(NativeContractClassV1),
}

impl TryFrom<starknet_api::contract_class::ContractClass> for ContractClass {
Expand All @@ -68,13 +81,15 @@ impl ContractClass {
match self {
ContractClass::V0(class) => class.constructor_selector(),
ContractClass::V1(class) => class.constructor_selector(),
ContractClass::V1Native(class) => class.constructor_selector(),
}
}

pub fn estimate_casm_hash_computation_resources(&self) -> ExecutionResources {
match self {
ContractClass::V0(class) => class.estimate_casm_hash_computation_resources(),
ContractClass::V1(class) => class.estimate_casm_hash_computation_resources(),
ContractClass::V1Native(_) => todo!("Native estimate casm hash computation resources."),
}
}

Expand All @@ -87,13 +102,15 @@ impl ContractClass {
panic!("get_visited_segments is not supported for v0 contracts.")
}
ContractClass::V1(class) => class.get_visited_segments(visited_pcs),
ContractClass::V1Native(_) => todo!("Native visited segments."),
}
}

pub fn bytecode_length(&self) -> usize {
match self {
ContractClass::V0(class) => class.bytecode_length(),
ContractClass::V1(class) => class.bytecode_length(),
ContractClass::V1Native(_) => todo!("Native estimate casm hash computation resources."),
}
}
}
Expand Down Expand Up @@ -468,9 +485,8 @@ pub fn deserialize_program<'de, D: Deserializer<'de>>(
}

// V1 utilities.

// TODO(spapini): Share with cairo-lang-runner.
fn hint_to_hint_params(hint: &cairo_lang_casm::hints::Hint) -> Result<HintParams, ProgramError> {
fn hint_to_hint_params(hint: &Hint) -> Result<HintParams, ProgramError> {
Ok(HintParams {
code: serde_json::to_string(hint)?,
accessible_scopes: vec![],
Expand Down Expand Up @@ -550,7 +566,7 @@ impl ClassInfo {
) -> ContractClassResult<Self> {
let (contract_class_version, condition) = match contract_class {
ContractClass::V0(_) => (0, sierra_program_length == 0),
ContractClass::V1(_) => (1, sierra_program_length > 0),
ContractClass::V1(_) | ContractClass::V1Native(_) => (1, sierra_program_length > 0),
};

if condition {
Expand All @@ -563,3 +579,203 @@ impl ClassInfo {
}
}
}

// Cairo-native utilities.

#[derive(Clone, Debug, PartialEq)]
pub struct NativeContractClassV1(pub Arc<NativeContractClassV1Inner>);
impl Deref for NativeContractClassV1 {
type Target = NativeContractClassV1Inner;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[derive(Debug, thiserror::Error)]
pub enum NativeEntryPointError {
#[error("FunctionId {0} not found")]
FunctionIdNotFound(usize),
}

impl NativeContractClassV1 {
fn constructor_selector(&self) -> Option<EntryPointSelector> {
self.entry_points_by_type.constructor.first().map(|ep| ep.selector)
}

/// Initialize a compiled contract class for native.
///
/// executor must be derived from sierra_program which in turn must be derived from
/// sierra_contract_class.
pub fn new(
executor: AotNativeExecutor,
sierra_contract_class: SierraContractClass,
) -> Result<NativeContractClassV1, NativeEntryPointError> {
let contract = NativeContractClassV1Inner::new(executor, sierra_contract_class)?;

Ok(Self(Arc::new(contract)))
}

pub fn to_casm_contract_class(
self,
) -> Result<CasmContractClass, StarknetSierraCompilationError> {
let sierra_contract_class = SierraContractClass {
// Cloning because these are behind an Arc.
sierra_program: self.sierra_program_raw.clone(),
entry_points_by_type: self.fallback_entry_points_by_type.clone(),
abi: None,
sierra_program_debug_info: None,
contract_class_version: String::default(),
};

CasmContractClass::from_contract_class(sierra_contract_class, false, usize::MAX)
}

/// Returns an entry point into the natively compiled contract.
pub fn get_entry_point(&self, call: &CallEntryPoint) -> Result<&FunctionId, PreExecutionError> {
if call.entry_point_type == EntryPointType::Constructor
&& call.entry_point_selector != selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME)
{
return Err(PreExecutionError::InvalidConstructorEntryPointName);
}

let entry_points_of_same_type = &self.0.entry_points_by_type[call.entry_point_type];
let filtered_entry_points: Vec<_> = entry_points_of_same_type
.iter()
.filter(|ep| ep.selector == call.entry_point_selector)
.collect();

match &filtered_entry_points[..] {
[] => Err(PreExecutionError::EntryPointNotFound(call.entry_point_selector)),
[entry_point] => Ok(&entry_point.function_id),
_ => Err(PreExecutionError::DuplicatedEntryPointSelector {
selector: call.entry_point_selector,
typ: call.entry_point_type,
}),
}
}
}

#[derive(Debug)]
pub struct NativeContractClassV1Inner {
pub executor: AotNativeExecutor,
entry_points_by_type: NativeContractEntryPoints,
// Storing the raw sierra program and entry points to be able to fallback to the vm
sierra_program_raw: Vec<BigUintAsHex>,
fallback_entry_points_by_type: SierraContractEntryPoints,
}

impl NativeContractClassV1Inner {
fn new(
executor: AotNativeExecutor,
sierra_contract_class: SierraContractClass,
) -> Result<Self, NativeEntryPointError> {
// This exception should never occur as it was also used to create the AotNativeExecutor.
let sierra_program =
sierra_contract_class.extract_sierra_program().expect("can't extract sierra program");
// Note [Cairo Native ABI]
// The supplied (compiled) sierra program might have been populated with debug info and this
// affects the ABI, because the debug info is appended to the function name and the
// function name is what is used by Cairo Native to lookup the function.
// Therefore it's not enough to know the function index and we need enrich the contract
// entry point with FunctionIds from SierraProgram.
let lookup_fid: HashMap<usize, &FunctionId> =
HashMap::from_iter(sierra_program.funcs.iter().map(|fid| {
// This exception should never occur as the id is also in [SierraContractClass]
let id: usize = fid.id.id.try_into().expect("function id exceeds usize");
(id, &fid.id)
}));

Ok(NativeContractClassV1Inner {
executor,
entry_points_by_type: NativeContractEntryPoints::try_from(
&lookup_fid,
&sierra_contract_class.entry_points_by_type,
)?,
sierra_program_raw: sierra_contract_class.sierra_program,
fallback_entry_points_by_type: sierra_contract_class.entry_points_by_type,
})
}
}

// The location where the compiled contract is loaded into memory will not
// be the same therefore we exclude it from the comparison.
impl PartialEq for NativeContractClassV1Inner {
fn eq(&self, other: &Self) -> bool {
self.entry_points_by_type == other.entry_points_by_type
&& self.sierra_program_raw == other.sierra_program_raw
}
}

#[derive(Debug, PartialEq)]
/// Modelled after [SierraContractEntryPoints]
/// and enriched with information for the Cairo Native ABI.
/// See Note [Cairo Native ABI]
struct NativeContractEntryPoints {
constructor: Vec<NativeEntryPoint>,
external: Vec<NativeEntryPoint>,
l1_handler: Vec<NativeEntryPoint>,
}

impl NativeContractEntryPoints {
/// Convert [SierraContractEntryPoints] to [NativeContractEntryPoints] via a
/// [FunctionId] lookup table.
///
/// On failure returns the first FunctionId that it couldn't find.
fn try_from(
lookup: &HashMap<usize, &FunctionId>,
sep: &SierraContractEntryPoints,
) -> Result<NativeContractEntryPoints, NativeEntryPointError> {
let constructor = sep
.constructor
.iter()
.map(|c| NativeEntryPoint::try_from(lookup, c))
.collect::<Result<_, _>>()?;
let external = sep
.external
.iter()
.map(|c| NativeEntryPoint::try_from(lookup, c))
.collect::<Result<_, _>>()?;
let l1_handler = sep
.l1_handler
.iter()
.map(|c| NativeEntryPoint::try_from(lookup, c))
.collect::<Result<_, _>>()?;

Ok(NativeContractEntryPoints { constructor, external, l1_handler })
}
}

impl Index<EntryPointType> for NativeContractEntryPoints {
type Output = Vec<NativeEntryPoint>;

fn index(&self, index: EntryPointType) -> &Self::Output {
match index {
EntryPointType::Constructor => &self.constructor,
EntryPointType::External => &self.external,
EntryPointType::L1Handler => &self.l1_handler,
}
}
}

#[derive(Debug, PartialEq)]
/// Provides a relation between a function in a contract and a compiled contract.
struct NativeEntryPoint {
/// The selector is the key to find the function in the contract.
selector: EntryPointSelector,
/// And the function_id is the key to find the function in the compiled contract.
function_id: FunctionId,
}

impl NativeEntryPoint {
fn try_from(
lookup: &HashMap<usize, &FunctionId>,
cep: &ContractEntryPoint,
) -> Result<NativeEntryPoint, NativeEntryPointError> {
let &function_id = lookup
.get(&cep.function_idx)
.ok_or(NativeEntryPointError::FunctionIdNotFound(cep.function_idx))?;
let selector = contract_entrypoint_to_entrypoint_selector(cep);
Ok(NativeEntryPoint { selector, function_id: function_id.clone() })
}
}
Loading
Loading