Files
reth/examples/custom-evm/src/main.rs

180 lines
5.5 KiB
Rust

//! 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::{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<EXT, DB>(handler: &mut EvmHandler<EXT, DB>)
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(
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)
}
}
impl ConfigureEvm for MyEvmConfig {
type DefaultExternalContext<'a> = ();
fn evm<'a, DB: Database + 'a>(&self, db: DB) -> Evm<'a, Self::DefaultExternalContext<'a>, DB> {
EvmBuilder::default()
.with_db(db)
// add additional precompiles
.append_handler_register(MyEvmConfig::set_precompiles)
.build()
}
fn evm_with_inspector<'a, DB, I>(&self, db: DB, inspector: I) -> Evm<'a, I, DB>
where
DB: Database + 'a,
I: GetInspector<DB>,
{
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()
}
}
/// Builds a regular ethereum block executor that uses the custom EVM.
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct MyExecutorBuilder;
impl<Node> ExecutorBuilder<Node> for MyExecutorBuilder
where
Node: FullNodeTypes,
{
type EVM = MyEvmConfig;
type Executor = EthExecutorProvider<Self::EVM>;
async fn build_evm(
self,
ctx: &BuilderContext<Node>,
) -> 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::<EthereumNode>()
// use default ethereum components but with our executor
.with_components(EthereumNode::components().executor(MyExecutorBuilder::default()))
.launch()
.await
.unwrap();
println!("Node started");
handle.node_exit_future.await
}