diff --git a/src/eth/executor/evm/mod.rs b/src/eth/executor/evm/mod.rs index d9958097c..d0aea4a72 100644 --- a/src/eth/executor/evm/mod.rs +++ b/src/eth/executor/evm/mod.rs @@ -24,8 +24,8 @@ use revm_inspectors::tracing::TracingInspector; use revm_inspectors::tracing::TracingInspectorConfig; use revm_inspectors::tracing::js::JsInspector; use session::RevmSession; -pub use types::EvmKind; pub use types::GeneralRevm; +pub use types::Lane; use util::default_trace; use util::enhance_trace_with_decoded_errors; @@ -39,10 +39,12 @@ use crate::eth::executor::evm::types::InspectorInput; use crate::eth::executor::evm::util::EvmExt; use crate::eth::executor::evm::util::create_evm; use crate::eth::rpc::BlockFilter; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::storage::StorageError; use crate::eth::storage::StratusStorage; +use crate::eth::types::Job; use crate::eth::types::MinedData; +use crate::eth::types::StateView; use crate::eth::types::StratusError; pub type RevmResultAndState = ExecResultAndState; @@ -50,21 +52,21 @@ pub type RevmResultAndState = ExecResultAndState; /// Implementation of EVM using [`revm`](https://crates.io/crates/revm). pub struct Evm { evm: GeneralRevm, - kind: EvmKind, + job: Job, _input_type: PhantomData, } impl Evm { /// Creates a new instance of the Evm. - pub fn new(storage: Arc, config: &ExecutorConfig, kind: EvmKind) -> Self { + pub fn new(storage: Arc, config: &ExecutorConfig, job: Job) -> Self { tracing::info!(?config, "creating revm"); // configure revm let chain_id = config.executor_chain_id; Self { - evm: create_evm(chain_id, config.executor_evm_spec, RevmSession::new(storage), kind), - kind, + evm: create_evm(chain_id, config.executor_evm_spec, RevmSession::new(storage), job), + job, _input_type: PhantomData, } } @@ -74,7 +76,7 @@ impl Evm { let metrics_context = input.metrics_context(); // configure session - self.evm.journaled_state.database.reset(input.kind()); + self.evm.journaled_state.database.reset(input.context()); input.fill_env(&mut self.evm); let tx = std::mem::take(&mut self.evm.tx); @@ -144,12 +146,12 @@ impl Evm { }; let inspect_input: TransactionExecutionInput = tx.input; let target = inspect_input.block_number.prev().unwrap_or_default(); - self.evm.journaled_state.database.reset(ExecutionKind::CallPast(target)); + self.evm.journaled_state.database.reset(ExecutionContext::call(StateView::Past(target))); let spec = self.evm.cfg.spec; let mut cache_db = CacheDB::new(&self.evm.journaled_state.database); - let mut evm = create_evm(inspect_input.chain_id.unwrap_or_default().into(), spec, &mut cache_db, self.kind); + let mut evm = create_evm(inspect_input.chain_id.unwrap_or_default().into(), spec, &mut cache_db, self.job); // Execute all transactions before target tx_hash for tx in block.transactions.into_iter() { diff --git a/src/eth/executor/evm/session.rs b/src/eth/executor/evm/session.rs index b13831e1e..dda4d3e8e 100644 --- a/src/eth/executor/evm/session.rs +++ b/src/eth/executor/evm/session.rs @@ -12,7 +12,7 @@ use revm::state::AccountInfo; use crate::alias::RevmAddress; use crate::alias::RevmBytecode; use crate::eth::executor::evm::types::StorageMetrics; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::storage::FoundAt; use crate::eth::storage::StorageError; use crate::eth::storage::StratusStorage; @@ -25,8 +25,8 @@ pub struct RevmSession { /// Service to communicate with the storage. pub storage: Arc, - /// Input passed to EVM to execute the transaction. - pub kind: ExecutionKind, + /// Execution context that determines how storage reads resolve during EVM execution. + pub context: ExecutionContext, /// Metrics collected during EVM execution. pub metrics: StorageMetrics, @@ -37,14 +37,14 @@ impl RevmSession { pub fn new(storage: Arc) -> Self { Self { storage, - kind: ExecutionKind::default(), + context: ExecutionContext::default(), metrics: StorageMetrics::default(), } } /// Resets the session to be used with a new transaction. - pub fn reset(&mut self, kind: ExecutionKind) { - self.kind = kind; + pub fn reset(&mut self, context: ExecutionContext) { + self.context = context; self.metrics = StorageMetrics::default(); } } @@ -103,7 +103,7 @@ impl RevmSession { return Ok((None, FoundAt::Temp)); } - let (account, found_at) = self.storage.read_account(address, self.kind)?; + let (account, found_at) = self.storage.read_account(address, self.context)?; Ok((Some(account.into()), found_at)) } @@ -117,7 +117,7 @@ impl RevmSession { let index: SlotIndex = index.into(); // load slot from storage - let (slot, found_at) = self.storage.read_slot(address, index, self.kind)?; + let (slot, found_at) = self.storage.read_slot(address, index, self.context)?; Ok((slot.value.into(), found_at)) } diff --git a/src/eth/executor/evm/types/execution_metrics.rs b/src/eth/executor/evm/types/execution_metrics.rs index 2ef67c98e..d8ba2b012 100644 --- a/src/eth/executor/evm/types/execution_metrics.rs +++ b/src/eth/executor/evm/types/execution_metrics.rs @@ -14,7 +14,7 @@ use crate::eth::codegen::SoliditySignature; use crate::eth::storage::FoundAt; use crate::eth::types::Address; use crate::eth::types::Bytes; -use crate::eth::types::ExecutionKind; +use crate::eth::types::ExecutionContext; use crate::eth::types::Gas; #[derive(Debug, Default, Clone, Copy)] @@ -58,7 +58,7 @@ pub struct StorageMetrics { #[derive(Debug, Clone, Copy)] pub struct ExecutionMetricsContext { #[cfg(feature = "metrics")] - kind: ExecutionKind, + execution_context: ExecutionContext, #[cfg(feature = "metrics")] contract: ContractName, #[cfg(feature = "metrics")] @@ -66,18 +66,18 @@ pub struct ExecutionMetricsContext { } impl ExecutionMetricsContext { - pub fn new(kind: ExecutionKind, to: &Option
, input: &Bytes) -> Self { + pub fn new(context: ExecutionContext, to: &Option
, input: &Bytes) -> Self { #[cfg(feature = "metrics")] { Self { - kind, + execution_context: context, contract: codegen::contract_name(to), function: codegen::function_sig(input), } } #[cfg(not(feature = "metrics"))] { - let _ = (kind, to, input); + let _ = (context, to, input); Self {} } } @@ -104,7 +104,12 @@ impl ExecutionMetrics { fn publish(&self) { let context = &self.context; self.storage_metrics.publish(context); - metrics::inc_evm_execution_gas(self.gas_used.as_u64() as usize, context.kind.as_ref(), context.contract, context.function); + metrics::inc_evm_execution_gas( + self.gas_used.as_u64() as usize, + context.execution_context.metrics_label(), + context.contract, + context.function, + ); } } @@ -118,7 +123,7 @@ impl Drop for ExecutionMetrics { impl StorageMetrics { #[cfg(feature = "metrics")] fn publish(&self, context: &ExecutionMetricsContext) { - let execution_kind = context.kind.as_ref(); + let execution_kind = context.execution_context.metrics_label(); for (found_at, stats) in self.account_reads.iter() { if stats.count > 0 { metrics::inc_n_evm_execution_account_reads(stats.count as u64, execution_kind, found_at.as_str(), context.contract, context.function); diff --git a/src/eth/executor/evm/types/input/call_execution.rs b/src/eth/executor/evm/types/input/call_execution.rs index a82bd28fc..f822f220d 100644 --- a/src/eth/executor/evm/types/input/call_execution.rs +++ b/src/eth/executor/evm/types/input/call_execution.rs @@ -7,7 +7,7 @@ use crate::eth::executor::evm::types::EvmInput; use crate::eth::executor::evm::types::ExecutionMetricsContext; use crate::eth::executor::evm::types::GAS_MAX_LIMIT; use crate::eth::executor::evm::types::GeneralRevm; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::types::Address; use crate::eth::types::BlockInfo; use crate::eth::types::BlockNumber; @@ -56,11 +56,11 @@ pub struct CallExecutionInput { /// Timestamp of the block where the transaction will be or was included. pub block_timestamp: UnixTime, - pub kind: ExecutionKind, + pub context: ExecutionContext, } impl CallExecutionInput { - pub fn create(input: CallInput, block_info: BlockInfo, kind: ExecutionKind) -> Self { + pub fn create(input: CallInput, block_info: BlockInfo, context: ExecutionContext) -> Self { Self { from: input.from.unwrap_or(Address::ZERO), to: input.to.map_into(), @@ -68,7 +68,7 @@ impl CallExecutionInput { data: input.data, block_number: block_info.number, block_timestamp: *block_info.timestamp, - kind, + context, } } } @@ -95,11 +95,11 @@ impl EvmInput for CallExecutionInput { evm.tx.gas_priority_fee = None; } - fn kind(&self) -> ExecutionKind { - self.kind + fn context(&self) -> ExecutionContext { + self.context } fn metrics_context(&self) -> ExecutionMetricsContext { - ExecutionMetricsContext::new(self.kind, &self.to, &self.data) + ExecutionMetricsContext::new(self.context, &self.to, &self.data) } } diff --git a/src/eth/executor/evm/types/input/mod.rs b/src/eth/executor/evm/types/input/mod.rs index d2c695e4d..98715964e 100644 --- a/src/eth/executor/evm/types/input/mod.rs +++ b/src/eth/executor/evm/types/input/mod.rs @@ -2,14 +2,14 @@ use revm::Database; use crate::eth::executor::evm::GeneralRevm; use crate::eth::executor::evm::types::ExecutionMetricsContext; -use crate::eth::types::ExecutionKind; +use crate::eth::types::ExecutionContext; pub mod call_execution; pub mod inspector; pub mod transaction_execution; pub trait EvmInput: Default + Clone { - fn kind(&self) -> ExecutionKind; + fn context(&self) -> ExecutionContext; fn metrics_context(&self) -> ExecutionMetricsContext; diff --git a/src/eth/executor/evm/types/input/transaction_execution.rs b/src/eth/executor/evm/types/input/transaction_execution.rs index 48b617d08..e64f4d5a0 100644 --- a/src/eth/executor/evm/types/input/transaction_execution.rs +++ b/src/eth/executor/evm/types/input/transaction_execution.rs @@ -7,7 +7,7 @@ use crate::eth::executor::evm::types::EvmInput; use crate::eth::executor::evm::types::ExecutionMetricsContext; use crate::eth::executor::evm::types::GAS_MAX_LIMIT; use crate::eth::executor::evm::types::GeneralRevm; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::types::Address; use crate::eth::types::BlockInfo; use crate::eth::types::BlockNumber; @@ -77,7 +77,7 @@ pub struct TransactionExecutionInput { /// If not specified, it will not be validated. pub chain_id: Option, - pub kind: ExecutionKind, + pub context: ExecutionContext, } impl TransactionExecutionInput { @@ -94,7 +94,7 @@ impl TransactionExecutionInput { block_number: block_info.number, block_timestamp: *block_info.timestamp, chain_id: input.execution_info.chain_id, - kind: ExecutionKind::Transaction, + context: ExecutionContext::transaction(), } } } @@ -127,11 +127,11 @@ impl EvmInput for TransactionExecutionInput { evm.tx.gas_priority_fee = None; } - fn kind(&self) -> ExecutionKind { - self.kind + fn context(&self) -> ExecutionContext { + self.context } fn metrics_context(&self) -> ExecutionMetricsContext { - ExecutionMetricsContext::new(self.kind, &self.to, &self.data) + ExecutionMetricsContext::new(self.context, &self.to, &self.data) } } diff --git a/src/eth/executor/evm/types/mod.rs b/src/eth/executor/evm/types/mod.rs index 220861995..2b436f388 100644 --- a/src/eth/executor/evm/types/mod.rs +++ b/src/eth/executor/evm/types/mod.rs @@ -34,25 +34,12 @@ pub const GAS_MAX_LIMIT: u64 = 100_000_000; pub type ContextWithDB = Context>; pub type GeneralRevm = RevmEvm, I, EthInstructions>, EthPrecompiles, EthFrame>; -/// Classification of an EVM by the kind of execution it performs. Used to route -/// work to the right EVM worker pool and as a metrics label. +/// Executor worker pool lane. Determines which pool executes the task and +/// labels the `executor_workers_busy` gauge. #[derive(Clone, Copy)] -pub enum EvmKind { +pub enum Lane { Transaction, - CallPast, CallPresent, - Inspect, -} - -impl EvmKind { - pub fn is_call(&self) -> bool { - match self { - EvmKind::Transaction => false, - EvmKind::CallPast | EvmKind::CallPresent | EvmKind::Inspect => true, - } - } - - pub fn is_transaction(&self) -> bool { - !self.is_call() - } + CallPast, + Inspector, } diff --git a/src/eth/executor/evm/util.rs b/src/eth/executor/evm/util.rs index 48360cacf..7bdabe764 100644 --- a/src/eth/executor/evm/util.rs +++ b/src/eth/executor/evm/util.rs @@ -18,12 +18,12 @@ use revm::handler::instructions::EthInstructions; use revm::primitives::hardfork::SpecId; use crate::eth::codegen; -use crate::eth::executor::EvmKind; use crate::eth::executor::TransactionExecution; use crate::eth::executor::TransactionExecutionInput; use crate::eth::executor::evm::GeneralRevm; use crate::eth::executor::evm::types::GAS_MAX_LIMIT; use crate::eth::types::Address; +use crate::eth::types::Job; use crate::ext::OptionExt; pub fn default_trace(tracer_type: GethDebugTracerType, tx: TransactionExecution) -> GethTrace { @@ -119,18 +119,18 @@ impl BlockEnvExt for BlockEnv { } } -pub fn create_evm(chain_id: u64, spec: SpecId, db: DB, kind: EvmKind) -> GeneralRevm { +pub fn create_evm(chain_id: u64, spec: SpecId, db: DB, job: Job) -> GeneralRevm { let ctx = Context::new(db, spec) .modify_cfg_chained(|cfg_env| { cfg_env.chain_id = chain_id; cfg_env.spec = spec; - cfg_env.tx_chain_id_check = kind.is_transaction(); + cfg_env.tx_chain_id_check = job.is_transaction(); cfg_env.limit_contract_initcode_size = None; - cfg_env.disable_nonce_check = kind.is_call(); + cfg_env.disable_nonce_check = !job.is_transaction(); cfg_env.max_blobs_per_tx = None; cfg_env.tx_gas_limit_cap = None; cfg_env.blob_base_fee_update_fraction = None; - cfg_env.disable_eip3607 = kind.is_call(); + cfg_env.disable_eip3607 = !job.is_transaction(); cfg_env.limit_contract_code_size = Some(usize::MAX); cfg_env.memory_limit = (1 << 32) - 1; cfg_env.disable_balance_check = false; diff --git a/src/eth/executor/evm_worker_pool.rs b/src/eth/executor/evm_worker_pool.rs index 85ab2611d..6f5365542 100644 --- a/src/eth/executor/evm_worker_pool.rs +++ b/src/eth/executor/evm_worker_pool.rs @@ -7,8 +7,9 @@ use crate::GlobalState; use crate::eth::executor::ExecutionMetrics; use crate::eth::executor::ExecutorConfig; use crate::eth::executor::ExecutorError; +use crate::eth::executor::Job; use crate::eth::executor::evm::Evm; -use crate::eth::executor::evm::EvmKind; +use crate::eth::executor::evm::Lane; use crate::eth::executor::evm::RevmResultAndState; use crate::eth::executor::evm::types::CallExecutionInput; use crate::eth::executor::evm::types::InspectorInput; @@ -44,9 +45,10 @@ impl EvmWorkerPool { storage: Arc, config: ExecutorConfig, task_rx: crossbeam_channel::Receiver>, - kind: EvmKind, + lane: Lane, + job: Job, ) { - let mut evm = Evm::new(Arc::clone(&storage), &config, kind); + let mut evm = Evm::new(Arc::clone(&storage), &config, job); // keep executing transactions until the channel is closed while let Ok(task) = task_rx.recv() { @@ -54,10 +56,10 @@ impl EvmWorkerPool { return; } - let _guard = kind.mark_executor_pool_busy(); + let _guard = lane.mark_executor_pool_busy(); if let Err(StratusError::Executor(ExecutorError::Panic { err: panic_err })) = task.execute(&mut evm) { tracing::error!(?panic_err, "executor panicked; recreating EVM"); - evm = Evm::new(Arc::clone(&storage), &config, kind); + evm = Evm::new(Arc::clone(&storage), &config, job); } } warn_task_tx_closed(task_name); @@ -67,7 +69,8 @@ impl EvmWorkerPool { fn spawn_evms( task_name: &str, num_evms: usize, - kind: EvmKind, + lane: Lane, + job: Job, storage: &Arc, config: &ExecutorConfig, ) -> crossbeam_channel::Sender> { @@ -80,16 +83,16 @@ impl EvmWorkerPool { let evm_rx = evm_rx.clone(); let thread_name = evm_task_name.clone(); spawn_thread(&thread_name, move || { - worker(&evm_task_name, evm_storage, evm_config, evm_rx, kind); + worker(&evm_task_name, evm_storage, evm_config, evm_rx, lane, job); }); } - metrics::set_executor_workers_busy(0, kind); + metrics::set_executor_workers_busy(0, lane); evm_tx } - let call_present = spawn_evms("evm-call-present", config.call_present_evms, EvmKind::CallPresent, &storage, config); - let call_past = spawn_evms("evm-call-past", config.call_past_evms, EvmKind::CallPast, &storage, config); - let inspector = spawn_evms("inspector", config.inspector_evms, EvmKind::Inspect, &storage, config); + let call_present = spawn_evms("evm-call-present", config.call_present_evms, Lane::CallPresent, Job::Call, &storage, config); + let call_past = spawn_evms("evm-call-past", config.call_past_evms, Lane::CallPast, Job::Call, &storage, config); + let inspector = spawn_evms("inspector", config.inspector_evms, Lane::Inspector, Job::Call, &storage, config); EvmWorkerPool { call_present, diff --git a/src/eth/executor/mod.rs b/src/eth/executor/mod.rs index 1aeae0268..94b32ac3b 100644 --- a/src/eth/executor/mod.rs +++ b/src/eth/executor/mod.rs @@ -16,8 +16,8 @@ use anyhow::bail; pub use config::ExecutorConfig; pub use evm::types::AccessListOutput; pub use evm::types::CallExecutionOutput; -pub use evm::types::EvmKind; pub use evm::types::ExecutionMetrics; +pub use evm::types::Lane; pub use evm::types::TransactionExecutionInput; pub use evm::types::TransactionExecutionOutput; pub use evm::types::TransactionExecutionResult; @@ -41,7 +41,7 @@ use crate::eth::executor::evm_worker_pool::EvmWorkerPool; use crate::eth::executor::transaction_worker::TransactionWorker; use crate::eth::executor::types::EvmRoute; use crate::eth::miner::Miner; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::storage::StorageError; use crate::eth::storage::StratusStorage; use crate::eth::types::Address; @@ -52,7 +52,8 @@ use crate::eth::types::ExternalReceipt; use crate::eth::types::ExternalReceipts; use crate::eth::types::ExternalTransaction; use crate::eth::types::Hash; -use crate::eth::types::PointInTime; +pub use crate::eth::types::Job; +use crate::eth::types::StateView; use crate::eth::types::StratusError; use crate::eth::types::TransactionInput; #[cfg(feature = "metrics")] @@ -194,7 +195,7 @@ impl Executor { // // failed external transaction, re-create from receipt without re-executing false => { - let (sender, _) = storage.read_account(receipt.from.into(), ExecutionKind::Transaction)?; + let (sender, _) = storage.read_account(receipt.from.into(), ExecutionContext::transaction())?; if tx_input.execution_info.nonce != sender.nonce { bail!( "reverted external transaction should have the correct nonce. address: {:?}, input: {:?}, sender: {:?}", @@ -224,9 +225,9 @@ impl Executor { // Local transactions // ------------------------------------------------------------------------- - /// Validates that the target account is a contract, reading it from storage at the given point in time. - pub fn validate_to_is_contract(&self, to_address: Address, kind: ExecutionKind) -> Result<(), StratusError> { - let (account, _) = self.storage.read_account(to_address, kind)?; + /// Validates that the target account is a contract, reading it from storage at the given execution context. + pub fn validate_to_is_contract(&self, to_address: Address, context: ExecutionContext) -> Result<(), StratusError> { + let (account, _) = self.storage.read_account(to_address, context)?; if account.bytecode.is_none() { if self.reject_not_contract { return Err(ExecutorError::AccountNotContract { address: to_address }.into()); @@ -269,10 +270,10 @@ impl Executor { success = result.is_ok(), contract = |call_input| codegen::contract_name(&call_input.to), function = |call_input| codegen::function_sig(&call_input.data), - kind = |kind| kind.as_ref() + kind = |context| context.metrics_label() ) )] - pub fn execute_local_call(&self, call_input: CallInput, kind: ExecutionKind) -> Result + pub fn execute_local_call(&self, call_input: CallInput, context: ExecutionContext) -> Result where Output: TryFrom, { @@ -281,8 +282,8 @@ impl Executor { s.rec_opt("to", &call_input.to); }); - let filter = kind.into(); - let block_info_opt = if matches!(kind, ExecutionKind::AccessList) { + let filter = context.into(); + let block_info_opt = if matches!(context.job, Job::AccessList) { Some(self.storage.read_latest_block_info_relaxed()) } else { self.storage.read_block_info(filter)? @@ -291,11 +292,11 @@ impl Executor { return Err(StorageError::BlockNotFound { filter }.into()); }; - let evm_input = CallExecutionInput::create(call_input, block_info, kind); + let evm_input = CallExecutionInput::create(call_input, block_info, context); - let evm_route = match kind.point_in_time() { - PointInTime::Pending | PointInTime::Latest => EvmRoute::CallPresent(evm_input), - PointInTime::Past(_) => EvmRoute::CallPast(evm_input), + let evm_route = match context.at { + StateView::Pending | StateView::Latest(_) => EvmRoute::CallPresent(evm_input), + StateView::Past(_) => EvmRoute::CallPast(evm_input), }; self.evms.execute::(evm_route).map(|(output, _metrics)| output) diff --git a/src/eth/executor/transaction_worker.rs b/src/eth/executor/transaction_worker.rs index 81cae68df..eae89570b 100644 --- a/src/eth/executor/transaction_worker.rs +++ b/src/eth/executor/transaction_worker.rs @@ -10,12 +10,12 @@ use tracing::Span; use crate::GlobalState; #[cfg(feature = "metrics")] use crate::eth::codegen; -use crate::eth::executor::EvmKind; use crate::eth::executor::ExecutionMetrics; use crate::eth::executor::ExecutionResult; use crate::eth::executor::Executor; use crate::eth::executor::ExecutorConfig; use crate::eth::executor::ExecutorError; +use crate::eth::executor::Job; use crate::eth::executor::TransactionExecution; use crate::eth::executor::TransactionExecutionInput; use crate::eth::executor::TransactionExecutionOutput; @@ -49,7 +49,7 @@ impl TransactionWorker { let config = *config; spawn_thread(TASK_NAME, move || { - let mut evm = Evm::new(Arc::clone(&storage), &config, EvmKind::Transaction); + let mut evm = Evm::new(Arc::clone(&storage), &config, Job::Transaction); while let Ok(task) = task_rx.recv() { if GlobalState::is_shutdown_warn(TASK_NAME) { @@ -58,7 +58,7 @@ impl TransactionWorker { if let Err(StratusError::Executor(ExecutorError::Panic { err: panic_err })) = task.execute(&storage, &miner, &mut evm) { tracing::error!(?panic_err, "executor panicked; recreating EVM"); - evm = Evm::new(Arc::clone(&storage), &config, EvmKind::Transaction); + evm = Evm::new(Arc::clone(&storage), &config, Job::Transaction); } } warn_task_tx_closed(TASK_NAME); diff --git a/src/eth/follower/consensus.rs b/src/eth/follower/consensus.rs index 667a4230f..b50e2ecac 100644 --- a/src/eth/follower/consensus.rs +++ b/src/eth/follower/consensus.rs @@ -7,7 +7,7 @@ use crate::eth::executor::AccessListOutput; use crate::eth::executor::Executor; use crate::eth::follower::importer::BlockchainClient; use crate::eth::types::Bytes; -use crate::eth::types::ExecutionKind; +use crate::eth::types::ExecutionContext; use crate::eth::types::Hash; use crate::eth::types::StratusError; use crate::eth::types::TransactionInput; @@ -69,7 +69,7 @@ pub trait Consensus: Send + Sync { fn prepare_forward_access_list(&self, tx: TransactionInput) -> Result, StratusError> { if self.forward_access_list() { self.get_executor() - .execute_local_call::(tx.into(), ExecutionKind::AccessList) + .execute_local_call::(tx.into(), ExecutionContext::access_list()) .map(Some) } else { Ok(None) diff --git a/src/eth/follower/importer/mod.rs b/src/eth/follower/importer/mod.rs index cf0ffa68a..0aa2d669c 100644 --- a/src/eth/follower/importer/mod.rs +++ b/src/eth/follower/importer/mod.rs @@ -283,7 +283,7 @@ mod tests { use crate::eth::follower::importer::importers::replication::ReplicationWorker; use crate::eth::miner::Miner; use crate::eth::miner::MinerMode; - use crate::eth::storage::ExecutionKind; + use crate::eth::storage::ExecutionContext; use crate::eth::storage::StratusStorage; use crate::eth::storage::permanent::rocks::types::AccountChangesRocksdb; use crate::eth::storage::permanent::rocks::types::AddressRocksdb; @@ -388,7 +388,7 @@ mod tests { // Block 3 did not change B.balance, so the committed value must equal block 3's pre-state // (block 2 = 200). Completing at import time (perm caught up) yields 200; completing at // post-process time (perm behind) would yield the stale 100. - let (account, _) = storage.read_account(address, ExecutionKind::RPC(PointInTime::Latest)).expect("read account"); + let (account, _) = storage.read_account(address, ExecutionContext::RPC(PointInTime::Latest)).expect("read account"); assert_eq!( account.balance, Wei::from(200u64), diff --git a/src/eth/rpc/server.rs b/src/eth/rpc/server.rs index 7251cb2f9..ec1d42017 100644 --- a/src/eth/rpc/server.rs +++ b/src/eth/rpc/server.rs @@ -85,7 +85,7 @@ use crate::eth::rpc::pagination; use crate::eth::rpc::parser::RpcExtensionsExt; use crate::eth::rpc::parser::parse_rpc_rlp; use crate::eth::rpc::subscriptions::RpcSubscriptionsHandles; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::storage::StorageError; use crate::eth::storage::StratusStorage; use crate::eth::types::Address; @@ -1224,7 +1224,7 @@ fn eth_estimate_gas(params: Params<'_>, ctx: Arc, ext: Extensions) - { ctx.server .executor - .validate_to_is_contract(to_address, ExecutionKind::RPC(PointInTime::Latest))?; + .validate_to_is_contract(to_address, ExecutionContext::RPC(PointInTime::Latest))?; } let block_number = ctx.server.storage.read_mined_block_number(); @@ -1232,7 +1232,7 @@ fn eth_estimate_gas(params: Params<'_>, ctx: Arc, ext: Extensions) - match ctx .server .executor - .execute_local_call::(call, ExecutionKind::call_from_pit(PointInTime::Latest, block_number)) + .execute_local_call::(call, ExecutionContext::call_from_pit(PointInTime::Latest, block_number)) { // result is success Ok(result) if result.success => { @@ -1275,11 +1275,11 @@ fn rpc_call(params: Params<'_>, ctx: Arc) -> Result, ctx: Arc, ext: Extensions) -> Result { @@ -1384,12 +1384,12 @@ fn stratus_access_list(params: Params<'_>, ctx: Arc, ext: Extensions { ctx.server .executor - .validate_to_is_contract(to_address, ExecutionKind::RPC(PointInTime::Latest))?; + .validate_to_is_contract(to_address, ExecutionContext::RPC(PointInTime::Latest))?; } ctx.server .executor - .execute_local_call::(call, ExecutionKind::AccessList) + .execute_local_call::(call, ExecutionContext::access_list()) .map(to_json_value) .inspect(|_| tracing::info!("executed stratus_accessList with success")) .inspect_err(|e| tracing::warn!(reason = ?e, "failed to execute stratus_accessList")) @@ -1501,7 +1501,7 @@ fn _prepare_eth_send_raw_transaction( if let Some(to_address) = tx.execution_info.to && !tx.execution_info.input.is_empty() { - ctx.server.executor.validate_to_is_contract(to_address, ExecutionKind::Transaction)?; + ctx.server.executor.validate_to_is_contract(to_address, ExecutionContext::transaction())?; }; // Execute locally, or prepare synchronous access-list work before forwarding asynchronously. @@ -1619,7 +1619,7 @@ fn eth_get_transaction_count(params: Params<'_>, ctx: Arc, ext: Exte tracing::info!(%address, %filter, "reading account nonce"); let point_in_time = ctx.server.storage.translate_to_point_in_time(filter)?; - let (account, _) = ctx.server.storage.read_account(address, ExecutionKind::RPC(point_in_time))?; + let (account, _) = ctx.server.storage.read_account(address, ExecutionContext::RPC(point_in_time))?; Ok(hex_num(account.nonce)) } @@ -1641,7 +1641,7 @@ fn eth_get_balance(params: Params<'_>, ctx: Arc, ext: Extensions) -> // execute let point_in_time = ctx.server.storage.translate_to_point_in_time(filter)?; - let (account, _) = ctx.server.storage.read_account(address, ExecutionKind::RPC(point_in_time))?; + let (account, _) = ctx.server.storage.read_account(address, ExecutionContext::RPC(point_in_time))?; Ok(hex_num(account.balance)) } @@ -1662,7 +1662,7 @@ fn eth_get_code(params: Params<'_>, ctx: Arc, ext: Extensions) -> Re // execute let point_in_time = ctx.server.storage.translate_to_point_in_time(filter)?; - let (account, _) = ctx.server.storage.read_account(address, ExecutionKind::RPC(point_in_time))?; + let (account, _) = ctx.server.storage.read_account(address, ExecutionContext::RPC(point_in_time))?; Ok(account.bytecode.map(|bytecode| hex_data(bytecode.original_bytes())).unwrap_or_else(hex_null)) } @@ -1754,7 +1754,7 @@ fn eth_get_storage_at(params: Params<'_>, ctx: Arc, ext: Extensions) // execute let point_in_time = ctx.server.storage.translate_to_point_in_time(block_filter)?; - let (slot, _) = ctx.server.storage.read_slot(address, index, ExecutionKind::RPC(point_in_time))?; + let (slot, _) = ctx.server.storage.read_slot(address, index, ExecutionContext::RPC(point_in_time))?; // It must be padded, even if it is zero. Ok(hex_num_zero_padded(slot.value.as_u256())) diff --git a/src/eth/storage/mod.rs b/src/eth/storage/mod.rs index 6a1d99c02..f4d992147 100644 --- a/src/eth/storage/mod.rs +++ b/src/eth/storage/mod.rs @@ -23,7 +23,9 @@ use clap::Parser; use display_json::DebugAsJson; pub use temporary::compute_pending_block_number; -pub use crate::eth::types::ExecutionKind; +pub use crate::eth::types::ExecutionContext; +pub use crate::eth::types::Job; +pub use crate::eth::types::StateView; use crate::eth::types::StratusError; // ----------------------------------------------------------------------------- @@ -174,7 +176,9 @@ mod tests { assert_ne!(call_block, latest); // The in-flight call (pinned to the first block) reads the slot. - let (slot, _) = storage.read_slot(address, index, ExecutionKind::CallLatest(call_block)).expect("read slot"); + let (slot, _) = storage + .read_slot(address, index, ExecutionContext::call(StateView::Latest(Some(call_block)))) + .expect("read slot"); // Must reflect the first block (100), not the freshly mined latest (200). assert_eq!(slot.value, SlotValue::from([100u64, 0, 0, 0])); @@ -201,7 +205,9 @@ mod tests { let latest = storage.mine_block_with_mock_execution(changes2); assert_ne!(call_block, latest); - let (account, _) = storage.read_account(address, ExecutionKind::CallLatest(call_block)).expect("read account"); + let (account, _) = storage + .read_account(address, ExecutionContext::call(StateView::Latest(Some(call_block)))) + .expect("read account"); // Must reflect the first block (100), not the freshly mined latest (200). assert_eq!(account.balance, Wei::from(100u64)); diff --git a/src/eth/storage/resolve_pending.rs b/src/eth/storage/resolve_pending.rs index a697f6ab4..eba2af383 100644 --- a/src/eth/storage/resolve_pending.rs +++ b/src/eth/storage/resolve_pending.rs @@ -1,12 +1,11 @@ use stratus_metrics::MetricLabelValue; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::StateView; use crate::eth::storage::StratusStorage; use crate::eth::storage::types::entity::EntityRead; use crate::eth::storage::types::state_lock::LatestStateReadGuard; use crate::eth::types::Account; use crate::eth::types::BlockNumber; -use crate::eth::types::PointInTime; use crate::eth::types::Slot; /// Prevents construction of [`MinedPointInTime`] outside this module. @@ -59,13 +58,13 @@ pub(super) enum Resolved<'a, T> { /// Pending-state resolution, generic over the entity being read. pub(super) trait Resolve: EntityRead { - fn resolve(s: &StratusStorage, key: Self::Key, kind: ExecutionKind) -> Resolved<'_, Self> { - if kind.point_in_time() == PointInTime::Pending + fn resolve(s: &StratusStorage, key: Self::Key, view: StateView) -> Resolved<'_, Self> { + if view == StateView::Pending && let Some(value) = Self::read_temp(s, key) { return Resolved::Temp(value); } - Resolved::Miss(s.resolve_mined_point(kind)) + Resolved::Miss(s.resolve_mined_point(view)) } } @@ -84,11 +83,11 @@ impl StratusStorage { } /// Determines the mined point-in-time for a read. - fn resolve_mined_point(&self, kind: ExecutionKind) -> MinedPointInTime<'_> { - match kind { - ExecutionKind::RPC(PointInTime::Past(number)) | ExecutionKind::CallPast(number) => MinedPointInTime::past(number), - ExecutionKind::CallLatest(block_number) => self.resolve_call_point(block_number), - ExecutionKind::Transaction | ExecutionKind::RPC(_) | ExecutionKind::AccessList => MinedPointInTime::latest(None), + fn resolve_mined_point(&self, view: StateView) -> MinedPointInTime<'_> { + match view { + StateView::Past(number) => MinedPointInTime::past(number), + StateView::Latest(Some(block_number)) => self.resolve_call_point(block_number), + StateView::Pending | StateView::Latest(None) => MinedPointInTime::latest(None), } } } @@ -98,7 +97,7 @@ mod tests { use super::super::StratusStorage; use super::Resolve; use crate::eth::executor::State; - use crate::eth::storage::ExecutionKind; + use crate::eth::storage::ExecutionContext; use crate::eth::types::Address; use crate::eth::types::BlockNumber; use crate::eth::types::Slot; @@ -114,9 +113,9 @@ mod tests { // Mined Full call: block_number = 5, mined = 5 → valid (b >= mined). let call_block = BlockNumber::from(5u64); - let kind = ExecutionKind::CallLatest(call_block); + let context = ExecutionContext::call(crate::eth::types::StateView::Latest(Some(call_block))); - let resolved = Slot::resolve(&storage, (address, index), kind); + let resolved = Slot::resolve(&storage, (address, index), context.at); match resolved { super::Resolved::Miss(point) => { assert!( @@ -134,7 +133,7 @@ mod tests { storage.mine_block_with_mock_execution(State::default()); // Stale: b=5 < mined=6. Full → MinedPast(5), NOT MinedPast(4). - let resolved = Slot::resolve(&storage, (address, index), kind); + let resolved = Slot::resolve(&storage, (address, index), context.at); match resolved { super::Resolved::Miss(point) => { assert!(!matches!(point, super::MinedPointInTime::Latest(_, _)), "stale call should not read latest"); diff --git a/src/eth/storage/stratus_storage.rs b/src/eth/storage/stratus_storage.rs index d088be13e..20a373409 100644 --- a/src/eth/storage/stratus_storage.rs +++ b/src/eth/storage/stratus_storage.rs @@ -12,8 +12,9 @@ use crate::eth::executor::types::state::Complete; use crate::eth::genesis::GenesisConfig; use crate::eth::rpc::BlockFilter; use crate::eth::rpc::LogFilter; -use crate::eth::storage::ExecutionKind; +use crate::eth::storage::ExecutionContext; use crate::eth::storage::InMemoryTemporaryStorage; +use crate::eth::storage::Job; use crate::eth::storage::RocksPermanentStorage; use crate::eth::storage::StorageCache; use crate::eth::storage::StorageError; @@ -165,9 +166,9 @@ impl StratusStorage { } /// Generic read algorithm shared by [`read_account`] and [`read_slot`]. - fn read(&self, key: E::Key, kind: ExecutionKind) -> Result<(E, FoundAt), StorageError> { + fn read(&self, key: E::Key, context: ExecutionContext) -> Result<(E, FoundAt), StorageError> { let (value, found_at) = 'query: { - match E::resolve(self, key, kind) { + match E::resolve(self, key, context.at) { resolve_pending::Resolved::Temp(value) => break 'query (value, FoundAt::Temp), resolve_pending::Resolved::Miss(mined_point) => { let found_at = match &mined_point { @@ -199,28 +200,22 @@ impl StratusStorage { // This bug exists for any flow that updates the cache without holding the latest-state lock. We could consider // adding the latest block State to the cache too. Would requiring some locking but if we're smart // about it we can make it so this lock is more relaxed than the latest_state_lock. - if matches!( - (kind, found_at), - ( - ExecutionKind::CallLatest(_) | ExecutionKind::CallPast(_) | ExecutionKind::AccessList, - FoundAt::PermLatest - ) - ) { + if matches!((context.job, found_at), (Job::Call | Job::AccessList, FoundAt::PermLatest)) { E::cache_latest_if_missing(self, key, value.clone()); } Ok((value, found_at)) } - pub fn read_account(&self, address: Address, kind: ExecutionKind) -> Result<(Account, FoundAt), StorageError> { + pub fn read_account(&self, address: Address, context: ExecutionContext) -> Result<(Account, FoundAt), StorageError> { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("storage::read_account", %address).entered(); - self.read::(address, kind) + self.read::(address, context) } - pub fn read_slot(&self, address: Address, index: SlotIndex, kind: ExecutionKind) -> Result<(Slot, FoundAt), StorageError> { + pub fn read_slot(&self, address: Address, index: SlotIndex, context: ExecutionContext) -> Result<(Slot, FoundAt), StorageError> { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("storage::read_slot", %address, %index).entered(); - self.read::((address, index), kind) + self.read::((address, index), context) } // ------------------------------------------------------------------------- diff --git a/src/eth/types/execution_context.rs b/src/eth/types/execution_context.rs new file mode 100644 index 000000000..d15260ed3 --- /dev/null +++ b/src/eth/types/execution_context.rs @@ -0,0 +1,141 @@ +use crate::eth::rpc::BlockFilter; +use crate::eth::types::BlockNumber; +use crate::eth::types::PointInTime; + +/// What kind of work the EVM is executing. Drives the EVM policy (chain-id, +/// nonce and sender validation) and the execution metrics labels. +#[derive(Clone, Copy, serde::Serialize, PartialEq, Eq, Debug, Default)] +#[cfg_attr(test, derive(fake::Dummy))] +pub enum Job { + #[default] + Transaction, + Call, + /// Plain RPC state read (e.g. `eth_getBalance`), not an execution. + Rpc, + AccessList, +} + +impl Job { + pub fn is_transaction(&self) -> bool { + matches!(self, Job::Transaction) + } +} + +/// Which state the storage reads resolve to. +#[derive(Clone, Copy, serde::Serialize, PartialEq, Eq, Debug, Default)] +#[cfg_attr(test, derive(fake::Dummy))] +pub enum StateView { + /// State of the pending block being mined: temp reads first, latest state as fallback. + #[default] + Pending, + + /// State of the latest mined block. `Some(number)` keeps the requested block + /// number, so a mid-read staleness can downgrade it to [`StateView::Past`]. + Latest(Option), + + /// State at a specific mined block in the past. + Past(BlockNumber), +} + +impl From for StateView { + fn from(pit: PointInTime) -> Self { + match pit { + PointInTime::Pending => Self::Pending, + PointInTime::Latest => Self::Latest(None), + PointInTime::Past(number) => Self::Past(number), + } + } +} + +impl From<&StateView> for PointInTime { + fn from(view: &StateView) -> Self { + match view { + StateView::Pending => Self::Pending, + StateView::Latest(_) => Self::Latest, + StateView::Past(number) => Self::Past(*number), + } + } +} + +/// What is being executed ([`Job`]) and which state it reads ([`StateView`]). +#[derive(Clone, Copy, serde::Serialize, PartialEq, Eq, Debug, Default)] +#[cfg_attr(test, derive(fake::Dummy))] +pub struct ExecutionContext { + pub job: Job, + pub at: StateView, +} + +impl ExecutionContext { + /// Local transaction execution against the pending state. + pub fn transaction() -> Self { + Self { + job: Job::Transaction, + at: StateView::Pending, + } + } + + /// Read-only contract call against the given state view. + pub fn call(at: StateView) -> Self { + Self { job: Job::Call, at } + } + + /// Access-list creation against the latest state. + pub fn access_list() -> Self { + Self { + job: Job::AccessList, + at: StateView::Latest(None), + } + } + + /// Contract call at `pit`, with `block_number` as the requested block for pending/latest points. + pub fn call_from_pit(pit: PointInTime, block_number: BlockNumber) -> Self { + match pit { + PointInTime::Latest | PointInTime::Pending => Self::call(StateView::Latest(Some(block_number))), + PointInTime::Past(number) => Self::call(StateView::Past(number)), + } + } + + /// Plain RPC state read at the given point in time. + #[allow(non_snake_case)] + pub fn RPC(pit: PointInTime) -> Self { + Self { job: Job::Rpc, at: pit.into() } + } + + /// The point in time the state view resolves to. + pub fn point_in_time(&self) -> PointInTime { + (&self.at).into() + } + + /// Prometheus label value, matching the historical `ExecutionKind` AsRefStr strings. + pub fn metrics_label(&self) -> &'static str { + match (self.job, self.at) { + (Job::Transaction, _) => "transaction", + (Job::AccessList, _) => "access_list", + (Job::Rpc, StateView::Pending) => "rpc_pending", + (Job::Rpc, StateView::Latest(_)) => "rpc", + (Job::Rpc, StateView::Past(_)) => "rpc_past", + (Job::Call, StateView::Pending) => "call_pending", + (Job::Call, StateView::Latest(_)) => "call_latest", + (Job::Call, StateView::Past(_)) => "call_past", + } + } +} + +impl From for BlockFilter { + fn from(value: ExecutionContext) -> Self { + match value { + ExecutionContext { job: Job::AccessList, .. } => Self::Pending, + ExecutionContext { at: StateView::Pending, .. } => Self::Pending, + ExecutionContext { + at: StateView::Latest(None), .. + } => Self::Latest, + ExecutionContext { + at: StateView::Latest(Some(number)), + .. + } => Self::Number(number), + ExecutionContext { + at: StateView::Past(number), .. + } => Self::Number(number), + } + } +} diff --git a/src/eth/types/execution_kind.rs b/src/eth/types/execution_kind.rs deleted file mode 100644 index ceb166166..000000000 --- a/src/eth/types/execution_kind.rs +++ /dev/null @@ -1,51 +0,0 @@ -use derive_more::Debug; - -use crate::eth::rpc::BlockFilter; -use crate::eth::types::BlockNumber; -use crate::eth::types::PointInTime; - -#[derive(Clone, Copy, serde::Serialize, PartialEq, Default, Eq, Debug, strum::AsRefStr)] -#[strum(serialize_all = "snake_case")] -#[cfg_attr(test, derive(fake::Dummy))] -pub enum ExecutionKind { - CallLatest(BlockNumber), - CallPast(BlockNumber), - #[default] - Transaction, - RPC(PointInTime), - AccessList, -} - -impl ExecutionKind { - pub fn point_in_time(&self) -> PointInTime { - self.into() - } - - pub fn call_from_pit(pit: PointInTime, block_number: BlockNumber) -> Self { - match pit { - PointInTime::Latest | PointInTime::Pending => Self::CallLatest(block_number), - PointInTime::Past(number) => Self::CallPast(number), - } - } -} - -impl From<&ExecutionKind> for PointInTime { - fn from(value: &ExecutionKind) -> Self { - match value { - ExecutionKind::RPC(pit) => *pit, - ExecutionKind::Transaction => PointInTime::Pending, - ExecutionKind::CallPast(number) => PointInTime::Past(*number), - ExecutionKind::CallLatest(_) | ExecutionKind::AccessList => PointInTime::Latest, - } - } -} - -impl From for BlockFilter { - fn from(value: ExecutionKind) -> Self { - match value { - ExecutionKind::Transaction | ExecutionKind::RPC(PointInTime::Pending) | ExecutionKind::AccessList => crate::eth::rpc::BlockFilter::Pending, - ExecutionKind::CallLatest(block_number) | ExecutionKind::CallPast(block_number) => crate::eth::rpc::BlockFilter::Number(block_number), - ExecutionKind::RPC(pit) => pit.into(), - } - } -} diff --git a/src/eth/types/mod.rs b/src/eth/types/mod.rs index 170618781..e1ddd5579 100644 --- a/src/eth/types/mod.rs +++ b/src/eth/types/mod.rs @@ -1,6 +1,6 @@ pub mod block; mod error; -pub mod execution_kind; +pub mod execution_context; pub mod external; pub mod primitives; pub mod transaction; @@ -13,7 +13,9 @@ pub use error::ErrorCode; pub use error::StateError; pub use error::StratusError; pub use error::UnexpectedError; -pub use execution_kind::ExecutionKind; +pub use execution_context::ExecutionContext; +pub use execution_context::Job; +pub use execution_context::StateView; pub use external::ExternalBlock; pub use external::ExternalBlockWithReceipts; pub use external::ExternalReceipt; diff --git a/src/infra/metrics.rs b/src/infra/metrics.rs index 65ff79bcb..7debf8ff2 100644 --- a/src/infra/metrics.rs +++ b/src/infra/metrics.rs @@ -3,25 +3,25 @@ use stratus_metrics::ToMetricLabelValue; use stratus_metrics::dec_executor_workers_busy; use stratus_metrics::inc_executor_workers_busy; -use crate::eth::executor::EvmKind; +use crate::eth::executor::Lane; // ----------------------------------------------------------------------------- -// EvmKind metric labels +// Lane metric labels // ----------------------------------------------------------------------------- -impl ToMetricLabelValue for EvmKind { +impl ToMetricLabelValue for Lane { fn to_metric_label_value(&self) -> MetricLabelValue { (*self).into() } } -impl From for MetricLabelValue { - fn from(value: EvmKind) -> Self { +impl From for MetricLabelValue { + fn from(value: Lane) -> Self { let label = match value { - EvmKind::Transaction => "transaction", - EvmKind::CallPresent => "call_present", - EvmKind::CallPast => "call_past", - EvmKind::Inspect => "inspector", + Lane::Transaction => "transaction", + Lane::CallPresent => "call_present", + Lane::CallPast => "call_past", + Lane::Inspector => "inspector", }; Self::Some(label.to_owned()) } @@ -31,7 +31,7 @@ impl From for MetricLabelValue { // Executor pool busy workers gauge // ----------------------------------------------------------------------------- -impl EvmKind { +impl Lane { /// Marks a worker in the given executor pool as busy by atomically incrementing the `executor_workers_busy` gauge. /// Returns a guard that atomically decrements the gauge when dropped. pub fn mark_executor_pool_busy(&self) -> BusyGuard { @@ -45,7 +45,7 @@ impl EvmKind { } } -pub struct BusyGuard(EvmKind); +pub struct BusyGuard(Lane); impl Drop for BusyGuard { fn drop(&mut self) {