//! This example shows how to implement a node with a custom EVM #![cfg_attr(not(test), warn(unused_crate_dependencies))] use alloy_genesis::Genesis; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, primitives::{ address, revm_primitives::{Env, PrecompileResult}, Bytes, }, revm::{ handler::register::EvmHandler, inspector_handle_register, precompile::{Precompile, PrecompileOutput, PrecompileSpecId}, ContextPrecompiles, Database, Evm, EvmBuilder, GetInspector, }, tasks::TaskManager, }; use reth_chainspec::{Chain, ChainSpec, Head}; use reth_evm_ethereum::EthEvmConfig; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{ node::{EthereumAddOns, EthereumPayloadBuilder}, EthExecutorProvider, EthereumNode, }; use reth_primitives::{ revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, Address, Header, TransactionSigned, U256, }; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; /// Custom EVM configuration #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub struct MyEvmConfig; impl MyEvmConfig { /// Sets the precompiles to the EVM handler /// /// This will be invoked when the EVM is created via [ConfigureEvm::evm] or /// [ConfigureEvm::evm_with_inspector] /// /// This will use the default mainnet precompiles and add additional precompiles. pub fn set_precompiles(handler: &mut EvmHandler) where DB: Database, { // first we need the evm spec id, which determines the precompiles let spec_id = handler.cfg.spec_id; // install the precompiles handler.pre_execution.load_precompiles = Arc::new(move || { let mut precompiles = ContextPrecompiles::new(PrecompileSpecId::from_spec_id(spec_id)); precompiles.extend([( address!("0000000000000000000000000000000000000999"), Precompile::Env(Self::my_precompile).into(), )]); precompiles }); } /// A custom precompile that does nothing fn my_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult { Ok(PrecompileOutput::new(0, Bytes::new())) } } impl ConfigureEvmEnv for MyEvmConfig { fn fill_cfg_env( &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, total_difficulty: U256, ) { let spec_id = reth_evm_ethereum::revm_spec( chain_spec, &Head { number: header.number, timestamp: header.timestamp, difficulty: header.difficulty, total_difficulty, hash: Default::default(), }, ); cfg_env.chain_id = chain_spec.chain().id(); cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; cfg_env.handler_cfg.spec_id = spec_id; } fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { EthEvmConfig::default().fill_tx_env(tx_env, transaction, sender) } fn fill_tx_env_system_contract_call( &self, env: &mut Env, caller: Address, contract: Address, data: Bytes, ) { EthEvmConfig::default().fill_tx_env_system_contract_call(env, caller, contract, data) } } impl ConfigureEvm for MyEvmConfig { type DefaultExternalContext<'a> = (); fn evm(&self, db: DB) -> Evm<'_, Self::DefaultExternalContext<'_>, DB> { EvmBuilder::default() .with_db(db) // add additional precompiles .append_handler_register(MyEvmConfig::set_precompiles) .build() } fn evm_with_inspector(&self, db: DB, inspector: I) -> Evm<'_, I, DB> where DB: Database, I: GetInspector, { EvmBuilder::default() .with_db(db) .with_external_context(inspector) // add additional precompiles .append_handler_register(MyEvmConfig::set_precompiles) .append_handler_register(inspector_handle_register) .build() } fn default_external_context<'a>(&self) -> Self::DefaultExternalContext<'a> {} } /// Builds a regular ethereum block executor that uses the custom EVM. #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] pub struct MyExecutorBuilder; impl ExecutorBuilder for MyExecutorBuilder where Node: FullNodeTypes, { type EVM = MyEvmConfig; type Executor = EthExecutorProvider; async fn build_evm( self, ctx: &BuilderContext, ) -> eyre::Result<(Self::EVM, Self::Executor)> { Ok(( MyEvmConfig::default(), EthExecutorProvider::new(ctx.chain_spec(), MyEvmConfig::default()), )) } } #[tokio::main] async fn main() -> eyre::Result<()> { let _guard = RethTracer::new().init()?; let tasks = TaskManager::current(); // create a custom chain spec let spec = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(Genesis::default()) .london_activated() .paris_activated() .shanghai_activated() .cancun_activated() .build(); let node_config = NodeConfig::test().with_rpc(RpcServerArgs::default().with_http()).with_chain(spec); let handle = NodeBuilder::new(node_config) .testing_node(tasks.executor()) // configure the node with regular ethereum types .with_types::() // use default ethereum components but with our executor .with_components( EthereumNode::components() .executor(MyExecutorBuilder::default()) .payload(EthereumPayloadBuilder::new(MyEvmConfig::default())), ) .with_add_ons::() .launch() .await .unwrap(); println!("Node started"); handle.node_exit_future.await }