mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
244 lines
7.6 KiB
Rust
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
|
|
}
|