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: abstract over Evm::Error #14085

Merged
merged 9 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions Cargo.lock

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

7 changes: 3 additions & 4 deletions crates/engine/util/src/reorg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use reth_errors::{BlockExecutionError, BlockValidationError, RethError, RethResu
use reth_ethereum_forks::EthereumHardforks;
use reth_evm::{
state_change::post_block_withdrawals_balance_increments, system_calls::SystemCaller,
ConfigureEvm, Evm,
ConfigureEvm, Evm, EvmError,
};
use reth_payload_primitives::EngineApiMessageVersion;
use reth_payload_validator::ExecutionPayloadValidator;
Expand All @@ -29,7 +29,6 @@ use reth_revm::{
DatabaseCommit,
};
use reth_rpc_types_compat::engine::payload::block_to_payload;
use revm_primitives::EVMError;
use std::{
collections::VecDeque,
future::Future,
Expand Down Expand Up @@ -328,8 +327,8 @@ where
let tx_env = evm_config.tx_env(&tx_recovered, tx_recovered.signer());
let exec_result = match evm.transact(tx_env) {
Ok(result) => result,
error @ Err(EVMError::Transaction(_) | EVMError::Header(_)) => {
trace!(target: "engine::stream::reorg", hash = %tx.tx_hash(), ?error, "Error executing transaction from next block");
Err(err) if err.is_invalid_tx_err() => {
trace!(target: "engine::stream::reorg", hash = %tx.tx_hash(), ?err, "Error executing transaction from next block");
continue
}
// Treat error as fatal
Expand Down
23 changes: 8 additions & 15 deletions crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,22 @@ use crate::{
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{BlockHeader, Transaction};
use alloy_eips::{eip6110, eip7685::Requests};
use core::fmt::Display;
use reth_chainspec::{ChainSpec, EthereumHardfork, EthereumHardforks, MAINNET};
use reth_consensus::ConsensusError;
use reth_ethereum_consensus::validate_block_post_execution;
use reth_evm::{
execute::{
balance_increment_state, BasicBlockExecutorProvider, BlockExecutionError,
BlockExecutionStrategy, BlockExecutionStrategyFactory, BlockValidationError, ExecuteOutput,
ProviderError,
},
state_change::post_block_balance_increments,
system_calls::{OnStateHook, SystemCaller},
ConfigureEvm, Evm,
ConfigureEvm, Database, Evm,
};
use reth_primitives::{EthPrimitives, Receipt, RecoveredBlock};
use reth_primitives_traits::{BlockBody, SignedTransaction};
use reth_revm::db::State;
use revm_primitives::{
db::{Database, DatabaseCommit},
ResultAndState,
};
use revm_primitives::{db::DatabaseCommit, ResultAndState};

/// Factory for [`EthExecutionStrategy`].
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -71,12 +66,11 @@ where
{
type Primitives = EthPrimitives;

type Strategy<DB: Database<Error: Into<ProviderError> + Display>> =
EthExecutionStrategy<DB, EvmConfig>;
type Strategy<DB: Database> = EthExecutionStrategy<DB, EvmConfig>;
Comment on lines -75 to +69
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so much butter indeed


fn create_strategy<DB>(&self, db: DB) -> Self::Strategy<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
{
let state =
State::builder().with_database(db).with_bundle_update().without_state_clear().build();
Expand Down Expand Up @@ -113,7 +107,7 @@ where

impl<DB, EvmConfig> BlockExecutionStrategy for EthExecutionStrategy<DB, EvmConfig>
where
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
EvmConfig: ConfigureEvm<
Header = alloy_consensus::Header,
Transaction = reth_primitives::TransactionSigned,
Expand Down Expand Up @@ -163,11 +157,10 @@ where

// Execute transaction.
let result_and_state = evm.transact(tx_env).map_err(move |err| {
let new_err = err.map_db_err(|e| e.into());
// Ensure hash is calculated for error log, if not already done
BlockValidationError::EVM {
hash: transaction.recalculate_hash(),
error: Box::new(new_err),
error: Box::new(err),
}
})?;
self.system_caller.on_state(&result_and_state.state);
Expand Down Expand Up @@ -307,7 +300,7 @@ mod tests {
use reth_primitives::{Account, Block, BlockBody, Transaction};
use reth_primitives_traits::{crypto::secp256k1::public_key_to_address, Block as _};
use reth_revm::{
database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState,
database::StateProviderDatabase, test_utils::StateProviderTest, Database, TransitionState,
};
use reth_testing_utils::generators::{self, sign_tx_with_key_pair};
use revm_primitives::{address, EvmState, BLOCKHASH_SERVE_WINDOW};
Expand Down Expand Up @@ -393,7 +386,7 @@ mod tests {
);

assert!(matches!(
err.as_validation().unwrap().clone(),
err.as_validation().unwrap(),
BlockValidationError::MissingParentBeaconBlockRoot
));

Expand Down
5 changes: 3 additions & 2 deletions crates/ethereum/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ use alloy_consensus::{BlockHeader, Header};
use alloy_primitives::{Address, U256};
use core::{convert::Infallible, fmt::Debug};
use reth_chainspec::ChainSpec;
use reth_evm::{env::EvmEnv, ConfigureEvm, ConfigureEvmEnv, Evm, NextBlockEnvAttributes};
use reth_evm::{env::EvmEnv, ConfigureEvm, ConfigureEvmEnv, Database, Evm, NextBlockEnvAttributes};
use reth_primitives::TransactionSigned;
use reth_primitives_traits::transaction::execute::FillTxEnv;
use reth_revm::{inspector_handle_register, Database, EvmBuilder};
use reth_revm::{inspector_handle_register, EvmBuilder};
use revm_primitives::{
AnalysisKind, BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, CfgEnvWithHandlerCfg, EVMError,
HandlerCfg, ResultAndState, SpecId, TxEnv, TxKind,
Expand Down Expand Up @@ -237,6 +237,7 @@ impl ConfigureEvmEnv for EthEvmConfig {

impl ConfigureEvm for EthEvmConfig {
type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>;
type EvmError<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;

fn evm_with_env<DB: Database>(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> {
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {
Expand Down
44 changes: 20 additions & 24 deletions crates/ethereum/payload/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use reth_basic_payload_builder::{
use reth_chainspec::{ChainSpec, ChainSpecProvider};
use reth_errors::RethError;
use reth_evm::{
env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, Evm, NextBlockEnvAttributes,
env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, Evm, EvmError, InvalidTxError,
NextBlockEnvAttributes,
};
use reth_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig};
use reth_execution_types::ExecutionOutcome;
Expand All @@ -44,7 +45,7 @@ use reth_transaction_pool::{
};
use revm::{
db::{states::bundle_state::BundleRetention, State},
primitives::{EVMError, InvalidTransaction, ResultAndState},
primitives::ResultAndState,
DatabaseCommit,
};
use std::sync::Arc;
Expand Down Expand Up @@ -276,30 +277,25 @@ where
let ResultAndState { result, state } = match evm.transact(tx_env) {
Ok(res) => res,
Err(err) => {
match err {
EVMError::Transaction(err) => {
if matches!(err, InvalidTransaction::NonceTooLow { .. }) {
// if the nonce is too low, we can skip this transaction
trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction");
} else {
// if the transaction is invalid, we can skip it and all of its
// descendants
trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants");
best_txs.mark_invalid(
&pool_tx,
InvalidPoolTransactionError::Consensus(
InvalidTransactionError::TxTypeNotSupported,
),
);
}

continue
}
err => {
// this is an error that we should treat as fatal for this attempt
return Err(PayloadBuilderError::EvmExecutionError(err))
if let Some(err) = err.as_invalid_tx_err() {
if err.is_nonce_too_low() {
// if the nonce is too low, we can skip this transaction
trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction");
} else {
// if the transaction is invalid, we can skip it and all of its
// descendants
trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants");
best_txs.mark_invalid(
&pool_tx,
InvalidPoolTransactionError::Consensus(
InvalidTransactionError::TxTypeNotSupported,
),
);
}
continue
}
// this is an error that we should treat as fatal for this attempt
return Err(PayloadBuilderError::evm(err))
}
};

Expand Down
2 changes: 0 additions & 2 deletions crates/evm/execution-errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ reth-prune-types.workspace = true
alloy-primitives.workspace = true
alloy-rlp.workspace = true
alloy-eips.workspace = true
revm-primitives.workspace = true
nybbles.workspace = true

thiserror.workspace = true
Expand All @@ -30,7 +29,6 @@ std = [
"reth-consensus/std",
"alloy-eips/std",
"alloy-primitives/std",
"revm-primitives/std",
"alloy-rlp/std",
"thiserror/std",
"nybbles/std",
Expand Down
5 changes: 2 additions & 3 deletions crates/evm/execution-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@ use alloy_primitives::B256;
use reth_consensus::ConsensusError;
use reth_prune_types::PruneSegmentError;
use reth_storage_errors::provider::ProviderError;
use revm_primitives::EVMError;
use thiserror::Error;

pub mod trie;
pub use trie::*;

/// Transaction validation errors
#[derive(Error, Clone, Debug)]
#[derive(Error, Debug)]
pub enum BlockValidationError {
/// EVM error with transaction hash and message
#[error("EVM reported invalid transaction ({hash}): {error}")]
EVM {
/// The hash of the transaction
hash: B256,
/// The EVM error.
error: Box<EVMError<ProviderError>>,
error: Box<dyn core::error::Error + Send + Sync>,
},
/// Error when recovering the sender for a transaction
#[error("failed to recover sender for transaction")]
Expand Down
19 changes: 7 additions & 12 deletions crates/evm/src/either.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
//! Helper type that represents one of two possible executor types

use core::fmt::Display;

use crate::{
execute::{BatchExecutor, BlockExecutorProvider, Executor},
system_calls::OnStateHook,
Database,
};
use reth_storage_errors::provider::ProviderError;
use revm_primitives::db::Database;

// re-export Either
pub use futures_util::future::Either;
Expand All @@ -20,15 +17,13 @@ where
{
type Primitives = A::Primitives;

type Executor<DB: Database<Error: Into<ProviderError> + Display>> =
Either<A::Executor<DB>, B::Executor<DB>>;
type Executor<DB: Database> = Either<A::Executor<DB>, B::Executor<DB>>;

type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> =
Either<A::BatchExecutor<DB>, B::BatchExecutor<DB>>;
type BatchExecutor<DB: Database> = Either<A::BatchExecutor<DB>, B::BatchExecutor<DB>>;

fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
{
match self {
Self::Left(a) => Either::Left(a.executor(db)),
Expand All @@ -38,7 +33,7 @@ where

fn batch_executor<DB>(&self, db: DB) -> Self::BatchExecutor<DB>
where
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
{
match self {
Self::Left(a) => Either::Left(a.batch_executor(db)),
Expand All @@ -51,7 +46,7 @@ impl<A, B, DB> Executor<DB> for Either<A, B>
where
A: Executor<DB>,
B: for<'a> Executor<DB, Input<'a> = A::Input<'a>, Output = A::Output, Error = A::Error>,
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
{
type Input<'a> = A::Input<'a>;
type Output = A::Output;
Expand Down Expand Up @@ -97,7 +92,7 @@ impl<A, B, DB> BatchExecutor<DB> for Either<A, B>
where
A: BatchExecutor<DB>,
B: for<'a> BatchExecutor<DB, Input<'a> = A::Input<'a>, Output = A::Output, Error = A::Error>,
DB: Database<Error: Into<ProviderError> + Display>,
DB: Database,
{
type Input<'a> = A::Input<'a>;
type Output = A::Output;
Expand Down
49 changes: 49 additions & 0 deletions crates/evm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use revm_primitives::{EVMError, InvalidTransaction};

/// Abstraction over transaction validation error.
pub trait InvalidTxError: core::error::Error + Send + Sync + 'static {
/// Returns whether the error cause by transaction having a nonce lower than expected.
fn is_nonce_too_low(&self) -> bool;
}

impl InvalidTxError for InvalidTransaction {
fn is_nonce_too_low(&self) -> bool {
matches!(self, Self::NonceTooLow { .. })
}
}

/// Abstraction over errors that can occur during EVM execution.
///
/// It's assumed that errors can occur either because of an invalid transaction, meaning that other
/// transaction might still result in successful execution, or because of a general EVM
/// misconfiguration.
///
/// If caller occurs a error different from [`EvmError::InvalidTransaction`], it should most likely
/// be treated as fatal error flagging some EVM misconfiguration.
pub trait EvmError: core::error::Error + Send + Sync + 'static {
/// Errors which might occur as a result of an invalid transaction. i.e unrelated to general EVM
/// configuration.
type InvalidTransaction: InvalidTxError;

/// Returns the [`EvmError::InvalidTransaction`] if the error is an invalid transaction error.
fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction>;

/// Returns `true` if the error is an invalid transaction error.
fn is_invalid_tx_err(&self) -> bool {
self.as_invalid_tx_err().is_some()
}
}

impl<DBError> EvmError for EVMError<DBError>
where
DBError: core::error::Error + Send + Sync + 'static,
{
type InvalidTransaction = InvalidTransaction;

fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction> {
match self {
Self::Transaction(err) => Some(err),
_ => None,
}
}
}
Loading
Loading