mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: integrate builder (#6611)
This commit is contained in:
@ -8,6 +8,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
jsonrpsee = { workspace = true, features = ["server", "macros"] }
|
||||
|
||||
@ -14,24 +14,38 @@
|
||||
|
||||
use clap::Parser;
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use reth::cli::{
|
||||
components::{RethNodeComponents, RethRpcComponents},
|
||||
config::RethRpcConfig,
|
||||
ext::{RethCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
};
|
||||
use reth::cli::Cli;
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
|
||||
fn main() {
|
||||
Cli::<MyRethCliExt>::parse().run().unwrap();
|
||||
}
|
||||
Cli::<RethCliTxpoolExt>::parse()
|
||||
.run(|builder, args| async move {
|
||||
let handle = builder
|
||||
.node(EthereumNode::default())
|
||||
.extend_rpc_modules(move |ctx| {
|
||||
if !args.enable_ext {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
/// The type that tells the reth CLI what extensions to use
|
||||
struct MyRethCliExt;
|
||||
// here we get the configured pool.
|
||||
let pool = ctx.pool().clone();
|
||||
|
||||
impl RethCliExt for MyRethCliExt {
|
||||
/// This tells the reth CLI to install the `txpool` rpc namespace via `RethCliTxpoolExt`
|
||||
type Node = RethCliTxpoolExt;
|
||||
let ext = TxpoolExt { pool };
|
||||
|
||||
// now we merge our extension namespace into all configured transports
|
||||
ctx.modules.merge_configured(ext.into_rpc())?;
|
||||
|
||||
println!("txpool extension enabled");
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
@ -42,34 +56,6 @@ struct RethCliTxpoolExt {
|
||||
pub enable_ext: bool,
|
||||
}
|
||||
|
||||
impl RethNodeCommandConfig for RethCliTxpoolExt {
|
||||
// This is the entrypoint for the CLI to extend the RPC server with custom rpc namespaces.
|
||||
fn extend_rpc_modules<Conf, Reth>(
|
||||
&mut self,
|
||||
_config: &Conf,
|
||||
_components: &Reth,
|
||||
rpc_components: RethRpcComponents<'_, Reth>,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Conf: RethRpcConfig,
|
||||
Reth: RethNodeComponents,
|
||||
{
|
||||
if !self.enable_ext {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// here we get the configured pool type from the CLI.
|
||||
let pool = rpc_components.registry.pool().clone();
|
||||
let ext = TxpoolExt { pool };
|
||||
|
||||
// now we merge our extension namespace into all configured transports
|
||||
rpc_components.modules.merge_configured(ext.into_rpc())?;
|
||||
|
||||
println!("txpool extension enabled");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// trait interface for a custom rpc namespace: `txpool`
|
||||
///
|
||||
/// This defines an additional namespace where all methods are configured as trait functions.
|
||||
|
||||
@ -7,10 +7,9 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
eyre.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
|
||||
clap.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
futures-util.workspace = true
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
|
||||
@ -15,33 +15,26 @@
|
||||
//!
|
||||
//! See lighthouse beacon Node API: <https://lighthouse-book.sigmaprime.io/api-bn.html#beacon-node-api>
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use clap::Parser;
|
||||
use futures_util::stream::StreamExt;
|
||||
use mev_share_sse::{client::EventStream, EventClient};
|
||||
use reth::{
|
||||
cli::{
|
||||
components::RethNodeComponents,
|
||||
ext::{RethCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
rpc::types::beacon::events::PayloadAttributesEvent,
|
||||
tasks::TaskSpawner,
|
||||
};
|
||||
use reth::{cli::Cli, rpc::types::beacon::events::PayloadAttributesEvent};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use tracing::{info, warn};
|
||||
|
||||
fn main() {
|
||||
Cli::<BeaconEventsExt>::parse().run().unwrap();
|
||||
}
|
||||
Cli::<BeaconEventsConfig>::parse()
|
||||
.run(|builder, args| async move {
|
||||
let handle = builder.node(EthereumNode::default()).launch().await?;
|
||||
|
||||
/// The type that tells the reth CLI what extensions to use
|
||||
#[derive(Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
struct BeaconEventsExt;
|
||||
handle.node.task_executor.spawn(Box::pin(args.run()));
|
||||
|
||||
impl RethCliExt for BeaconEventsExt {
|
||||
/// This tells the reth CLI to install additional CLI arguments
|
||||
type Node = BeaconEventsConfig;
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
@ -95,13 +88,6 @@ impl BeaconEventsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl RethNodeCommandConfig for BeaconEventsConfig {
|
||||
fn on_node_started<Reth: RethNodeComponents>(&mut self, components: &Reth) -> eyre::Result<()> {
|
||||
components.task_executor().spawn(Box::pin(self.clone().run()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -7,5 +7,4 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
clap.workspace = true
|
||||
eyre.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
@ -15,38 +15,30 @@
|
||||
//! > "Node started"
|
||||
//! once the node has been started.
|
||||
|
||||
use clap::Parser;
|
||||
use reth::cli::{
|
||||
components::RethNodeComponents,
|
||||
ext::{NoArgsCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
};
|
||||
use reth::cli::Cli;
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
|
||||
fn main() {
|
||||
Cli::<NoArgsCliExt<MyRethConfig>>::parse()
|
||||
.with_node_extension(MyRethConfig::default())
|
||||
.run()
|
||||
Cli::parse_args()
|
||||
.run(|builder, _| async move {
|
||||
let handle = builder
|
||||
.node(EthereumNode::default())
|
||||
.on_node_started(|_ctx| {
|
||||
println!("Node started");
|
||||
Ok(())
|
||||
})
|
||||
.on_rpc_started(|_ctx, _handles| {
|
||||
println!("RPC started");
|
||||
Ok(())
|
||||
})
|
||||
.on_component_initialized(|_ctx| {
|
||||
println!("All components initialized");
|
||||
Ok(())
|
||||
})
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
struct MyRethConfig;
|
||||
|
||||
impl RethNodeCommandConfig for MyRethConfig {
|
||||
fn on_components_initialized<Reth: RethNodeComponents>(
|
||||
&mut self,
|
||||
_components: &Reth,
|
||||
) -> eyre::Result<()> {
|
||||
println!("All components initialized");
|
||||
Ok(())
|
||||
}
|
||||
fn on_node_started<Reth: RethNodeComponents>(
|
||||
&mut self,
|
||||
_components: &Reth,
|
||||
) -> eyre::Result<()> {
|
||||
println!("Node started");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
18
examples/custom-dev-node/Cargo.toml
Normal file
18
examples/custom-dev-node/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "custom-dev-node"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
futures-util.workspace = true
|
||||
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
serde_json.workspace = true
|
||||
96
examples/custom-dev-node/src/main.rs
Normal file
96
examples/custom-dev-node/src/main.rs
Normal file
@ -0,0 +1,96 @@
|
||||
//! This example shows how to run a custom dev node programmatically and submit a transaction
|
||||
//! through rpc.
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use reth::{
|
||||
builder::{NodeBuilder, NodeHandle},
|
||||
providers::CanonStateSubscriptions,
|
||||
rpc::eth::EthTransactions,
|
||||
tasks::TaskManager,
|
||||
};
|
||||
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_primitives::{b256, hex, ChainSpec, Genesis};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let tasks = TaskManager::current();
|
||||
|
||||
// create node config
|
||||
let node_config = NodeConfig::test()
|
||||
.dev()
|
||||
.with_rpc(RpcServerArgs::default().with_http())
|
||||
.with_chain(custom_chain());
|
||||
|
||||
let NodeHandle { mut node, node_exit_future } = NodeBuilder::new(node_config)
|
||||
.testing_node(tasks.executor())
|
||||
.node(EthereumNode::default())
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
let mut notifications = node.provider.canonical_state_stream();
|
||||
|
||||
// submit tx through rpc
|
||||
let raw_tx = hex!("02f876820a28808477359400847735940082520894ab0840c0e43688012c1adb0f5e3fc665188f83d28a029d394a5d630544000080c080a0a044076b7e67b5deecc63f61a8d7913fab86ca365b344b5759d1fe3563b4c39ea019eab979dd000da04dfc72bb0377c092d30fd9e1cab5ae487de49586cc8b0090");
|
||||
|
||||
let eth_api = node.rpc_registry.eth_api();
|
||||
|
||||
let hash = eth_api.send_raw_transaction(raw_tx.into()).await?;
|
||||
|
||||
let expected = b256!("b1c6512f4fc202c04355fbda66755e0e344b152e633010e8fd75ecec09b63398");
|
||||
|
||||
assert_eq!(hash, expected);
|
||||
println!("submitted transaction: {hash}");
|
||||
|
||||
let head = notifications.next().await.unwrap();
|
||||
|
||||
let tx = head.tip().transactions().next().unwrap();
|
||||
assert_eq!(tx.hash(), hash);
|
||||
println!("mined transaction: {hash}");
|
||||
|
||||
node_exit_future.await
|
||||
}
|
||||
|
||||
fn custom_chain() -> Arc<ChainSpec> {
|
||||
let custom_genesis = r#"
|
||||
{
|
||||
"nonce": "0x42",
|
||||
"timestamp": "0x0",
|
||||
"extraData": "0x5343",
|
||||
"gasLimit": "0x1388",
|
||||
"difficulty": "0x400000000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b": {
|
||||
"balance": "0x4a47e3c12448f4ad000000"
|
||||
}
|
||||
},
|
||||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"config": {
|
||||
"ethash": {},
|
||||
"chainId": 2600,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"terminalTotalDifficulty": 0,
|
||||
"terminalTotalDifficultyPassed": true,
|
||||
"shanghaiTime": 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let genesis: Genesis = serde_json::from_str(custom_genesis).unwrap();
|
||||
Arc::new(genesis.into())
|
||||
}
|
||||
18
examples/custom-evm/Cargo.toml
Normal file
18
examples/custom-evm/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "custom-evm"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
|
||||
alloy-chains.workspace = true
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
147
examples/custom-evm/src/main.rs
Normal file
147
examples/custom-evm/src/main.rs
Normal file
@ -0,0 +1,147 @@
|
||||
//! This example shows how to implement a node with a custom EVM
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use alloy_chains::Chain;
|
||||
use reth::{
|
||||
builder::{node::NodeTypes, NodeBuilder},
|
||||
primitives::{
|
||||
address,
|
||||
revm_primitives::{CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv},
|
||||
Address, Bytes, U256,
|
||||
},
|
||||
revm::{
|
||||
handler::register::EvmHandler,
|
||||
precompile::{Precompile, PrecompileSpecId, Precompiles},
|
||||
Database, Evm, EvmBuilder,
|
||||
},
|
||||
tasks::TaskManager,
|
||||
};
|
||||
use reth_node_api::{ConfigureEvm, ConfigureEvmEnv};
|
||||
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
||||
use reth_node_ethereum::{EthEngineTypes, EthEvmConfig, EthereumNode};
|
||||
use reth_primitives::{ChainSpec, Genesis, Header, Transaction};
|
||||
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 = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).clone();
|
||||
precompiles.inner.insert(
|
||||
address!("0000000000000000000000000000000000000999"),
|
||||
Precompile::Env(Self::my_precompile),
|
||||
);
|
||||
precompiles
|
||||
});
|
||||
}
|
||||
|
||||
/// A custom precompile that does nothing
|
||||
fn my_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult {
|
||||
Ok((0, Bytes::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureEvmEnv for MyEvmConfig {
|
||||
type TxMeta = ();
|
||||
|
||||
fn fill_tx_env<T>(tx_env: &mut TxEnv, transaction: T, sender: Address, meta: Self::TxMeta)
|
||||
where
|
||||
T: AsRef<Transaction>,
|
||||
{
|
||||
EthEvmConfig::fill_tx_env(tx_env, transaction, sender, meta)
|
||||
}
|
||||
|
||||
fn fill_cfg_env(
|
||||
cfg_env: &mut CfgEnvWithHandlerCfg,
|
||||
chain_spec: &ChainSpec,
|
||||
header: &Header,
|
||||
total_difficulty: U256,
|
||||
) {
|
||||
EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureEvm for MyEvmConfig {
|
||||
fn evm<'a, DB: Database + 'a>(&self, db: DB) -> Evm<'a, (), DB> {
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
// add additional precompiles
|
||||
.append_handler_register(MyEvmConfig::set_precompiles)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn evm_with_inspector<'a, DB: Database + 'a, I>(&self, db: DB, inspector: I) -> Evm<'a, I, DB> {
|
||||
EvmBuilder::default()
|
||||
.with_db(db)
|
||||
.with_external_context(inspector)
|
||||
// add additional precompiles
|
||||
.append_handler_register(MyEvmConfig::set_precompiles)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
struct MyCustomNode;
|
||||
|
||||
/// Configure the node types
|
||||
impl NodeTypes for MyCustomNode {
|
||||
type Primitives = ();
|
||||
type Engine = EthEngineTypes;
|
||||
type Evm = MyEvmConfig;
|
||||
|
||||
fn evm_config(&self) -> Self::Evm {
|
||||
Self::Evm::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let _guard = RethTracer::new().init()?;
|
||||
|
||||
let tasks = TaskManager::current();
|
||||
|
||||
// create optimism genesis with canyon at block 2
|
||||
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())
|
||||
.with_types(MyCustomNode::default())
|
||||
.with_components(EthereumNode::components())
|
||||
.launch()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Node started");
|
||||
|
||||
handle.node_exit_future.await
|
||||
}
|
||||
@ -7,6 +7,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
futures-util.workspace = true
|
||||
eyre.workspace = true
|
||||
futures-util.workspace = true
|
||||
@ -11,15 +11,11 @@
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use clap::Parser;
|
||||
use futures_util::stream::StreamExt;
|
||||
use futures_util::StreamExt;
|
||||
use reth::{
|
||||
cli::{
|
||||
components::{RethNodeComponents, RethRpcComponents, RethRpcServerHandles},
|
||||
config::RethRpcConfig,
|
||||
ext::{RethCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
primitives::{Address, BlockId, IntoRecoveredTransaction},
|
||||
builder::NodeHandle,
|
||||
cli::Cli,
|
||||
primitives::{Address, BlockNumberOrTag, IntoRecoveredTransaction},
|
||||
revm::{
|
||||
inspector_handle_register,
|
||||
interpreter::{Interpreter, OpCode},
|
||||
@ -29,21 +25,79 @@ use reth::{
|
||||
compat::transaction::transaction_to_call_request,
|
||||
eth::{revm_utils::EvmOverrides, EthTransactions},
|
||||
},
|
||||
tasks::TaskSpawner,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_node_ethereum::node::EthereumNode;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main() {
|
||||
Cli::<MyRethCliExt>::parse().run().unwrap();
|
||||
}
|
||||
Cli::<RethCliTxpoolExt>::parse()
|
||||
.run(|builder, args| async move {
|
||||
// launch the node
|
||||
let NodeHandle { mut node, node_exit_future } =
|
||||
builder.node(EthereumNode::default()).launch().await?;
|
||||
|
||||
/// The type that tells the reth CLI what extensions to use
|
||||
struct MyRethCliExt;
|
||||
let recipients = args.recipients.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
impl RethCliExt for MyRethCliExt {
|
||||
/// This tells the reth CLI to trace addresses via `RethCliTxpoolExt`
|
||||
type Node = RethCliTxpoolExt;
|
||||
// create a new subscription to pending transactions
|
||||
let mut pending_transactions = node.pool.new_pending_pool_transactions_listener();
|
||||
|
||||
// get an instance of the `trace_` API handler
|
||||
let eth_api = node.rpc_registry.eth_api();
|
||||
|
||||
println!("Spawning trace task!");
|
||||
|
||||
// Spawn an async block to listen for transactions.
|
||||
node.task_executor.spawn(Box::pin(async move {
|
||||
// Waiting for new transactions
|
||||
while let Some(event) = pending_transactions.next().await {
|
||||
let tx = event.transaction;
|
||||
println!("Transaction received: {tx:?}");
|
||||
|
||||
if recipients.is_empty() {
|
||||
// convert the pool transaction
|
||||
let call_request =
|
||||
transaction_to_call_request(tx.to_recovered_transaction());
|
||||
|
||||
let result = eth_api
|
||||
.spawn_with_call_at(
|
||||
call_request,
|
||||
BlockNumberOrTag::Latest.into(),
|
||||
EvmOverrides::default(),
|
||||
move |db, env| {
|
||||
let mut dummy_inspector = DummyInspector::default();
|
||||
{
|
||||
// configure the evm with the custom inspector
|
||||
let mut evm = Evm::builder()
|
||||
.with_db(db)
|
||||
.with_external_context(&mut dummy_inspector)
|
||||
.with_env_with_handler_cfg(env)
|
||||
.append_handler_register(inspector_handle_register)
|
||||
.build();
|
||||
// execute the transaction on a blocking task and await the
|
||||
// inspector result
|
||||
let _ = evm.transact()?;
|
||||
}
|
||||
Ok(dummy_inspector)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(ret_val) = result {
|
||||
let hash = tx.hash();
|
||||
println!(
|
||||
"Inspector result for transaction {}: \n {}",
|
||||
hash,
|
||||
ret_val.ret_val.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
node_exit_future.await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
@ -74,76 +128,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RethNodeCommandConfig for RethCliTxpoolExt {
|
||||
/// Sets up a subscription to listen for new pending transactions and traces them.
|
||||
/// If the transaction is from one of the specified recipients, it will be traced.
|
||||
/// If no recipients are specified, all transactions will be traced.
|
||||
fn on_rpc_server_started<Conf, Reth>(
|
||||
&mut self,
|
||||
_config: &Conf,
|
||||
components: &Reth,
|
||||
rpc_components: RethRpcComponents<'_, Reth>,
|
||||
_handles: RethRpcServerHandles,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Conf: RethRpcConfig,
|
||||
Reth: RethNodeComponents,
|
||||
{
|
||||
let recipients = self.recipients.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
// create a new subscription to pending transactions
|
||||
let mut pending_transactions = components.pool().new_pending_pool_transactions_listener();
|
||||
|
||||
let eth_api = rpc_components.registry.eth_api();
|
||||
|
||||
println!("Spawning trace task!");
|
||||
// Spawn an async block to listen for transactions.
|
||||
components.task_executor().spawn(Box::pin(async move {
|
||||
// Waiting for new transactions
|
||||
while let Some(event) = pending_transactions.next().await {
|
||||
let tx = event.transaction;
|
||||
println!("Transaction received: {tx:?}");
|
||||
|
||||
if recipients.is_empty() {
|
||||
// convert the pool transaction
|
||||
let call_request = transaction_to_call_request(tx.to_recovered_transaction());
|
||||
|
||||
let result = eth_api
|
||||
.spawn_with_call_at(
|
||||
call_request,
|
||||
BlockId::default(),
|
||||
EvmOverrides::default(),
|
||||
move |db, env| {
|
||||
let mut dummy_inspector = DummyInspector::default();
|
||||
{
|
||||
// configure the evm with the custom inspector
|
||||
let mut evm = Evm::builder()
|
||||
.with_db(db)
|
||||
.with_external_context(&mut dummy_inspector)
|
||||
.with_env_with_handler_cfg(env)
|
||||
.append_handler_register(inspector_handle_register)
|
||||
.build();
|
||||
// execute the transaction on a blocking task and await the
|
||||
// inspector result
|
||||
let _ = evm.transact()?;
|
||||
}
|
||||
Ok(dummy_inspector)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(ret_val) = result {
|
||||
let hash = tx.hash();
|
||||
println!(
|
||||
"Inspector result for transaction {}: \n {}",
|
||||
hash,
|
||||
ret_val.ret_val.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
16
examples/custom-node-components/Cargo.toml
Normal file
16
examples/custom-node-components/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "custom-node-components"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
|
||||
|
||||
eyre.workspace = true
|
||||
104
examples/custom-node-components/src/main.rs
Normal file
104
examples/custom-node-components/src/main.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! This example shows how to configure custom components for a reth node.
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use reth::{
|
||||
builder::{components::PoolBuilder, BuilderContext, FullNodeTypes},
|
||||
cli::Cli,
|
||||
providers::CanonStateSubscriptions,
|
||||
transaction_pool::{
|
||||
blobstore::InMemoryBlobStore, EthTransactionPool, TransactionValidationTaskExecutor,
|
||||
},
|
||||
};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_tracing::tracing::{debug, info};
|
||||
use reth_transaction_pool::PoolConfig;
|
||||
|
||||
fn main() {
|
||||
Cli::parse_args()
|
||||
.run(|builder, _| async move {
|
||||
let handle = builder
|
||||
// use the default ethereum node types
|
||||
.with_types(EthereumNode::default())
|
||||
// Configure the components of the node
|
||||
// use default ethereum components but use our custom pool
|
||||
.with_components(EthereumNode::components().pool(CustomPoolBuilder::default()))
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// A custom pool builder
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct CustomPoolBuilder {
|
||||
/// Use custom pool config
|
||||
pool_config: PoolConfig,
|
||||
}
|
||||
|
||||
/// Implement the `PoolBuilder` trait for the custom pool builder
|
||||
///
|
||||
/// This will be used to build the transaction pool and its maintenance tasks during launch.
|
||||
impl<Node> PoolBuilder<Node> for CustomPoolBuilder
|
||||
where
|
||||
Node: FullNodeTypes,
|
||||
{
|
||||
type Pool = EthTransactionPool<Node::Provider, InMemoryBlobStore>;
|
||||
|
||||
async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
|
||||
let data_dir = ctx.data_dir();
|
||||
let blob_store = InMemoryBlobStore::default();
|
||||
let validator = TransactionValidationTaskExecutor::eth_builder(ctx.chain_spec())
|
||||
.with_head_timestamp(ctx.head().timestamp)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.with_additional_tasks(5)
|
||||
.build_with_tasks(
|
||||
ctx.provider().clone(),
|
||||
ctx.task_executor().clone(),
|
||||
blob_store.clone(),
|
||||
);
|
||||
|
||||
let transaction_pool =
|
||||
reth_transaction_pool::Pool::eth_pool(validator, blob_store, self.pool_config);
|
||||
info!(target: "reth::cli", "Transaction pool initialized");
|
||||
let transactions_path = data_dir.txpool_transactions_path();
|
||||
|
||||
// spawn txpool maintenance task
|
||||
{
|
||||
let pool = transaction_pool.clone();
|
||||
let chain_events = ctx.provider().canonical_state_stream();
|
||||
let client = ctx.provider().clone();
|
||||
let transactions_backup_config =
|
||||
reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path);
|
||||
|
||||
ctx.task_executor().spawn_critical_with_graceful_shutdown_signal(
|
||||
"local transactions backup task",
|
||||
|shutdown| {
|
||||
reth_transaction_pool::maintain::backup_local_transactions_task(
|
||||
shutdown,
|
||||
pool.clone(),
|
||||
transactions_backup_config,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// spawn the maintenance task
|
||||
ctx.task_executor().spawn_critical(
|
||||
"txpool maintenance task",
|
||||
reth_transaction_pool::maintain::maintain_transaction_pool_future(
|
||||
client,
|
||||
pool,
|
||||
chain_events,
|
||||
ctx.task_executor().clone(),
|
||||
Default::default(),
|
||||
),
|
||||
);
|
||||
debug!(target: "reth::cli", "Spawned txpool maintenance task");
|
||||
}
|
||||
|
||||
Ok(transaction_pool)
|
||||
}
|
||||
}
|
||||
@ -5,19 +5,19 @@ publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-rpc-api.workspace = true
|
||||
reth-rpc-types.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-payload-builder.workspace = true
|
||||
reth-basic-payload-builder.workspace = true
|
||||
reth-ethereum-payload-builder.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
|
||||
alloy-chains.workspace = true
|
||||
jsonrpsee.workspace = true
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
@ -15,25 +15,43 @@
|
||||
//! Once traits are implemented and custom types are defined, the [EngineTypes] trait can be
|
||||
//! implemented:
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use alloy_chains::Chain;
|
||||
use jsonrpsee::http_client::HttpClient;
|
||||
use reth::builder::spawn_node;
|
||||
use reth::{
|
||||
builder::{
|
||||
components::{ComponentsBuilder, PayloadServiceBuilder},
|
||||
node::NodeTypes,
|
||||
BuilderContext, FullNodeTypes, Node, NodeBuilder, PayloadBuilderConfig,
|
||||
},
|
||||
primitives::revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg},
|
||||
providers::{CanonStateSubscriptions, StateProviderFactory},
|
||||
tasks::TaskManager,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_basic_payload_builder::{
|
||||
BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig, BuildArguments, BuildOutcome,
|
||||
PayloadBuilder, PayloadConfig,
|
||||
};
|
||||
use reth_node_api::{
|
||||
validate_version_specific_fields, AttributesValidationError, EngineApiMessageVersion,
|
||||
EngineTypes, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes,
|
||||
};
|
||||
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
||||
use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes};
|
||||
use reth_primitives::{
|
||||
revm::config::revm_spec_by_timestamp_after_merge,
|
||||
revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId},
|
||||
Address, ChainSpec, Genesis, Header, Withdrawals, B256, U256,
|
||||
use reth_node_ethereum::{
|
||||
node::{EthereumNetworkBuilder, EthereumPoolBuilder},
|
||||
EthEvmConfig,
|
||||
};
|
||||
use reth_rpc_api::{EngineApiClient, EthApiClient};
|
||||
use reth_payload_builder::{
|
||||
error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, PayloadBuilderHandle,
|
||||
PayloadBuilderService,
|
||||
};
|
||||
use reth_primitives::{Address, ChainSpec, Genesis, Header, Withdrawals, B256};
|
||||
use reth_rpc_types::{
|
||||
engine::{ForkchoiceState, PayloadAttributes as EthPayloadAttributes, PayloadId},
|
||||
engine::{PayloadAttributes as EthPayloadAttributes, PayloadId},
|
||||
withdrawal::Withdrawal,
|
||||
};
|
||||
use reth_tracing::{RethTracer, Tracer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::Infallible;
|
||||
use thiserror::Error;
|
||||
@ -84,7 +102,7 @@ impl PayloadAttributes for CustomPayloadAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Newtype around the payload builder attributes type
|
||||
/// New type around the payload builder attributes type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CustomPayloadBuilderAttributes(EthPayloadBuilderAttributes);
|
||||
|
||||
@ -123,50 +141,13 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes {
|
||||
fn withdrawals(&self) -> &Withdrawals {
|
||||
&self.0.withdrawals
|
||||
}
|
||||
|
||||
fn cfg_and_block_env(
|
||||
&self,
|
||||
chain_spec: &ChainSpec,
|
||||
parent: &Header,
|
||||
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
|
||||
// configure evm env based on parent block
|
||||
let mut cfg = CfgEnv::default();
|
||||
cfg.chain_id = chain_spec.chain().id();
|
||||
|
||||
// ensure we're not missing any timestamp based hardforks
|
||||
let spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp());
|
||||
|
||||
// if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
|
||||
// cancun now, we need to set the excess blob gas to the default value
|
||||
let blob_excess_gas_and_price = parent
|
||||
.next_block_excess_blob_gas()
|
||||
.or_else(|| {
|
||||
if spec_id == SpecId::CANCUN {
|
||||
// default excess blob gas is zero
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(BlobExcessGasAndPrice::new);
|
||||
|
||||
let block_env = BlockEnv {
|
||||
number: U256::from(parent.number + 1),
|
||||
coinbase: self.suggested_fee_recipient(),
|
||||
timestamp: U256::from(self.timestamp()),
|
||||
difficulty: U256::ZERO,
|
||||
prevrandao: Some(self.prev_randao()),
|
||||
gas_limit: U256::from(parent.gas_limit),
|
||||
// calculate basefee based on parent block's gas usage
|
||||
basefee: U256::from(
|
||||
parent
|
||||
.next_block_base_fee(chain_spec.base_fee_params(self.timestamp()))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
// calculate excess gas based on parent block's blob gas usage
|
||||
blob_excess_gas_and_price,
|
||||
};
|
||||
|
||||
(CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id), block_env)
|
||||
self.0.cfg_and_block_env(chain_spec, parent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,10 +171,156 @@ impl EngineTypes for CustomEngineTypes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
struct MyCustomNode;
|
||||
|
||||
/// Configure the node types
|
||||
impl NodeTypes for MyCustomNode {
|
||||
type Primitives = ();
|
||||
// use the custom engine types
|
||||
type Engine = CustomEngineTypes;
|
||||
// use the default ethereum EVM config
|
||||
type Evm = EthEvmConfig;
|
||||
|
||||
fn evm_config(&self) -> Self::Evm {
|
||||
Self::Evm::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the Node trait for the custom node
|
||||
///
|
||||
/// This provides a preset configuration for the node
|
||||
impl<N> Node<N> for MyCustomNode
|
||||
where
|
||||
N: FullNodeTypes<Engine = CustomEngineTypes>,
|
||||
{
|
||||
type PoolBuilder = EthereumPoolBuilder;
|
||||
type NetworkBuilder = EthereumNetworkBuilder;
|
||||
type PayloadBuilder = CustomPayloadServiceBuilder;
|
||||
|
||||
fn components(
|
||||
self,
|
||||
) -> ComponentsBuilder<N, Self::PoolBuilder, Self::PayloadBuilder, Self::NetworkBuilder> {
|
||||
ComponentsBuilder::default()
|
||||
.node_types::<N>()
|
||||
.pool(EthereumPoolBuilder::default())
|
||||
.payload(CustomPayloadServiceBuilder::default())
|
||||
.network(EthereumNetworkBuilder::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom payload service builder that supports the custom engine types
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct CustomPayloadServiceBuilder;
|
||||
|
||||
impl<Node, Pool> PayloadServiceBuilder<Node, Pool> for CustomPayloadServiceBuilder
|
||||
where
|
||||
Node: FullNodeTypes<Engine = CustomEngineTypes>,
|
||||
Pool: TransactionPool + Unpin + 'static,
|
||||
{
|
||||
async fn spawn_payload_service(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
pool: Pool,
|
||||
) -> eyre::Result<PayloadBuilderHandle<Node::Engine>> {
|
||||
let payload_builder = CustomPayloadBuilder::default();
|
||||
let conf = ctx.payload_builder_config();
|
||||
|
||||
let payload_job_config = BasicPayloadJobGeneratorConfig::default()
|
||||
.interval(conf.interval())
|
||||
.deadline(conf.deadline())
|
||||
.max_payload_tasks(conf.max_payload_tasks())
|
||||
.extradata(conf.extradata_rlp_bytes())
|
||||
.max_gas_limit(conf.max_gas_limit());
|
||||
|
||||
let payload_generator = BasicPayloadJobGenerator::with_builder(
|
||||
ctx.provider().clone(),
|
||||
pool,
|
||||
ctx.task_executor().clone(),
|
||||
payload_job_config,
|
||||
ctx.chain_spec(),
|
||||
payload_builder,
|
||||
);
|
||||
let (payload_service, payload_builder) =
|
||||
PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream());
|
||||
|
||||
ctx.task_executor().spawn_critical("payload builder service", Box::pin(payload_service));
|
||||
|
||||
Ok(payload_builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type responsible for building custom payloads
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct CustomPayloadBuilder;
|
||||
|
||||
impl<Pool, Client> PayloadBuilder<Pool, Client> for CustomPayloadBuilder
|
||||
where
|
||||
Client: StateProviderFactory,
|
||||
Pool: TransactionPool,
|
||||
{
|
||||
type Attributes = CustomPayloadBuilderAttributes;
|
||||
type BuiltPayload = EthBuiltPayload;
|
||||
|
||||
fn try_build(
|
||||
&self,
|
||||
args: BuildArguments<Pool, Client, Self::Attributes, Self::BuiltPayload>,
|
||||
) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError> {
|
||||
let BuildArguments { client, pool, cached_reads, config, cancel, best_payload } = args;
|
||||
let PayloadConfig {
|
||||
initialized_block_env,
|
||||
initialized_cfg,
|
||||
parent_block,
|
||||
extra_data,
|
||||
attributes,
|
||||
chain_spec,
|
||||
} = config;
|
||||
|
||||
// This reuses the default EthereumPayloadBuilder to build the payload
|
||||
// but any custom logic can be implemented here
|
||||
reth_ethereum_payload_builder::EthereumPayloadBuilder::default().try_build(BuildArguments {
|
||||
client,
|
||||
pool,
|
||||
cached_reads,
|
||||
config: PayloadConfig {
|
||||
initialized_block_env,
|
||||
initialized_cfg,
|
||||
parent_block,
|
||||
extra_data,
|
||||
attributes: attributes.0,
|
||||
chain_spec,
|
||||
},
|
||||
cancel,
|
||||
best_payload,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_empty_payload(
|
||||
client: &Client,
|
||||
config: PayloadConfig<Self::Attributes>,
|
||||
) -> Result<Self::BuiltPayload, PayloadBuilderError> {
|
||||
let PayloadConfig {
|
||||
initialized_block_env,
|
||||
initialized_cfg,
|
||||
parent_block,
|
||||
extra_data,
|
||||
attributes,
|
||||
chain_spec,
|
||||
} = config;
|
||||
<reth_ethereum_payload_builder::EthereumPayloadBuilder as PayloadBuilder<Pool,Client>> ::build_empty_payload(client,
|
||||
PayloadConfig { initialized_block_env, initialized_cfg, parent_block, extra_data, attributes: attributes.0, chain_spec }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
// this launches a test node with http
|
||||
let rpc_args = RpcServerArgs::default().with_http();
|
||||
let _guard = RethTracer::new().init()?;
|
||||
|
||||
let tasks = TaskManager::current();
|
||||
|
||||
// create optimism genesis with canyon at block 2
|
||||
let spec = ChainSpec::builder()
|
||||
@ -204,46 +331,17 @@ async fn main() -> eyre::Result<()> {
|
||||
.shanghai_activated()
|
||||
.build();
|
||||
|
||||
let genesis_hash = spec.genesis_hash();
|
||||
|
||||
// create node config
|
||||
let node_config = NodeConfig::test().with_rpc(rpc_args).with_chain(spec);
|
||||
let node_config =
|
||||
NodeConfig::test().with_rpc(RpcServerArgs::default().with_http()).with_chain(spec);
|
||||
|
||||
let (handle, _manager) = spawn_node(node_config).await.unwrap();
|
||||
let handle = NodeBuilder::new(node_config)
|
||||
.testing_node(tasks.executor())
|
||||
.launch_node(MyCustomNode::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// call a function on the node
|
||||
let client = handle.rpc_server_handles().auth.http_client();
|
||||
let block_number = client.block_number().await.unwrap();
|
||||
println!("Node started");
|
||||
|
||||
// it should be zero, since this is an ephemeral test node
|
||||
assert_eq!(block_number, U256::ZERO);
|
||||
|
||||
// call the engine_forkchoiceUpdated function with payload attributes
|
||||
let forkchoice_state = ForkchoiceState {
|
||||
head_block_hash: genesis_hash,
|
||||
safe_block_hash: genesis_hash,
|
||||
finalized_block_hash: genesis_hash,
|
||||
};
|
||||
|
||||
let payload_attributes = CustomPayloadAttributes {
|
||||
inner: EthPayloadAttributes {
|
||||
timestamp: 1,
|
||||
prev_randao: Default::default(),
|
||||
suggested_fee_recipient: Default::default(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: None,
|
||||
},
|
||||
custom: 42,
|
||||
};
|
||||
|
||||
// call the engine_forkchoiceUpdated function with payload attributes
|
||||
let res = <HttpClient as EngineApiClient<CustomEngineTypes>>::fork_choice_updated_v2(
|
||||
&client,
|
||||
forkchoice_state,
|
||||
Some(payload_attributes),
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
Ok(())
|
||||
handle.node_exit_future.await
|
||||
}
|
||||
|
||||
@ -5,17 +5,15 @@ publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-basic-payload-builder.workspace = true
|
||||
reth-payload-builder.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-ethereum-payload-builder.workspace = true
|
||||
|
||||
tracing.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
futures-util.workspace = true
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
eyre.workspace = true
|
||||
@ -8,58 +8,40 @@
|
||||
//! ```
|
||||
//!
|
||||
//! This launch the regular reth node overriding the engine api payload builder with our custom.
|
||||
use clap::Parser;
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use generator::EmptyBlockPayloadJobGenerator;
|
||||
use reth::{
|
||||
cli::{
|
||||
components::RethNodeComponents,
|
||||
config::PayloadBuilderConfig,
|
||||
ext::{NoArgsCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext},
|
||||
cli::{config::PayloadBuilderConfig, Cli},
|
||||
payload::PayloadBuilderHandle,
|
||||
providers::CanonStateSubscriptions,
|
||||
tasks::TaskSpawner,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadBuilder};
|
||||
use reth_node_api::EngineTypes;
|
||||
use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig;
|
||||
use reth_node_ethereum::{EthEngineTypes, EthereumNode};
|
||||
use reth_payload_builder::PayloadBuilderService;
|
||||
|
||||
pub mod generator;
|
||||
pub mod job;
|
||||
|
||||
fn main() {
|
||||
Cli::<NoArgsCliExt<MyCustomBuilder>>::parse()
|
||||
.with_node_extension(MyCustomBuilder::default())
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
struct MyCustomBuilder;
|
||||
pub struct CustomPayloadBuilder;
|
||||
|
||||
impl RethNodeCommandConfig for MyCustomBuilder {
|
||||
fn spawn_payload_builder_service<Conf, Reth, Builder, Engine>(
|
||||
&mut self,
|
||||
conf: &Conf,
|
||||
components: &Reth,
|
||||
payload_builder: Builder,
|
||||
) -> eyre::Result<PayloadBuilderHandle<Engine>>
|
||||
where
|
||||
Conf: PayloadBuilderConfig,
|
||||
Reth: RethNodeComponents,
|
||||
Engine: EngineTypes + 'static,
|
||||
Builder: PayloadBuilder<
|
||||
Reth::Pool,
|
||||
Reth::Provider,
|
||||
Attributes = Engine::PayloadBuilderAttributes,
|
||||
BuiltPayload = Engine::BuiltPayload,
|
||||
> + Unpin
|
||||
+ 'static,
|
||||
{
|
||||
impl<Node, Pool> PayloadServiceBuilder<Node, Pool> for CustomPayloadBuilder
|
||||
where
|
||||
Node: FullNodeTypes<Engine = EthEngineTypes>,
|
||||
Pool: TransactionPool + Unpin + 'static,
|
||||
{
|
||||
async fn spawn_payload_service(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
pool: Pool,
|
||||
) -> eyre::Result<PayloadBuilderHandle<Node::Engine>> {
|
||||
tracing::info!("Spawning a custom payload builder");
|
||||
let conf = ctx.payload_builder_config();
|
||||
|
||||
let payload_job_config = BasicPayloadJobGeneratorConfig::default()
|
||||
.interval(conf.interval())
|
||||
@ -69,23 +51,38 @@ impl RethNodeCommandConfig for MyCustomBuilder {
|
||||
.max_gas_limit(conf.max_gas_limit());
|
||||
|
||||
let payload_generator = EmptyBlockPayloadJobGenerator::with_builder(
|
||||
components.provider(),
|
||||
components.pool(),
|
||||
components.task_executor(),
|
||||
ctx.provider().clone(),
|
||||
pool,
|
||||
ctx.task_executor().clone(),
|
||||
payload_job_config,
|
||||
components.chain_spec().clone(),
|
||||
payload_builder,
|
||||
ctx.chain_spec().clone(),
|
||||
reth_ethereum_payload_builder::EthereumPayloadBuilder::default(),
|
||||
);
|
||||
|
||||
let (payload_service, payload_builder) = PayloadBuilderService::new(
|
||||
payload_generator,
|
||||
components.events().canonical_state_stream(),
|
||||
);
|
||||
let (payload_service, payload_builder) =
|
||||
PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream());
|
||||
|
||||
components
|
||||
.task_executor()
|
||||
ctx.task_executor()
|
||||
.spawn_critical("custom payload builder service", Box::pin(payload_service));
|
||||
|
||||
Ok(payload_builder)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Cli::parse_args()
|
||||
.run(|builder, _| async move {
|
||||
let handle = builder
|
||||
.with_types(EthereumNode::default())
|
||||
// Configure the components of the node
|
||||
// use default ethereum components but use our custom payload builder
|
||||
.with_components(
|
||||
EthereumNode::components().payload(CustomPayloadBuilder::default()),
|
||||
)
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -7,10 +7,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
jsonrpsee = { workspace = true, features = ["server", "macros"] }
|
||||
futures-util.workspace = true
|
||||
eyre.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio.workspace = true
|
||||
futures-util.workspace = true
|
||||
@ -8,35 +8,65 @@
|
||||
//!
|
||||
//! If no recipients are specified, all transactions will be traced.
|
||||
|
||||
#![warn(unused_crate_dependencies)]
|
||||
|
||||
use clap::Parser;
|
||||
use futures_util::StreamExt;
|
||||
use reth::{
|
||||
cli::{
|
||||
components::{RethNodeComponents, RethRpcComponents, RethRpcServerHandles},
|
||||
config::RethRpcConfig,
|
||||
ext::{RethCliExt, RethNodeCommandConfig},
|
||||
Cli,
|
||||
},
|
||||
builder::NodeHandle,
|
||||
cli::Cli,
|
||||
primitives::{Address, IntoRecoveredTransaction},
|
||||
rpc::{
|
||||
compat::transaction::transaction_to_call_request,
|
||||
types::trace::{parity::TraceType, tracerequest::TraceCallRequest},
|
||||
},
|
||||
tasks::TaskSpawner,
|
||||
transaction_pool::TransactionPool,
|
||||
};
|
||||
use reth_node_ethereum::node::EthereumNode;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main() {
|
||||
Cli::<MyRethCliExt>::parse().run().unwrap();
|
||||
}
|
||||
Cli::<RethCliTxpoolExt>::parse()
|
||||
.run(|builder, args| async move {
|
||||
// launch the node
|
||||
let NodeHandle { mut node, node_exit_future } =
|
||||
builder.node(EthereumNode::default()).launch().await?;
|
||||
|
||||
/// The type that tells the reth CLI what extensions to use
|
||||
struct MyRethCliExt;
|
||||
let recipients = args.recipients.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
impl RethCliExt for MyRethCliExt {
|
||||
/// This tells the reth CLI to trace addresses via `RethCliTxpoolExt`
|
||||
type Node = RethCliTxpoolExt;
|
||||
// create a new subscription to pending transactions
|
||||
let mut pending_transactions = node.pool.new_pending_pool_transactions_listener();
|
||||
|
||||
// get an instance of the `trace_` API handler
|
||||
let traceapi = node.rpc_registry.trace_api();
|
||||
|
||||
println!("Spawning trace task!");
|
||||
// Spawn an async block to listen for transactions.
|
||||
node.task_executor.spawn(Box::pin(async move {
|
||||
// Waiting for new transactions
|
||||
while let Some(event) = pending_transactions.next().await {
|
||||
let tx = event.transaction;
|
||||
println!("Transaction received: {tx:?}");
|
||||
|
||||
if let Some(tx_recipient_address) = tx.to() {
|
||||
if recipients.is_empty() || recipients.contains(&tx_recipient_address) {
|
||||
// trace the transaction with `trace_call`
|
||||
let callrequest =
|
||||
transaction_to_call_request(tx.to_recovered_transaction());
|
||||
let tracerequest = TraceCallRequest::new(callrequest)
|
||||
.with_trace_type(TraceType::Trace);
|
||||
if let Ok(trace_result) = traceapi.trace_call(tracerequest).await {
|
||||
let hash = tx.hash();
|
||||
println!("trace result for transaction {hash}: {trace_result:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
node_exit_future.await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Our custom cli args extension that adds one flag to reth default CLI.
|
||||
@ -46,50 +76,3 @@ struct RethCliTxpoolExt {
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub recipients: Vec<Address>,
|
||||
}
|
||||
|
||||
impl RethNodeCommandConfig for RethCliTxpoolExt {
|
||||
fn on_rpc_server_started<Conf, Reth>(
|
||||
&mut self,
|
||||
_config: &Conf,
|
||||
components: &Reth,
|
||||
rpc_components: RethRpcComponents<'_, Reth>,
|
||||
_handles: RethRpcServerHandles,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Conf: RethRpcConfig,
|
||||
Reth: RethNodeComponents,
|
||||
{
|
||||
let recipients = self.recipients.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
// create a new subscription to pending transactions
|
||||
let mut pending_transactions = components.pool().new_pending_pool_transactions_listener();
|
||||
|
||||
// get an instance of the `trace_` API handler
|
||||
let traceapi = rpc_components.registry.trace_api();
|
||||
|
||||
println!("Spawning trace task!");
|
||||
// Spawn an async block to listen for transactions.
|
||||
components.task_executor().spawn(Box::pin(async move {
|
||||
// Waiting for new transactions
|
||||
while let Some(event) = pending_transactions.next().await {
|
||||
let tx = event.transaction;
|
||||
println!("Transaction received: {tx:?}");
|
||||
|
||||
if let Some(tx_recipient_address) = tx.to() {
|
||||
if recipients.is_empty() || recipients.contains(&tx_recipient_address) {
|
||||
// trace the transaction with `trace_call`
|
||||
let callrequest =
|
||||
transaction_to_call_request(tx.to_recovered_transaction());
|
||||
let tracerequest =
|
||||
TraceCallRequest::new(callrequest).with_trace_type(TraceType::Trace);
|
||||
if let Ok(trace_result) = traceapi.trace_call(tracerequest).await {
|
||||
let hash = tx.hash();
|
||||
println!("trace result for transaction {hash}: {trace_result:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user