paradigmxyz / reth

Modular, contributor-friendly and blazing-fast implementation of the Ethereum protocol, in Rust
https://reth.rs/
Apache License 2.0
3.78k stars 1.02k forks source link

Add abstraction around `Evm` in `ConfigureEvm` #8672

Open tcoratger opened 3 months ago

tcoratger commented 3 months ago

Describe the feature

At the moment, ConfigureEvm trait

https://github.com/paradigmxyz/reth/blob/dd2113173e4408e0565c5edda439c1b15092ed8e/crates/evm/src/lib.rs#L26-L93

implements methods that return Evm which is a structure of revm implementation.

In order to be able to support other implementations of the EVM, like the one written in Cairo by Kakarot for example (https://github.com/kkrt-labs/kakarot) we would like to add a level of abstraction around the type of elements returned by ConfigureEvm. This would allow any EVM implementation to easily plug into reth and run with all other elements of the stack smoothly. As a first draft, we thought of the following two features that could be implemented and returned by the various methods of ConfigureEvm in order to add this missing level of abstraction:

/// Trait for executing and committing EVM transactions.
pub trait EvmExecutionAndState<DB: Database> {
  /// The associated context type that provides access to EVM environments and configurations.
    type Context: EvmContext;

    /// Execute the transaction.
    ///
    /// # Parameters
    /// - `tx`: The transaction to be executed.
    ///
    /// # Returns
    /// A result indicating success or failure of the transaction execution.
    fn transact(&mut self, tx: &TxEnv) -> EVMResult<DB::Error>;

    /// Commit the changes to the database.
    ///
    /// # Returns
    /// A result indicating success or failure of the commit operation.
    fn transact_commit(&mut self) -> Result<ExecutionResult, EVMError<DB::Error>>;

    /// Returns database and EVM environment with the chain spec ID.
    ///
    /// # Returns
    /// A tuple containing the database and EVM environment.
    fn into_db_and_env_with_handler_cfg(&self) -> (Database, EvmEnv);
}
/// Trait for managing the EVM context.
pub trait EvmContext {
    /// Returns a reference to the EVM configuration environment.
    ///
    /// # Returns
    /// A reference to the `CfgEnv` struct.
    fn cfg(&self) -> &CfgEnv;

    /// Returns a mutable reference to the EVM configuration environment.
    ///
    /// # Returns
    /// A mutable reference to the `CfgEnv` struct.
    fn cfg_mut(&mut self) -> &mut CfgEnv;

    /// Returns a reference to the transaction environment.
    ///
    /// # Returns
    /// A reference to the `TxEnv` struct.
    fn tx(&self) -> &TxEnv;

    /// Returns a mutable reference to the transaction environment.
    ///
    /// # Returns
    /// A mutable reference to the `TxEnv` struct.
    fn tx_mut(&mut self) -> &mut TxEnv;

    /// Returns a reference to the block environment.
    ///
    /// # Returns
    /// A reference to the `BlockEnv` struct.
    fn block(&self) -> &BlockEnv;

    /// Returns a mutable reference to the block environment.
    ///
    /// # Returns
    /// A mutable reference to the `BlockEnv` struct.
    fn block_mut(&mut self) -> &mut BlockEnv;

    /// Returns the specification (hardfork) that the EVM is instanced with.
    ///
    /// # Returns
    /// The current `SpecId` of the EVM.
    fn spec_id(&self) -> SpecId;

    /// Sets the specification (hardfork) for the EVM instance.
    ///
    /// # Parameters
    /// - `spec_id`: The desired `SpecId` to set.
    fn with_spec_id(&mut self, spec_id: SpecId);
}

cc @greged93 @mattsse

Additional context

No response

mattsse commented 3 months ago

this issue is mostly solved for execution via:

https://github.com/paradigmxyz/reth/blob/c874374be659b50ab01cf6749b2d523dcb3271ba/crates/evm/src/execute.rs#L164-L164

which does not require any evm

but we still have it in rpc.

if we look at the most common usecase, eth_call/tracing then this follows this pattern:

https://github.com/paradigmxyz/reth/blob/c874374be659b50ab01cf6749b2d523dcb3271ba/crates/rpc/rpc/src/eth/api/transactions.rs#L552-L565

so ideally transact and inspect would be part of the high level interface of the type responsible for this.

there's perhaps a use case to do a tiered abstraction:

greged93 commented 3 months ago

@mattsse Would you agree with the following changes:

mattsse commented 3 months ago

Should EvmExecutionAndState be an associated type

I believe so

Update EvmContext to EvmConfig, which returns the current context (including any external context which we don't have for now).

I think that makes sense.

I'd try to model this from the transact/execute pov, for example executing a transaction generally works as follows:

  1. caller provides the state (revm::Database)
  2. caller provides execution input: Transaction + height (Header)
  3. caller modifies certain config settings like gas limit (this is only needed for the eth_call usecase, see https://github.com/paradigmxyz/reth/blob/70b6bc45ad9c61ef1e64697d59fb975169df2df4/crates/rpc/rpc/src/eth/revm_utils.rs#L127-L132)
  4. calls transact and gets execution output in return
  5. optionally commit execution output to state (1.)

so a model for this could look smth like:

trait Executor {
  type Input;
  type Output;

  type Config; // though this could be part of input
}

this is similar what we have here https://github.com/paradigmxyz/reth/blob/70b6bc45ad9c61ef1e64697d59fb975169df2df4/crates/evm/src/execute.rs#L11-L15