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

244 lines
7.6 KiB
Rust

//! This example shows how to implement a node with a custom EVM
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use alloy_consensus::Header;
use alloy_genesis::Genesis;
use alloy_primitives::{address, Address, Bytes, U256};
use reth::{
builder::{
components::{ExecutorBuilder, PayloadServiceBuilder},
BuilderContext, NodeBuilder,
},
payload::{EthBuiltPayload, EthPayloadBuilderAttributes},
revm::{
handler::register::EvmHandler,
inspector_handle_register,
precompile::{Precompile, PrecompileOutput, PrecompileSpecId},
primitives::{CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv},
ContextPrecompiles, Database, Evm, EvmBuilder, GetInspector,
},
rpc::types::engine::PayloadAttributes,
tasks::TaskManager,
transaction_pool::{PoolTransaction, TransactionPool},
};
use reth_chainspec::{Chain, ChainSpec};
use reth_evm::env::EvmEnv;
use reth_evm_ethereum::EthEvmConfig;
use reth_node_api::{
ConfigureEvm, ConfigureEvmEnv, FullNodeTypes, NextBlockEnvAttributes, NodeTypes,
NodeTypesWithEngine, PayloadTypes,
};
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
use reth_node_ethereum::{
node::{EthereumAddOns, EthereumPayloadBuilder},
BasicBlockExecutorProvider, EthExecutionStrategyFactory, EthereumNode,
};
use reth_primitives::{EthPrimitives, TransactionSigned};
use reth_tracing::{RethTracer, Tracer};
use std::{convert::Infallible, 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<ChainSpec>) -> 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<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 {
type Header = Header;
type Transaction = TransactionSigned;
type Error = Infallible;
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);
}
fn fill_cfg_env(
&self,
cfg_env: &mut CfgEnvWithHandlerCfg,
header: &Self::Header,
total_difficulty: U256,
) {
self.inner.fill_cfg_env(cfg_env, header, total_difficulty);
}
fn next_cfg_and_block_env(
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> Result<EvmEnv, Self::Error> {
self.inner.next_cfg_and_block_env(parent, attributes)
}
}
impl ConfigureEvm for MyEvmConfig {
type DefaultExternalContext<'a> = ();
fn evm<DB: Database>(&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<DB, I>(&self, db: DB, inspector: I) -> Evm<'_, I, DB>
where
DB: Database,
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()
}
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<Node> ExecutorBuilder<Node> for MyExecutorBuilder
where
Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
{
type EVM = MyEvmConfig;
type Executor = BasicBlockExecutorProvider<EthExecutionStrategyFactory<Self::EVM>>;
async fn build_evm(
self,
ctx: &BuilderContext<Node>,
) -> eyre::Result<(Self::EVM, Self::Executor)> {
Ok((
MyEvmConfig::new(ctx.chain_spec()),
BasicBlockExecutorProvider::new(EthExecutionStrategyFactory::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<Types, Node, Pool> PayloadServiceBuilder<Node, Pool> for MyPayloadBuilder
where
Types: NodeTypesWithEngine<ChainSpec = ChainSpec, Primitives = EthPrimitives>,
Node: FullNodeTypes<Types = Types>,
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TransactionSigned>>
+ Unpin
+ 'static,
Types::Engine: PayloadTypes<
BuiltPayload = EthBuiltPayload,
PayloadAttributes = PayloadAttributes,
PayloadBuilderAttributes = EthPayloadBuilderAttributes,
>,
{
async fn spawn_payload_service(
self,
ctx: &BuilderContext<Node>,
pool: Pool,
) -> eyre::Result<reth::payload::PayloadBuilderHandle<Types::Engine>> {
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::<EthereumNode>()
// use default ethereum components but with our executor
.with_components(
EthereumNode::components()
.executor(MyExecutorBuilder::default())
.payload(MyPayloadBuilder::default()),
)
.with_add_ons(EthereumAddOns::default())
.launch()
.await
.unwrap();
println!("Node started");
handle.node_exit_future.await
}