Add --forward-call for eth_call and eth_estimateGas

This commit is contained in:
sprites0
2025-05-24 04:05:13 +00:00
parent 41d72ae949
commit 4b793c496b
5 changed files with 117 additions and 7 deletions

1
Cargo.lock generated
View File

@ -6653,6 +6653,7 @@ dependencies = [
"alloy-primitives",
"alloy-rlp",
"alloy-rpc-types",
"alloy-rpc-types-eth",
"aquamarine",
"backon",
"clap",

View File

@ -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

View File

@ -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<BlockId>,
state_overrides: Option<StateOverride>,
block_overrides: Option<Box<BlockOverrides>>,
) -> RpcResult<Bytes>;
/// 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<BlockId>,
state_override: Option<StateOverride>,
) -> RpcResult<U256>;
}
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<BlockId>,
state_overrides: Option<StateOverride>,
block_overrides: Option<Box<BlockOverrides>>,
) -> RpcResult<Bytes> {
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<BlockId>,
state_override: Option<StateOverride>,
) -> RpcResult<U256> {
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)
}
}

View File

@ -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(())