From aab46ab8746ce476112b367d64964ac62cc4f808 Mon Sep 17 00:00:00 2001 From: Milos Stankovic <82043364+morph-dev@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:52:24 +0300 Subject: [PATCH] feat: add support for async transaction execution (#1439) --- portal-bridge/src/bridge/state.rs | 2 +- trin-execution/src/era/binary_search.rs | 2 +- trin-execution/src/era/types.rs | 2 +- trin-execution/src/evm/async_db.rs | 96 ++++++++++++++++ trin-execution/src/evm/block_executor.rs | 106 ++++++------------ trin-execution/src/evm/blocking.rs | 24 ---- trin-execution/src/{ => evm}/dao_fork.rs | 0 trin-execution/src/evm/mod.rs | 67 ++++++++++- .../src/evm/post_block_beneficiaries.rs | 6 +- trin-execution/src/{ => evm}/spec_id.rs | 0 .../tx_env_modifier.rs} | 15 +++ trin-execution/src/lib.rs | 3 - trin-execution/src/main.rs | 2 +- 13 files changed, 218 insertions(+), 107 deletions(-) create mode 100644 trin-execution/src/evm/async_db.rs delete mode 100644 trin-execution/src/evm/blocking.rs rename trin-execution/src/{ => evm}/dao_fork.rs (100%) rename trin-execution/src/{ => evm}/spec_id.rs (100%) rename trin-execution/src/{transaction.rs => evm/tx_env_modifier.rs} (93%) diff --git a/portal-bridge/src/bridge/state.rs b/portal-bridge/src/bridge/state.rs index f0cdf60ad..a606711a0 100644 --- a/portal-bridge/src/bridge/state.rs +++ b/portal-bridge/src/bridge/state.rs @@ -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}, diff --git a/trin-execution/src/era/binary_search.rs b/trin-execution/src/era/binary_search.rs index b46732351..eb544629a 100644 --- a/trin-execution/src/era/binary_search.rs +++ b/trin-execution/src/era/binary_search.rs @@ -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, diff --git a/trin-execution/src/era/types.rs b/trin-execution/src/era/types.rs index e42efb0fc..a3a5148a8 100644 --- a/trin-execution/src/era/types.rs +++ b/trin-execution/src/era/types.rs @@ -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 { diff --git a/trin-execution/src/evm/async_db.rs b/trin-execution/src/evm/async_db.rs new file mode 100644 index 000000000..03c7cbac9 --- /dev/null +++ b/trin-execution/src/evm/async_db.rs @@ -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, Self::Error>> + Send; + + /// Get account code by its hash. + fn code_by_hash_async( + &mut self, + code_hash: B256, + ) -> impl Future> + Send; + + /// Get storage value of address at index. + fn storage_async( + &mut self, + address: Address, + index: U256, + ) -> impl Future> + Send; + + /// Get block hash by block number. + fn block_hash_async( + &mut self, + number: u64, + ) -> impl Future> + 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: DB, + rt: runtime::Runtime, +} + +impl WrapAsyncDatabase { + pub fn new(db: DB, rt: runtime::Runtime) -> Self { + Self { db, rt } + } +} + +impl Database for WrapAsyncDatabase { + type Error = DB::Error; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + self.rt.block_on(self.db.basic_async(address)) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.rt.block_on(self.db.code_by_hash_async(code_hash)) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + self.rt.block_on(self.db.storage_async(address, index)) + } + + fn block_hash(&mut self, number: u64) -> Result { + self.rt.block_on(self.db.block_hash_async(number)) + } +} + +pub async fn execute_transaction( + block_env: BlockEnv, + tx: Tx, + db: DB, +) -> EVMResult +where + DB: AsyncDatabase + 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:?}" + ))) + }) +} diff --git a/trin-execution/src/evm/block_executor.rs b/trin-execution/src/evm/block_executor.rs index 885a056d5..4211fefbe 100644 --- a/trin-execution/src/evm/block_executor.rs +++ b/trin-execution/src/evm/block_executor.rs @@ -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"; @@ -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); } @@ -211,7 +172,7 @@ 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 = @@ -219,11 +180,14 @@ impl<'a> BlockExecutor<'a> { 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); @@ -253,11 +217,15 @@ impl<'a> BlockExecutor<'a> { &mut self, tx: &TransactionsWithSender, ) -> anyhow::Result { - 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 @@ -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()? }; @@ -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::() - } } diff --git a/trin-execution/src/evm/blocking.rs b/trin-execution/src/evm/blocking.rs deleted file mode 100644 index 2331b0fdc..000000000 --- a/trin-execution/src/evm/blocking.rs +++ /dev/null @@ -1,24 +0,0 @@ -use revm::{inspector_handle_register, Database, Evm, GetInspector}; -use revm_primitives::{EVMResult, Env}; - -use crate::spec_id::get_spec_id; - -/// Executes the transaction with external context in the blocking manner. -pub fn execute_transaction_with_external_context< - DB: Database, - EXT: revm::Inspector + for<'a> GetInspector<&'a mut DB>, ->( - evm_environment: Env, - external_context: EXT, - database: &mut DB, -) -> EVMResult { - let block_number = evm_environment.block.number.to::(); - Evm::builder() - .with_env(Box::new(evm_environment)) - .with_spec_id(get_spec_id(block_number)) - .with_db(database) - .with_external_context(external_context) - .append_handler_register(inspector_handle_register) - .build() - .transact() -} diff --git a/trin-execution/src/dao_fork.rs b/trin-execution/src/evm/dao_fork.rs similarity index 100% rename from trin-execution/src/dao_fork.rs rename to trin-execution/src/evm/dao_fork.rs diff --git a/trin-execution/src/evm/mod.rs b/trin-execution/src/evm/mod.rs index 78581ecb2..89bdda20d 100644 --- a/trin-execution/src/evm/mod.rs +++ b/trin-execution/src/evm/mod.rs @@ -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() +} diff --git a/trin-execution/src/evm/post_block_beneficiaries.rs b/trin-execution/src/evm/post_block_beneficiaries.rs index 62ef5ef51..0516c4742 100644 --- a/trin-execution/src/evm/post_block_beneficiaries.rs +++ b/trin-execution/src/evm/post_block_beneficiaries.rs @@ -5,11 +5,11 @@ use alloy_primitives::Address; use revm::{db::State, Evm}; use revm_primitives::SpecId; -use crate::{ +use crate::{era::types::ProcessedBlock, storage::evm_db::EvmDB}; + +use super::{ dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, - era::types::ProcessedBlock, spec_id::get_spec_block_number, - storage::evm_db::EvmDB, }; // Calculate block reward diff --git a/trin-execution/src/spec_id.rs b/trin-execution/src/evm/spec_id.rs similarity index 100% rename from trin-execution/src/spec_id.rs rename to trin-execution/src/evm/spec_id.rs diff --git a/trin-execution/src/transaction.rs b/trin-execution/src/evm/tx_env_modifier.rs similarity index 93% rename from trin-execution/src/transaction.rs rename to trin-execution/src/evm/tx_env_modifier.rs index 2d43c895e..22308533a 100644 --- a/trin-execution/src/transaction.rs +++ b/trin-execution/src/evm/tx_env_modifier.rs @@ -1,15 +1,30 @@ use alloy_primitives::U256; use ethportal_api::types::execution::transaction::{ AccessListTransaction, BlobTransaction, EIP1559Transaction, LegacyTransaction, ToAddress, + Transaction, }; use revm_primitives::{AccessListItem, SpecId, TransactTo, TxEnv}; +use crate::era::types::TransactionsWithSender; + use super::spec_id::get_spec_id; pub trait TxEnvModifier { fn modify(&self, block_number: u64, tx_env: &mut TxEnv); } +impl TxEnvModifier for TransactionsWithSender { + fn modify(&self, block_number: u64, tx_env: &mut TxEnv) { + tx_env.caller = self.sender_address; + match &self.transaction { + Transaction::Legacy(tx) => tx.modify(block_number, tx_env), + Transaction::AccessList(tx) => tx.modify(block_number, tx_env), + Transaction::EIP1559(tx) => tx.modify(block_number, tx_env), + Transaction::Blob(tx) => tx.modify(block_number, tx_env), + } + } +} + impl TxEnvModifier for LegacyTransaction { fn modify(&self, block_number: u64, tx_env: &mut TxEnv) { tx_env.gas_limit = self.gas.to::(); diff --git a/trin-execution/src/lib.rs b/trin-execution/src/lib.rs index 73772193b..01f1568d8 100644 --- a/trin-execution/src/lib.rs +++ b/trin-execution/src/lib.rs @@ -1,14 +1,11 @@ pub mod cli; pub mod config; pub mod content; -pub mod dao_fork; pub mod era; pub mod evm; pub mod execution; pub mod metrics; -pub mod spec_id; pub mod storage; -pub mod transaction; pub mod trie_walker; pub mod types; pub mod utils; diff --git a/trin-execution/src/main.rs b/trin-execution/src/main.rs index e777f33ce..2360d329d 100644 --- a/trin-execution/src/main.rs +++ b/trin-execution/src/main.rs @@ -3,7 +3,7 @@ use clap::Parser; use revm_primitives::SpecId; use tracing::info; use trin_execution::{ - cli::TrinExecutionConfig, execution::TrinExecution, spec_id::get_spec_block_number, + cli::TrinExecutionConfig, evm::spec_id::get_spec_block_number, execution::TrinExecution, storage::utils::setup_temp_dir, }; use trin_utils::log::init_tracing_logger;