//! 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, PayloadServiceBuilder}, BuilderContext, NodeBuilder, }, payload::{EthBuiltPayload, EthPayloadBuilderAttributes}, primitives::{ address, revm_primitives::{Env, PrecompileResult}, Bytes, }, revm::{ handler::register::EvmHandler, inspector_handle_register, precompile::{Precompile, PrecompileOutput, PrecompileSpecId}, ContextPrecompiles, Database, Evm, EvmBuilder, GetInspector, }, rpc::types::engine::PayloadAttributes, tasks::TaskManager, transaction_pool::TransactionPool, }; use reth_chainspec::{Chain, ChainSpec}; use reth_evm_ethereum::EthEvmConfig; use reth_node_api::{ ConfigureEvm, ConfigureEvmEnv, FullNodeTypes, NodeTypes, NodeTypesWithEngine, PayloadTypes, }; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{ node::{EthereumAddOns, EthereumPayloadBuilder}, EthExecutorProvider, EthereumNode, }; use reth_primitives::{ revm_primitives::{CfgEnvWithHandlerCfg, TxEnv}, Address, Header, TransactionSigned, U256, }; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; /// Custom EVM configuration #[derive(Debug, Clone)] #[non_exhaustive] pub struct MyEvmConfig { /// Wrapper around mainnet configuration inner: EthEvmConfig, } impl MyEvmConfig { pub const fn new(chain_spec: Arc) -> Self { Self { inner: EthEvmConfig::new(chain_spec) } } } 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, header: &Header, total_difficulty: U256, ) { self.inner.fill_cfg_env(cfg_env, header, total_difficulty); } fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { self.inner.fill_tx_env(tx_env, transaction, sender); } fn fill_tx_env_system_contract_call( &self, env: &mut Env, caller: Address, contract: Address, data: Bytes, ) { self.inner.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::new(ctx.chain_spec()), EthExecutorProvider::new(ctx.chain_spec(), MyEvmConfig::new(ctx.chain_spec())), )) } } /// Builds a regular ethereum block executor that uses the custom EVM. #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct MyPayloadBuilder { inner: EthereumPayloadBuilder, } impl PayloadServiceBuilder for MyPayloadBuilder where Types: NodeTypesWithEngine, Node: FullNodeTypes, Pool: TransactionPool + Unpin + 'static, Types::Engine: PayloadTypes< BuiltPayload = EthBuiltPayload, PayloadAttributes = PayloadAttributes, PayloadBuilderAttributes = EthPayloadBuilderAttributes, >, { async fn spawn_payload_service( self, ctx: &BuilderContext, pool: Pool, ) -> eyre::Result> { self.inner.spawn(MyEvmConfig::new(ctx.chain_spec()), ctx, pool) } } #[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(MyPayloadBuilder::default()), ) .with_add_ons::() .launch() .await .unwrap(); println!("Node started"); handle.node_exit_future.await }