Skip to content

Commit

Permalink
feat: add support for async transaction execution (#1439)
Browse files Browse the repository at this point in the history
  • Loading branch information
morph-dev authored Sep 12, 2024
1 parent 13a5111 commit aab46ab
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 107 deletions.
2 changes: 1 addition & 1 deletion portal-bridge/src/bridge/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use trin_execution::{
create_account_content_key, create_account_content_value, create_contract_content_key,
create_contract_content_value, create_storage_content_key, create_storage_content_value,
},
evm::spec_id::get_spec_block_number,
execution::TrinExecution,
spec_id::get_spec_block_number,
storage::utils::setup_temp_dir,
trie_walker::TrieWalker,
types::{block_to_trace::BlockToTrace, trie_proof::TrieProof},
Expand Down
2 changes: 1 addition & 1 deletion trin-execution/src/era/binary_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use e2store::{
use revm_primitives::SpecId;
use surf::Client;

use crate::spec_id::get_spec_block_number;
use crate::evm::spec_id::get_spec_block_number;

use super::{
constants::FIRST_ERA_EPOCH_WITH_EXECUTION_PAYLOAD,
Expand Down
2 changes: 1 addition & 1 deletion trin-execution/src/era/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ethportal_api::{
};
use revm_primitives::{Address, SpecId};

use crate::spec_id::get_spec_block_number;
use crate::evm::spec_id::get_spec_block_number;

#[derive(Debug, Clone)]
pub struct TransactionsWithSender {
Expand Down
96 changes: 96 additions & 0 deletions trin-execution/src/evm/async_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::future::Future;

use revm::Database;
use revm_primitives::{AccountInfo, Address, BlockEnv, Bytecode, EVMError, EVMResult, B256, U256};
use tokio::{runtime, task};

use super::{create_evm, tx_env_modifier::TxEnvModifier};

/// The async version of the [revm::Database].
pub trait AsyncDatabase {
/// The database error type.
type Error;

/// Get basic account information.
fn basic_async(
&mut self,
address: Address,
) -> impl Future<Output = Result<Option<AccountInfo>, Self::Error>> + Send;

/// Get account code by its hash.
fn code_by_hash_async(
&mut self,
code_hash: B256,
) -> impl Future<Output = Result<Bytecode, Self::Error>> + Send;

/// Get storage value of address at index.
fn storage_async(
&mut self,
address: Address,
index: U256,
) -> impl Future<Output = Result<U256, Self::Error>> + Send;

/// Get block hash by block number.
fn block_hash_async(
&mut self,
number: u64,
) -> impl Future<Output = Result<B256, Self::Error>> + Send;
}

/// Wraps the [AsyncDatabase] to provide [revm::Database] implementation.
///
/// This should only be used when blocking thread is allowed, e.g. from within spawn::blocking.
pub(super) struct WrapAsyncDatabase<DB: AsyncDatabase> {
db: DB,
rt: runtime::Runtime,
}

impl<DB: AsyncDatabase> WrapAsyncDatabase<DB> {
pub fn new(db: DB, rt: runtime::Runtime) -> Self {
Self { db, rt }
}
}

impl<DB: AsyncDatabase> Database for WrapAsyncDatabase<DB> {
type Error = DB::Error;

fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
self.rt.block_on(self.db.basic_async(address))
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.rt.block_on(self.db.code_by_hash_async(code_hash))
}

fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
self.rt.block_on(self.db.storage_async(address, index))
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
self.rt.block_on(self.db.block_hash_async(number))
}
}

pub async fn execute_transaction<DB, DBError, Tx>(
block_env: BlockEnv,
tx: Tx,
db: DB,
) -> EVMResult<DB::Error>
where
DB: AsyncDatabase<Error = DBError> + Send + 'static,
DBError: Send + 'static,
Tx: TxEnvModifier + Send + 'static,
{
task::spawn_blocking(move || {
let rt = runtime::Runtime::new().expect("to create Runtime within spawn_blocking");
let mut db = WrapAsyncDatabase::new(db, rt);
let mut evm = create_evm(block_env, &tx, &mut db);
evm.transact()
})
.await
.unwrap_or_else(|err| {
Err(EVMError::Custom(format!(
"Error while executing transactions asynchronously: {err:?}"
)))
})
}
106 changes: 34 additions & 72 deletions trin-execution/src/evm/block_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,31 @@ use std::{

use anyhow::{bail, ensure};
use eth_trie::{RootWithTrieDiff, Trie};
use ethportal_api::{
types::{
execution::transaction::Transaction,
state_trie::account_state::AccountState as AccountStateInfo,
},
Header,
};
use ethportal_api::{types::state_trie::account_state::AccountState as AccountStateInfo, Header};
use revm::{
db::{states::bundle_state::BundleRetention, State},
inspectors::TracerEip3155,
DatabaseCommit, Evm,
};
use revm_primitives::{keccak256, Address, Env, ResultAndState, SpecId, B256, U256};
use revm_primitives::{keccak256, Address, ResultAndState, SpecId, B256, U256};
use serde::{Deserialize, Serialize};
use tracing::info;

use crate::{
era::types::{ProcessedBlock, TransactionsWithSender},
evm::post_block_beneficiaries::get_post_block_beneficiaries,
metrics::{
set_int_gauge_vec, start_timer_vec, stop_timer, BLOCK_HEIGHT, BLOCK_PROCESSING_TIMES,
TRANSACTION_PROCESSING_TIMES,
},
spec_id::get_spec_id,
storage::{account::Account as RocksAccount, evm_db::EvmDB},
transaction::TxEnvModifier,
types::block_to_trace::BlockToTrace,
};

use super::blocking::execute_transaction_with_external_context;
use super::{
create_block_env, create_evm_with_tracer,
post_block_beneficiaries::get_post_block_beneficiaries, spec_id::get_spec_id,
tx_env_modifier::TxEnvModifier,
};

const BLOCKHASH_SERVE_WINDOW: u64 = 256;
const GENESIS_STATE_FILE: &str = "trin-execution/resources/genesis/mainnet.json";
Expand Down Expand Up @@ -86,54 +81,20 @@ impl<'a> BlockExecutor<'a> {
}
}

fn set_evm_environment_from_block(&mut self, header: &Header) {
let timer: prometheus_exporter::prometheus::HistogramTimer =
start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]);
if get_spec_id(header.number).is_enabled_in(SpecId::SPURIOUS_DRAGON) {
self.evm.db_mut().set_state_clear_flag(true);
} else {
self.evm.db_mut().set_state_clear_flag(false);
};
fn set_evm_environment(&mut self, header: &Header) {
let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["initialize_evm"]);

// initialize evm environment
let mut env = Env::default();
env.block.number = U256::from(header.number);
env.block.coinbase = header.author;
env.block.timestamp = U256::from(header.timestamp);
if get_spec_id(header.number).is_enabled_in(SpecId::MERGE) {
env.block.difficulty = U256::ZERO;
env.block.prevrandao = header.mix_hash;
} else {
env.block.difficulty = header.difficulty;
env.block.prevrandao = None;
}
env.block.basefee = header.base_fee_per_gas.unwrap_or_default();
env.block.gas_limit = header.gas_limit;

// EIP-4844 excess blob gas of this block, introduced in Cancun
if let Some(excess_blob_gas) = header.excess_blob_gas {
env.block
.set_blob_excess_gas_and_price(u64::from_be_bytes(excess_blob_gas.to_be_bytes()));
}

self.evm.context.evm.env = Box::new(env);
self.evm.handler.modify_spec_id(get_spec_id(header.number));
stop_timer(timer);
}

fn set_transaction_evm_context(&mut self, tx: &TransactionsWithSender) {
let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]);
// update spec id
let spec_id = get_spec_id(header.number);
self.evm.modify_spec_id(spec_id);
self.evm
.db_mut()
.set_state_clear_flag(spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON));

let block_number = self.block_number();
let tx_env = &mut self.evm.context.evm.env.tx;
// initialize evm environment
*self.evm.block_mut() = create_block_env(header);
self.evm.tx_mut().clear();

tx_env.caller = tx.sender_address;
match &tx.transaction {
Transaction::Legacy(tx) => tx.modify(block_number, tx_env),
Transaction::EIP1559(tx) => tx.modify(block_number, tx_env),
Transaction::AccessList(tx) => tx.modify(block_number, tx_env),
Transaction::Blob(tx) => tx.modify(block_number, tx_env),
}
stop_timer(timer);
}

Expand Down Expand Up @@ -211,19 +172,22 @@ impl<'a> BlockExecutor<'a> {
return Ok(());
}

self.set_evm_environment_from_block(&block.header);
self.set_evm_environment(&block.header);

// execute transactions
let cumulative_transaction_timer =
start_timer_vec(&BLOCK_PROCESSING_TIMES, &["cumulative_transaction"]);
let mut block_gas_used = 0;
for transaction in block.transactions.iter() {
let transaction_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["transaction"]);

let evm_result = self.execute_transaction(transaction)?;
block_gas_used += evm_result.result.gas_used();
let timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]);

let commit_timer = start_timer_vec(&BLOCK_PROCESSING_TIMES, &["commit_state"]);
self.evm.db_mut().commit(evm_result.state);
stop_timer(timer);
stop_timer(commit_timer);

stop_timer(transaction_timer);
}
stop_timer(cumulative_transaction_timer);
Expand Down Expand Up @@ -253,11 +217,15 @@ impl<'a> BlockExecutor<'a> {
&mut self,
tx: &TransactionsWithSender,
) -> anyhow::Result<ResultAndState> {
self.set_transaction_evm_context(tx);
let block_number = self.block_number();
let block_number = self.evm.block().number.to();

let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["transact"]);
// Set transaction environment
let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["modify_tx"]);
tx.modify(block_number, self.evm.tx_mut());
stop_timer(timer);

// Execute transaction
let timer = start_timer_vec(&TRANSACTION_PROCESSING_TIMES, &["transact"]);
let result = if self.block_to_trace.should_trace(block_number) {
let output_path = self
.node_data_directory
Expand All @@ -268,11 +236,9 @@ impl<'a> BlockExecutor<'a> {
let output_file =
File::create(output_path.join(format!("tx_{}.json", tx.transaction.hash())))?;
let tracer = TracerEip3155::new(Box::new(output_file));
execute_transaction_with_external_context(
*self.evm.context.evm.inner.env.clone(),
tracer,
&mut self.evm.context.evm.inner.db,
)?

create_evm_with_tracer(self.evm.block().clone(), tx, self.evm.db_mut(), tracer)
.transact()?
} else {
self.evm.transact()?
};
Expand Down Expand Up @@ -308,8 +274,4 @@ impl<'a> BlockExecutor<'a> {
pub fn bundle_size_hint(&self) -> usize {
self.evm.db().bundle_size_hint()
}

fn block_number(&self) -> u64 {
self.evm.context.evm.env.block.number.to::<u64>()
}
}
24 changes: 0 additions & 24 deletions trin-execution/src/evm/blocking.rs

This file was deleted.

File renamed without changes.
67 changes: 66 additions & 1 deletion trin-execution/src/evm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,68 @@
use ethportal_api::Header;
use revm::{inspector_handle_register, inspectors::TracerEip3155, Database, Evm};
use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, SpecId, U256};
use spec_id::get_spec_id;
use tx_env_modifier::TxEnvModifier;

pub mod async_db;
pub mod block_executor;
pub mod blocking;
pub mod dao_fork;
pub mod post_block_beneficiaries;
pub mod spec_id;
pub mod tx_env_modifier;

/// Creates [BlockEnv] based on data in the [Header].
pub fn create_block_env(header: &Header) -> BlockEnv {
// EIP-4844: Excess blob gas and blob gasprice, introduced in Cancun
let blob_excess_gas_and_price = header
.excess_blob_gas
.map(|excess_blob_gas| BlobExcessGasAndPrice::new(excess_blob_gas.to()));

// EIP-4399: Expose beacon chain randomness in eth EVM, introduced in Paris (aka the Merge)
let prevrandao = if get_spec_id(header.number).is_enabled_in(SpecId::MERGE) {
header.mix_hash
} else {
None
};

BlockEnv {
number: U256::from(header.number),
coinbase: header.author,
timestamp: U256::from(header.timestamp),
gas_limit: header.gas_limit,
basefee: header.base_fee_per_gas.unwrap_or_default(),
difficulty: header.difficulty,
prevrandao,
blob_excess_gas_and_price,
}
}

/// Creates [Evm] that is ready to execute provided transaction.
pub fn create_evm<'evm, 'db, DB: Database, Tx: TxEnvModifier>(
block_env: BlockEnv,
tx: &Tx,
db: &'db mut DB,
) -> Evm<'evm, (), &'db mut DB> {
let block_number = block_env.number.to();
let spec_id = get_spec_id(block_number);
Evm::builder()
.with_block_env(block_env)
.modify_tx_env(|tx_env| tx.modify(block_number, tx_env))
.with_spec_id(spec_id)
.with_db(db)
.build()
}

/// Creates [Evm] that is ready to execute provided transaction, with attached tracer.
pub fn create_evm_with_tracer<'evm, 'db, DB: Database, Tx: TxEnvModifier>(
block_env: BlockEnv,
tx: &Tx,
db: &'db mut DB,
tracer: TracerEip3155,
) -> Evm<'evm, TracerEip3155, &'db mut DB> {
create_evm(block_env, tx, db)
.modify()
.reset_handler_with_external_context(tracer)
.append_handler_register(inspector_handle_register)
.build()
}
Loading

0 comments on commit aab46ab

Please sign in to comment.