diff --git a/Cargo.lock b/Cargo.lock index 11d01dcfb..2ed119bcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6653,6 +6653,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types", + "alloy-rpc-types-eth", "aquamarine", "backon", "clap", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 9ffce7347..aec1a9faf 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -72,6 +72,7 @@ alloy-rlp.workspace = true alloy-rpc-types = { workspace = true, features = ["engine"] } alloy-consensus.workspace = true alloy-primitives.workspace = true +alloy-rpc-types-eth.workspace = true # tracing tracing.workspace = true diff --git a/bin/reth/src/call_forwarder.rs b/bin/reth/src/call_forwarder.rs new file mode 100644 index 000000000..1330bd984 --- /dev/null +++ b/bin/reth/src/call_forwarder.rs @@ -0,0 +1,97 @@ +use alloy_eips::BlockId; +use alloy_primitives::{Bytes, U256}; +use alloy_rpc_types_eth::{state::StateOverride, transaction::TransactionRequest, BlockOverrides}; +use jsonrpsee::{ + http_client::{HttpClient, HttpClientBuilder}, + proc_macros::rpc, + rpc_params, + types::{error::INTERNAL_ERROR_CODE, ErrorObject}, +}; +use jsonrpsee_core::{async_trait, client::ClientT, ClientError, RpcResult}; + +#[rpc(server, namespace = "eth")] +pub(crate) trait CallForwarderApi { + /// Executes a new message call immediately without creating a transaction on the block chain. + #[method(name = "call")] + async fn call( + &self, + request: TransactionRequest, + block_number: Option, + state_overrides: Option, + block_overrides: Option>, + ) -> RpcResult; + + /// Generates and returns an estimate of how much gas is necessary to allow the transaction to + /// complete. + #[method(name = "estimateGas")] + async fn estimate_gas( + &self, + request: TransactionRequest, + block_number: Option, + state_override: Option, + ) -> RpcResult; +} + +pub(crate) struct CallForwarderExt { + client: HttpClient, +} + +impl CallForwarderExt { + pub(crate) 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 CallForwarderApiServer for CallForwarderExt { + async fn call( + &self, + request: TransactionRequest, + block_number: Option, + state_overrides: Option, + block_overrides: Option>, + ) -> RpcResult { + let result = self + .client + .clone() + .request( + "eth_call", + rpc_params![request, block_number, state_overrides, block_overrides], + ) + .await + .map_err(|e| match e { + ClientError::Call(e) => e, + _ => ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("Failed to call: {:?}", e), + Some(()), + ), + })?; + Ok(result) + } + + async fn estimate_gas( + &self, + request: TransactionRequest, + block_number: Option, + state_override: Option, + ) -> RpcResult { + let result = self + .client + .clone() + .request("eth_estimateGas", rpc_params![request, block_number, state_override]) + .await + .map_err(|e| match e { + ClientError::Call(e) => e, + _ => ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("Failed to estimate gas: {:?}", e), + Some(()), + ), + })?; + Ok(result) + } +} diff --git a/bin/reth/src/main.rs b/bin/reth/src/main.rs index 46aae12da..d0e27d761 100644 --- a/bin/reth/src/main.rs +++ b/bin/reth/src/main.rs @@ -4,23 +4,29 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); mod block_ingest; -mod forwarder; +mod call_forwarder; mod serialized; mod spot_meta; +mod tx_forwarder; use block_ingest::BlockIngest; +use call_forwarder::CallForwarderApiServer; use clap::{Args, Parser}; -use forwarder::EthForwarderApiServer; use reth::cli::Cli; use reth_ethereum_cli::chainspec::EthereumChainSpecParser; use reth_node_ethereum::EthereumNode; use tracing::info; +use tx_forwarder::EthForwarderApiServer; #[derive(Args, Debug, Clone)] struct HyperliquidExtArgs { /// Upstream RPC URL to forward incoming transactions. #[arg(long, default_value = "https://rpc.hyperliquid.xyz/evm")] pub upstream_rpc_url: String, + + /// Forward eth_call and eth_estimateGas to the upstream RPC. + #[arg(long)] + pub forward_call: bool, } fn main() { @@ -38,12 +44,17 @@ fn main() { let handle = builder .node(EthereumNode::default()) .extend_rpc_modules(move |ctx| { - let upstream_rpc_url = ext_args.upstream_rpc_url.clone(); - let rpc = forwarder::EthForwarderExt::new(upstream_rpc_url).into_rpc(); - for method_name in rpc.method_names() { - ctx.modules.remove_method_from_configured(method_name); + let upstream_rpc_url = ext_args.upstream_rpc_url; + ctx.modules.replace_configured( + tx_forwarder::EthForwarderExt::new(upstream_rpc_url.clone()).into_rpc(), + )?; + + if ext_args.forward_call { + ctx.modules.replace_configured( + call_forwarder::CallForwarderExt::new(upstream_rpc_url.clone()) + .into_rpc(), + )?; } - ctx.modules.merge_configured(rpc)?; info!("Transaction forwarder extension enabled"); Ok(()) diff --git a/bin/reth/src/forwarder.rs b/bin/reth/src/tx_forwarder.rs similarity index 100% rename from bin/reth/src/forwarder.rs rename to bin/reth/src/tx_forwarder.rs