Skip to content

Commit

Permalink
refactor(katana-core): refactor out execution logic into separate crate
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy committed Dec 4, 2023
1 parent 7eae957 commit a8c5c35
Show file tree
Hide file tree
Showing 8 changed files with 643 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/dojo-world",
"crates/katana",
"crates/katana/core",
"crates/katana/executor",
"crates/katana/primitives",
"crates/katana/rpc",
"crates/katana/storage/db",
Expand Down
21 changes: 21 additions & 0 deletions crates/katana/executor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
description = "Katana execution engine"
edition.workspace = true
name = "katana-executor"
version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
katana-primitives = { path = "../primitives" }
katana-provider = { path = "../storage/provider" }

convert_case.workspace = true
parking_lot.workspace = true
starknet.workspace = true
tracing.workspace = true

# blockifier deps
blockifier.workspace = true
starknet_api.workspace = true
tokio.workspace = true
167 changes: 167 additions & 0 deletions crates/katana/executor/src/blockifier/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
pub mod outcome;
pub mod state;
pub mod utils;

use std::sync::Arc;

use blockifier::block_context::BlockContext;
use blockifier::state::state_api::StateReader;
use blockifier::transaction::errors::TransactionExecutionError;
use blockifier::transaction::objects::TransactionExecutionInfo;
use blockifier::transaction::transaction_execution::Transaction as BlockifierExecuteTx;
use blockifier::transaction::transactions::ExecutableTransaction;
use katana_primitives::transaction::{DeclareTxWithClasses, ExecutionTx};
use parking_lot::RwLock;
use tracing::{trace, warn};

use self::outcome::ExecutedTx;
use self::state::{CachedStateWrapper, StateRefDb};
use self::utils::events_from_exec_info;
use crate::blockifier::utils::{
pretty_print_resources, trace_events, warn_message_transaction_error_exec_error,
};

/// The result of a transaction execution.
type TxExecutionResult = Result<TransactionExecutionInfo, TransactionExecutionError>;

/// A transaction executor.
///
/// The transactions will be executed in an iterator fashion, sequentially, in the
/// exact order they are provided to the executor. The execution is done within its
/// implementation of the [`Iterator`] trait.
pub struct TransactionExecutor<'a, S: StateReader> {
/// A flag to enable/disable fee charging.
charge_fee: bool,
/// The block context the transactions will be executed on.
block_context: &'a BlockContext,
/// The transactions to be executed (in the exact order they are in the iterator).
transactions: std::vec::IntoIter<ExecutionTx>,
/// The state the transactions will be executed on.
state: &'a mut CachedStateWrapper<S>,

// logs flags
error_log: bool,
events_log: bool,
resources_log: bool,
}

impl<'a, S: StateReader> TransactionExecutor<'a, S> {
pub fn new(
state: &'a mut CachedStateWrapper<S>,
block_context: &'a BlockContext,
charge_fee: bool,
transactions: Vec<ExecutionTx>,
) -> Self {
Self {
state,
charge_fee,
block_context,
error_log: false,
events_log: false,
resources_log: false,
transactions: transactions.into_iter(),
}
}

pub fn with_events_log(self) -> Self {
Self { events_log: true, ..self }
}

pub fn with_error_log(self) -> Self {
Self { error_log: true, ..self }
}

pub fn with_resources_log(self) -> Self {
Self { resources_log: true, ..self }
}

/// A method to conveniently execute all the transactions and return their results.
pub fn execute(self) -> Vec<TxExecutionResult> {
self.collect()
}
}

impl<'a, S: StateReader> Iterator for TransactionExecutor<'a, S> {
type Item = TxExecutionResult;
fn next(&mut self) -> Option<Self::Item> {
self.transactions.next().map(|tx| {
let res = execute_tx(tx, &mut self.state, self.block_context, self.charge_fee);

match res {
Ok(info) => {
if self.error_log {
if let Some(err) = &info.revert_error {
let formatted_err = format!("{err:?}").replace("\\n", "\n");
warn!(target: "executor", "Transaction execution error: {formatted_err}");
}
}

if self.resources_log {
trace!(
target: "executor",
"Transaction resource usage: {}",
pretty_print_resources(&info.actual_resources)
);
}

if self.events_log {
trace_events(&events_from_exec_info(&info));
}

Ok(info)
}

Err(err) => {
if self.error_log {
warn_message_transaction_error_exec_error(&err);
}

Err(err)
}
}
})
}
}

pub struct PendingState {
pub state: RwLock<CachedStateWrapper<StateRefDb>>,
/// The transactions that have been executed.
pub executed_transactions: RwLock<Vec<Arc<ExecutedTx>>>,
}

fn execute_tx<S: StateReader>(
tx: ExecutionTx,
state: &mut CachedStateWrapper<S>,
block_context: &BlockContext,
charge_fee: bool,
) -> TxExecutionResult {
let sierra = if let ExecutionTx::Declare(DeclareTxWithClasses {
tx,
sierra_class: Some(sierra_class),
..
}) = &tx
{
Some((tx.class_hash, sierra_class.clone()))
} else {
None
};

let res = match tx.into() {
BlockifierExecuteTx::AccountTransaction(tx) => {
tx.execute(&mut state.inner_mut(), block_context, charge_fee)
}
BlockifierExecuteTx::L1HandlerTransaction(tx) => {
tx.execute(&mut state.inner_mut(), block_context, charge_fee)
}
};

if let res @ Ok(_) = res {
if let Some((class_hash, sierra_class)) = sierra {
state.sierra_class_mut().insert(class_hash, sierra_class);
}

res
} else {
res
}
}
69 changes: 69 additions & 0 deletions crates/katana/executor/src/blockifier/outcome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::collections::HashMap;

use blockifier::state::cached_state::CommitmentStateDiff;
use blockifier::transaction::objects::TransactionExecutionInfo;
use katana_primitives::contract::{ClassHash, CompiledContractClass, ContractAddress, SierraClass};
use katana_primitives::transaction::{Receipt, Tx};

use super::utils::{events_from_exec_info, l2_to_l1_messages_from_exec_info};

pub struct ExecutedTx {
pub tx: Tx,
pub receipt: Receipt,
pub execution_info: TransactionExecutionInfo,
}

impl ExecutedTx {
pub(super) fn new(tx: Tx, execution_info: TransactionExecutionInfo) -> Self {
let actual_fee = execution_info.actual_fee.0;
let events = events_from_exec_info(&execution_info);
let revert_error = execution_info.revert_error.clone();
let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info);
let actual_resources = execution_info.actual_resources.0.clone();

let contract_address = if let Tx::DeployAccount(ref tx) = tx {
Some(ContractAddress(tx.contract_address.into()))
} else {
None
};

Self {
tx,
execution_info,
receipt: Receipt {
events,
actual_fee,
revert_error,
messages_sent,
actual_resources,
contract_address,
},
}
}
}

/// The outcome that after executing a list of transactions.
pub struct ExecutionOutcome {
pub transactions: Vec<ExecutedTx>,
pub state_diff: CommitmentStateDiff,
pub declared_classes: HashMap<ClassHash, CompiledContractClass>,
pub declared_sierra_classes: HashMap<ClassHash, SierraClass>,
}

impl Default for ExecutionOutcome {
fn default() -> Self {
let state_diff = CommitmentStateDiff {
storage_updates: Default::default(),
address_to_nonce: Default::default(),
address_to_class_hash: Default::default(),
class_hash_to_compiled_class_hash: Default::default(),
};

Self {
state_diff,
transactions: Default::default(),
declared_classes: Default::default(),
declared_sierra_classes: Default::default(),
}
}
}
Loading

0 comments on commit a8c5c35

Please sign in to comment.