From 067b0ff420882b31b00629936071ccd11f19e775 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 3 May 2024 13:39:46 +0200 Subject: [PATCH] feat: integrate ExecutorProvider (#7798) --- Cargo.lock | 16 +- bin/reth/Cargo.toml | 1 + .../src/commands/debug_cmd/build_block.rs | 41 +- bin/reth/src/commands/debug_cmd/execution.rs | 9 +- .../commands/debug_cmd/in_memory_merkle.rs | 41 +- bin/reth/src/commands/debug_cmd/merkle.rs | 7 +- .../src/commands/debug_cmd/replay_engine.rs | 23 +- bin/reth/src/commands/import.rs | 9 +- bin/reth/src/commands/stage/dump/execution.rs | 19 +- bin/reth/src/commands/stage/dump/merkle.rs | 7 +- bin/reth/src/commands/stage/run.rs | 9 +- bin/reth/src/commands/stage/unwind.rs | 26 +- bin/reth/src/lib.rs | 1 + bin/reth/src/macros.rs | 20 + crates/blockchain-tree/Cargo.toml | 3 + crates/blockchain-tree/src/blockchain_tree.rs | 35 +- crates/blockchain-tree/src/chain.rs | 44 +- crates/blockchain-tree/src/externals.rs | 10 +- crates/blockchain-tree/src/shareable.rs | 27 +- crates/consensus/auto-seal/src/lib.rs | 204 ++--- crates/consensus/auto-seal/src/task.rs | 24 +- crates/consensus/beacon/Cargo.toml | 1 + .../consensus/beacon/src/engine/test_utils.rs | 47 +- crates/ethereum/evm/Cargo.toml | 1 - crates/ethereum/evm/src/execute.rs | 99 +- crates/ethereum/evm/src/lib.rs | 1 + crates/ethereum/evm/src/verify.rs | 53 ++ crates/evm/Cargo.toml | 8 + crates/evm/src/either.rs | 119 +++ crates/evm/src/execute.rs | 142 ++- crates/evm/src/lib.rs | 5 + crates/evm/src/test_utils.rs | 80 ++ crates/exex/src/context.rs | 13 +- crates/node-ethereum/src/evm.rs | 2 + crates/node-ethereum/src/lib.rs | 2 +- crates/node-ethereum/src/node.rs | 14 +- crates/node/api/src/node.rs | 7 + crates/node/builder/Cargo.toml | 1 + crates/node/builder/src/builder/mod.rs | 23 + crates/node/builder/src/builder/states.rs | 5 + crates/node/builder/src/components/builder.rs | 14 +- crates/node/builder/src/components/execute.rs | 19 +- crates/node/builder/src/components/mod.rs | 23 +- crates/node/builder/src/launch/mod.rs | 9 +- crates/node/builder/src/setup.rs | 37 +- crates/optimism/evm/Cargo.toml | 1 + crates/optimism/evm/src/execute.rs | 107 +-- .../mod.rs => optimism/evm/src/l1.rs} | 23 +- crates/optimism/evm/src/lib.rs | 4 + crates/optimism/evm/src/verify.rs | 58 ++ crates/optimism/node/src/node.rs | 15 +- crates/optimism/node/src/txpool.rs | 5 +- crates/payload/optimism/Cargo.toml | 2 + crates/payload/optimism/src/builder.rs | 2 +- crates/revm/Cargo.toml | 5 +- crates/revm/src/factory.rs | 56 -- crates/revm/src/lib.rs | 13 - crates/revm/src/optimism/processor.rs | 401 -------- crates/revm/src/processor.rs | 865 ------------------ crates/rpc/rpc-builder/Cargo.toml | 3 +- crates/rpc/rpc-builder/tests/it/auth.rs | 2 +- crates/rpc/rpc-builder/tests/it/utils.rs | 3 +- crates/rpc/rpc/Cargo.toml | 4 + crates/rpc/rpc/src/eth/api/block.rs | 2 +- crates/rpc/rpc/src/eth/api/transactions.rs | 26 +- crates/stages/Cargo.toml | 2 + crates/stages/src/lib.rs | 24 +- crates/stages/src/sets.rs | 72 +- crates/stages/src/stages/execution.rs | 60 +- crates/stages/src/stages/mod.rs | 10 +- .../bundle_state_with_receipts.rs | 17 + examples/custom-evm/src/main.rs | 15 +- testing/ef-tests/Cargo.toml | 6 +- testing/ef-tests/src/cases/blockchain_test.rs | 10 +- 74 files changed, 1087 insertions(+), 2027 deletions(-) create mode 100644 bin/reth/src/macros.rs create mode 100644 crates/ethereum/evm/src/verify.rs create mode 100644 crates/evm/src/either.rs create mode 100644 crates/evm/src/test_utils.rs rename crates/{revm/src/optimism/mod.rs => optimism/evm/src/l1.rs} (97%) create mode 100644 crates/optimism/evm/src/verify.rs delete mode 100644 crates/revm/src/factory.rs delete mode 100644 crates/revm/src/optimism/processor.rs delete mode 100644 crates/revm/src/processor.rs diff --git a/Cargo.lock b/Cargo.lock index 24b07b8ab3..b2e179a955 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2670,8 +2670,8 @@ dependencies = [ "alloy-rlp", "rayon", "reth-db", + "reth-evm-ethereum", "reth-interfaces", - "reth-node-ethereum", "reth-primitives", "reth-provider", "reth-revm", @@ -6393,6 +6393,7 @@ dependencies = [ "reth-discv4", "reth-downloaders", "reth-ethereum-payload-builder", + "reth-evm", "reth-exex", "reth-interfaces", "reth-network", @@ -6490,6 +6491,7 @@ dependencies = [ "reth-downloaders", "reth-engine-primitives", "reth-ethereum-engine-primitives", + "reth-evm", "reth-evm-ethereum", "reth-interfaces", "reth-metrics", @@ -6537,6 +6539,7 @@ dependencies = [ "parking_lot 0.12.2", "reth-consensus", "reth-db", + "reth-evm", "reth-evm-ethereum", "reth-interfaces", "reth-metrics", @@ -6954,6 +6957,8 @@ dependencies = [ name = "reth-evm" version = "0.2.0-beta.6" dependencies = [ + "futures-util", + "parking_lot 0.12.2", "reth-interfaces", "reth-primitives", "revm", @@ -6967,7 +6972,6 @@ dependencies = [ "reth-evm", "reth-interfaces", "reth-primitives", - "reth-provider", "reth-revm", "revm-primitives", "tracing", @@ -6982,6 +6986,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-revm", + "revm", "revm-primitives", "tracing", ] @@ -7262,6 +7267,7 @@ dependencies = [ "reth-consensus", "reth-db", "reth-downloaders", + "reth-evm", "reth-exex", "reth-interfaces", "reth-network", @@ -7457,6 +7463,7 @@ dependencies = [ "reth-basic-payload-builder", "reth-engine-primitives", "reth-evm", + "reth-evm-optimism", "reth-payload-builder", "reth-primitives", "reth-provider", @@ -7645,6 +7652,7 @@ dependencies = [ "reth-consensus-common", "reth-evm", "reth-evm-ethereum", + "reth-evm-optimism", "reth-interfaces", "reth-metrics", "reth-network-api", @@ -7711,12 +7719,13 @@ dependencies = [ "pin-project", "reth-beacon-consensus", "reth-engine-primitives", + "reth-ethereum-engine-primitives", "reth-evm", + "reth-evm-ethereum", "reth-interfaces", "reth-ipc", "reth-metrics", "reth-network-api", - "reth-node-ethereum", "reth-payload-builder", "reth-primitives", "reth-provider", @@ -7826,6 +7835,7 @@ dependencies = [ "reth-db", "reth-downloaders", "reth-etl", + "reth-evm", "reth-evm-ethereum", "reth-exex", "reth-interfaces", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index c323017d0a..3f5d788347 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -19,6 +19,7 @@ reth-primitives = { workspace = true, features = ["arbitrary", "clap"] } reth-db = { workspace = true, features = ["mdbx"] } reth-exex.workspace = true reth-provider = { workspace = true } +reth-evm.workspace = true reth-revm.workspace = true reth-stages.workspace = true reth-interfaces = { workspace = true, features = ["clap"] } diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 9d5942ae15..22361aada5 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -6,6 +6,7 @@ use crate::{ DatabaseArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, }; use alloy_rlp::Decodable; use clap::Parser; @@ -20,10 +21,9 @@ use reth_blockchain_tree::{ use reth_cli_runner::CliContext; use reth_consensus::Consensus; use reth_db::{init_db, DatabaseEnv}; +use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; use reth_interfaces::RethResult; use reth_node_api::PayloadBuilderAttributes; -#[cfg(not(feature = "optimism"))] -use reth_node_ethereum::EthEvmConfig; use reth_payload_builder::database::CachedReads; use reth_primitives::{ constants::eip4844::{LoadKzgSettingsError, MAINNET_KZG_TRUSTED_SETUP}, @@ -31,13 +31,14 @@ use reth_primitives::{ revm_primitives::KzgSettings, stage::StageId, Address, BlobTransaction, BlobTransactionSidecar, Bytes, ChainSpec, PooledTransactionsElement, - SealedBlock, SealedBlockWithSenders, Transaction, TransactionSigned, TxEip4844, B256, U256, + Receipts, SealedBlock, SealedBlockWithSenders, Transaction, TransactionSigned, TxEip4844, B256, + U256, }; use reth_provider::{ - providers::BlockchainProvider, BlockHashReader, BlockReader, BlockWriter, ExecutorFactory, - ProviderFactory, StageCheckpointReader, StateProviderFactory, + providers::BlockchainProvider, BlockHashReader, BlockReader, BlockWriter, + BundleStateWithReceipts, ProviderFactory, StageCheckpointReader, StateProviderFactory, }; -use reth_revm::EvmProcessorFactory; +use reth_revm::database::StateProviderDatabase; #[cfg(feature = "optimism")] use reth_rpc_types::engine::OptimismPayloadAttributes; use reth_rpc_types::engine::{BlobsBundleV1, PayloadAttributes}; @@ -161,18 +162,11 @@ impl Command { let consensus: Arc = Arc::new(BeaconConsensus::new(Arc::clone(&self.chain))); - #[cfg(feature = "optimism")] - let evm_config = reth_node_optimism::OptimismEvmConfig::default(); - - #[cfg(not(feature = "optimism"))] - let evm_config = EthEvmConfig::default(); + let executor = block_executor!(self.chain.clone()); // configure blockchain tree - let tree_externals = TreeExternals::new( - provider_factory.clone(), - Arc::clone(&consensus), - EvmProcessorFactory::new(self.chain.clone(), evm_config), - ); + let tree_externals = + TreeExternals::new(provider_factory.clone(), Arc::clone(&consensus), executor); let tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default(), None)?; let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); @@ -309,11 +303,16 @@ impl Command { let block_with_senders = SealedBlockWithSenders::new(block.clone(), senders).unwrap(); - let executor_factory = EvmProcessorFactory::new(self.chain.clone(), evm_config); - let mut executor = executor_factory.with_state(blockchain_db.latest()?); - executor - .execute_and_verify_receipt(&block_with_senders.clone().unseal(), U256::MAX)?; - let state = executor.take_output_state(); + let db = StateProviderDatabase::new(blockchain_db.latest()?); + let executor = block_executor!(self.chain.clone()).executor(db); + + let BlockExecutionOutput { state, receipts, .. } = + executor.execute((&block_with_senders.clone().unseal(), U256::MAX).into())?; + let state = BundleStateWithReceipts::new( + state, + Receipts::from_block_receipt(receipts), + block.number, + ); debug!(target: "reth::cli", ?state, "Executed block"); let hashed_state = state.hash_state_slow(); diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index a83ea19fde..33b07368a4 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -7,6 +7,7 @@ use crate::{ DatabaseArgs, NetworkArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, utils::get_single_header, }; use clap::Parser; @@ -25,7 +26,6 @@ use reth_interfaces::p2p::{bodies::client::BodiesClient, headers::client::Header use reth_network::{NetworkEvents, NetworkHandle}; use reth_network_api::NetworkInfo; use reth_node_core::init::init_genesis; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::{ fs, stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, PruneModes, B256, }; @@ -111,8 +111,7 @@ impl Command { let stage_conf = &config.stages; let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let factory = - reth_revm::EvmProcessorFactory::new(self.chain.clone(), EthEvmConfig::default()); + let executor = block_executor!(self.chain.clone()); let header_mode = HeaderSyncMode::Tip(tip_rx); let pipeline = Pipeline::builder() @@ -124,14 +123,14 @@ impl Command { Arc::clone(&consensus), header_downloader, body_downloader, - factory.clone(), + executor.clone(), stage_conf.etl.clone(), ) .set(SenderRecoveryStage { commit_threshold: stage_conf.sender_recovery.commit_threshold, }) .set(ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: None, max_changes: None, diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index e68231a768..008530c531 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -7,6 +7,7 @@ use crate::{ DatabaseArgs, NetworkArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, utils::{get_single_body, get_single_header}, }; use backon::{ConstantBuilder, Retryable}; @@ -14,16 +15,17 @@ use clap::Parser; use reth_cli_runner::CliContext; use reth_config::Config; use reth_db::{init_db, DatabaseEnv}; +use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; use reth_interfaces::executor::BlockValidationError; use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; -use reth_node_ethereum::EthEvmConfig; -use reth_primitives::{fs, stage::StageId, BlockHashOrNumber, ChainSpec}; +use reth_primitives::{fs, stage::StageId, BlockHashOrNumber, ChainSpec, Receipts}; use reth_provider::{ - AccountExtReader, ExecutorFactory, HashingWriter, HeaderProvider, LatestStateProviderRef, - OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StaticFileProviderFactory, - StorageReader, + AccountExtReader, BundleStateWithReceipts, HashingWriter, HeaderProvider, + LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, + StaticFileProviderFactory, StorageReader, }; +use reth_revm::database::StateProviderDatabase; use reth_tasks::TaskExecutor; use reth_trie::{updates::TrieKey, StateRoot}; use std::{net::SocketAddr, path::PathBuf, sync::Arc}; @@ -162,24 +164,31 @@ impl Command { ) .await?; - let executor_factory = - reth_revm::EvmProcessorFactory::new(self.chain.clone(), EthEvmConfig::default()); - let mut executor = executor_factory.with_state(LatestStateProviderRef::new( + let db = StateProviderDatabase::new(LatestStateProviderRef::new( provider.tx_ref(), factory.static_file_provider(), )); + let executor = block_executor!(self.chain.clone()).executor(db); + let merkle_block_td = provider.header_td_by_number(merkle_block_number)?.unwrap_or_default(); - executor.execute_and_verify_receipt( - &block - .clone() - .unseal() - .with_recovered_senders() - .ok_or(BlockValidationError::SenderRecoveryError)?, - merkle_block_td + block.difficulty, + let BlockExecutionOutput { state, receipts, .. } = executor.execute( + ( + &block + .clone() + .unseal() + .with_recovered_senders() + .ok_or(BlockValidationError::SenderRecoveryError)?, + merkle_block_td + block.difficulty, + ) + .into(), )?; - let block_state = executor.take_output_state(); + let block_state = BundleStateWithReceipts::new( + state, + Receipts::from_block_receipt(receipts), + block.number, + ); // Unpacked `BundleState::state_root_slow` function let (in_memory_state_root, in_memory_updates) = diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 07075ff267..6d895fccf4 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -7,6 +7,7 @@ use crate::{ DatabaseArgs, NetworkArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, utils::get_single_header, }; use backon::{ConstantBuilder, Retryable}; @@ -20,7 +21,6 @@ use reth_exex::ExExManagerHandle; use reth_interfaces::p2p::full_block::FullBlockClient; use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::{ fs, stage::{StageCheckpoint, StageId}, @@ -201,10 +201,9 @@ impl Command { checkpoint.stage_checkpoint.is_some() }); - let factory = - reth_revm::EvmProcessorFactory::new(self.chain.clone(), EthEvmConfig::default()); + let executor = block_executor!(self.chain.clone()); let mut execution_stage = ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: Some(1), max_changes: None, diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 947c127452..da2e458be2 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -5,6 +5,7 @@ use crate::{ DatabaseArgs, NetworkArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, }; use clap::Parser; use eyre::Context; @@ -20,15 +21,12 @@ use reth_db::{init_db, DatabaseEnv}; use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_node_core::engine::engine_store::{EngineMessageStore, StoredEngineApiMessage}; -#[cfg(not(feature = "optimism"))] -use reth_node_ethereum::{EthEngineTypes, EthEvmConfig}; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_primitives::{fs, ChainSpec, PruneModes}; use reth_provider::{ providers::BlockchainProvider, CanonStateSubscriptions, ProviderFactory, StaticFileProviderFactory, }; -use reth_revm::EvmProcessorFactory; use reth_stages::Pipeline; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; @@ -126,18 +124,11 @@ impl Command { let consensus: Arc = Arc::new(BeaconConsensus::new(Arc::clone(&self.chain))); - #[cfg(not(feature = "optimism"))] - let evm_config = EthEvmConfig::default(); - - #[cfg(feature = "optimism")] - let evm_config = reth_node_optimism::OptimismEvmConfig::default(); + let executor = block_executor!(self.chain.clone()); // Configure blockchain tree - let tree_externals = TreeExternals::new( - provider_factory.clone(), - Arc::clone(&consensus), - EvmProcessorFactory::new(self.chain.clone(), evm_config), - ); + let tree_externals = + TreeExternals::new(provider_factory.clone(), Arc::clone(&consensus), executor); let tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default(), None)?; let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); @@ -184,8 +175,10 @@ impl Command { ) = PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream()); #[cfg(not(feature = "optimism"))] - let (payload_service, payload_builder): (_, PayloadBuilderHandle) = - PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream()); + let (payload_service, payload_builder): ( + _, + PayloadBuilderHandle, + ) = PayloadBuilderService::new(payload_generator, blockchain_db.canonical_state_stream()); ctx.task_executor.spawn_critical("payload builder service", payload_service); diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index 3c191d8bbe..0d5b242751 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -6,6 +6,7 @@ use crate::{ DatabaseArgs, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, version::SHORT_VERSION, }; use clap::Parser; @@ -26,7 +27,6 @@ use reth_interfaces::p2p::{ headers::downloader::{HeaderDownloader, SyncTarget}, }; use reth_node_core::init::init_genesis; -use reth_node_ethereum::EthEvmConfig; use reth_node_events::node::NodeEvent; use reth_primitives::{stage::StageId, ChainSpec, PruneModes, B256}; use reth_provider::{ @@ -269,8 +269,7 @@ where .expect("failed to set download range"); let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let factory = - reth_revm::EvmProcessorFactory::new(provider_factory.chain_spec(), EthEvmConfig::default()); + let executor = block_executor!(provider_factory.chain_spec()); let max_block = file_client.max_block().unwrap_or(0); @@ -285,14 +284,14 @@ where consensus.clone(), header_downloader, body_downloader, - factory.clone(), + executor.clone(), config.stages.etl.clone(), ) .set(SenderRecoveryStage { commit_threshold: config.stages.sender_recovery.commit_threshold, }) .set(ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: config.stages.execution.max_blocks, max_changes: config.stages.execution.max_changes, diff --git a/bin/reth/src/commands/stage/dump/execution.rs b/bin/reth/src/commands/stage/dump/execution.rs index 571ce486a6..d8f12b50af 100644 --- a/bin/reth/src/commands/stage/dump/execution.rs +++ b/bin/reth/src/commands/stage/dump/execution.rs @@ -1,15 +1,12 @@ use super::setup; -use crate::utils::DbTool; -use eyre::Result; +use crate::{macros::block_executor, utils::DbTool}; use reth_db::{ cursor::DbCursorRO, database::Database, table::TableImporter, tables, transaction::DbTx, DatabaseEnv, }; use reth_node_core::dirs::{ChainPath, DataDirPath}; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::stage::StageCheckpoint; use reth_provider::{ChainSpecProvider, ProviderFactory}; -use reth_revm::EvmProcessorFactory; use reth_stages::{stages::ExecutionStage, Stage, UnwindInput}; use tracing::info; @@ -19,7 +16,7 @@ pub(crate) async fn dump_execution_stage( to: u64, output_datadir: ChainPath, should_run: bool, -) -> Result<()> { +) -> eyre::Result<()> { let (output_db, tip_block_number) = setup(from, to, &output_datadir.db(), db_tool)?; import_tables_with_range(&output_db, db_tool, from, to)?; @@ -127,10 +124,8 @@ async fn unwind_and_copy( ) -> eyre::Result<()> { let provider = db_tool.provider_factory.provider_rw()?; - let mut exec_stage = ExecutionStage::new_with_factory(EvmProcessorFactory::new( - db_tool.chain.clone(), - EthEvmConfig::default(), - )); + let executor = block_executor!(db_tool.chain.clone()); + let mut exec_stage = ExecutionStage::new_with_executor(executor); exec_stage.unwind( &provider, @@ -159,10 +154,8 @@ async fn dry_run( ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage. [dry-run]"); - let mut exec_stage = ExecutionStage::new_with_factory(EvmProcessorFactory::new( - output_provider_factory.chain_spec(), - EthEvmConfig::default(), - )); + let executor = block_executor!(output_provider_factory.chain_spec()); + let mut exec_stage = ExecutionStage::new_with_executor(executor); let input = reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)) }; diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/bin/reth/src/commands/stage/dump/merkle.rs index 55a8ec76d1..9b421be7ca 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/bin/reth/src/commands/stage/dump/merkle.rs @@ -1,11 +1,10 @@ use super::setup; -use crate::utils::DbTool; +use crate::{macros::block_executor, utils::DbTool}; use eyre::Result; use reth_config::config::EtlConfig; use reth_db::{database::Database, table::TableImporter, tables, DatabaseEnv}; use reth_exex::ExExManagerHandle; use reth_node_core::dirs::{ChainPath, DataDirPath}; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::{stage::StageCheckpoint, BlockNumber, PruneModes}; use reth_provider::ProviderFactory; use reth_stages::{ @@ -81,9 +80,11 @@ async fn unwind_and_copy( MerkleStage::default_unwind().unwind(&provider, unwind)?; + let executor = block_executor!(db_tool.chain.clone()); + // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( - reth_revm::EvmProcessorFactory::new(db_tool.chain.clone(), EthEvmConfig::default()), + executor, ExecutionStageThresholds { max_blocks: Some(u64::MAX), max_changes: None, diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index d798c87d1f..562b7e1b3e 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -9,6 +9,7 @@ use crate::{ DatabaseArgs, NetworkArgs, StageEnum, }, dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, prometheus_exporter, version::SHORT_VERSION, }; @@ -19,7 +20,6 @@ use reth_config::{config::EtlConfig, Config}; use reth_db::init_db; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_exex::ExExManagerHandle; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::ChainSpec; use reth_provider::{ ProviderFactory, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory, @@ -224,13 +224,10 @@ impl Command { } StageEnum::Senders => (Box::new(SenderRecoveryStage::new(batch_size)), None), StageEnum::Execution => { - let factory = reth_revm::EvmProcessorFactory::new( - self.chain.clone(), - EthEvmConfig::default(), - ); + let executor = block_executor!(self.chain.clone()); ( Box::new(ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: Some(batch_size), max_changes: None, diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 0c4260c0c6..c6dea1a059 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -1,12 +1,5 @@ //! Unwinding a certain block range -use crate::{ - args::{ - utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}, - DatabaseArgs, - }, - dirs::{DataDirPath, MaybePlatformPath}, -}; use clap::{Parser, Subcommand}; use reth_beacon_consensus::BeaconConsensus; use reth_config::{Config, PruneConfig}; @@ -21,7 +14,6 @@ use reth_node_core::{ args::{get_secret_key, NetworkArgs}, dirs::ChainPath, }; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::{BlockHashOrNumber, ChainSpec, PruneModes, B256}; use reth_provider::{ BlockExecutionWriter, BlockNumReader, ChainSpecProvider, HeaderSyncMode, ProviderFactory, @@ -42,6 +34,15 @@ use std::{ops::RangeInclusive, sync::Arc}; use tokio::sync::watch; use tracing::info; +use crate::{ + args::{ + utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}, + DatabaseArgs, + }, + dirs::{DataDirPath, MaybePlatformPath}, + macros::block_executor, +}; + /// `reth stage unwind` command #[derive(Debug, Parser)] pub struct Command { @@ -178,10 +179,7 @@ impl Command { let stage_conf = &config.stages; let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let factory = reth_revm::EvmProcessorFactory::new( - provider_factory.chain_spec(), - EthEvmConfig::default(), - ); + let executor = block_executor!(provider_factory.chain_spec()); let header_mode = HeaderSyncMode::Tip(tip_rx); let pipeline = Pipeline::builder() @@ -193,14 +191,14 @@ impl Command { Arc::clone(&consensus), header_downloader, body_downloader, - factory.clone(), + executor.clone(), stage_conf.etl.clone(), ) .set(SenderRecoveryStage { commit_threshold: stage_conf.sender_recovery.commit_threshold, }) .set(ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: None, max_changes: None, diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 42f26115c5..9dd43bcd24 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -31,6 +31,7 @@ pub mod cli; pub mod commands; +mod macros; pub mod utils; /// Re-exported payload related types diff --git a/bin/reth/src/macros.rs b/bin/reth/src/macros.rs new file mode 100644 index 0000000000..7ff81a0f90 --- /dev/null +++ b/bin/reth/src/macros.rs @@ -0,0 +1,20 @@ +//! Helper macros + +/// Creates the block executor type based on the configured feature. +/// +/// Note(mattsse): This is incredibly horrible and will be replaced +#[cfg(not(feature = "optimism"))] +macro_rules! block_executor { + ($chain_spec:expr) => { + reth_node_ethereum::EthExecutorProvider::ethereum($chain_spec) + }; +} + +#[cfg(feature = "optimism")] +macro_rules! block_executor { + ($chain_spec:expr) => { + reth_node_optimism::OpExecutorProvider::optimism($chain_spec) + }; +} + +pub(crate) use block_executor; diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index ecb2e4ef36..912f593dc4 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -15,6 +15,8 @@ workspace = true reth-primitives.workspace = true reth-interfaces.workspace = true reth-db.workspace = true +reth-evm.workspace = true +reth-revm.workspace = true reth-provider.workspace = true reth-stages-api.workspace = true reth-trie = { workspace = true, features = ["metrics"] } @@ -40,6 +42,7 @@ reth-db = { workspace = true, features = ["test-utils"] } reth-interfaces = { workspace = true, features = ["test-utils"] } reth-primitives = { workspace = true , features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } +reth-evm = { workspace = true, features = ["test-utils"] } reth-revm.workspace = true reth-evm-ethereum.workspace = true parking_lot.workspace = true diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index b2b30f132d..5346eafbdc 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -7,6 +7,7 @@ use crate::{ }; use reth_consensus::{Consensus, ConsensusError}; use reth_db::database::Database; +use reth_evm::execute::BlockExecutorProvider; use reth_interfaces::{ blockchain_tree::{ error::{BlockchainTreeError, CanonicalError, InsertBlockError, InsertBlockErrorKind}, @@ -24,7 +25,7 @@ use reth_provider::{ chain::{ChainSplit, ChainSplitTarget}, BlockExecutionWriter, BlockNumReader, BlockWriter, BundleStateWithReceipts, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, Chain, - ChainSpecProvider, DisplayBlocksChain, ExecutorFactory, HeaderProvider, ProviderError, + ChainSpecProvider, DisplayBlocksChain, HeaderProvider, ProviderError, }; use reth_stages_api::{MetricEvent, MetricEventsSender}; use std::{ @@ -57,13 +58,13 @@ use tracing::{debug, error, info, instrument, trace, warn}; /// * [BlockchainTree::make_canonical]: Check if we have the hash of a block that is the current /// canonical head and commit it to db. #[derive(Debug)] -pub struct BlockchainTree { +pub struct BlockchainTree { /// The state of the tree /// /// Tracks all the chains, the block indices, and the block buffer. state: TreeState, /// External components (the database, consensus engine etc.) - externals: TreeExternals, + externals: TreeExternals, /// Tree configuration config: BlockchainTreeConfig, /// Broadcast channel for canon state changes notifications. @@ -75,7 +76,7 @@ pub struct BlockchainTree { prune_modes: Option, } -impl BlockchainTree { +impl BlockchainTree { /// Subscribe to new blocks events. /// /// Note: Only canonical blocks are emitted by the tree. @@ -89,10 +90,10 @@ impl BlockchainTree { } } -impl BlockchainTree +impl BlockchainTree where DB: Database + Clone, - EVM: ExecutorFactory, + E: BlockExecutorProvider, { /// Builds the blockchain tree for the node. /// @@ -115,7 +116,7 @@ where /// storage space efficiently. It's important to validate this configuration to ensure it does /// not lead to unintended data loss. pub fn new( - externals: TreeExternals, + externals: TreeExternals, config: BlockchainTreeConfig, prune_modes: Option, ) -> RethResult { @@ -1273,7 +1274,8 @@ mod tests { use linked_hash_set::LinkedHashSet; use reth_consensus::test_utils::TestConsensus; use reth_db::{tables, test_utils::TempDatabase, transaction::DbTxMut, DatabaseEnv}; - use reth_evm_ethereum::EthEvmConfig; + use reth_evm::test_utils::MockExecutorProvider; + use reth_evm_ethereum::execute::EthExecutorProvider; #[cfg(not(feature = "optimism"))] use reth_primitives::proofs::calculate_receipt_root; #[cfg(feature = "optimism")] @@ -1289,19 +1291,15 @@ mod tests { MAINNET, }; use reth_provider::{ - test_utils::{ - blocks::BlockchainTestData, create_test_provider_factory_with_chain_spec, - TestExecutorFactory, - }, + test_utils::{blocks::BlockchainTestData, create_test_provider_factory_with_chain_spec}, ProviderFactory, }; - use reth_revm::EvmProcessorFactory; use reth_trie::StateRoot; use std::collections::HashMap; fn setup_externals( exec_res: Vec, - ) -> TreeExternals>, TestExecutorFactory> { + ) -> TreeExternals>, MockExecutorProvider> { let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1311,7 +1309,7 @@ mod tests { ); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); let consensus = Arc::new(TestConsensus::default()); - let executor_factory = TestExecutorFactory::default(); + let executor_factory = MockExecutorProvider::default(); executor_factory.extend(exec_res); TreeExternals::new(provider_factory, consensus, executor_factory) @@ -1395,7 +1393,7 @@ mod tests { self } - fn assert(self, tree: &BlockchainTree) { + fn assert(self, tree: &BlockchainTree) { if let Some(chain_num) = self.chain_num { assert_eq!(tree.state.chains.len(), chain_num); } @@ -1439,8 +1437,7 @@ mod tests { ); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); let consensus = Arc::new(TestConsensus::default()); - let executor_factory = - EvmProcessorFactory::new(chain_spec.clone(), EthEvmConfig::default()); + let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone()); { let provider_rw = provider_factory.provider_rw().unwrap(); @@ -1548,7 +1545,7 @@ mod tests { mock_block(3, Some(sidechain_block_1.hash()), Vec::from([mock_tx(2)]), 3); let mut tree = BlockchainTree::new( - TreeExternals::new(provider_factory, consensus, executor_factory), + TreeExternals::new(provider_factory, consensus, executor_provider), BlockchainTreeConfig::default(), None, ) diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index c091b800ab..637ea52e7e 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -7,6 +7,7 @@ use super::externals::TreeExternals; use crate::BundleStateDataRef; use reth_consensus::{Consensus, ConsensusError}; use reth_db::database::Database; +use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; use reth_interfaces::{ blockchain_tree::{ error::{BlockchainTreeError, InsertBlockErrorKind}, @@ -15,13 +16,14 @@ use reth_interfaces::{ RethResult, }; use reth_primitives::{ - BlockHash, BlockNumber, ForkBlock, GotExpected, SealedBlockWithSenders, SealedHeader, U256, + BlockHash, BlockNumber, ForkBlock, GotExpected, Receipts, SealedBlockWithSenders, SealedHeader, + U256, }; use reth_provider::{ providers::{BundleStateProvider, ConsistentDbView}, - BundleStateDataProvider, BundleStateWithReceipts, Chain, ExecutorFactory, ProviderError, - StateRootProvider, + BundleStateDataProvider, BundleStateWithReceipts, Chain, ProviderError, StateRootProvider, }; +use reth_revm::database::StateProviderDatabase; use reth_trie::updates::TrieUpdates; use reth_trie_parallel::parallel_root::ParallelStateRoot; use std::{ @@ -66,18 +68,18 @@ impl AppendableChain { /// /// if [BlockValidationKind::Exhaustive] is specified, the method will verify the state root of /// the block. - pub fn new_canonical_fork( + pub fn new_canonical_fork( block: SealedBlockWithSenders, parent_header: &SealedHeader, canonical_block_hashes: &BTreeMap, canonical_fork: ForkBlock, - externals: &TreeExternals, + externals: &TreeExternals, block_attachment: BlockAttachment, block_validation_kind: BlockValidationKind, ) -> Result where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { let state = BundleStateWithReceipts::default(); let empty = BTreeMap::new(); @@ -104,18 +106,18 @@ impl AppendableChain { /// Create a new chain that forks off of an existing sidechain. /// /// This differs from [AppendableChain::new_canonical_fork] in that this starts a new fork. - pub(crate) fn new_chain_fork( + pub(crate) fn new_chain_fork( &self, block: SealedBlockWithSenders, side_chain_block_hashes: BTreeMap, canonical_block_hashes: &BTreeMap, canonical_fork: ForkBlock, - externals: &TreeExternals, + externals: &TreeExternals, block_validation_kind: BlockValidationKind, ) -> Result where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { let parent_number = block.number - 1; let parent = self.blocks().get(&parent_number).ok_or( @@ -166,18 +168,18 @@ impl AppendableChain { /// - [BlockAttachment] represents if the block extends the canonical chain, and thus we can /// cache the trie state updates. /// - [BlockValidationKind] determines if the state root __should__ be validated. - fn validate_and_execute( + fn validate_and_execute( block: SealedBlockWithSenders, parent_block: &SealedHeader, bundle_state_data_provider: BSDP, - externals: &TreeExternals, + externals: &TreeExternals, block_attachment: BlockAttachment, block_validation_kind: BlockValidationKind, ) -> RethResult<(BundleStateWithReceipts, Option)> where BSDP: BundleStateDataProvider, DB: Database + Clone, - EVM: ExecutorFactory, + E: BlockExecutorProvider, { // some checks are done before blocks comes here. externals.consensus.validate_header_against_parent(&block, parent_block)?; @@ -203,11 +205,17 @@ impl AppendableChain { let provider = BundleStateProvider::new(state_provider, bundle_state_data_provider); - let mut executor = externals.executor_factory.with_state(&provider); + let db = StateProviderDatabase::new(&provider); + let executor = externals.executor_factory.executor(db); let block_hash = block.hash(); let block = block.unseal(); - executor.execute_and_verify_receipt(&block, U256::MAX)?; - let bundle_state = executor.take_output_state(); + let state = executor.execute((&block, U256::MAX).into())?; + let BlockExecutionOutput { state, receipts, .. } = state; + let bundle_state = BundleStateWithReceipts::new( + state, + Receipts::from_block_receipt(receipts), + block.number, + ); // check state root if the block extends the canonical chain __and__ if state root // validation was requested. @@ -259,19 +267,19 @@ impl AppendableChain { /// __not__ the canonical head. #[track_caller] #[allow(clippy::too_many_arguments)] - pub(crate) fn append_block( + pub(crate) fn append_block( &mut self, block: SealedBlockWithSenders, side_chain_block_hashes: BTreeMap, canonical_block_hashes: &BTreeMap, - externals: &TreeExternals, + externals: &TreeExternals, canonical_fork: ForkBlock, block_attachment: BlockAttachment, block_validation_kind: BlockValidationKind, ) -> Result<(), InsertBlockErrorKind> where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { let parent_block = self.chain.tip(); diff --git a/crates/blockchain-tree/src/externals.rs b/crates/blockchain-tree/src/externals.rs index 36f3041738..a311281c94 100644 --- a/crates/blockchain-tree/src/externals.rs +++ b/crates/blockchain-tree/src/externals.rs @@ -19,27 +19,27 @@ use std::{collections::BTreeMap, sync::Arc}; /// - The executor factory to execute blocks with /// - The chain spec #[derive(Debug)] -pub struct TreeExternals { +pub struct TreeExternals { /// The provider factory, used to commit the canonical chain, or unwind it. pub(crate) provider_factory: ProviderFactory, /// The consensus engine. pub(crate) consensus: Arc, /// The executor factory to execute blocks with. - pub(crate) executor_factory: EVM, + pub(crate) executor_factory: E, } -impl TreeExternals { +impl TreeExternals { /// Create new tree externals. pub fn new( provider_factory: ProviderFactory, consensus: Arc, - executor_factory: EVM, + executor_factory: E, ) -> Self { Self { provider_factory, consensus, executor_factory } } } -impl TreeExternals { +impl TreeExternals { /// Fetches the latest canonical block hashes by walking backwards from the head. /// /// Returns the hashes sorted by increasing block numbers diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index 7a0eb36fa4..061b49f4c4 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -3,6 +3,7 @@ use super::BlockchainTree; use parking_lot::RwLock; use reth_db::database::Database; +use reth_evm::execute::BlockExecutorProvider; use reth_interfaces::{ blockchain_tree::{ error::{CanonicalError, InsertBlockError}, @@ -17,7 +18,7 @@ use reth_primitives::{ }; use reth_provider::{ BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonStateSubscriptions, - ExecutorFactory, ProviderError, + ProviderError, }; use std::{ collections::{BTreeMap, HashSet}, @@ -27,22 +28,22 @@ use tracing::trace; /// Shareable blockchain tree that is behind a RwLock #[derive(Clone, Debug)] -pub struct ShareableBlockchainTree { +pub struct ShareableBlockchainTree { /// BlockchainTree - pub tree: Arc>>, + pub tree: Arc>>, } -impl ShareableBlockchainTree { +impl ShareableBlockchainTree { /// Create a new shareable database. - pub fn new(tree: BlockchainTree) -> Self { + pub fn new(tree: BlockchainTree) -> Self { Self { tree: Arc::new(RwLock::new(tree)) } } } -impl BlockchainTreeEngine for ShareableBlockchainTree +impl BlockchainTreeEngine for ShareableBlockchainTree where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { fn buffer_block(&self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { let mut tree = self.tree.write(); @@ -99,10 +100,10 @@ where } } -impl BlockchainTreeViewer for ShareableBlockchainTree +impl BlockchainTreeViewer for ShareableBlockchainTree where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { fn blocks(&self) -> BTreeMap> { trace!(target: "blockchain_tree", "Returning all blocks in blockchain tree"); @@ -181,10 +182,10 @@ where } } -impl BlockchainTreePendingStateProvider for ShareableBlockchainTree +impl BlockchainTreePendingStateProvider for ShareableBlockchainTree where DB: Database + Clone, - EF: ExecutorFactory, + E: BlockExecutorProvider, { fn find_pending_state_provider( &self, @@ -196,10 +197,10 @@ where } } -impl CanonStateSubscriptions for ShareableBlockchainTree +impl CanonStateSubscriptions for ShareableBlockchainTree where DB: Send + Sync, - EF: Send + Sync, + E: Send + Sync, { fn subscribe_to_canonical_state(&self) -> reth_provider::CanonStateNotifications { trace!(target: "blockchain_tree", "Registered subscriber for canonical state"); diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 9f2f2c4029..e954108c8c 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -18,23 +18,18 @@ use reth_beacon_consensus::BeaconEngineMessage; use reth_consensus::{Consensus, ConsensusError}; use reth_engine_primitives::EngineTypes; -use reth_evm::ConfigureEvm; use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ - constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, + constants::{EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, eip4844::calculate_excess_blob_gas, - proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Bloom, - ChainSpec, Header, ReceiptWithBloom, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, - B256, U256, + proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Header, + Receipts, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; use reth_provider::{ - BlockExecutor, BlockReaderIdExt, BundleStateWithReceipts, CanonStateNotificationSender, - StateProviderFactory, -}; -use reth_revm::{ - database::StateProviderDatabase, db::states::bundle_state::BundleRetention, - processor::EVMProcessor, State, + BlockReaderIdExt, BundleStateWithReceipts, CanonStateNotificationSender, StateProviderFactory, + StateRootProvider, }; +use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::TransactionPool; use std::{ collections::HashMap, @@ -50,6 +45,7 @@ mod task; pub use crate::client::AutoSealClient; pub use mode::{FixedBlockTimeMiner, MiningMode, ReadyTransactionMiner}; +use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; pub use task::MiningTask; /// A consensus implementation intended for local development and testing purposes. @@ -281,6 +277,18 @@ impl StorageInner { parent.next_block_base_fee(chain_spec.base_fee_params_at_timestamp(timestamp)) }); + let blob_gas_used = if chain_spec.is_cancun_active_at_timestamp(timestamp) { + let mut sum_blob_gas_used = 0; + for tx in transactions { + if let Some(blob_tx) = tx.transaction.as_eip4844() { + sum_blob_gas_used += blob_tx.blob_gas(); + } + } + Some(sum_blob_gas_used) + } else { + None + }; + let mut header = Header { parent_hash: self.best_hash, ommers_hash: proofs::calculate_ommers_root(ommers), @@ -298,7 +306,7 @@ impl StorageInner { mix_hash: Default::default(), nonce: 0, base_fee_per_gas, - blob_gas_used: None, + blob_gas_used, excess_blob_gas: None, extra_data: Default::default(), parent_beacon_block_root: None, @@ -334,111 +342,26 @@ impl StorageInner { header } - /// Executes the block with the given block and senders, on the provided [EVMProcessor]. - /// - /// This returns the poststate from execution and post-block changes, as well as the gas used. - pub(crate) fn execute( - &self, - block: &BlockWithSenders, - executor: &mut EVMProcessor<'_, EvmConfig>, - ) -> Result<(BundleStateWithReceipts, u64), BlockExecutionError> - where - EvmConfig: ConfigureEvm, - { - trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions"); - // TODO: there isn't really a parent beacon block root here, so not sure whether or not to - // call the 4788 beacon contract - - // set the first block to find the correct index in bundle state - executor.set_first_block(block.number); - - let (receipts, gas_used) = executor.execute_transactions(block, U256::ZERO)?; - - // Save receipts. - executor.save_receipts(receipts)?; - - // add post execution state change - // Withdrawals, rewards etc. - executor.apply_post_execution_state_change(block, U256::ZERO)?; - - // merge transitions - executor.db_mut().merge_transitions(BundleRetention::Reverts); - - // apply post block changes - Ok((executor.take_output_state(), gas_used)) - } - - /// Fills in the post-execution header fields based on the given BundleState and gas used. - /// In doing this, the state root is calculated and the final header is returned. - pub(crate) fn complete_header( - &self, - mut header: Header, - bundle_state: &BundleStateWithReceipts, - client: &S, - gas_used: u64, - blob_gas_used: Option, - #[cfg(feature = "optimism")] chain_spec: &ChainSpec, - ) -> Result { - let receipts = bundle_state.receipts_by_block(header.number); - header.receipts_root = if receipts.is_empty() { - EMPTY_RECEIPTS - } else { - let receipts_with_bloom = receipts - .iter() - .map(|r| (*r).clone().expect("receipts have not been pruned").into()) - .collect::>(); - header.logs_bloom = - receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); - #[cfg(feature = "optimism")] - { - proofs::calculate_receipt_root_optimism( - &receipts_with_bloom, - chain_spec, - header.timestamp, - ) - } - #[cfg(not(feature = "optimism"))] - { - proofs::calculate_receipt_root(&receipts_with_bloom) - } - }; - - header.gas_used = gas_used; - header.blob_gas_used = blob_gas_used; - - // calculate the state root - let state_root = client - .latest() - .map_err(BlockExecutionError::LatestBlock)? - .state_root(bundle_state.state()) - .unwrap(); - header.state_root = state_root; - Ok(header) - } - - /// Builds and executes a new block with the given transactions, on the provided [EVMProcessor]. + /// Builds and executes a new block with the given transactions, on the provided executor. /// /// This returns the header of the executed block, as well as the poststate from execution. - pub(crate) fn build_and_execute( + pub(crate) fn build_and_execute( &mut self, transactions: Vec, ommers: Vec
, withdrawals: Option, - client: &impl StateProviderFactory, + provider: &Provider, chain_spec: Arc, - evm_config: &EvmConfig, + executor: &Executor, ) -> Result<(SealedHeader, BundleStateWithReceipts), BlockExecutionError> where - EvmConfig: ConfigureEvm, + Executor: BlockExecutorProvider, + Provider: StateProviderFactory, { - let header = self.build_header_template( - &transactions, - &ommers, - withdrawals.as_ref(), - chain_spec.clone(), - ); + let header = + self.build_header_template(&transactions, &ommers, withdrawals.as_ref(), chain_spec); - let block = Block { + let mut block = Block { header, body: transactions, ommers: ommers.clone(), @@ -449,45 +372,46 @@ impl StorageInner { trace!(target: "consensus::auto", transactions=?&block.body, "executing transactions"); - // now execute the block - let db = State::builder() - .with_database_boxed(Box::new(StateProviderDatabase::new( - client.latest().map_err(BlockExecutionError::LatestBlock)?, - ))) - .with_bundle_update() - .build(); - let mut executor = EVMProcessor::new_with_state(chain_spec.clone(), db, evm_config); + let mut db = StateProviderDatabase::new( + provider.latest().map_err(BlockExecutionError::LatestBlock)?, + ); - let (bundle_state, gas_used) = self.execute(&block, &mut executor)?; - - let Block { header, body, .. } = block.block; - let body = BlockBody { transactions: body, ommers, withdrawals }; - - let blob_gas_used = if chain_spec.is_cancun_active_at_timestamp(header.timestamp) { - let mut sum_blob_gas_used = 0; - for tx in &body.transactions { - if let Some(blob_tx) = tx.transaction.as_eip4844() { - sum_blob_gas_used += blob_tx.blob_gas(); + // TODO(mattsse): At this point we don't know certain fields of the header, so we first + // execute it and then update the header this can be improved by changing the executor + // input, for now we intercept the errors and retry + loop { + match executor.executor(&mut db).execute((&block, U256::ZERO).into()) { + Err(BlockExecutionError::Validation(BlockValidationError::BlockGasUsed { + gas, + .. + })) => { + block.block.header.gas_used = gas.got; } - } - Some(sum_blob_gas_used) - } else { - None - }; + Err(BlockExecutionError::Validation(BlockValidationError::ReceiptRootDiff( + err, + ))) => { + block.block.header.receipts_root = err.got; + } + _ => break, + }; + } + + // now execute the block + let BlockExecutionOutput { state, receipts, .. } = + executor.executor(&mut db).execute((&block, U256::ZERO).into())?; + let bundle_state = BundleStateWithReceipts::new( + state, + Receipts::from_block_receipt(receipts), + block.number, + ); + + let Block { mut header, body, .. } = block.block; + let body = BlockBody { transactions: body, ommers, withdrawals }; trace!(target: "consensus::auto", ?bundle_state, ?header, ?body, "executed block, calculating state root and completing header"); - // fill in the rest of the fields - let header = self.complete_header( - header, - &bundle_state, - client, - gas_used, - blob_gas_used, - #[cfg(feature = "optimism")] - chain_spec.as_ref(), - )?; - + // calculate the state root + header.state_root = db.state_root(bundle_state.state())?; trace!(target: "consensus::auto", root=?header.state_root, ?body, "calculated root"); // finally insert into storage diff --git a/crates/consensus/auto-seal/src/task.rs b/crates/consensus/auto-seal/src/task.rs index 7e2a700ef4..42f1268f33 100644 --- a/crates/consensus/auto-seal/src/task.rs +++ b/crates/consensus/auto-seal/src/task.rs @@ -2,7 +2,7 @@ use crate::{mode::MiningMode, Storage}; use futures_util::{future::BoxFuture, FutureExt}; use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus}; use reth_engine_primitives::EngineTypes; -use reth_evm::ConfigureEvm; +use reth_evm::execute::BlockExecutorProvider; use reth_primitives::{ Block, ChainSpec, IntoRecoveredTransaction, SealedBlockWithSenders, Withdrawals, }; @@ -22,7 +22,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::{debug, error, warn}; /// A Future that listens for new ready transactions and puts new blocks into storage -pub struct MiningTask { +pub struct MiningTask { /// The configured chain spec chain_spec: Arc, /// The client used to interact with the state @@ -43,14 +43,14 @@ pub struct MiningTask>, - /// The type that defines how to configure the EVM. - evm_config: EvmConfig, + /// The type used for block execution + block_executor: Executor, } // === impl MiningTask === -impl - MiningTask +impl + MiningTask { /// Creates a new instance of the task #[allow(clippy::too_many_arguments)] @@ -62,7 +62,7 @@ impl storage: Storage, client: Client, pool: Pool, - evm_config: EvmConfig, + block_executor: Executor, ) -> Self { Self { chain_spec, @@ -75,7 +75,7 @@ impl canon_state_notification, queued: Default::default(), pipe_line_events: None, - evm_config, + block_executor, } } @@ -85,13 +85,13 @@ impl } } -impl Future for MiningTask +impl Future for MiningTask where Client: StateProviderFactory + CanonChainTracker + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, ::Transaction: IntoRecoveredTransaction, Engine: EngineTypes + 'static, - EvmConfig: ConfigureEvm + Clone + Unpin + Send + Sync + 'static, + Executor: BlockExecutorProvider, { type Output = (); @@ -121,7 +121,7 @@ where let pool = this.pool.clone(); let events = this.pipe_line_events.take(); let canon_state_notification = this.canon_state_notification.clone(); - let evm_config = this.evm_config.clone(); + let executor = this.block_executor.clone(); // Create the mining future that creates a block, notifies the engine that drives // the pipeline @@ -145,7 +145,7 @@ where withdrawals.clone(), &client, chain_spec, - &evm_config, + &executor, ) { Ok((new_header, bundle_state)) => { // clear all transactions from pool diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 4e35d06f0f..8fb9d3ec3b 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -51,6 +51,7 @@ reth-stages = { workspace = true, features = ["test-utils"] } reth-blockchain-tree = { workspace = true, features = ["test-utils"] } reth-db = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } +reth-evm = { workspace = true, features = ["test-utils"] } reth-rpc-types-compat.workspace = true reth-rpc.workspace = true reth-tracing.workspace = true diff --git a/crates/consensus/beacon/src/engine/test_utils.rs b/crates/consensus/beacon/src/engine/test_utils.rs index 513987e758..27fc6b44cf 100644 --- a/crates/consensus/beacon/src/engine/test_utils.rs +++ b/crates/consensus/beacon/src/engine/test_utils.rs @@ -14,9 +14,9 @@ use reth_downloaders::{ headers::reverse_headers::ReverseHeadersDownloaderBuilder, }; use reth_ethereum_engine_primitives::EthEngineTypes; -use reth_evm_ethereum::EthEvmConfig; +use reth_evm::{either::Either, test_utils::MockExecutorProvider}; +use reth_evm_ethereum::execute::EthExecutorProvider; use reth_interfaces::{ - executor::BlockExecutionError, p2p::{bodies::client::BodiesClient, either::EitherDownloader, headers::client::HeadersClient}, sync::NoopSyncStateUpdater, test_utils::NoopFullBlockClient, @@ -24,13 +24,10 @@ use reth_interfaces::{ use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::{BlockNumber, ChainSpec, FinishedExExHeight, PruneModes, B256}; use reth_provider::{ - providers::BlockchainProvider, - test_utils::{create_test_provider_factory_with_chain_spec, TestExecutorFactory}, - BundleStateWithReceipts, ExecutorFactory, HeaderSyncMode, PrunableBlockExecutor, - StaticFileProviderFactory, + providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, + BundleStateWithReceipts, HeaderSyncMode, StaticFileProviderFactory, }; use reth_prune::Pruner; -use reth_revm::EvmProcessorFactory; use reth_rpc_types::engine::{ CancunPayloadFields, ExecutionPayload, ForkchoiceState, ForkchoiceUpdated, PayloadStatus, }; @@ -155,31 +152,6 @@ impl Default for TestExecutorConfig { } } -/// A type that represents one of two possible executor factories. -#[derive(Debug, Clone)] -pub enum EitherExecutorFactory { - /// The first factory variant - Left(A), - /// The second factory variant - Right(B), -} - -impl ExecutorFactory for EitherExecutorFactory -where - A: ExecutorFactory, - B: ExecutorFactory, -{ - fn with_state<'a, SP: reth_provider::StateProvider + 'a>( - &'a self, - sp: SP, - ) -> Box + 'a> { - match self { - EitherExecutorFactory::Left(a) => a.with_state::<'a, SP>(sp), - EitherExecutorFactory::Right(b) => b.with_state::<'a, SP>(sp), - } - } -} - /// The basic configuration for a `TestConsensusEngine`, without generics for the client or /// consensus engine. #[derive(Debug)] @@ -366,14 +338,13 @@ where // use either test executor or real executor let executor_factory = match self.base_config.executor_config { TestExecutorConfig::Test(results) => { - let executor_factory = TestExecutorFactory::default(); + let executor_factory = MockExecutorProvider::default(); executor_factory.extend(results); - EitherExecutorFactory::Left(executor_factory) + Either::Left(executor_factory) + } + TestExecutorConfig::Real => { + Either::Right(EthExecutorProvider::ethereum(self.base_config.chain_spec.clone())) } - TestExecutorConfig::Real => EitherExecutorFactory::Right(EvmProcessorFactory::new( - self.base_config.chain_spec.clone(), - EthEvmConfig::default(), - )), }; let static_file_producer = StaticFileProducer::new( diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index ea7cfab8c2..6fa61e34ff 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -16,7 +16,6 @@ reth-evm.workspace = true reth-primitives.workspace = true reth-revm.workspace = true reth-interfaces.workspace = true -reth-provider.workspace = true # Ethereum revm-primitives.workspace = true diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 4239fe4492..c3dd315f74 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -1,10 +1,10 @@ //! Ethereum block executor. -use crate::EthEvmConfig; +use crate::{verify::verify_receipts, EthEvmConfig}; use reth_evm::{ execute::{ - BatchBlockOutput, BatchExecutor, EthBlockExecutionInput, EthBlockOutput, Executor, - ExecutorProvider, + BatchBlockExecutionOutput, BatchExecutor, BlockExecutionInput, BlockExecutionOutput, + BlockExecutorProvider, Executor, }, ConfigureEvm, ConfigureEvmEnv, }; @@ -13,15 +13,13 @@ use reth_interfaces::{ provider::ProviderError, }; use reth_primitives::{ - BlockWithSenders, ChainSpec, GotExpected, Hardfork, Header, PruneModes, Receipt, Receipts, - Withdrawals, U256, + BlockNumber, BlockWithSenders, ChainSpec, GotExpected, Hardfork, Header, PruneModes, Receipt, + Receipts, Withdrawals, MAINNET, U256, }; -use reth_provider::BundleStateWithReceipts; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, db::states::bundle_state::BundleRetention, eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, - processor::verify_receipt, stack::InspectorStack, state_change::{apply_beacon_root_contract_call, post_block_balance_increments}, Evm, State, @@ -35,35 +33,33 @@ use tracing::debug; /// Provides executors to execute regular ethereum blocks #[derive(Debug, Clone)] -pub struct EthExecutorProvider { +pub struct EthExecutorProvider { chain_spec: Arc, evm_config: EvmConfig, inspector: Option, - prune_modes: PruneModes, } -impl EthExecutorProvider { +impl EthExecutorProvider { /// Creates a new default ethereum executor provider. pub fn ethereum(chain_spec: Arc) -> Self { Self::new(chain_spec, Default::default()) } + + /// Returns a new provider for the mainnet. + pub fn mainnet() -> Self { + Self::ethereum(MAINNET.clone()) + } } impl EthExecutorProvider { /// Creates a new executor provider. pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { - Self { chain_spec, evm_config, inspector: None, prune_modes: PruneModes::none() } + Self { chain_spec, evm_config, inspector: None } } /// Configures an optional inspector stack for debugging. - pub fn with_inspector(mut self, inspector: InspectorStack) -> Self { - self.inspector = Some(inspector); - self - } - - /// Configures the prune modes for the executor. - pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { - self.prune_modes = prune_modes; + pub fn with_inspector(mut self, inspector: Option) -> Self { + self.inspector = inspector; self } } @@ -86,7 +82,7 @@ where } } -impl ExecutorProvider for EthExecutorProvider +impl BlockExecutorProvider for EthExecutorProvider where EvmConfig: ConfigureEvm, EvmConfig: ConfigureEvmEnv, @@ -102,14 +98,14 @@ where self.eth_executor(db) } - fn batch_executor(&self, db: DB) -> Self::BatchExecutor + fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where DB: Database, { let executor = self.eth_executor(db); EthBatchExecutor { executor, - batch_record: BlockBatchRecord::new(self.prune_modes.clone()), + batch_record: BlockBatchRecord::new(prune_modes), stats: BlockExecutorStats::default(), } } @@ -318,9 +314,11 @@ where // transaction This was replaced with is_success flag. // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 if self.chain_spec().is_byzantium_active_at_block(block.header.number) { - if let Err(error) = - verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts.iter()) - { + if let Err(error) = verify_receipts( + block.header.receipts_root, + block.header.logs_bloom, + receipts.iter(), + ) { debug!(target: "evm", %error, ?receipts, "receipts verification failed"); return Err(error) }; @@ -382,8 +380,8 @@ where EvmConfig: ConfigureEvmEnv, DB: Database, { - type Input<'a> = EthBlockExecutionInput<'a, BlockWithSenders>; - type Output = EthBlockOutput; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; type Error = BlockExecutionError; /// Executes the block and commits the state changes. @@ -394,13 +392,13 @@ where /// /// State changes are committed to the database. fn execute(mut self, input: Self::Input<'_>) -> Result { - let EthBlockExecutionInput { block, total_difficulty } = input; + let BlockExecutionInput { block, total_difficulty } = input; let (receipts, gas_used) = self.execute_and_verify(block, total_difficulty)?; - // prepare the state for extraction - self.state.merge_transitions(BundleRetention::PlainState); + // NOTE: we need to merge keep the reverts for the bundle retention + self.state.merge_transitions(BundleRetention::Reverts); - Ok(EthBlockOutput { state: self.state.take_bundle(), receipts, gas_used }) + Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, gas_used }) } } @@ -433,12 +431,12 @@ where EvmConfig: ConfigureEvmEnv, DB: Database, { - type Input<'a> = EthBlockExecutionInput<'a, BlockWithSenders>; - type Output = BundleStateWithReceipts; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BatchBlockExecutionOutput; type Error = BlockExecutionError; - fn execute_one(&mut self, input: Self::Input<'_>) -> Result { - let EthBlockExecutionInput { block, total_difficulty } = input; + fn execute_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> { + let BlockExecutionInput { block, total_difficulty } = input; let (receipts, _gas_used) = self.executor.execute_and_verify(block, total_difficulty)?; // prepare the state according to the prune mode @@ -448,18 +446,30 @@ where // store receipts in the set self.batch_record.save_receipts(receipts)?; - Ok(BatchBlockOutput { size_hint: Some(self.executor.state.bundle_size_hint()) }) + if self.batch_record.first_block().is_none() { + self.batch_record.set_first_block(block.number); + } + + Ok(()) } fn finalize(mut self) -> Self::Output { self.stats.log_debug(); - BundleStateWithReceipts::new( + BatchBlockExecutionOutput::new( self.executor.state.take_bundle(), self.batch_record.take_receipts(), self.batch_record.first_block().unwrap_or_default(), ) } + + fn set_tip(&mut self, tip: BlockNumber) { + self.batch_record.set_tip(tip); + } + + fn size_hint(&self) -> Option { + Some(self.executor.state.bundle_state.size_hint()) + } } #[cfg(test)] @@ -468,7 +478,7 @@ mod tests { use reth_primitives::{ bytes, constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS}, - keccak256, Account, Block, Bytes, ChainSpecBuilder, ForkCondition, B256, MAINNET, + keccak256, Account, Block, Bytes, ChainSpecBuilder, ForkCondition, B256, }; use reth_revm::{ database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState, @@ -497,12 +507,7 @@ mod tests { } fn executor_provider(chain_spec: Arc) -> EthExecutorProvider { - EthExecutorProvider { - chain_spec, - evm_config: Default::default(), - inspector: None, - prune_modes: Default::default(), - } + EthExecutorProvider { chain_spec, evm_config: Default::default(), inspector: None } } #[test] @@ -697,7 +702,8 @@ mod tests { let provider = executor_provider(chain_spec); - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = + provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); // attempt to execute the genesis block with non-zero parent beacon block root, expect err header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); @@ -777,7 +783,8 @@ mod tests { let provider = executor_provider(chain_spec); // execute header - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = + provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); // Now execute a block with the fixed header, ensure that it does not fail executor diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index adcfd700db..88621a66aa 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -16,6 +16,7 @@ use reth_primitives::{ }; use reth_revm::{Database, EvmBuilder}; pub mod execute; +pub mod verify; /// Ethereum-related EVM configuration. #[derive(Debug, Clone, Copy, Default)] diff --git a/crates/ethereum/evm/src/verify.rs b/crates/ethereum/evm/src/verify.rs new file mode 100644 index 0000000000..6f552fe424 --- /dev/null +++ b/crates/ethereum/evm/src/verify.rs @@ -0,0 +1,53 @@ +//! Helpers for verifying the receipts. + +use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; +use reth_primitives::{Bloom, GotExpected, Receipt, ReceiptWithBloom, B256}; + +/// Calculate the receipts root, and compare it against against the expected receipts root and logs +/// bloom. +pub fn verify_receipts<'a>( + expected_receipts_root: B256, + expected_logs_bloom: Bloom, + receipts: impl Iterator + Clone, +) -> Result<(), BlockExecutionError> { + // Calculate receipts root. + let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); + let receipts_root = reth_primitives::proofs::calculate_receipt_root(&receipts_with_bloom); + + // Create header log bloom. + let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); + + compare_receipts_root_and_logs_bloom( + receipts_root, + logs_bloom, + expected_receipts_root, + expected_logs_bloom, + )?; + + Ok(()) +} + +/// Compare the calculated receipts root with the expected receipts root, also compare +/// the calculated logs bloom with the expected logs bloom. +pub fn compare_receipts_root_and_logs_bloom( + calculated_receipts_root: B256, + calculated_logs_bloom: Bloom, + expected_receipts_root: B256, + expected_logs_bloom: Bloom, +) -> Result<(), BlockExecutionError> { + if calculated_receipts_root != expected_receipts_root { + return Err(BlockValidationError::ReceiptRootDiff( + GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(), + ) + .into()) + } + + if calculated_logs_bloom != expected_logs_bloom { + return Err(BlockValidationError::BloomLogDiff( + GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(), + ) + .into()) + } + + Ok(()) +} diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index f13c471a7a..854dcd95a2 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -17,3 +17,11 @@ revm-primitives.workspace = true revm.workspace = true reth-interfaces.workspace = true +futures-util.workspace = true +parking_lot = { workspace = true, optional = true } + +[dev-dependencies] +parking_lot.workspace = true + +[features] +test-utils = ["dep:parking_lot"] \ No newline at end of file diff --git a/crates/evm/src/either.rs b/crates/evm/src/either.rs new file mode 100644 index 0000000000..d1ae4ed78f --- /dev/null +++ b/crates/evm/src/either.rs @@ -0,0 +1,119 @@ +//! Helper type that represents one of two possible executor types + +use crate::execute::{ + BatchBlockExecutionOutput, BatchExecutor, BlockExecutionInput, BlockExecutionOutput, + BlockExecutorProvider, Executor, +}; +use reth_interfaces::{executor::BlockExecutionError, provider::ProviderError}; +use reth_primitives::{BlockNumber, BlockWithSenders, PruneModes, Receipt}; +use revm_primitives::db::Database; + +// re-export Either +pub use futures_util::future::Either; + +impl BlockExecutorProvider for Either +where + A: BlockExecutorProvider, + B: BlockExecutorProvider, +{ + type Executor> = Either, B::Executor>; + type BatchExecutor> = + Either, B::BatchExecutor>; + + fn executor(&self, db: DB) -> Self::Executor + where + DB: Database, + { + match self { + Either::Left(a) => Either::Left(a.executor(db)), + Either::Right(b) => Either::Right(b.executor(db)), + } + } + + fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor + where + DB: Database, + { + match self { + Either::Left(a) => Either::Left(a.batch_executor(db, prune_modes)), + Either::Right(b) => Either::Right(b.batch_executor(db, prune_modes)), + } + } +} + +impl Executor for Either +where + A: for<'a> Executor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BlockExecutionOutput, + Error = BlockExecutionError, + >, + B: for<'a> Executor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BlockExecutionOutput, + Error = BlockExecutionError, + >, + DB: Database, +{ + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; + type Error = BlockExecutionError; + + fn execute(self, input: Self::Input<'_>) -> Result { + match self { + Either::Left(a) => a.execute(input), + Either::Right(b) => b.execute(input), + } + } +} + +impl BatchExecutor for Either +where + A: for<'a> BatchExecutor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BatchBlockExecutionOutput, + Error = BlockExecutionError, + >, + B: for<'a> BatchExecutor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BatchBlockExecutionOutput, + Error = BlockExecutionError, + >, + DB: Database, +{ + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BatchBlockExecutionOutput; + type Error = BlockExecutionError; + + fn execute_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> { + match self { + Either::Left(a) => a.execute_one(input), + Either::Right(b) => b.execute_one(input), + } + } + + fn finalize(self) -> Self::Output { + match self { + Either::Left(a) => a.finalize(), + Either::Right(b) => b.finalize(), + } + } + + fn set_tip(&mut self, tip: BlockNumber) { + match self { + Either::Left(a) => a.set_tip(tip), + Either::Right(b) => b.set_tip(tip), + } + } + + fn size_hint(&self) -> Option { + match self { + Either::Left(a) => a.size_hint(), + Either::Right(b) => b.size_hint(), + } + } +} diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index b8c1536029..7b3e586467 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -1,7 +1,7 @@ //! Traits for execution. -use reth_interfaces::provider::ProviderError; -use reth_primitives::U256; +use reth_interfaces::{executor::BlockExecutionError, provider::ProviderError}; +use reth_primitives::{BlockNumber, BlockWithSenders, PruneModes, Receipt, Receipts, U256}; use revm::db::BundleState; use revm_primitives::db::Database; @@ -21,8 +21,8 @@ pub trait Executor { fn execute(self, input: Self::Input<'_>) -> Result; } -/// An executor that can execute multiple blocks in a row and keep track of the state over the -/// entire batch. +/// A general purpose executor that can execute multiple inputs in sequence and keep track of the +/// state over the entire batch. pub trait BatchExecutor { /// The input type for the executor. type Input<'a>; @@ -32,17 +32,20 @@ pub trait BatchExecutor { type Error; /// Executes the next block in the batch and update the state internally. - fn execute_one(&mut self, input: Self::Input<'_>) -> Result; + fn execute_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error>; /// Finishes the batch and return the final state. fn finalize(self) -> Self::Output; -} -/// The output of an executed block in a batch. -#[derive(Debug, Clone, Copy)] -pub struct BatchBlockOutput { - /// The size hint of the batch's tracked state. - pub size_hint: Option, + /// Set the expected tip of the batch. + /// + /// This can be used to optimize state pruning during execution. + fn set_tip(&mut self, tip: BlockNumber); + + /// The size hint of the batch's tracked state size. + /// + /// This is used to optimize DB commits depending on the size of the state. + fn size_hint(&self) -> Option; } /// The output of an ethereum block. @@ -51,7 +54,7 @@ pub struct BatchBlockOutput { /// /// TODO(mattsse): combine with BundleStateWithReceipts #[derive(Debug)] -pub struct EthBlockOutput { +pub struct BlockExecutionOutput { /// The changed state of the block after execution. pub state: BundleState, /// All the receipts of the transactions in the block. @@ -60,42 +63,94 @@ pub struct EthBlockOutput { pub gas_used: u64, } +/// The output of a batch of ethereum blocks. +#[derive(Debug)] +pub struct BatchBlockExecutionOutput { + /// Bundle state with reverts. + pub bundle: BundleState, + /// The collection of receipts. + /// Outer vector stores receipts for each block sequentially. + /// The inner vector stores receipts ordered by transaction number. + /// + /// If receipt is None it means it is pruned. + pub receipts: Receipts, + /// First block of bundle state. + pub first_block: BlockNumber, +} + +impl BatchBlockExecutionOutput { + /// Create Bundle State. + pub fn new(bundle: BundleState, receipts: Receipts, first_block: BlockNumber) -> Self { + Self { bundle, receipts, first_block } + } +} + /// A helper type for ethereum block inputs that consists of a block and the total difficulty. #[derive(Debug)] -pub struct EthBlockExecutionInput<'a, Block> { +pub struct BlockExecutionInput<'a, Block> { /// The block to execute. pub block: &'a Block, /// The total difficulty of the block. pub total_difficulty: U256, } -impl<'a, Block> EthBlockExecutionInput<'a, Block> { +impl<'a, Block> BlockExecutionInput<'a, Block> { /// Creates a new input. pub fn new(block: &'a Block, total_difficulty: U256) -> Self { Self { block, total_difficulty } } } -impl<'a, Block> From<(&'a Block, U256)> for EthBlockExecutionInput<'a, Block> { +impl<'a, Block> From<(&'a Block, U256)> for BlockExecutionInput<'a, Block> { fn from((block, total_difficulty): (&'a Block, U256)) -> Self { Self::new(block, total_difficulty) } } -/// A type that can create a new executor. -pub trait ExecutorProvider: Send + Sync + Clone { +/// A type that can create a new executor for block execution. +pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { /// An executor that can execute a single block given a database. - type Executor>: Executor; - /// An executor that can execute a batch of blocks given a database. + /// + /// # Verification + /// + /// The on [Executor::execute] the executor is expected to validate the execution output of the + /// input, this includes: + /// - Cumulative gas used must match the input's gas used. + /// - Receipts must match the input's receipts root. + /// + /// It is not expected to validate the state trie root, this must be done by the caller using + /// the returned state. + type Executor>: for<'a> Executor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BlockExecutionOutput, + Error = BlockExecutionError, + >; + + /// An executor that can execute a batch of blocks given a database. + type BatchExecutor>: for<'a> BatchExecutor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + // TODO: change to bundle state with receipts + Output = BatchBlockExecutionOutput, + Error = BlockExecutionError, + >; - type BatchExecutor>: BatchExecutor; /// Creates a new executor for single block execution. + /// + /// This is used to execute a single block and get the changed state. fn executor(&self, db: DB) -> Self::Executor where DB: Database; - /// Creates a new batch executor - fn batch_executor(&self, db: DB) -> Self::BatchExecutor + /// Creates a new batch executor with the given database and pruning modes. + /// + /// Batch executor is used to execute multiple blocks in sequence and keep track of the state + /// during historical sync which involves executing multiple blocks in sequence. + /// + /// The pruning modes are used to determine which parts of the state should be kept during + /// execution. + fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where DB: Database; } @@ -103,13 +158,14 @@ pub trait ExecutorProvider: Send + Sync + Clone { #[cfg(test)] mod tests { use super::*; + use reth_primitives::Block; use revm::db::{CacheDB, EmptyDBTyped}; use std::marker::PhantomData; #[derive(Clone, Default)] struct TestExecutorProvider; - impl ExecutorProvider for TestExecutorProvider { + impl BlockExecutorProvider for TestExecutorProvider { type Executor> = TestExecutor; type BatchExecutor> = TestExecutor; @@ -120,7 +176,7 @@ mod tests { TestExecutor(PhantomData) } - fn batch_executor(&self, _db: DB) -> Self::BatchExecutor + fn batch_executor(&self, _db: DB, _prune_modes: PruneModes) -> Self::BatchExecutor where DB: Database, { @@ -131,28 +187,35 @@ mod tests { struct TestExecutor(PhantomData); impl Executor for TestExecutor { - type Input<'a> = &'static str; - type Output = (); - type Error = String; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; + type Error = BlockExecutionError; fn execute(self, _input: Self::Input<'_>) -> Result { - Ok(()) + Err(BlockExecutionError::UnavailableForTest) } } impl BatchExecutor for TestExecutor { - type Input<'a> = &'static str; - type Output = (); - type Error = String; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BatchBlockExecutionOutput; + type Error = BlockExecutionError; - fn execute_one( - &mut self, - _input: Self::Input<'_>, - ) -> Result { - Ok(BatchBlockOutput { size_hint: None }) + fn execute_one(&mut self, _input: Self::Input<'_>) -> Result<(), Self::Error> { + Ok(()) } - fn finalize(self) -> Self::Output {} + fn finalize(self) -> Self::Output { + todo!() + } + + fn set_tip(&mut self, _tip: BlockNumber) { + todo!() + } + + fn size_hint(&self) -> Option { + None + } } #[test] @@ -160,6 +223,9 @@ mod tests { let provider = TestExecutorProvider; let db = CacheDB::>::default(); let executor = provider.executor(db); - executor.execute("test").unwrap(); + let block = + Block { header: Default::default(), body: vec![], ommers: vec![], withdrawals: None }; + let block = BlockWithSenders::new(block, Default::default()).unwrap(); + let _ = executor.execute(BlockExecutionInput::new(&block, U256::ZERO)); } } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index d8e50b759e..c69e33d652 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -12,8 +12,13 @@ use reth_primitives::{revm::env::fill_block_env, Address, ChainSpec, Header, Tra use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; +pub mod either; pub mod execute; +#[cfg(any(test, feature = "test-utils"))] +/// test helpers for mocking executor +pub mod test_utils; + /// Trait for configuring the EVM for executing full blocks. pub trait ConfigureEvm: ConfigureEvmEnv { /// Associated type for the default external context that should be configured for the EVM. diff --git a/crates/evm/src/test_utils.rs b/crates/evm/src/test_utils.rs new file mode 100644 index 0000000000..e0ee469170 --- /dev/null +++ b/crates/evm/src/test_utils.rs @@ -0,0 +1,80 @@ +//! Helpers for testing. + +use crate::execute::{ + BatchBlockExecutionOutput, BatchExecutor, BlockExecutionInput, BlockExecutionOutput, + BlockExecutorProvider, Executor, +}; +use parking_lot::Mutex; +use reth_interfaces::{executor::BlockExecutionError, provider::ProviderError}; +use reth_primitives::{BlockNumber, BlockWithSenders, PruneModes, Receipt}; +use revm_primitives::db::Database; +use std::sync::Arc; + +/// A [BlockExecutorProvider] that returns mocked execution results. +#[derive(Clone, Debug, Default)] +pub struct MockExecutorProvider { + exec_results: Arc>>, +} + +impl MockExecutorProvider { + /// Extend the mocked execution results + pub fn extend(&self, results: impl IntoIterator>) { + self.exec_results.lock().extend(results.into_iter().map(Into::into)); + } +} + +impl BlockExecutorProvider for MockExecutorProvider { + type Executor> = Self; + + type BatchExecutor> = Self; + + fn executor(&self, _: DB) -> Self::Executor + where + DB: Database, + { + self.clone() + } + + fn batch_executor(&self, _: DB, _: PruneModes) -> Self::BatchExecutor + where + DB: Database, + { + self.clone() + } +} + +impl Executor for MockExecutorProvider { + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; + type Error = BlockExecutionError; + + fn execute(self, _: Self::Input<'_>) -> Result { + let BatchBlockExecutionOutput { bundle, receipts, .. } = + self.exec_results.lock().pop().unwrap(); + Ok(BlockExecutionOutput { + state: bundle, + receipts: receipts.into_iter().flatten().flatten().collect(), + gas_used: 0, + }) + } +} + +impl BatchExecutor for MockExecutorProvider { + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BatchBlockExecutionOutput; + type Error = BlockExecutionError; + + fn execute_one(&mut self, _: Self::Input<'_>) -> Result<(), Self::Error> { + Ok(()) + } + + fn finalize(self) -> Self::Output { + self.exec_results.lock().pop().unwrap() + } + + fn set_tip(&mut self, _: BlockNumber) {} + + fn size_hint(&self) -> Option { + None + } +} diff --git a/crates/exex/src/context.rs b/crates/exex/src/context.rs index 733047400a..7cedb49778 100644 --- a/crates/exex/src/context.rs +++ b/crates/exex/src/context.rs @@ -53,11 +53,20 @@ impl FullNodeTypes for ExExContext { impl FullNodeComponents for ExExContext { type Pool = Node::Pool; type Evm = Node::Evm; + type Executor = Node::Executor; fn pool(&self) -> &Self::Pool { self.components.pool() } + fn evm_config(&self) -> &Self::Evm { + self.components.evm_config() + } + + fn block_executor(&self) -> &Self::Executor { + self.components.block_executor() + } + fn provider(&self) -> &Self::Provider { self.components.provider() } @@ -73,8 +82,4 @@ impl FullNodeComponents for ExExContext { fn task_executor(&self) -> &TaskExecutor { self.components.task_executor() } - - fn evm_config(&self) -> &Self::Evm { - self.components.evm_config() - } } diff --git a/crates/node-ethereum/src/evm.rs b/crates/node-ethereum/src/evm.rs index a5528d74a4..d710d8d8d4 100644 --- a/crates/node-ethereum/src/evm.rs +++ b/crates/node-ethereum/src/evm.rs @@ -1,4 +1,6 @@ //! Ethereum EVM support +#[doc(inline)] +pub use reth_evm_ethereum::execute::EthExecutorProvider; #[doc(inline)] pub use reth_evm_ethereum::EthEvmConfig; diff --git a/crates/node-ethereum/src/lib.rs b/crates/node-ethereum/src/lib.rs index cea2e7be0d..44ec6836c8 100644 --- a/crates/node-ethereum/src/lib.rs +++ b/crates/node-ethereum/src/lib.rs @@ -11,7 +11,7 @@ pub use reth_ethereum_engine_primitives::EthEngineTypes; pub mod evm; -pub use evm::EthEvmConfig; +pub use evm::{EthEvmConfig, EthExecutorProvider}; pub mod node; pub use node::EthereumNode; diff --git a/crates/node-ethereum/src/node.rs b/crates/node-ethereum/src/node.rs index 815b949de4..235130b426 100644 --- a/crates/node-ethereum/src/node.rs +++ b/crates/node-ethereum/src/node.rs @@ -2,6 +2,7 @@ use crate::{EthEngineTypes, EthEvmConfig}; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; +use reth_evm_ethereum::execute::EthExecutorProvider; use reth_network::NetworkHandle; use reth_node_builder::{ components::{ @@ -76,9 +77,18 @@ where Node: FullNodeTypes, { type EVM = EthEvmConfig; + type Executor = EthExecutorProvider; - async fn build_evm(self, _ctx: &BuilderContext) -> eyre::Result { - Ok(EthEvmConfig::default()) + async fn build_evm( + self, + ctx: &BuilderContext, + ) -> eyre::Result<(Self::EVM, Self::Executor)> { + let chain_spec = ctx.chain_spec(); + let evm_config = EthEvmConfig::default(); + let executor = + EthExecutorProvider::new(chain_spec, evm_config).with_inspector(ctx.inspector_stack()); + + Ok((evm_config, executor)) } } diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index 0a76f75046..355a7ecab0 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -5,6 +5,7 @@ use reth_db::{ database::Database, database_metrics::{DatabaseMetadata, DatabaseMetrics}, }; +use reth_evm::execute::BlockExecutorProvider; use reth_network::NetworkHandle; use reth_payload_builder::PayloadBuilderHandle; use reth_provider::FullProvider; @@ -88,12 +89,18 @@ pub trait FullNodeComponents: FullNodeTypes + 'static { /// The node's EVM configuration, defining settings for the Ethereum Virtual Machine. type Evm: ConfigureEvm; + /// The type that knows how to execute blocks. + type Executor: BlockExecutorProvider; + /// Returns the transaction pool of the node. fn pool(&self) -> &Self::Pool; /// Returns the node's evm config. fn evm_config(&self) -> &Self::Evm; + /// Returns the node's executor type. + fn block_executor(&self) -> &Self::Executor; + /// Returns the provider of the node. fn provider(&self) -> &Self::Provider; diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 136c27d7c4..68c1d5f0c3 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -17,6 +17,7 @@ reth-auto-seal-consensus.workspace = true reth-beacon-consensus.workspace = true reth-blockchain-tree.workspace = true reth-exex.workspace = true +reth-evm.workspace = true reth-provider.workspace = true reth-revm.workspace = true reth-db.workspace = true diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index b6f0a191e3..0457bbe3e9 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -27,6 +27,7 @@ use reth_node_core::{ }; use reth_primitives::{constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, ChainSpec}; use reth_provider::{providers::BlockchainProvider, ChainSpecProvider}; +use reth_revm::stack::{InspectorStack, InspectorStackConfig}; use reth_tasks::TaskExecutor; use reth_transaction_pool::{PoolConfig, TransactionPool}; pub use states::*; @@ -460,6 +461,28 @@ impl BuilderContext { &self.config } + /// Returns an inspector stack if configured. + /// + /// This can be used to debug block execution. + pub fn inspector_stack(&self) -> Option { + use reth_revm::stack::Hook; + let stack_config = InspectorStackConfig { + use_printer_tracer: self.config.debug.print_inspector, + hook: if let Some(hook_block) = self.config.debug.hook_block { + Hook::Block(hook_block) + } else if let Some(tx) = self.config.debug.hook_transaction { + Hook::Transaction(tx) + } else if self.config.debug.hook_all { + Hook::All + } else { + // no inspector + return None + }, + }; + + Some(InspectorStack::new(stack_config)) + } + /// Returns the data dir of the node. /// /// This gives access to all relevant files and directories of the node's datadir. diff --git a/crates/node/builder/src/builder/states.rs b/crates/node/builder/src/builder/states.rs index 753978de19..103e4f1746 100644 --- a/crates/node/builder/src/builder/states.rs +++ b/crates/node/builder/src/builder/states.rs @@ -98,6 +98,7 @@ impl> FullNodeTypes for NodeAdapter impl> FullNodeComponents for NodeAdapter { type Pool = C::Pool; type Evm = C::Evm; + type Executor = C::Executor; fn pool(&self) -> &Self::Pool { self.components.pool() @@ -107,6 +108,10 @@ impl> FullNodeComponents for NodeAdapter< self.components.evm_config() } + fn block_executor(&self) -> &Self::Executor { + self.components.block_executor() + } + fn provider(&self) -> &Self::Provider { &self.provider } diff --git a/crates/node/builder/src/components/builder.rs b/crates/node/builder/src/components/builder.rs index d17cdc8eea..abeb2ca054 100644 --- a/crates/node/builder/src/components/builder.rs +++ b/crates/node/builder/src/components/builder.rs @@ -7,6 +7,7 @@ use crate::{ }, BuilderContext, ConfigureEvm, FullNodeTypes, }; +use reth_evm::execute::BlockExecutorProvider; use reth_transaction_pool::TransactionPool; use std::{future::Future, marker::PhantomData}; @@ -232,7 +233,7 @@ where PayloadB: PayloadServiceBuilder, ExecB: ExecutorBuilder, { - type Components = Components; + type Components = Components; async fn build_components( self, @@ -246,12 +247,12 @@ where _marker, } = self; - let evm_config = evm_builder.build_evm(context).await?; + let (evm_config, executor) = evm_builder.build_evm(context).await?; let pool = pool_builder.build_pool(context).await?; let network = network_builder.build_network(context, pool.clone()).await?; let payload_builder = payload_builder.spawn_payload_service(context, pool.clone()).await?; - Ok(Components { transaction_pool: pool, evm_config, network, payload_builder }) + Ok(Components { transaction_pool: pool, evm_config, network, payload_builder, executor }) } } @@ -287,15 +288,16 @@ pub trait NodeComponentsBuilder: Send { ) -> impl Future> + Send; } -impl NodeComponentsBuilder for F +impl NodeComponentsBuilder for F where Node: FullNodeTypes, F: FnOnce(&BuilderContext) -> Fut + Send, - Fut: Future>> + Send, + Fut: Future>> + Send, Pool: TransactionPool + Unpin + 'static, EVM: ConfigureEvm, + Executor: BlockExecutorProvider, { - type Components = Components; + type Components = Components; fn build_components( self, diff --git a/crates/node/builder/src/components/execute.rs b/crates/node/builder/src/components/execute.rs index 01684e9c2b..891f8e01fe 100644 --- a/crates/node/builder/src/components/execute.rs +++ b/crates/node/builder/src/components/execute.rs @@ -1,34 +1,41 @@ //! EVM component for the node builder. use crate::{BuilderContext, FullNodeTypes}; +use reth_evm::execute::BlockExecutorProvider; use reth_node_api::ConfigureEvm; use std::future::Future; /// A type that knows how to build the executor types. pub trait ExecutorBuilder: Send { - /// The EVM config to build. + /// The EVM config to use. + /// + /// This provides the node with the necessary configuration to configure an EVM. type EVM: ConfigureEvm; - // TODO(mattsse): integrate `Executor` + + /// The type that knows how to execute blocks. + type Executor: BlockExecutorProvider; /// Creates the EVM config. fn build_evm( self, ctx: &BuilderContext, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } -impl ExecutorBuilder for F +impl ExecutorBuilder for F where Node: FullNodeTypes, EVM: ConfigureEvm, + Executor: BlockExecutorProvider, F: FnOnce(&BuilderContext) -> Fut + Send, - Fut: Future> + Send, + Fut: Future> + Send, { type EVM = EVM; + type Executor = Executor; fn build_evm( self, ctx: &BuilderContext, - ) -> impl Future> { + ) -> impl Future> { self(ctx) } } diff --git a/crates/node/builder/src/components/mod.rs b/crates/node/builder/src/components/mod.rs index 24d83da0da..ef5ea49956 100644 --- a/crates/node/builder/src/components/mod.rs +++ b/crates/node/builder/src/components/mod.rs @@ -13,6 +13,7 @@ pub use execute::*; pub use network::*; pub use payload::*; pub use pool::*; +use reth_evm::execute::BlockExecutorProvider; use reth_network::NetworkHandle; use reth_payload_builder::PayloadBuilderHandle; use reth_transaction_pool::TransactionPool; @@ -35,12 +36,18 @@ pub trait NodeComponents: Clone + Send + Sync + 'stati /// The node's EVM configuration, defining settings for the Ethereum Virtual Machine. type Evm: ConfigureEvm; + /// The type that knows how to execute blocks. + type Executor: BlockExecutorProvider; + /// Returns the transaction pool of the node. fn pool(&self) -> &Self::Pool; /// Returns the node's evm config. fn evm_config(&self) -> &Self::Evm; + /// Returns the node's executor type. + fn block_executor(&self) -> &Self::Executor; + /// Returns the handle to the network fn network(&self) -> &NetworkHandle; @@ -52,25 +59,29 @@ pub trait NodeComponents: Clone + Send + Sync + 'stati /// /// This provides access to all the components of the node. #[derive(Debug)] -pub struct Components { +pub struct Components { /// The transaction pool of the node. pub transaction_pool: Pool, /// The node's EVM configuration, defining settings for the Ethereum Virtual Machine. pub evm_config: EVM, + /// The node's executor type used to execute individual blocks and batches of blocks. + pub executor: Executor, /// The network implementation of the node. pub network: NetworkHandle, /// The handle to the payload builder service. pub payload_builder: PayloadBuilderHandle, } -impl NodeComponents for Components +impl NodeComponents for Components where Node: FullNodeTypes, Pool: TransactionPool + Unpin + 'static, EVM: ConfigureEvm, + Executor: BlockExecutorProvider, { type Pool = Pool; type Evm = EVM; + type Executor = Executor; fn pool(&self) -> &Self::Pool { &self.transaction_pool @@ -80,6 +91,10 @@ where &self.evm_config } + fn block_executor(&self) -> &Self::Executor { + &self.executor + } + fn network(&self) -> &NetworkHandle { &self.network } @@ -89,16 +104,18 @@ where } } -impl Clone for Components +impl Clone for Components where Node: FullNodeTypes, Pool: TransactionPool, EVM: ConfigureEvm, + Executor: BlockExecutorProvider, { fn clone(&self) -> Self { Self { transaction_pool: self.transaction_pool.clone(), evm_config: self.evm_config.clone(), + executor: self.executor.clone(), network: self.network.clone(), payload_builder: self.payload_builder.clone(), } diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 201965fa9b..e8c5b2967e 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -30,7 +30,6 @@ use reth_node_core::{ use reth_node_events::{cl::ConsensusLayerHealthEvents, node}; use reth_primitives::format_ether; use reth_provider::{providers::BlockchainProvider, CanonStateSubscriptions}; -use reth_revm::EvmProcessorFactory; use reth_rpc_engine_api::EngineApi; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, info}; @@ -157,7 +156,7 @@ where let tree_externals = TreeExternals::new( ctx.provider_factory().clone(), consensus.clone(), - EvmProcessorFactory::new(ctx.chain_spec(), components.evm_config().clone()), + components.block_executor().clone(), ); let tree = BlockchainTree::new(tree_externals, tree_config, ctx.prune_modes())? .with_sync_metrics_tx(sync_metrics_tx.clone()) @@ -303,7 +302,7 @@ where consensus_engine_tx.clone(), canon_state_notification_sender, mining_mode, - node_adapter.components.evm_config().clone(), + node_adapter.components.block_executor().clone(), ) .build(); @@ -318,7 +317,7 @@ where ctx.prune_config(), max_block, static_file_producer, - node_adapter.components.evm_config().clone(), + node_adapter.components.block_executor().clone(), pipeline_exex_handle, ) .await?; @@ -341,7 +340,7 @@ where ctx.prune_config(), max_block, static_file_producer, - node_adapter.components.evm_config().clone(), + node_adapter.components.block_executor().clone(), pipeline_exex_handle, ) .await?; diff --git a/crates/node/builder/src/setup.rs b/crates/node/builder/src/setup.rs index 03bf458933..8033ab1c68 100644 --- a/crates/node/builder/src/setup.rs +++ b/crates/node/builder/src/setup.rs @@ -1,6 +1,5 @@ //! Helpers for setting up parts of the node. -use crate::ConfigureEvm; use reth_config::{config::StageConfig, PruneConfig}; use reth_consensus::Consensus; use reth_db::database::Database; @@ -8,6 +7,7 @@ use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, headers::reverse_headers::ReverseHeadersDownloaderBuilder, }; +use reth_evm::execute::BlockExecutorProvider; use reth_exex::ExExManagerHandle; use reth_interfaces::p2p::{ bodies::{client::BodiesClient, downloader::BodyDownloader}, @@ -18,7 +18,6 @@ use reth_node_core::{ primitives::{BlockNumber, B256}, }; use reth_provider::{HeaderSyncMode, ProviderFactory}; -use reth_revm::stack::{Hook, InspectorStackConfig}; use reth_stages::{ prelude::DefaultStages, stages::{ @@ -36,7 +35,7 @@ use tokio::sync::watch; /// Constructs a [Pipeline] that's wired to the network #[allow(clippy::too_many_arguments)] -pub async fn build_networked_pipeline( +pub async fn build_networked_pipeline( node_config: &NodeConfig, config: &StageConfig, client: Client, @@ -47,13 +46,13 @@ pub async fn build_networked_pipeline( prune_config: Option, max_block: Option, static_file_producer: StaticFileProducer, - evm_config: EvmConfig, + executor: Executor, exex_manager_handle: ExExManagerHandle, ) -> eyre::Result> where DB: Database + Unpin + Clone + 'static, Client: HeadersClient + BodiesClient + Clone + 'static, - EvmConfig: ConfigureEvm + Clone + 'static, + Executor: BlockExecutorProvider, { // building network downloaders using the fetch client let header_downloader = ReverseHeadersDownloaderBuilder::new(config.headers) @@ -75,7 +74,7 @@ where metrics_tx, prune_config, static_file_producer, - evm_config, + executor, exex_manager_handle, ) .await?; @@ -85,7 +84,7 @@ where /// Builds the [Pipeline] with the given [ProviderFactory] and downloaders. #[allow(clippy::too_many_arguments)] -pub async fn build_pipeline( +pub async fn build_pipeline( node_config: &NodeConfig, provider_factory: ProviderFactory, stage_config: &StageConfig, @@ -96,14 +95,14 @@ pub async fn build_pipeline( metrics_tx: reth_stages::MetricEventsSender, prune_config: Option, static_file_producer: StaticFileProducer, - evm_config: EvmConfig, + executor: Executor, exex_manager_handle: ExExManagerHandle, ) -> eyre::Result> where DB: Database + Clone + 'static, H: HeaderDownloader + 'static, B: BodyDownloader + 'static, - EvmConfig: ConfigureEvm + Clone + 'static, + Executor: BlockExecutorProvider, { let mut builder = Pipeline::builder(); @@ -113,22 +112,6 @@ where } let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let factory = reth_revm::EvmProcessorFactory::new(node_config.chain.clone(), evm_config); - - let stack_config = InspectorStackConfig { - use_printer_tracer: node_config.debug.print_inspector, - hook: if let Some(hook_block) = node_config.debug.hook_block { - Hook::Block(hook_block) - } else if let Some(tx) = node_config.debug.hook_transaction { - Hook::Transaction(tx) - } else if node_config.debug.hook_all { - Hook::All - } else { - Hook::None - }, - }; - - let factory = factory.with_stack_config(stack_config); let prune_modes = prune_config.map(|prune| prune.segments).unwrap_or_default(); @@ -147,7 +130,7 @@ where Arc::clone(&consensus), header_downloader, body_downloader, - factory.clone(), + executor.clone(), stage_config.etl.clone(), ) .set(SenderRecoveryStage { @@ -155,7 +138,7 @@ where }) .set( ExecutionStage::new( - factory, + executor, ExecutionStageThresholds { max_blocks: stage_config.execution.max_blocks, max_changes: stage_config.execution.max_changes, diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index fbffa12455..8e5afc5efc 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -19,6 +19,7 @@ reth-interfaces.workspace = true reth-provider.workspace = true # Optimism +revm.workspace = true revm-primitives.workspace = true # misc diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 0a5e057806..2ea32782c7 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -1,10 +1,10 @@ //! Optimism block executor. -use crate::OptimismEvmConfig; +use crate::{l1::ensure_create2_deployer, verify::verify_receipts, OptimismEvmConfig}; use reth_evm::{ execute::{ - BatchBlockOutput, BatchExecutor, EthBlockExecutionInput, EthBlockOutput, Executor, - ExecutorProvider, + BatchBlockExecutionOutput, BatchExecutor, BlockExecutionInput, BlockExecutionOutput, + BlockExecutorProvider, Executor, }, ConfigureEvm, ConfigureEvmEnv, }; @@ -13,16 +13,12 @@ use reth_interfaces::{ provider::ProviderError, }; use reth_primitives::{ - proofs::calculate_receipt_root_optimism, BlockWithSenders, Bloom, Bytes, ChainSpec, - GotExpected, Hardfork, Header, PruneModes, Receipt, ReceiptWithBloom, Receipts, TxType, - Withdrawals, B256, U256, + BlockNumber, BlockWithSenders, Bytes, ChainSpec, GotExpected, Hardfork, Header, PruneModes, + Receipt, Receipts, TxType, Withdrawals, U256, }; -use reth_provider::BundleStateWithReceipts; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, db::states::bundle_state::BundleRetention, - optimism::ensure_create2_deployer, - processor::compare_receipts_root_and_logs_bloom, stack::InspectorStack, state_change::{apply_beacon_root_contract_call, post_block_balance_increments}, Evm, State, @@ -36,14 +32,13 @@ use tracing::{debug, trace}; /// Provides executors to execute regular ethereum blocks #[derive(Debug, Clone)] -pub struct OpExecutorProvider { +pub struct OpExecutorProvider { chain_spec: Arc, evm_config: EvmConfig, inspector: Option, - prune_modes: PruneModes, } -impl OpExecutorProvider { +impl OpExecutorProvider { /// Creates a new default optimism executor provider. pub fn optimism(chain_spec: Arc) -> Self { Self::new(chain_spec, Default::default()) @@ -53,7 +48,7 @@ impl OpExecutorProvider { impl OpExecutorProvider { /// Creates a new executor provider. pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { - Self { chain_spec, evm_config, inspector: None, prune_modes: PruneModes::none() } + Self { chain_spec, evm_config, inspector: None } } /// Configures an optional inspector stack for debugging. @@ -61,12 +56,6 @@ impl OpExecutorProvider { self.inspector = inspector; self } - - /// Configures the prune modes for the executor. - pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { - self.prune_modes = prune_modes; - self - } } impl OpExecutorProvider @@ -87,7 +76,7 @@ where } } -impl ExecutorProvider for OpExecutorProvider +impl BlockExecutorProvider for OpExecutorProvider where EvmConfig: ConfigureEvm, EvmConfig: ConfigureEvmEnv, @@ -102,14 +91,14 @@ where self.op_executor(db) } - fn batch_executor(&self, db: DB) -> Self::BatchExecutor + fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where DB: Database, { let executor = self.op_executor(db); OpBatchExecutor { executor, - batch_record: BlockBatchRecord::new(self.prune_modes.clone()), + batch_record: BlockBatchRecord::new(prune_modes), stats: BlockExecutorStats::default(), } } @@ -370,7 +359,7 @@ where // transaction This was replaced with is_success flag. // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 if self.chain_spec().is_byzantium_active_at_block(block.header.number) { - if let Err(error) = verify_receipt_optimism( + if let Err(error) = verify_receipts( block.header.receipts_root, block.header.logs_bloom, receipts.iter(), @@ -424,8 +413,8 @@ where EvmConfig: ConfigureEvmEnv, DB: Database, { - type Input<'a> = EthBlockExecutionInput<'a, BlockWithSenders>; - type Output = EthBlockOutput; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BlockExecutionOutput; type Error = BlockExecutionError; /// Executes the block and commits the state changes. @@ -436,13 +425,13 @@ where /// /// State changes are committed to the database. fn execute(mut self, input: Self::Input<'_>) -> Result { - let EthBlockExecutionInput { block, total_difficulty } = input; + let BlockExecutionInput { block, total_difficulty } = input; let (receipts, gas_used) = self.execute_and_verify(block, total_difficulty)?; - // prepare the state for extraction - self.state.merge_transitions(BundleRetention::PlainState); + // NOTE: we need to merge keep the reverts for the bundle retention + self.state.merge_transitions(BundleRetention::Reverts); - Ok(EthBlockOutput { state: self.state.take_bundle(), receipts, gas_used }) + Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, gas_used }) } } @@ -478,12 +467,12 @@ where EvmConfig: ConfigureEvmEnv, DB: Database, { - type Input<'a> = EthBlockExecutionInput<'a, BlockWithSenders>; - type Output = BundleStateWithReceipts; + type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; + type Output = BatchBlockExecutionOutput; type Error = BlockExecutionError; - fn execute_one(&mut self, input: Self::Input<'_>) -> Result { - let EthBlockExecutionInput { block, total_difficulty } = input; + fn execute_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> { + let BlockExecutionInput { block, total_difficulty } = input; let (receipts, _gas_used) = self.executor.execute_and_verify(block, total_difficulty)?; // prepare the state according to the prune mode @@ -493,45 +482,30 @@ where // store receipts in the set self.batch_record.save_receipts(receipts)?; - Ok(BatchBlockOutput { size_hint: Some(self.executor.state.bundle_size_hint()) }) + if self.batch_record.first_block().is_none() { + self.batch_record.set_first_block(block.number); + } + + Ok(()) } fn finalize(mut self) -> Self::Output { - // TODO: track stats self.stats.log_debug(); - BundleStateWithReceipts::new( + BatchBlockExecutionOutput::new( self.executor.state.take_bundle(), self.batch_record.take_receipts(), self.batch_record.first_block().unwrap_or_default(), ) } -} -/// Verify the calculated receipts root against the expected receipts root. -pub fn verify_receipt_optimism<'a>( - expected_receipts_root: B256, - expected_logs_bloom: Bloom, - receipts: impl Iterator + Clone, - chain_spec: &ChainSpec, - timestamp: u64, -) -> Result<(), BlockExecutionError> { - // Calculate receipts root. - let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); - let receipts_root = - calculate_receipt_root_optimism(&receipts_with_bloom, chain_spec, timestamp); + fn set_tip(&mut self, tip: BlockNumber) { + self.batch_record.set_tip(tip); + } - // Create header log bloom. - let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); - - compare_receipts_root_and_logs_bloom( - receipts_root, - logs_bloom, - expected_receipts_root, - expected_logs_bloom, - )?; - - Ok(()) + fn size_hint(&self) -> Option { + Some(self.executor.state.bundle_state.size_hint()) + } } #[cfg(test)] @@ -574,12 +548,7 @@ mod tests { } fn executor_provider(chain_spec: Arc) -> OpExecutorProvider { - OpExecutorProvider { - chain_spec, - evm_config: Default::default(), - inspector: None, - prune_modes: Default::default(), - } + OpExecutorProvider { chain_spec, evm_config: Default::default(), inspector: None } } #[test] @@ -626,7 +595,8 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = + provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); executor.state_mut().load_cache_account(L1_BLOCK_CONTRACT).unwrap(); @@ -706,7 +676,8 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = + provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); executor.state_mut().load_cache_account(L1_BLOCK_CONTRACT).unwrap(); diff --git a/crates/revm/src/optimism/mod.rs b/crates/optimism/evm/src/l1.rs similarity index 97% rename from crates/revm/src/optimism/mod.rs rename to crates/optimism/evm/src/l1.rs index 0dc6c68770..896cbc36ad 100644 --- a/crates/revm/src/optimism/mod.rs +++ b/crates/optimism/evm/src/l1.rs @@ -1,3 +1,5 @@ +//! Optimism-specific implementation and utilities for the executor + use reth_interfaces::{ executor::{self as reth_executor, BlockExecutionError}, RethError, @@ -10,14 +12,13 @@ use revm::{ use std::sync::Arc; use tracing::trace; -/// Optimism-specific processor implementation for the `EVMProcessor` -pub mod processor; - /// The address of the create2 deployer const CREATE_2_DEPLOYER_ADDR: Address = address!("13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"); + /// The codehash of the create2 deployer contract. const CREATE_2_DEPLOYER_CODEHASH: B256 = b256!("b0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2"); + /// The raw bytecode of the create2 deployer contract. const CREATE_2_DEPLOYER_BYTECODE: [u8; 1584] = hex!("6080604052600436106100435760003560e01c8063076c37b21461004f578063481286e61461007157806356299481146100ba57806366cfa057146100da57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a366004610327565b6100fa565b005b34801561007d57600080fd5b5061009161008c366004610327565b61014a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100c657600080fd5b506100916100d5366004610349565b61015d565b3480156100e657600080fd5b5061006f6100f53660046103ca565b610172565b61014582826040518060200161010f9061031a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604052610183565b505050565b600061015683836102e7565b9392505050565b600061016a8484846102f0565b949350505050565b61017d838383610183565b50505050565b6000834710156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b815160000361025f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016101eb565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016101eb565b60006101568383305b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b61014e806104ad83390190565b6000806040838503121561033a57600080fd5b50508035926020909101359150565b60008060006060848603121561035e57600080fd5b8335925060208401359150604084013573ffffffffffffffffffffffffffffffffffffffff8116811461039057600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156103df57600080fd5b8335925060208401359150604084013567ffffffffffffffff8082111561040557600080fd5b818601915086601f83011261041957600080fd5b81358181111561042b5761042b61039b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104715761047161039b565b8160405282815289602084870101111561048a57600080fd5b826020860160208301376000602084830101528095505050505050925092509256fe608060405234801561001057600080fd5b5061012e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063249cb3fa14602d575b600080fd5b603c603836600460b1565b604e565b60405190815260200160405180910390f35b60008281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915281205460ff16608857600060aa565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b9392505050565b6000806040838503121560c357600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811460ed57600080fd5b80915050925092905056fea26469706673582212205ffd4e6cede7d06a5daf93d48d0541fc68189eeb16608c1999a82063b666eb1164736f6c63430008130033a2646970667358221220fdc4a0fe96e3b21c108ca155438d37c9143fb01278a3c1d274948bad89c564ba64736f6c63430008130033"); @@ -75,21 +76,21 @@ pub fn parse_l1_info_tx_bedrock(data: &[u8]) -> Result( + expected_receipts_root: B256, + expected_logs_bloom: Bloom, + receipts: impl Iterator + Clone, + chain_spec: &ChainSpec, + timestamp: u64, +) -> Result<(), BlockExecutionError> { + // Calculate receipts root. + let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); + let receipts_root = + calculate_receipt_root_optimism(&receipts_with_bloom, chain_spec, timestamp); + + // Create header log bloom. + let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); + + compare_receipts_root_and_logs_bloom( + receipts_root, + logs_bloom, + expected_receipts_root, + expected_logs_bloom, + )?; + + Ok(()) +} + +/// Compare the calculated receipts root with the expected receipts root, also compare +/// the calculated logs bloom with the expected logs bloom. +pub fn compare_receipts_root_and_logs_bloom( + calculated_receipts_root: B256, + calculated_logs_bloom: Bloom, + expected_receipts_root: B256, + expected_logs_bloom: Bloom, +) -> Result<(), BlockExecutionError> { + if calculated_receipts_root != expected_receipts_root { + return Err(BlockValidationError::ReceiptRootDiff( + GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(), + ) + .into()) + } + + if calculated_logs_bloom != expected_logs_bloom { + return Err(BlockValidationError::BloomLogDiff( + GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(), + ) + .into()) + } + + Ok(()) +} diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 7e7d547030..a2cbc287cd 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -7,7 +7,7 @@ use crate::{ }; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; use reth_evm::ConfigureEvm; -use reth_evm_optimism::OptimismEvmConfig; +use reth_evm_optimism::{OpExecutorProvider, OptimismEvmConfig}; use reth_network::{NetworkHandle, NetworkManager}; use reth_node_builder::{ components::{ @@ -97,9 +97,18 @@ where Node: FullNodeTypes, { type EVM = OptimismEvmConfig; + type Executor = OpExecutorProvider; - async fn build_evm(self, _ctx: &BuilderContext) -> eyre::Result { - Ok(OptimismEvmConfig::default()) + async fn build_evm( + self, + ctx: &BuilderContext, + ) -> eyre::Result<(Self::EVM, Self::Executor)> { + let chain_spec = ctx.chain_spec(); + let evm_config = OptimismEvmConfig::default(); + let executor = + OpExecutorProvider::new(chain_spec, evm_config).with_inspector(ctx.inspector_stack()); + + Ok((evm_config, executor)) } } diff --git a/crates/optimism/node/src/txpool.rs b/crates/optimism/node/src/txpool.rs index 7ee1bb9ece..db6a6266ee 100644 --- a/crates/optimism/node/src/txpool.rs +++ b/crates/optimism/node/src/txpool.rs @@ -1,8 +1,9 @@ //! OP transaction pool types use parking_lot::RwLock; +use reth_evm_optimism::RethL1BlockInfo; use reth_primitives::{Block, ChainSpec, GotExpected, InvalidTransactionError, SealedBlock}; use reth_provider::{BlockReaderIdExt, StateProviderFactory}; -use reth_revm::{optimism::RethL1BlockInfo, L1BlockInfo}; +use reth_revm::L1BlockInfo; use reth_transaction_pool::{ CoinbaseTipOrdering, EthPoolTransaction, EthPooledTransaction, EthTransactionValidator, Pool, TransactionOrigin, TransactionValidationOutcome, TransactionValidationTaskExecutor, @@ -75,7 +76,7 @@ where /// Update the L1 block info. fn update_l1_block_info(&self, block: &Block) { self.block_info.timestamp.store(block.timestamp, Ordering::Relaxed); - if let Ok(cost_addition) = reth_revm::optimism::extract_l1_info(block) { + if let Ok(cost_addition) = reth_evm_optimism::extract_l1_info(block) { *self.block_info.l1_block_info.write() = cost_addition; } } diff --git a/crates/payload/optimism/Cargo.toml b/crates/payload/optimism/Cargo.toml index ebc776e746..c58d0ecb58 100644 --- a/crates/payload/optimism/Cargo.toml +++ b/crates/payload/optimism/Cargo.toml @@ -21,6 +21,7 @@ reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-engine-primitives.workspace = true reth-evm.workspace = true +reth-evm-optimism.workspace = true reth-payload-builder.workspace = true reth-basic-payload-builder.workspace = true @@ -39,4 +40,5 @@ optimism = [ "reth-revm/optimism", "reth-provider/optimism", "reth-rpc-types-compat/optimism", + "reth-evm-optimism/optimism", ] \ No newline at end of file diff --git a/crates/payload/optimism/src/builder.rs b/crates/payload/optimism/src/builder.rs index 8e8bfb8f0f..2794ad9689 100644 --- a/crates/payload/optimism/src/builder.rs +++ b/crates/payload/optimism/src/builder.rs @@ -303,7 +303,7 @@ where // blocks will always have at least a single transaction in them (the L1 info transaction), // so we can safely assume that this will always be triggered upon the transition and that // the above check for empty blocks will never be hit on OP chains. - reth_revm::optimism::ensure_create2_deployer( + reth_evm_optimism::ensure_create2_deployer( chain_spec.clone(), attributes.payload_attributes.timestamp, &mut db, diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 5c62f324eb..151d53a978 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -17,7 +17,7 @@ reth-primitives.workspace = true reth-interfaces.workspace = true reth-provider.workspace = true reth-consensus-common.workspace = true -reth-evm.workspace = true +reth-evm = { workspace = true, optional = true } reth-trie = { workspace = true, optional = true } # revm @@ -28,10 +28,11 @@ revm-inspectors.workspace = true tracing.workspace = true [dev-dependencies] +reth-evm.workspace = true reth-trie.workspace = true [features] -test-utils = ["dep:reth-trie"] +test-utils = ["dep:reth-trie", "dep:reth-evm"] optimism = [ "revm/optimism", "reth-primitives/optimism", diff --git a/crates/revm/src/factory.rs b/crates/revm/src/factory.rs deleted file mode 100644 index fdaae52c0e..0000000000 --- a/crates/revm/src/factory.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{ - database::StateProviderDatabase, - processor::EVMProcessor, - stack::{InspectorStack, InspectorStackConfig}, -}; -use reth_evm::ConfigureEvm; -use reth_interfaces::executor::BlockExecutionError; -use reth_primitives::ChainSpec; -use reth_provider::{ExecutorFactory, PrunableBlockExecutor, StateProvider}; -use std::sync::Arc; - -/// Factory for creating [EVMProcessor]. -#[derive(Clone, Debug)] -pub struct EvmProcessorFactory { - chain_spec: Arc, - stack: Option, - /// Type that defines how the produced EVM should be configured. - evm_config: EvmConfig, -} - -impl EvmProcessorFactory { - /// Create new factory - pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { - Self { chain_spec, stack: None, evm_config } - } - - /// Sets the inspector stack for all generated executors. - pub fn with_stack(mut self, stack: InspectorStack) -> Self { - self.stack = Some(stack); - self - } - - /// Sets the inspector stack for all generated executors using the provided config. - pub fn with_stack_config(mut self, config: InspectorStackConfig) -> Self { - self.stack = Some(InspectorStack::new(config)); - self - } -} - -impl ExecutorFactory for EvmProcessorFactory -where - EvmConfig: ConfigureEvm + Send + Sync + Clone + 'static, -{ - fn with_state<'a, SP: StateProvider + 'a>( - &'a self, - sp: SP, - ) -> Box + 'a> { - let database_state = StateProviderDatabase::new(sp); - let mut evm = - EVMProcessor::new_with_db(self.chain_spec.clone(), database_state, &self.evm_config); - if let Some(stack) = &self.stack { - evm.set_stack(stack.clone()); - } - Box::new(evm) - } -} diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index f4ed01ada2..d8c5761d03 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -11,20 +11,11 @@ /// Contains glue code for integrating reth database into revm's [Database]. pub mod database; -/// revm implementation of reth block and transaction executors. -mod factory; - pub mod batch; -/// new revm account state executor -pub mod processor; - /// State changes that are not related to transactions. pub mod state_change; -/// revm executor factory. -pub use factory::EvmProcessorFactory; - /// Ethereum DAO hardfork state change data. pub mod eth_dao_fork; @@ -33,10 +24,6 @@ pub mod eth_dao_fork; /// used in the main Reth executor. pub mod stack; -/// Optimism-specific implementation and utilities for the executor -#[cfg(feature = "optimism")] -pub mod optimism; - /// Common test helpers #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/crates/revm/src/optimism/processor.rs b/crates/revm/src/optimism/processor.rs deleted file mode 100644 index 9fe51d059c..0000000000 --- a/crates/revm/src/optimism/processor.rs +++ /dev/null @@ -1,401 +0,0 @@ -use crate::processor::{compare_receipts_root_and_logs_bloom, EVMProcessor}; -use reth_evm::ConfigureEvm; -use reth_interfaces::executor::{ - BlockExecutionError, BlockValidationError, OptimismBlockExecutionError, -}; -use reth_primitives::{ - proofs::calculate_receipt_root_optimism, revm_primitives::ResultAndState, BlockWithSenders, - Bloom, ChainSpec, Hardfork, Receipt, ReceiptWithBloom, TxType, B256, U256, -}; -use reth_provider::{BlockExecutor, BundleStateWithReceipts}; -use revm::DatabaseCommit; -use std::time::Instant; -use tracing::{debug, trace}; - -/// Verify the calculated receipts root against the expected receipts root. -pub fn verify_receipt_optimism<'a>( - expected_receipts_root: B256, - expected_logs_bloom: Bloom, - receipts: impl Iterator + Clone, - chain_spec: &ChainSpec, - timestamp: u64, -) -> Result<(), BlockExecutionError> { - // Calculate receipts root. - let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); - let receipts_root = - calculate_receipt_root_optimism(&receipts_with_bloom, chain_spec, timestamp); - - // Create header log bloom. - let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); - - compare_receipts_root_and_logs_bloom( - receipts_root, - logs_bloom, - expected_receipts_root, - expected_logs_bloom, - )?; - - Ok(()) -} - -impl<'a, EvmConfig> BlockExecutor for EVMProcessor<'a, EvmConfig> -where - EvmConfig: ConfigureEvm, -{ - type Error = BlockExecutionError; - - fn execute_and_verify_receipt( - &mut self, - block: &BlockWithSenders, - total_difficulty: U256, - ) -> Result<(), BlockExecutionError> { - // execute block - let receipts = self.execute_inner(block, total_difficulty)?; - - // TODO Before Byzantium, receipts contained state root that would mean that expensive - // operation as hashing that is needed for state root got calculated in every - // transaction This was replaced with is_success flag. - // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 - if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) { - let time = Instant::now(); - if let Err(error) = verify_receipt_optimism( - block.header.receipts_root, - block.header.logs_bloom, - receipts.iter(), - self.chain_spec.as_ref(), - block.timestamp, - ) { - debug!(target: "evm", %error, ?receipts, "receipts verification failed"); - return Err(error) - }; - self.stats.receipt_root_duration += time.elapsed(); - } - - self.batch_record.save_receipts(receipts) - } - - fn execute_transactions( - &mut self, - block: &BlockWithSenders, - total_difficulty: U256, - ) -> Result<(Vec, u64), BlockExecutionError> { - self.init_env(&block.header, total_difficulty); - - // perf: do not execute empty blocks - if block.body.is_empty() { - return Ok((Vec::new(), 0)) - } - - let is_regolith = - self.chain_spec.fork(Hardfork::Regolith).active_at_timestamp(block.timestamp); - - // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - // blocks will always have at least a single transaction in them (the L1 info transaction), - // so we can safely assume that this will always be triggered upon the transition and that - // the above check for empty blocks will never be hit on OP chains. - super::ensure_create2_deployer(self.chain_spec().clone(), block.timestamp, self.db_mut()) - .map_err(|_| { - BlockExecutionError::OptimismBlockExecution( - OptimismBlockExecutionError::ForceCreate2DeployerFail, - ) - })?; - - let mut cumulative_gas_used = 0; - let mut receipts = Vec::with_capacity(block.body.len()); - for (sender, transaction) in block.transactions_with_sender() { - let time = Instant::now(); - // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, - // must be no greater than the block’s gasLimit. - let block_available_gas = block.header.gas_limit - cumulative_gas_used; - if transaction.gas_limit() > block_available_gas && - (is_regolith || !transaction.is_system_transaction()) - { - return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { - transaction_gas_limit: transaction.gas_limit(), - block_available_gas, - } - .into()) - } - - // An optimism block should never contain blob transactions. - if matches!(transaction.tx_type(), TxType::Eip4844) { - return Err(BlockExecutionError::OptimismBlockExecution( - OptimismBlockExecutionError::BlobTransactionRejected, - )) - } - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (is_regolith && transaction.is_deposit()) - .then(|| { - self.db_mut() - .load_cache_account(*sender) - .map(|acc| acc.account_info().unwrap_or_default()) - }) - .transpose() - .map_err(|_| { - BlockExecutionError::OptimismBlockExecution( - OptimismBlockExecutionError::AccountLoadFailed(*sender), - ) - })?; - - // Execute transaction. - let ResultAndState { result, state } = self.transact(transaction, *sender)?; - trace!( - target: "evm", - ?transaction, ?result, ?state, - "Executed transaction" - ); - self.stats.execution_duration += time.elapsed(); - let time = Instant::now(); - - self.db_mut().commit(state); - - self.stats.apply_state_duration += time.elapsed(); - - // append gas used - cumulative_gas_used += result.gas_used(); - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Receipt { - tx_type: transaction.tx_type(), - // Success flag was added in `EIP-658: Embedding transaction status code in - // receipts`. - success: result.is_success(), - cumulative_gas_used, - // convert to reth log - logs: result.into_logs().into_iter().map(Into::into).collect(), - #[cfg(feature = "optimism")] - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to how - // receipt hashes should be computed when set. The state transition process ensures - // this is only set for post-Canyon deposit transactions. - #[cfg(feature = "optimism")] - deposit_receipt_version: (transaction.is_deposit() && - self.chain_spec() - .is_fork_active_at_timestamp(Hardfork::Canyon, block.timestamp)) - .then_some(1), - }); - } - - Ok((receipts, cumulative_gas_used)) - } - - fn take_output_state(&mut self) -> BundleStateWithReceipts { - BundleStateWithReceipts::new( - self.evm.context.evm.db.take_bundle(), - self.batch_record.take_receipts(), - self.batch_record.first_block().unwrap_or_default(), - ) - } - - fn size_hint(&self) -> Option { - Some(self.evm.context.evm.db.bundle_size_hint()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - database::StateProviderDatabase, - test_utils::{StateProviderTest, TestEvmConfig}, - }; - use reth_primitives::{ - b256, Account, Address, Block, ChainSpecBuilder, Header, Signature, StorageKey, - StorageValue, Transaction, TransactionSigned, TxEip1559, BASE_MAINNET, - }; - use revm::L1_BLOCK_CONTRACT; - use std::{collections::HashMap, str::FromStr, sync::Arc}; - - fn create_op_state_provider() -> StateProviderTest { - let mut db = StateProviderTest::default(); - - let l1_block_contract_account = - Account { balance: U256::ZERO, bytecode_hash: None, nonce: 1 }; - - let mut l1_block_storage = HashMap::new(); - // base fee - l1_block_storage.insert(StorageKey::with_last_byte(1), StorageValue::from(1000000000)); - // l1 fee overhead - l1_block_storage.insert(StorageKey::with_last_byte(5), StorageValue::from(188)); - // l1 fee scalar - l1_block_storage.insert(StorageKey::with_last_byte(6), StorageValue::from(684000)); - // l1 free scalars post ecotone - l1_block_storage.insert( - StorageKey::with_last_byte(3), - StorageValue::from_str( - "0x0000000000000000000000000000000000001db0000d27300000000000000005", - ) - .unwrap(), - ); - - db.insert_account(L1_BLOCK_CONTRACT, l1_block_contract_account, None, l1_block_storage); - - db - } - - fn create_op_evm_processor<'a>( - chain_spec: Arc, - db: StateProviderTest, - ) -> EVMProcessor<'a, TestEvmConfig> { - static CONFIG: std::sync::OnceLock = std::sync::OnceLock::new(); - let mut executor = EVMProcessor::new_with_db( - chain_spec, - StateProviderDatabase::new(db), - CONFIG.get_or_init(TestEvmConfig::default), - ); - executor.evm.context.evm.db.load_cache_account(L1_BLOCK_CONTRACT).unwrap(); - executor - } - - #[test] - fn op_deposit_fields_pre_canyon() { - let header = Header { - timestamp: 1, - number: 1, - gas_limit: 1_000_000, - gas_used: 42_000, - receipts_root: b256!( - "83465d1e7d01578c0d609be33570f91242f013e9e295b0879905346abbd63731" - ), - ..Default::default() - }; - - let mut db = create_op_state_provider(); - - let addr = Address::ZERO; - let account = Account { balance: U256::MAX, ..Account::default() }; - db.insert_account(addr, account, None, HashMap::new()); - - let chain_spec = - Arc::new(ChainSpecBuilder::from(&*BASE_MAINNET).regolith_activated().build()); - - let tx = TransactionSigned::from_transaction_and_signature( - Transaction::Eip1559(TxEip1559 { - chain_id: chain_spec.chain.id(), - nonce: 0, - gas_limit: 21_000, - to: addr.into(), - ..Default::default() - }), - Signature::default(), - ); - - let tx_deposit = TransactionSigned::from_transaction_and_signature( - Transaction::Deposit(reth_primitives::TxDeposit { - from: addr, - to: addr.into(), - gas_limit: 21_000, - ..Default::default() - }), - Signature::default(), - ); - - let mut executor = create_op_evm_processor(chain_spec, db); - - // Attempt to execute a block with one deposit and one non-deposit transaction - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header, - body: vec![tx, tx_deposit], - ommers: vec![], - withdrawals: None, - }, - senders: vec![addr, addr], - }, - U256::ZERO, - ) - .unwrap(); - - let tx_receipt = executor.receipts()[0][0].as_ref().unwrap(); - let deposit_receipt = executor.receipts()[0][1].as_ref().unwrap(); - - // deposit_receipt_version is not present in pre canyon transactions - assert!(deposit_receipt.deposit_receipt_version.is_none()); - assert!(tx_receipt.deposit_receipt_version.is_none()); - - // deposit_nonce is present only in deposit transactions - assert!(deposit_receipt.deposit_nonce.is_some()); - assert!(tx_receipt.deposit_nonce.is_none()); - } - - #[test] - fn op_deposit_fields_post_canyon() { - // ensure_create2_deployer will fail if timestamp is set to less then 2 - let header = Header { - timestamp: 2, - number: 1, - gas_limit: 1_000_000, - gas_used: 42_000, - receipts_root: b256!( - "fffc85c4004fd03c7bfbe5491fae98a7473126c099ac11e8286fd0013f15f908" - ), - ..Default::default() - }; - - let mut db = create_op_state_provider(); - let addr = Address::ZERO; - let account = Account { balance: U256::MAX, ..Account::default() }; - - db.insert_account(addr, account, None, HashMap::new()); - - let chain_spec = - Arc::new(ChainSpecBuilder::from(&*BASE_MAINNET).canyon_activated().build()); - - let tx = TransactionSigned::from_transaction_and_signature( - Transaction::Eip1559(TxEip1559 { - chain_id: chain_spec.chain.id(), - nonce: 0, - gas_limit: 21_000, - to: addr.into(), - ..Default::default() - }), - Signature::default(), - ); - - let tx_deposit = TransactionSigned::from_transaction_and_signature( - Transaction::Deposit(reth_primitives::TxDeposit { - from: addr, - to: addr.into(), - gas_limit: 21_000, - ..Default::default() - }), - Signature::optimism_deposit_tx_signature(), - ); - - let mut executor = create_op_evm_processor(chain_spec, db); - - // attempt to execute an empty block with parent beacon block root, this should not fail - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header, - body: vec![tx, tx_deposit], - ommers: vec![], - withdrawals: None, - }, - senders: vec![addr, addr], - }, - U256::ZERO, - ) - .expect("Executing a block while canyon is active should not fail"); - - let tx_receipt = executor.receipts()[0][0].as_ref().unwrap(); - let deposit_receipt = executor.receipts()[0][1].as_ref().unwrap(); - - // deposit_receipt_version is set to 1 for post canyon deposit transactions - assert_eq!(deposit_receipt.deposit_receipt_version, Some(1)); - assert!(tx_receipt.deposit_receipt_version.is_none()); - - // deposit_nonce is present only in deposit transactions - assert!(deposit_receipt.deposit_nonce.is_some()); - assert!(tx_receipt.deposit_nonce.is_none()); - } -} diff --git a/crates/revm/src/processor.rs b/crates/revm/src/processor.rs deleted file mode 100644 index 487cec5280..0000000000 --- a/crates/revm/src/processor.rs +++ /dev/null @@ -1,865 +0,0 @@ -#[cfg(not(feature = "optimism"))] -use revm::DatabaseCommit; -use revm::{ - db::StateDBBox, - inspector_handle_register, - interpreter::Host, - primitives::{CfgEnvWithHandlerCfg, ResultAndState}, - Evm, State, -}; -use std::{marker::PhantomData, sync::Arc, time::Instant}; -#[cfg(not(feature = "optimism"))] -use tracing::{debug, trace}; - -use reth_evm::ConfigureEvm; -use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; -#[cfg(feature = "optimism")] -use reth_primitives::revm::env::fill_op_tx_env; -#[cfg(not(feature = "optimism"))] -use reth_primitives::revm::env::fill_tx_env; -use reth_primitives::{ - Address, Block, BlockNumber, BlockWithSenders, Bloom, ChainSpec, GotExpected, Hardfork, Header, - PruneModes, Receipt, ReceiptWithBloom, Receipts, TransactionSigned, Withdrawals, B256, U256, -}; -#[cfg(not(feature = "optimism"))] -use reth_provider::BundleStateWithReceipts; -use reth_provider::{BlockExecutor, ProviderError, PrunableBlockExecutor, StateProvider}; - -use crate::{ - batch::{BlockBatchRecord, BlockExecutorStats}, - database::StateProviderDatabase, - eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, - stack::{InspectorStack, InspectorStackConfig}, - state_change::{apply_beacon_root_contract_call, post_block_balance_increments}, -}; - -/// EVMProcessor is a block executor that uses revm to execute blocks or multiple blocks. -/// -/// Output is obtained by calling `take_output_state` function. -/// -/// It is capable of pruning the data that will be written to the database -/// and implemented [PrunableBlockExecutor] traits. -/// -/// It implemented the [BlockExecutor] that give it the ability to take block -/// apply pre state (Cancun system contract call), execute transaction and apply -/// state change and then apply post execution changes (block reward, withdrawals, irregular DAO -/// hardfork state change). And if `execute_and_verify_receipt` is called it will verify the -/// receipt. -/// -/// InspectorStack are used for optional inspecting execution. And it contains -/// various duration of parts of execution. -#[allow(missing_debug_implementations)] -pub struct EVMProcessor<'a, EvmConfig> { - /// The configured chain-spec - pub(crate) chain_spec: Arc, - /// revm instance that contains database and env environment. - pub(crate) evm: Evm<'a, InspectorStack, StateDBBox<'a, ProviderError>>, - /// Keeps track of the recorded receipts and pruning configuration. - pub(crate) batch_record: BlockBatchRecord, - /// Execution stats - pub(crate) stats: BlockExecutorStats, - /// The type that is able to configure the EVM environment. - _phantom: PhantomData, -} - -impl<'a, EvmConfig> EVMProcessor<'a, EvmConfig> -where - EvmConfig: ConfigureEvm, -{ - /// Return chain spec. - pub fn chain_spec(&self) -> &Arc { - &self.chain_spec - } - - /// Creates a new executor from the given chain spec and database. - pub fn new_with_db( - chain_spec: Arc, - db: StateProviderDatabase, - evm_config: &'a EvmConfig, - ) -> Self { - let state = State::builder() - .with_database_boxed(Box::new(db)) - .with_bundle_update() - .without_state_clear() - .build(); - EVMProcessor::new_with_state(chain_spec, state, evm_config) - } - - /// Create a new EVM processor with the given revm state. - pub fn new_with_state( - chain_spec: Arc, - revm_state: StateDBBox<'a, ProviderError>, - evm_config: &'a EvmConfig, - ) -> Self { - let stack = InspectorStack::new(InspectorStackConfig::default()); - let evm = evm_config.evm_with_inspector(revm_state, stack); - EVMProcessor { - chain_spec, - evm, - batch_record: BlockBatchRecord::default(), - stats: BlockExecutorStats::default(), - _phantom: PhantomData, - } - } - - /// Configures the executor with the given inspectors. - pub fn set_stack(&mut self, stack: InspectorStack) { - self.evm.context.external = stack; - } - - /// Configure the executor with the given block. - pub fn set_first_block(&mut self, num: BlockNumber) { - self.batch_record.set_first_block(num); - } - - /// Saves the receipts to the batch record. - pub fn save_receipts(&mut self, receipts: Vec) -> Result<(), BlockExecutionError> { - self.batch_record.save_receipts(receipts) - } - - /// Returns the recorded receipts. - pub fn receipts(&self) -> &Receipts { - self.batch_record.receipts() - } - - /// Returns a reference to the database - pub fn db_mut(&mut self) -> &mut StateDBBox<'a, ProviderError> { - &mut self.evm.context.evm.db - } - - /// Initializes the config and block env. - pub(crate) fn init_env(&mut self, header: &Header, total_difficulty: U256) { - // Set state clear flag. - let state_clear_flag = - self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(header.number); - - self.db_mut().set_state_clear_flag(state_clear_flag); - - let mut cfg = - CfgEnvWithHandlerCfg::new_with_spec_id(self.evm.cfg().clone(), self.evm.spec_id()); - EvmConfig::fill_cfg_and_block_env( - &mut cfg, - self.evm.block_mut(), - &self.chain_spec, - header, - total_difficulty, - ); - *self.evm.cfg_mut() = cfg.cfg_env; - - // This will update the spec in case it changed - self.evm.modify_spec_id(cfg.handler_cfg.spec_id); - } - - /// Applies the pre-block call to the EIP-4788 beacon block root contract. - /// - /// If cancun is not activated or the block is the genesis block, then this is a no-op, and no - /// state changes are made. - fn apply_beacon_root_contract_call( - &mut self, - block: &Block, - ) -> Result<(), BlockExecutionError> { - apply_beacon_root_contract_call( - &self.chain_spec, - block.timestamp, - block.number, - block.parent_beacon_block_root, - &mut self.evm, - )?; - Ok(()) - } - - /// Apply post execution state changes, including block rewards, withdrawals, and irregular DAO - /// hardfork state change. - pub fn apply_post_execution_state_change( - &mut self, - block: &Block, - total_difficulty: U256, - ) -> Result<(), BlockExecutionError> { - let mut balance_increments = post_block_balance_increments( - &self.chain_spec, - block.number, - block.difficulty, - block.beneficiary, - block.timestamp, - total_difficulty, - &block.ommers, - block.withdrawals.as_ref().map(Withdrawals::as_ref), - ); - - // Irregular state change at Ethereum DAO hardfork - if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(block.number) { - // drain balances from hardcoded addresses. - let drained_balance: u128 = self - .db_mut() - .drain_balances(DAO_HARDKFORK_ACCOUNTS) - .map_err(|_| BlockValidationError::IncrementBalanceFailed)? - .into_iter() - .sum(); - - // return balance to DAO beneficiary. - *balance_increments.entry(DAO_HARDFORK_BENEFICIARY).or_default() += drained_balance; - } - // increment balances - self.db_mut() - .increment_balances(balance_increments) - .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; - - Ok(()) - } - - /// Runs a single transaction in the configured environment and proceeds - /// to return the result and state diff (without applying it). - /// - /// Assumes the rest of the block environment has been filled via `init_block_env`. - pub fn transact( - &mut self, - transaction: &TransactionSigned, - sender: Address, - ) -> Result { - // Fill revm structure. - #[cfg(not(feature = "optimism"))] - fill_tx_env(self.evm.tx_mut(), transaction, sender); - - #[cfg(feature = "optimism")] - { - let mut envelope_buf = Vec::with_capacity(transaction.length_without_header()); - transaction.encode_enveloped(&mut envelope_buf); - fill_op_tx_env(self.evm.tx_mut(), transaction, sender, envelope_buf.into()); - } - - let hash = transaction.hash_ref(); - let should_inspect = self.evm.context.external.should_inspect(self.evm.env(), hash); - let out = if should_inspect { - // push inspector handle register. - self.evm.handler.append_handler_register_plain(inspector_handle_register); - let output = self.evm.transact(); - tracing::trace!( - target: "evm", - %hash, ?output, ?transaction, env = ?self.evm.context.evm.env, - "Executed transaction" - ); - // pop last handle register - self.evm.handler.pop_handle_register(); - output - } else { - // Main execution without needing the hash - self.evm.transact() - }; - - out.map_err(move |e| { - // Ensure hash is calculated for error log, if not already done - BlockValidationError::EVM { hash: transaction.recalculate_hash(), error: e.into() } - .into() - }) - } - - /// Execute the block, verify gas usage and apply post-block state changes. - pub(crate) fn execute_inner( - &mut self, - block: &BlockWithSenders, - total_difficulty: U256, - ) -> Result, BlockExecutionError> { - self.init_env(&block.header, total_difficulty); - self.apply_beacon_root_contract_call(block)?; - let (receipts, cumulative_gas_used) = self.execute_transactions(block, total_difficulty)?; - - // Check if gas used matches the value set in header. - if block.gas_used != cumulative_gas_used { - let receipts = Receipts::from_block_receipt(receipts); - return Err(BlockValidationError::BlockGasUsed { - gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used }, - gas_spent_by_tx: receipts.gas_spent_by_tx()?, - } - .into()) - } - let time = Instant::now(); - self.apply_post_execution_state_change(block, total_difficulty)?; - self.stats.apply_post_execution_state_changes_duration += time.elapsed(); - - let time = Instant::now(); - let retention = self.batch_record.bundle_retention(block.number); - self.db_mut().merge_transitions(retention); - self.stats.merge_transitions_duration += time.elapsed(); - - if self.batch_record.first_block().is_none() { - self.batch_record.set_first_block(block.number); - } - - Ok(receipts) - } -} - -/// Default Ethereum implementation of the [BlockExecutor] trait for the [EVMProcessor]. -#[cfg(not(feature = "optimism"))] -impl<'a, EvmConfig> BlockExecutor for EVMProcessor<'a, EvmConfig> -where - EvmConfig: ConfigureEvm, -{ - type Error = BlockExecutionError; - - fn execute_and_verify_receipt( - &mut self, - block: &BlockWithSenders, - total_difficulty: U256, - ) -> Result<(), BlockExecutionError> { - // execute block - let receipts = self.execute_inner(block, total_difficulty)?; - - // TODO Before Byzantium, receipts contained state root that would mean that expensive - // operation as hashing that is needed for state root got calculated in every - // transaction This was replaced with is_success flag. - // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 - if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) { - let time = Instant::now(); - if let Err(error) = - verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts.iter()) - { - debug!(target: "evm", %error, ?receipts, "receipts verification failed"); - return Err(error) - }; - self.stats.receipt_root_duration += time.elapsed(); - } - - self.batch_record.save_receipts(receipts)?; - Ok(()) - } - - fn execute_transactions( - &mut self, - block: &BlockWithSenders, - total_difficulty: U256, - ) -> Result<(Vec, u64), BlockExecutionError> { - self.init_env(&block.header, total_difficulty); - - // perf: do not execute empty blocks - if block.body.is_empty() { - return Ok((Vec::new(), 0)) - } - - let mut cumulative_gas_used = 0; - let mut receipts = Vec::with_capacity(block.body.len()); - for (sender, transaction) in block.transactions_with_sender() { - let time = Instant::now(); - // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, - // must be no greater than the block’s gasLimit. - let block_available_gas = block.header.gas_limit - cumulative_gas_used; - if transaction.gas_limit() > block_available_gas { - return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { - transaction_gas_limit: transaction.gas_limit(), - block_available_gas, - } - .into()) - } - // Execute transaction. - let ResultAndState { result, state } = self.transact(transaction, *sender)?; - trace!( - target: "evm", - ?transaction, ?result, ?state, - "Executed transaction" - ); - self.stats.execution_duration += time.elapsed(); - let time = Instant::now(); - - self.db_mut().commit(state); - - self.stats.apply_state_duration += time.elapsed(); - - // append gas used - cumulative_gas_used += result.gas_used(); - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Receipt { - tx_type: transaction.tx_type(), - // Success flag was added in `EIP-658: Embedding transaction status code in - // receipts`. - success: result.is_success(), - cumulative_gas_used, - // convert to reth log - logs: result.into_logs().into_iter().map(Into::into).collect(), - }); - } - - Ok((receipts, cumulative_gas_used)) - } - - fn take_output_state(&mut self) -> BundleStateWithReceipts { - self.stats.log_debug(); - BundleStateWithReceipts::new( - self.evm.context.evm.db.take_bundle(), - self.batch_record.take_receipts(), - self.batch_record.first_block().unwrap_or_default(), - ) - } - - fn size_hint(&self) -> Option { - Some(self.evm.context.evm.db.bundle_size_hint()) - } -} - -impl<'a, EvmConfig> PrunableBlockExecutor for EVMProcessor<'a, EvmConfig> -where - EvmConfig: ConfigureEvm, -{ - fn set_tip(&mut self, tip: BlockNumber) { - self.batch_record.set_tip(tip); - } - - fn set_prune_modes(&mut self, prune_modes: PruneModes) { - self.batch_record.set_prune_modes(prune_modes); - } -} - -/// Calculate the receipts root, and copmare it against against the expected receipts root and logs -/// bloom. -pub fn verify_receipt<'a>( - expected_receipts_root: B256, - expected_logs_bloom: Bloom, - receipts: impl Iterator + Clone, -) -> Result<(), BlockExecutionError> { - // Calculate receipts root. - let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::>(); - let receipts_root = reth_primitives::proofs::calculate_receipt_root(&receipts_with_bloom); - - // Create header log bloom. - let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom); - - compare_receipts_root_and_logs_bloom( - receipts_root, - logs_bloom, - expected_receipts_root, - expected_logs_bloom, - )?; - - Ok(()) -} - -/// Compare the calculated receipts root with the expected receipts root, also copmare -/// the calculated logs bloom with the expected logs bloom. -pub fn compare_receipts_root_and_logs_bloom( - calculated_receipts_root: B256, - calculated_logs_bloom: Bloom, - expected_receipts_root: B256, - expected_logs_bloom: Bloom, -) -> Result<(), BlockExecutionError> { - if calculated_receipts_root != expected_receipts_root { - return Err(BlockValidationError::ReceiptRootDiff( - GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(), - ) - .into()) - } - - if calculated_logs_bloom != expected_logs_bloom { - return Err(BlockValidationError::BloomLogDiff( - GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(), - ) - .into()) - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{StateProviderTest, TestEvmConfig}; - use reth_primitives::{ - bytes, - constants::{BEACON_ROOTS_ADDRESS, EIP1559_INITIAL_BASE_FEE, SYSTEM_ADDRESS}, - keccak256, Account, Bytes, ChainSpecBuilder, ForkCondition, Signature, Transaction, - TxEip1559, MAINNET, - }; - use revm::{Database, TransitionState}; - use std::collections::HashMap; - - static BEACON_ROOT_CONTRACT_CODE: Bytes = bytes!("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"); - - fn create_state_provider_with_beacon_root_contract() -> StateProviderTest { - let mut db = StateProviderTest::default(); - - let beacon_root_contract_account = Account { - balance: U256::ZERO, - bytecode_hash: Some(keccak256(BEACON_ROOT_CONTRACT_CODE.clone())), - nonce: 1, - }; - - db.insert_account( - BEACON_ROOTS_ADDRESS, - beacon_root_contract_account, - Some(BEACON_ROOT_CONTRACT_CODE.clone()), - HashMap::new(), - ); - - db - } - - #[test] - fn eip_4788_non_genesis_call() { - let mut header = - Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() }; - - let db = create_state_provider_with_beacon_root_contract(); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - // execute invalid header (no parent beacon block root) - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - - // attempt to execute a block without parent beacon block root, expect err - let err = executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .expect_err( - "Executing cancun block without parent beacon block root field should fail", - ); - assert_eq!( - err, - BlockExecutionError::Validation(BlockValidationError::MissingParentBeaconBlockRoot) - ); - - // fix header, set a gas limit - header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); - - // Now execute a block with the fixed header, ensure that it does not fail - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .unwrap(); - - // check the actual storage of the contract - it should be: - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be - // header.timestamp - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH - // should be parent_beacon_block_root - let history_buffer_length = 8191u64; - let timestamp_index = header.timestamp % history_buffer_length; - let parent_beacon_block_root_index = - timestamp_index % history_buffer_length + history_buffer_length; - - // get timestamp storage and compare - let timestamp_storage = - executor.db_mut().storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap(); - assert_eq!(timestamp_storage, U256::from(header.timestamp)); - - // get parent beacon block root storage and compare - let parent_beacon_block_root_storage = executor - .db_mut() - .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)) - .expect("storage value should exist"); - assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); - } - - #[test] - fn eip_4788_no_code_cancun() { - // This test ensures that we "silently fail" when cancun is active and there is no code at - // BEACON_ROOTS_ADDRESS - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - excess_blob_gas: Some(0), - ..Header::default() - }; - - let db = StateProviderTest::default(); - - // DON'T deploy the contract at genesis - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - executor.init_env(&header, U256::ZERO); - - // get the env - let previous_env = executor.evm.context.evm.env.clone(); - - // attempt to execute an empty block with parent beacon block root, this should not fail - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .expect( - "Executing a block with no transactions while cancun is active should not fail", - ); - - // ensure that the env has not changed - assert_eq!(executor.evm.context.evm.env, previous_env); - } - - #[test] - fn eip_4788_empty_account_call() { - // This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account - // during the pre-block call - - let mut db = create_state_provider_with_beacon_root_contract(); - - // insert an empty SYSTEM_ADDRESS - db.insert_account(SYSTEM_ADDRESS, Account::default(), None, HashMap::new()); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - - // construct the header for block one - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - excess_blob_gas: Some(0), - ..Header::default() - }; - - executor.init_env(&header, U256::ZERO); - - // attempt to execute an empty block with parent beacon block root, this should not fail - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .expect( - "Executing a block with no transactions while cancun is active should not fail", - ); - - // ensure that the nonce of the system address account has not changed - let nonce = executor.db_mut().basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce; - assert_eq!(nonce, 0); - } - - #[test] - fn eip_4788_genesis_call() { - let db = create_state_provider_with_beacon_root_contract(); - - // activate cancun at genesis - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(0)) - .build(), - ); - - let mut header = chain_spec.genesis_header(); - - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - executor.init_env(&header, U256::ZERO); - - // attempt to execute the genesis block with non-zero parent beacon block root, expect err - header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); - let _err = executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .expect_err( - "Executing genesis cancun block with non-zero parent beacon block root field should fail", - ); - - // fix header - header.parent_beacon_block_root = Some(B256::ZERO); - - // now try to process the genesis block again, this time ensuring that a system contract - // call does not occur - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .unwrap(); - - // there is no system contract call so there should be NO STORAGE CHANGES - // this means we'll check the transition state - let state = executor.evm.context.evm.inner.db; - let transition_state = - state.transition_state.expect("the evm should be initialized with bundle updates"); - - // assert that it is the default (empty) transition state - assert_eq!(transition_state, TransitionState::default()); - } - - #[test] - fn eip_4788_high_base_fee() { - // This test ensures that if we have a base fee, then we don't return an error when the - // system contract is called, due to the gas price being less than the base fee. - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - base_fee_per_gas: Some(u64::MAX), - excess_blob_gas: Some(0), - ..Header::default() - }; - - let db = create_state_provider_with_beacon_root_contract(); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - // execute header - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - executor.init_env(&header, U256::ZERO); - - // ensure that the env is configured with a base fee - assert_eq!(executor.evm.block().basefee, U256::from(u64::MAX)); - - // Now execute a block with the fixed header, ensure that it does not fail - executor - .execute_and_verify_receipt( - &BlockWithSenders { - block: Block { - header: header.clone(), - body: vec![], - ommers: vec![], - withdrawals: None, - }, - senders: vec![], - }, - U256::ZERO, - ) - .unwrap(); - - // check the actual storage of the contract - it should be: - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be - // header.timestamp - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH - // should be parent_beacon_block_root - let history_buffer_length = 8191u64; - let timestamp_index = header.timestamp % history_buffer_length; - let parent_beacon_block_root_index = - timestamp_index % history_buffer_length + history_buffer_length; - - // get timestamp storage and compare - let timestamp_storage = - executor.db_mut().storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap(); - assert_eq!(timestamp_storage, U256::from(header.timestamp)); - - // get parent beacon block root storage and compare - let parent_beacon_block_root_storage = executor - .db_mut() - .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)) - .unwrap(); - assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); - } - - #[test] - fn test_transact_error_includes_correct_hash() { - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let db = StateProviderTest::default(); - let chain_id = chain_spec.chain.id(); - - // execute header - let evm_config = TestEvmConfig::default(); - let mut executor = - EVMProcessor::new_with_db(chain_spec, StateProviderDatabase::new(db), &evm_config); - - // Create a test transaction that gonna fail - let transaction = TransactionSigned::from_transaction_and_signature( - Transaction::Eip1559(TxEip1559 { - chain_id, - nonce: 1, - gas_limit: 21_000, - to: Address::ZERO.into(), - max_fee_per_gas: EIP1559_INITIAL_BASE_FEE as u128, - ..Default::default() - }), - Signature::default(), - ); - - let result = executor.transact(&transaction, Address::random()); - - let expected_hash = transaction.recalculate_hash(); - - // Check the error - match result { - Err(BlockExecutionError::Validation(BlockValidationError::EVM { hash, error: _ })) => { - assert_eq!(hash, expected_hash, "The EVM error does not include the correct transaction hash."); - }, - _ => panic!("Expected a BlockExecutionError::Validation error, but transaction did not fail as expected."), - } - } -} diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index ef79a7ed3d..7e198c9989 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -44,7 +44,8 @@ tracing.workspace = true reth-beacon-consensus.workspace = true reth-interfaces = { workspace = true, features = ["test-utils"] } reth-network-api.workspace = true -reth-node-ethereum.workspace = true +reth-evm-ethereum.workspace = true +reth-ethereum-engine-primitives.workspace = true reth-payload-builder = { workspace = true, features = ["test-utils"] } reth-primitives.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } diff --git a/crates/rpc/rpc-builder/tests/it/auth.rs b/crates/rpc/rpc-builder/tests/it/auth.rs index 51ba2f1452..4b95d11ed4 100644 --- a/crates/rpc/rpc-builder/tests/it/auth.rs +++ b/crates/rpc/rpc-builder/tests/it/auth.rs @@ -2,7 +2,7 @@ use crate::utils::launch_auth; use jsonrpsee::core::client::{ClientT, SubscriptionClientT}; -use reth_node_ethereum::EthEngineTypes; +use reth_ethereum_engine_primitives::EthEngineTypes; use reth_primitives::{Block, U64}; use reth_rpc::JwtSecret; use reth_rpc_api::clients::EngineApiClient; diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index c118014425..403e12a1b3 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -1,6 +1,7 @@ use reth_beacon_consensus::BeaconConsensusEngineHandle; +use reth_ethereum_engine_primitives::EthEngineTypes; +use reth_evm_ethereum::EthEvmConfig; use reth_network_api::noop::NoopNetwork; -use reth_node_ethereum::{EthEngineTypes, EthEvmConfig}; use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::MAINNET; use reth_provider::test_utils::{NoopProvider, TestCanonStateSubscriptions}; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 81788f0a39..513c7da134 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -29,6 +29,8 @@ revm-inspectors.workspace = true reth-evm.workspace = true reth-network-types.workspace = true +reth-evm-optimism = { workspace = true, optional = true } + # eth alloy-rlp.workspace = true alloy-dyn-abi = { workspace = true, features = ["eip712"] } @@ -89,4 +91,6 @@ optimism = [ "reth-primitives/optimism", "reth-rpc-types-compat/optimism", "reth-provider/optimism", + "dep:reth-evm-optimism", + "reth-evm-optimism/optimism", ] diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 95b6b6bc7a..cfc3fe058c 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -84,7 +84,7 @@ where #[cfg(feature = "optimism")] let (block_timestamp, l1_block_info) = { - let body = reth_revm::optimism::extract_l1_info(&block); + let body = reth_evm_optimism::extract_l1_info(&block); (block.timestamp, body.ok()) }; diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 51bde5bfaa..75470e1fe8 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -49,17 +49,9 @@ use revm::{ }; use std::future::Future; -#[cfg(feature = "optimism")] -use crate::eth::api::optimism::OptimismTxMeta; -#[cfg(feature = "optimism")] -use crate::eth::optimism::OptimismEthApiError; use crate::eth::revm_utils::FillableTransaction; #[cfg(feature = "optimism")] -use reth_revm::optimism::RethL1BlockInfo; -#[cfg(feature = "optimism")] use reth_rpc_types::OptimismTransactionReceiptFields; -#[cfg(feature = "optimism")] -use revm::L1BlockInfo; use revm_primitives::db::{Database, DatabaseRef}; /// Helper alias type for the state's [CacheDB] @@ -1498,7 +1490,7 @@ where .ok_or(EthApiError::UnknownBlockNumber)?; let block = block.unseal(); - let l1_block_info = reth_revm::optimism::extract_l1_info(&block).ok(); + let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok(); let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?; build_transaction_receipt_with_block_receipts( @@ -1510,17 +1502,19 @@ where ) } - /// Builds [OptimismTxMeta] object using the provided [TransactionSigned], - /// [L1BlockInfo] and `block_timestamp`. The [L1BlockInfo] is used to calculate - /// the l1 fee and l1 data gas for the transaction. - /// If the [L1BlockInfo] is not provided, the [OptimismTxMeta] will be empty. + /// Builds op metadata object using the provided [TransactionSigned], L1 block info and + /// `block_timestamp`. The L1BlockInfo is used to calculate the l1 fee and l1 data gas for the + /// transaction. If the L1BlockInfo is not provided, the meta info will be empty. #[cfg(feature = "optimism")] pub(crate) fn build_op_tx_meta( &self, tx: &TransactionSigned, - l1_block_info: Option, + l1_block_info: Option, block_timestamp: u64, - ) -> EthResult { + ) -> EthResult { + use crate::eth::{api::optimism::OptimismTxMeta, optimism::OptimismEthApiError}; + use reth_evm_optimism::RethL1BlockInfo; + let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) }; let (l1_fee, l1_data_gas) = if !tx.is_deposit() { @@ -1711,7 +1705,7 @@ pub(crate) fn build_transaction_receipt_with_block_receipts( meta: TransactionMeta, receipt: Receipt, all_receipts: &[Receipt], - #[cfg(feature = "optimism")] optimism_tx_meta: OptimismTxMeta, + #[cfg(feature = "optimism")] optimism_tx_meta: crate::eth::api::optimism::OptimismTxMeta, ) -> EthResult { // Note: we assume this transaction is valid, because it's mined (or part of pending block) and // we don't need to check for pre EIP-2 diff --git a/crates/stages/Cargo.toml b/crates/stages/Cargo.toml index f3bd16a5e2..ef91b2be23 100644 --- a/crates/stages/Cargo.toml +++ b/crates/stages/Cargo.toml @@ -24,6 +24,8 @@ reth-etl.workspace = true reth-config.workspace = true reth-stages-api = { workspace = true, features = ["test-utils"] } reth-consensus.workspace = true +reth-evm.workspace = true +reth-revm.workspace = true # async tokio = { workspace = true, features = ["sync"] } diff --git a/crates/stages/src/lib.rs b/crates/stages/src/lib.rs index 92c2b3a09a..2c6aaff251 100644 --- a/crates/stages/src/lib.rs +++ b/crates/stages/src/lib.rs @@ -16,7 +16,7 @@ //! # use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; //! # use reth_downloaders::headers::reverse_headers::ReverseHeadersDownloaderBuilder; //! # use reth_interfaces::test_utils::{TestBodiesClient, TestHeadersClient}; -//! # use reth_revm::EvmProcessorFactory; +//! # use reth_evm_ethereum::execute::EthExecutorProvider; //! # use reth_primitives::{MAINNET, B256, PruneModes}; //! # use reth_network_types::PeerId; //! # use reth_stages::Pipeline; @@ -45,7 +45,7 @@ //! # provider_factory.clone() //! # ); //! # let (tip_tx, tip_rx) = watch::channel(B256::default()); -//! # let executor_factory = EvmProcessorFactory::new(chain_spec.clone(), EthEvmConfig::default()); +//! # let executor_provider = EthExecutorProvider::mainnet(); //! # let static_file_producer = StaticFileProducer::new( //! # provider_factory.clone(), //! # provider_factory.static_file_provider(), @@ -55,17 +55,15 @@ //! # let pipeline = //! Pipeline::builder() //! .with_tip_sender(tip_tx) -//! .add_stages( -//! DefaultStages::new( -//! provider_factory.clone(), -//! HeaderSyncMode::Tip(tip_rx), -//! consensus, -//! headers_downloader, -//! bodies_downloader, -//! executor_factory, -//! EtlConfig::default(), -//! ) -//! ) +//! .add_stages(DefaultStages::new( +//! provider_factory.clone(), +//! HeaderSyncMode::Tip(tip_rx), +//! consensus, +//! headers_downloader, +//! bodies_downloader, +//! executor_provider, +//! EtlConfig::default(), +//! )) //! .build(provider_factory, static_file_producer); //! ``` //! diff --git a/crates/stages/src/sets.rs b/crates/stages/src/sets.rs index 99edf05b72..7ec85170fc 100644 --- a/crates/stages/src/sets.rs +++ b/crates/stages/src/sets.rs @@ -12,44 +12,29 @@ //! ```no_run //! # use reth_stages::Pipeline; //! # use reth_stages::sets::{OfflineStages}; -//! # use reth_revm::EvmProcessorFactory; //! # use reth_primitives::{PruneModes, MAINNET}; //! # use reth_evm_ethereum::EthEvmConfig; //! # use reth_provider::StaticFileProviderFactory; //! # use reth_provider::test_utils::create_test_provider_factory; //! # use reth_static_file::StaticFileProducer; //! # use reth_config::config::EtlConfig; +//! # use reth_evm::execute::BlockExecutorProvider; //! -//! # let executor_factory = EvmProcessorFactory::new(MAINNET.clone(), EthEvmConfig::default()); -//! # let provider_factory = create_test_provider_factory(); -//! # let static_file_producer = StaticFileProducer::new( +//! # fn create(exec: impl BlockExecutorProvider) { +//! +//! let provider_factory = create_test_provider_factory(); +//! let static_file_producer = StaticFileProducer::new( //! provider_factory.clone(), //! provider_factory.static_file_provider(), //! PruneModes::default(), //! ); //! // Build a pipeline with all offline stages. -//! # let pipeline = Pipeline::builder() -//! .add_stages(OfflineStages::new(executor_factory, EtlConfig::default())) +//! let pipeline = Pipeline::builder() +//! .add_stages(OfflineStages::new(exec, EtlConfig::default())) //! .build(provider_factory, static_file_producer); -//! ``` //! -//! ```ignore -//! # use reth_stages::Pipeline; -//! # use reth_stages::{StageSet, sets::OfflineStages}; -//! # use reth_revm::EvmProcessorFactory; -//! # use reth_node_ethereum::EthEvmConfig; -//! # use reth_primitives::MAINNET; -//! # use reth_config::config::EtlConfig; -//! -//! // Build a pipeline with all offline stages and a custom stage at the end. -//! # let executor_factory = EvmProcessorFactory::new(MAINNET.clone(), EthEvmConfig::default()); -//! Pipeline::builder() -//! .add_stages( -//! OfflineStages::new(executor_factory, EtlConfig::default()).builder().add_stage(MyCustomStage) -//! ) -//! .build(); +//! # } //! ``` - use crate::{ stages::{ AccountHashingStage, BodyStage, ExecutionStage, FinishStage, HeaderStage, @@ -61,10 +46,11 @@ use crate::{ use reth_config::config::EtlConfig; use reth_consensus::Consensus; use reth_db::database::Database; +use reth_evm::execute::BlockExecutorProvider; use reth_interfaces::p2p::{ bodies::downloader::BodyDownloader, headers::downloader::HeaderDownloader, }; -use reth_provider::{ExecutorFactory, HeaderSyncGapProvider, HeaderSyncMode}; +use reth_provider::{HeaderSyncGapProvider, HeaderSyncMode}; use std::sync::Arc; /// A set containing all stages to run a fully syncing instance of reth. @@ -98,7 +84,7 @@ pub struct DefaultStages { etl_config: EtlConfig, } -impl DefaultStages { +impl DefaultStages { /// Create a new set of default stages with default values. pub fn new( provider: Provider, @@ -106,11 +92,11 @@ impl DefaultStages { consensus: Arc, header_downloader: H, body_downloader: B, - executor_factory: EF, + executor_factory: E, etl_config: EtlConfig, ) -> Self where - EF: ExecutorFactory, + E: BlockExecutorProvider, { Self { online: OnlineStages::new( @@ -127,14 +113,14 @@ impl DefaultStages { } } -impl DefaultStages +impl DefaultStages where - EF: ExecutorFactory, + E: BlockExecutorProvider, { /// Appends the default offline stages and default finish stage to the given builder. pub fn add_offline_stages( default_offline: StageSetBuilder, - executor_factory: EF, + executor_factory: E, etl_config: EtlConfig, ) -> StageSetBuilder { StageSetBuilder::default() @@ -144,12 +130,12 @@ where } } -impl StageSet for DefaultStages +impl StageSet for DefaultStages where Provider: HeaderSyncGapProvider + 'static, H: HeaderDownloader + 'static, B: BodyDownloader + 'static, - EF: ExecutorFactory, + E: BlockExecutorProvider, DB: Database + 'static, { fn builder(self) -> StageSetBuilder { @@ -269,7 +255,11 @@ impl OfflineStages { } } -impl StageSet for OfflineStages { +impl StageSet for OfflineStages +where + E: BlockExecutorProvider, + DB: Database, +{ fn builder(self) -> StageSetBuilder { ExecutionStages::new(self.executor_factory) .builder() @@ -281,23 +271,27 @@ impl StageSet for OfflineStages { /// A set containing all stages that are required to execute pre-existing block data. #[derive(Debug)] #[non_exhaustive] -pub struct ExecutionStages { +pub struct ExecutionStages { /// Executor factory that will create executors. - executor_factory: EF, + executor_factory: E, } -impl ExecutionStages { +impl ExecutionStages { /// Create a new set of execution stages with default values. - pub fn new(executor_factory: EF) -> Self { + pub fn new(executor_factory: E) -> Self { Self { executor_factory } } } -impl StageSet for ExecutionStages { +impl StageSet for ExecutionStages +where + DB: Database, + E: BlockExecutorProvider, +{ fn builder(self) -> StageSetBuilder { StageSetBuilder::default() .add_stage(SenderRecoveryStage::default()) - .add_stage(ExecutionStage::new_with_factory(self.executor_factory)) + .add_stage(ExecutionStage::new_with_executor(self.executor_factory)) } } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 1771e2570d..0db907211d 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -3,6 +3,7 @@ use num_traits::Zero; use reth_db::{ cursor::DbCursorRO, database::Database, static_file::HeaderMask, tables, transaction::DbTx, }; +use reth_evm::execute::{BatchBlockExecutionOutput, BatchExecutor, BlockExecutorProvider}; use reth_exex::{ExExManagerHandle, ExExNotification}; use reth_primitives::{ stage::{ @@ -12,9 +13,10 @@ use reth_primitives::{ }; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, - BlockReader, Chain, DatabaseProviderRW, ExecutorFactory, HeaderProvider, + BlockReader, BundleStateWithReceipts, Chain, DatabaseProviderRW, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, StatsReader, TransactionVariant, }; +use reth_revm::database::StateProviderDatabase; use reth_stages_api::{ BlockErrorKind, ExecInput, ExecOutput, MetricEvent, MetricEventsSender, Stage, StageError, UnwindInput, UnwindOutput, @@ -59,10 +61,10 @@ use tracing::*; /// to [tables::PlainStorageState] // false positive, we cannot derive it if !DB: Debug. #[allow(missing_debug_implementations)] -pub struct ExecutionStage { +pub struct ExecutionStage { metrics_tx: Option, - /// The stage's internal executor - executor_factory: EF, + /// The stage's internal block executor + executor_provider: E, /// The commit thresholds of the execution stage. thresholds: ExecutionStageThresholds, /// The highest threshold (in number of blocks) for switching between incremental @@ -76,10 +78,10 @@ pub struct ExecutionStage { exex_manager_handle: ExExManagerHandle, } -impl ExecutionStage { +impl ExecutionStage { /// Create new execution stage with specified config. pub fn new( - executor_factory: EF, + executor_provider: E, thresholds: ExecutionStageThresholds, external_clean_threshold: u64, prune_modes: PruneModes, @@ -88,19 +90,19 @@ impl ExecutionStage { Self { metrics_tx: None, external_clean_threshold, - executor_factory, + executor_provider, thresholds, prune_modes, exex_manager_handle, } } - /// Create an execution stage with the provided executor factory. + /// Create an execution stage with the provided executor. /// /// The commit threshold will be set to 10_000. - pub fn new_with_factory(executor_factory: EF) -> Self { + pub fn new_with_executor(executor_provider: E) -> Self { Self::new( - executor_factory, + executor_provider, ExecutionStageThresholds::default(), MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, PruneModes::none(), @@ -144,7 +146,10 @@ impl ExecutionStage { } } -impl ExecutionStage { +impl ExecutionStage +where + E: BlockExecutorProvider, +{ /// Execute the stage. pub fn execute_inner( &mut self, @@ -169,12 +174,11 @@ impl ExecutionStage { None }; - // Build executor - let mut executor = self.executor_factory.with_state(LatestStateProviderRef::new( + let db = StateProviderDatabase(LatestStateProviderRef::new( provider.tx_ref(), provider.static_file_provider().clone(), )); - executor.set_prune_modes(prune_modes); + let mut executor = self.executor_provider.batch_executor(db, prune_modes); executor.set_tip(max_block); // Progress tracking @@ -213,7 +217,8 @@ impl ExecutionStage { // Execute the block let execute_start = Instant::now(); - executor.execute_and_verify_receipt(&block, td).map_err(|error| StageError::Block { + + executor.execute_one((&block, td).into()).map_err(|error| StageError::Block { block: Box::new(block.header.clone().seal_slow()), error: BlockErrorKind::Execution(error), })?; @@ -245,7 +250,8 @@ impl ExecutionStage { } } let time = Instant::now(); - let state = executor.take_output_state(); + let BatchBlockExecutionOutput { bundle, receipts, first_block } = executor.finalize(); + let state = BundleStateWithReceipts::new(bundle, receipts, first_block); let write_preparation_duration = time.elapsed(); // Check if we should send a [`ExExNotification`] to execution extensions. @@ -383,7 +389,11 @@ fn calculate_gas_used_from_headers( Ok(gas_total) } -impl Stage for ExecutionStage { +impl Stage for ExecutionStage +where + DB: Database, + E: BlockExecutorProvider, +{ /// Return the id of the stage fn id(&self) -> StageId { StageId::Execution @@ -609,7 +619,7 @@ mod tests { use alloy_rlp::Decodable; use assert_matches::assert_matches; use reth_db::{models::AccountBeforeTx, transaction::DbTxMut}; - use reth_evm_ethereum::EthEvmConfig; + use reth_evm_ethereum::execute::EthExecutorProvider; use reth_interfaces::executor::BlockValidationError; use reth_primitives::{ address, hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Address, @@ -620,16 +630,14 @@ mod tests { test_utils::create_test_provider_factory, AccountReader, ReceiptProvider, StaticFileProviderFactory, }; - use reth_revm::EvmProcessorFactory; use std::collections::BTreeMap; - fn stage() -> ExecutionStage> { - let executor_factory = EvmProcessorFactory::new( - Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build()), - EthEvmConfig::default(), - ); + fn stage() -> ExecutionStage { + let executor_provider = EthExecutorProvider::ethereum(Arc::new( + ChainSpecBuilder::mainnet().berlin_activated().build(), + )); ExecutionStage::new( - executor_factory, + executor_provider, ExecutionStageThresholds { max_blocks: Some(100), max_changes: None, @@ -864,7 +872,7 @@ mod tests { mode.receipts_log_filter = random_filter.clone(); } - let mut execution_stage: ExecutionStage> = stage(); + let mut execution_stage = stage(); execution_stage.prune_modes = mode.clone().unwrap_or_default(); let output = execution_stage.execute(&provider, input).unwrap(); diff --git a/crates/stages/src/stages/mod.rs b/crates/stages/src/stages/mod.rs index a40da1c496..7bb88ff96e 100644 --- a/crates/stages/src/stages/mod.rs +++ b/crates/stages/src/stages/mod.rs @@ -50,7 +50,7 @@ mod tests { transaction::{DbTx, DbTxMut}, AccountsHistory, DatabaseEnv, }; - use reth_evm_ethereum::EthEvmConfig; + use reth_evm_ethereum::execute::EthExecutorProvider; use reth_exex::ExExManagerHandle; use reth_interfaces::test_utils::generators::{self, random_block}; use reth_primitives::{ @@ -61,7 +61,6 @@ mod tests { providers::StaticFileWriter, AccountExtReader, ProviderFactory, ReceiptProvider, StorageReader, }; - use reth_revm::EvmProcessorFactory; use reth_stages_api::{ExecInput, Stage}; use std::sync::Arc; @@ -140,10 +139,9 @@ mod tests { // Check execution and create receipts and changesets according to the pruning // configuration let mut execution_stage = ExecutionStage::new( - EvmProcessorFactory::new( - Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build()), - EthEvmConfig::default(), - ), + EthExecutorProvider::ethereum(Arc::new( + ChainSpecBuilder::mainnet().berlin_activated().build(), + )), ExecutionStageThresholds { max_blocks: Some(100), max_changes: None, diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index baf5fa5977..a57f18f114 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -4,6 +4,7 @@ use reth_db::{ tables, transaction::{DbTx, DbTxMut}, }; +use reth_evm::execute::BatchBlockExecutionOutput; use reth_interfaces::provider::{ProviderError, ProviderResult}; use reth_primitives::{ logs_bloom, @@ -34,6 +35,22 @@ pub struct BundleStateWithReceipts { first_block: BlockNumber, } +// TODO(mattsse): unify the types, currently there's a cyclic dependency between +impl From for BundleStateWithReceipts { + fn from(value: BatchBlockExecutionOutput) -> Self { + let BatchBlockExecutionOutput { bundle, receipts, first_block } = value; + Self { bundle, receipts, first_block } + } +} + +// TODO(mattsse): unify the types, currently there's a cyclic dependency between +impl From for BatchBlockExecutionOutput { + fn from(value: BundleStateWithReceipts) -> Self { + let BundleStateWithReceipts { bundle, receipts, first_block } = value; + Self { bundle, receipts, first_block } + } +} + /// Type used to initialize revms bundle state. pub type BundleStateInit = HashMap, Option, HashMap)>; diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 31edf4f039..d2c016add2 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -19,7 +19,7 @@ use reth::{ }; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; -use reth_node_ethereum::{EthEvmConfig, EthereumNode}; +use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; use reth_primitives::{Chain, ChainSpec, Genesis, Header, Transaction}; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; @@ -106,7 +106,7 @@ impl ConfigureEvm for MyEvmConfig { } } -/// A regular ethereum evm and executor builder. +/// Builds a regular ethereum block executor that uses the custom EVM. #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] pub struct MyExecutorBuilder; @@ -116,9 +116,16 @@ where Node: FullNodeTypes, { type EVM = MyEvmConfig; + type Executor = EthExecutorProvider; - async fn build_evm(self, _ctx: &BuilderContext) -> eyre::Result { - Ok(MyEvmConfig::default()) + async fn build_evm( + self, + ctx: &BuilderContext, + ) -> eyre::Result<(Self::EVM, Self::Executor)> { + Ok(( + MyEvmConfig::default(), + EthExecutorProvider::new(ctx.chain_spec(), MyEvmConfig::default()), + )) } } diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index 3f21932271..2584c42d67 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -22,12 +22,12 @@ reth-provider = { workspace = true, features = ["test-utils"] } reth-stages.workspace = true reth-interfaces.workspace = true reth-revm.workspace = true -reth-node-ethereum.workspace = true +reth-evm-ethereum.workspace = true alloy-rlp.workspace = true -tokio = "1.28.1" +tokio.workspace = true walkdir = "2.3.3" -serde = "1.0.163" +serde.workspace = true serde_json.workspace = true thiserror.workspace = true rayon.workspace = true diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 424603cb4c..27f62f8869 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -7,7 +7,6 @@ use crate::{ use alloy_rlp::Decodable; use rayon::iter::{ParallelBridge, ParallelIterator}; use reth_db::test_utils::{create_test_rw_db, create_test_static_files_dir}; -use reth_node_ethereum::EthEvmConfig; use reth_primitives::{BlockBody, SealedBlock, StaticFileSegment}; use reth_provider::{providers::StaticFileWriter, HashingWriter, ProviderFactory}; use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; @@ -136,10 +135,11 @@ impl Case for BlockchainTestCase { // Execute the execution stage using the EVM processor factory for the test case // network. - let _ = ExecutionStage::new_with_factory(reth_revm::EvmProcessorFactory::new( - Arc::new(case.network.clone().into()), - EthEvmConfig::default(), - )) + let _ = ExecutionStage::new_with_executor( + reth_evm_ethereum::execute::EthExecutorProvider::ethereum(Arc::new( + case.network.clone().into(), + )), + ) .execute( &provider, ExecInput { target: last_block.as_ref().map(|b| b.number), checkpoint: None },