From 39ae8ae9f8c809241de2521173c0524bf83d16d1 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:31:29 +0000 Subject: [PATCH] feat: tx forwarder eth_sendRawTransaction will forward transactions into --upstream-rpc-url, if specified in cli args. --- README.md | 4 ++-- src/lib.rs | 1 + src/main.rs | 25 ++++++++++++++++++++++--- src/node/cli.rs | 3 +++ src/tx_forwarder.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/tx_forwarder.rs diff --git a/README.md b/README.md index a97e09ae1..b40d6d7d0 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Heavily inspired by [reth-bsc](https://github.com/loocapro/reth-bsc). ## TODOs -- [ ] Make it compilable +- [x] Make it compilable - [x] EVM - [x] Storage - - [ ] TX forwarder API + - [x] TX forwarder API - [x] Decide whether to include system txs, receipts in block or not - [x] Downloader - [x] S3 format (file) diff --git a/src/lib.rs b/src/lib.rs index 1a2b3d4ae..cbfdf5569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,6 @@ mod evm; mod hardforks; pub mod node; pub mod pseudo_peer; +pub mod tx_forwarder; pub use node::primitives::{HlBlock, HlBlockBody, HlPrimitives}; diff --git a/src/main.rs b/src/main.rs index da86cdb6d..00c983b5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,15 @@ use clap::Parser; use reth::builder::NodeHandle; +use reth_hl::tx_forwarder::{self, EthForwarderApiServer}; use reth_hl::{ chainspec::parser::HlChainSpecParser, - node::{cli::{Cli, HlNodeArgs}, storage::tables::Tables, HlNode}, + node::{ + cli::{Cli, HlNodeArgs}, + storage::tables::Tables, + HlNode, + }, }; +use tracing::info; // We use jemalloc for performance reasons #[cfg(all(feature = "jemalloc", unix))] @@ -21,8 +27,21 @@ fn main() -> eyre::Result<()> { Cli::::parse().run(|builder, ext| async move { builder.builder.database.create_tables_for::()?; let (node, engine_handle_tx) = HlNode::new(ext.block_source_args.parse().await?); - let NodeHandle { node, node_exit_future: exit_future } = - builder.node(node).launch().await?; + let NodeHandle { node, node_exit_future: exit_future } = builder + .node(node) + .extend_rpc_modules(|ctx| { + let upstream_rpc_url = ext.upstream_rpc_url; + if let Some(upstream_rpc_url) = upstream_rpc_url { + ctx.modules.replace_configured( + tx_forwarder::EthForwarderExt::new(upstream_rpc_url.clone()).into_rpc(), + )?; + + info!("Transaction forwarding enabled"); + } + Ok(()) + }) + .launch() + .await?; engine_handle_tx.send(node.beacon_engine_handle.clone()).unwrap(); diff --git a/src/node/cli.rs b/src/node/cli.rs index b15be7b9b..fc63a4ef2 100644 --- a/src/node/cli.rs +++ b/src/node/cli.rs @@ -31,6 +31,9 @@ use tracing::info; pub struct HlNodeArgs { #[command(flatten)] pub block_source_args: BlockSourceArgs, + + #[arg(long, env = "UPSTREAM_RPC_URL")] + pub upstream_rpc_url: Option, } /// The main reth_hl cli interface. diff --git a/src/tx_forwarder.rs b/src/tx_forwarder.rs new file mode 100644 index 000000000..0d8b2a1f6 --- /dev/null +++ b/src/tx_forwarder.rs @@ -0,0 +1,44 @@ +use alloy_primitives::{Bytes, B256}; +use jsonrpsee::{ + http_client::{HttpClient, HttpClientBuilder}, + proc_macros::rpc, + types::{error::INTERNAL_ERROR_CODE, ErrorObject}, +}; +use jsonrpsee_core::{async_trait, client::ClientT, ClientError, RpcResult}; + +#[rpc(server, namespace = "eth")] +pub trait EthForwarderApi { + #[method(name = "sendRawTransaction")] + async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult; +} + +pub struct EthForwarderExt { + client: HttpClient, +} + +impl EthForwarderExt { + pub fn new(upstream_rpc_url: String) -> Self { + let client = + HttpClientBuilder::default().build(upstream_rpc_url).expect("Failed to build client"); + + Self { client } + } +} + +#[async_trait] +impl EthForwarderApiServer for EthForwarderExt { + async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult { + let txhash = + self.client.clone().request("eth_sendRawTransaction", vec![tx]).await.map_err(|e| { + match e { + ClientError::Call(e) => e, + _ => ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("Failed to send transaction: {e:?}"), + Some(()), + ), + } + })?; + Ok(txhash) + } +}