From 99a7313cde13dd672ae47d863b3602fd6374e47f Mon Sep 17 00:00:00 2001 From: refcell Date: Mon, 26 Aug 2024 10:07:53 -0400 Subject: [PATCH] feat(rpc): port over sequencer forwarding --- Cargo.lock | 43 ++++++++++ Cargo.toml | 16 +++- crates/rpc/Cargo.toml | 23 +++++ crates/rpc/src/lib.rs | 5 +- crates/rpc/src/{rpc.rs => rollup.rs} | 2 +- crates/rpc/src/sequencer.rs | 123 +++++++++++++++++++++++++++ 6 files changed, 206 insertions(+), 6 deletions(-) rename crates/rpc/src/{rpc.rs => rollup.rs} (94%) create mode 100644 crates/rpc/src/sequencer.rs diff --git a/Cargo.lock b/Cargo.lock index d32061f..7815aa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2197,6 +2197,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "endian-type" version = "0.1.2" @@ -2863,6 +2872,7 @@ dependencies = [ "hyper-util", "log", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -4267,11 +4277,19 @@ dependencies = [ name = "op-rpc" version = "0.0.0" dependencies = [ + "alloy", "alloy-eips", "async-trait", "jsonrpsee", + "jsonrpsee-types", "op-alloy-rpc-jsonrpsee", "op-alloy-rpc-types", + "reqwest", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-types", + "serde_json", + "thiserror", "tracing", ] @@ -5010,8 +5028,10 @@ checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -5029,12 +5049,14 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -8251,6 +8273,27 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index d6b07d7..c76983b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ tokio = { version = "1.21", default-features = false } # rpc jsonrpsee = { version = "0.24", features = ["jsonrpsee-core", "client-core", "server-core", "macros"] } +jsonrpsee-types = "0.24" # Reth reth = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } @@ -153,13 +154,20 @@ reth-provider = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" reth-revm = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } reth-evm = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } reth-tracing = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } +reth-rpc-types = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" } + # Misc -tracing = "0.1.0" -eyre = "0.6.12" clap = "4" -async-trait = "0.1.81" +url = "2.5.2" +eyre = "0.6.12" +tracing = "0.1.0" +thiserror = "1.0" hashbrown = "0.14.5" +async-trait = "0.1.81" parking_lot = "0.12.3" +serde_json = "1.0.94" +reqwest = { version = "0.12", features = ["rustls-tls-native-roots"] } rand = { version = "0.8.3", features = ["small_rng"], default-features = false } -url = "2.5.2" diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index bc8e810..8ff1523 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -23,3 +23,26 @@ alloy-eips.workspace = true tracing.workspace = true jsonrpsee.workspace = true async-trait.workspace = true + +# `reth` feature flag dependencies +alloy = { workspace = true, optional = true } +reqwest = { workspace = true, optional = true } +thiserror = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +jsonrpsee-types = { workspace = true, optional = true } +reth-rpc-types = { workspace = true, optional = true } +reth-rpc-eth-types = { workspace = true, optional = true } +reth-rpc-eth-api = { workspace = true, optional = true } + +[features] +default = ["reth"] +reth = [ + "dep:alloy", + "dep:reqwest", + "dep:serde_json", + "dep:thiserror", + "dep:jsonrpsee-types", + "dep:reth-rpc-types", + "dep:reth-rpc-eth-types", + "dep:reth-rpc-eth-api", +] diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index af0b716..4b2beac 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -4,4 +4,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -pub mod rpc; +pub mod rollup; + +#[cfg(feature = "reth")] +pub mod sequencer; diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rollup.rs similarity index 94% rename from crates/rpc/src/rpc.rs rename to crates/rpc/src/rollup.rs index 60de00d..513eb71 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rollup.rs @@ -7,7 +7,7 @@ use op_alloy_rpc_jsonrpsee::traits::RollupNodeServer; use op_alloy_rpc_types::{config::RollupConfig, output::OutputResponse, sync::SyncStatus}; use tracing::trace; -/// An implementation of the [`RollupNode`] trait. +/// An implementation of the [`RollupNodeServer`] trait. #[derive(Debug, Clone)] pub struct RollupNodeRpc { /// The version of the node. diff --git a/crates/rpc/src/sequencer.rs b/crates/rpc/src/sequencer.rs new file mode 100644 index 0000000..dc19036 --- /dev/null +++ b/crates/rpc/src/sequencer.rs @@ -0,0 +1,123 @@ +//! Optimism reth RPC Extension used to forward raw transactions to the sequencer. + +use std::sync::{atomic::AtomicUsize, Arc}; + +use jsonrpsee_types::error::{ErrorObject, INTERNAL_ERROR_CODE}; +use reqwest::Client; +use reth_rpc_eth_api::RawTransactionForwarder; +use reth_rpc_eth_types::error::{EthApiError, EthResult}; +use reth_rpc_types::ToRpcError; + +/// Error type when interacting with the Sequencer +#[derive(Debug, thiserror::Error)] +pub enum SequencerRpcError { + /// Wrapper around an [`reqwest::Error`]. + #[error(transparent)] + HttpError(#[from] reqwest::Error), + /// Thrown when serializing transaction to forward to sequencer + #[error("invalid sequencer transaction")] + InvalidSequencerTransaction, +} + +impl ToRpcError for SequencerRpcError { + fn to_rpc_error(&self) -> ErrorObject<'static> { + ErrorObject::owned(INTERNAL_ERROR_CODE, self.to_string(), None::) + } +} + +impl From for EthApiError { + fn from(err: SequencerRpcError) -> Self { + Self::other(err) + } +} + +/// A client to interact with a Sequencer +#[derive(Debug, Clone)] +pub struct SequencerClient { + inner: Arc, +} + +impl SequencerClient { + /// Creates a new [`SequencerClient`]. + pub fn new(sequencer_endpoint: impl Into) -> Self { + let client = Client::builder().use_rustls_tls().build().unwrap(); + Self::with_client(sequencer_endpoint, client) + } + + /// Creates a new [`SequencerClient`]. + pub fn with_client(sequencer_endpoint: impl Into, http_client: Client) -> Self { + let inner = SequencerClientInner { + sequencer_endpoint: sequencer_endpoint.into(), + http_client, + id: AtomicUsize::new(0), + }; + Self { inner: Arc::new(inner) } + } + + /// Returns the network of the client + pub fn endpoint(&self) -> &str { + &self.inner.sequencer_endpoint + } + + /// Returns the client + pub fn http_client(&self) -> &Client { + &self.inner.http_client + } + + /// Returns the next id for the request + fn next_request_id(&self) -> usize { + self.inner.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + } + + /// Forwards a transaction to the sequencer endpoint. + pub async fn forward_raw_transaction(&self, tx: &[u8]) -> Result<(), SequencerRpcError> { + let body = serde_json::to_string(&serde_json::json!({ + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [format!("0x{}", alloy::primitives::hex::encode(tx))], + "id": self.next_request_id() + })) + .map_err(|_| { + tracing::warn!( + target = "rpc::eth", + "Failed to serialize transaction for forwarding to sequencer" + ); + SequencerRpcError::InvalidSequencerTransaction + })?; + + self.http_client() + .post(self.endpoint()) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(body) + .send() + .await + .inspect_err(|err| { + tracing::warn!( + target = "rpc::eth", + %err, + "Failed to forward transaction to sequencer", + ); + }) + .map_err(SequencerRpcError::HttpError)?; + + Ok(()) + } +} + +#[async_trait::async_trait] +impl RawTransactionForwarder for SequencerClient { + async fn forward_raw_transaction(&self, tx: &[u8]) -> EthResult<()> { + Self::forward_raw_transaction(self, tx).await?; + Ok(()) + } +} + +#[derive(Debug, Default)] +struct SequencerClientInner { + /// The endpoint of the sequencer + sequencer_endpoint: String, + /// The HTTP client + http_client: Client, + /// Keeps track of unique request ids + id: AtomicUsize, +}