diff --git a/Cargo.lock b/Cargo.lock index 6948a82a5..30b391f69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9280,6 +9280,7 @@ dependencies = [ "alloy-evm", "alloy-genesis", "alloy-json-abi", + "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-rlp", diff --git a/Cargo.toml b/Cargo.toml index 260af59e4..221dde715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ alloy-chains = "0.2.0" alloy-eips = "1.0.13" alloy-evm = "0.12" alloy-json-abi = { version = "1.0.0", default-features = false } +alloy-json-rpc = { version = "1.0.13", default-features = false } alloy-dyn-abi = "1.2.0" alloy-network = "1.0.13" alloy-primitives = { version = "1.2.0", default-features = false, features = ["map-foldhash"] } diff --git a/src/tx_forwarder.rs b/src/tx_forwarder.rs index 0d8b2a1f6..dd533740d 100644 --- a/src/tx_forwarder.rs +++ b/src/tx_forwarder.rs @@ -1,15 +1,28 @@ +use std::time::Duration; + +use alloy_json_rpc::RpcObject; +use alloy_network::Ethereum; use alloy_primitives::{Bytes, B256}; +use alloy_rpc_types::TransactionRequest; use jsonrpsee::{ http_client::{HttpClient, HttpClientBuilder}, proc_macros::rpc, types::{error::INTERNAL_ERROR_CODE, ErrorObject}, }; use jsonrpsee_core::{async_trait, client::ClientT, ClientError, RpcResult}; +use reth::rpc::{result::internal_rpc_err, server_types::eth::EthApiError}; +use reth_rpc_eth_api::RpcReceipt; #[rpc(server, namespace = "eth")] -pub trait EthForwarderApi { +pub trait EthForwarderApi { #[method(name = "sendRawTransaction")] async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult; + + #[method(name = "eth_sendTransaction")] + async fn send_transaction(&self, _tx: TransactionRequest) -> RpcResult; + + #[method(name = "eth_sendRawTransactionSync")] + async fn send_raw_transaction_sync(&self, tx: Bytes) -> RpcResult; } pub struct EthForwarderExt { @@ -23,22 +36,56 @@ impl EthForwarderExt { Self { client } } + + fn from_client_error(e: ClientError, internal_error_prefix: &str) -> ErrorObject { + match e { + ClientError::Call(e) => e, + _ => ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("{internal_error_prefix}: {e:?}"), + Some(()), + ), + } + } } #[async_trait] -impl EthForwarderApiServer for EthForwarderExt { +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(()), - ), - } - })?; + let txhash = self + .client + .clone() + .request("eth_sendRawTransaction", vec![tx]) + .await + .map_err(|e| Self::from_client_error(e, "Failed to send transaction"))?; Ok(txhash) } + + async fn send_transaction(&self, _tx: TransactionRequest) -> RpcResult { + Err(internal_rpc_err("Unimplemented")) + } + + async fn send_raw_transaction_sync(&self, tx: Bytes) -> RpcResult> { + let hash = self.send_raw_transaction(tx).await?; + const TIMEOUT_DURATION: Duration = Duration::from_secs(30); + const INTERVAL: Duration = Duration::from_secs(1); + + tokio::time::timeout(TIMEOUT_DURATION, async { + loop { + let receipt = + self.client.request("eth_getTransactionReceipt", vec![hash]).await.map_err( + |e| Self::from_client_error(e, "Failed to get transaction receipt"), + )?; + if let Some(receipt) = receipt { + return Ok(receipt); + } + tokio::time::sleep(INTERVAL).await; + } + }) + .await + .unwrap_or_else(|_elapsed| { + Err(EthApiError::TransactionConfirmationTimeout { hash, duration: TIMEOUT_DURATION } + .into()) + }) + } }