reth_optimism_rpc/
sequencer.rs

1//! Helpers for optimism specific RPC implementations.
2
3use std::sync::{
4    atomic::{self, AtomicUsize},
5    Arc,
6};
7
8use alloy_primitives::hex;
9use alloy_rpc_types_eth::erc4337::TransactionConditional;
10use reqwest::Client;
11use serde_json::{json, Value};
12use tracing::warn;
13
14use crate::SequencerClientError;
15
16/// A client to interact with a Sequencer
17#[derive(Debug, Clone)]
18pub struct SequencerClient {
19    inner: Arc<SequencerClientInner>,
20}
21
22impl SequencerClient {
23    /// Creates a new [`SequencerClient`].
24    pub fn new(sequencer_endpoint: impl Into<String>) -> Self {
25        let client = Client::builder().use_rustls_tls().build().unwrap();
26        Self::with_client(sequencer_endpoint, client)
27    }
28
29    /// Creates a new [`SequencerClient`].
30    pub fn with_client(sequencer_endpoint: impl Into<String>, http_client: Client) -> Self {
31        let inner = SequencerClientInner {
32            sequencer_endpoint: sequencer_endpoint.into(),
33            http_client,
34            id: AtomicUsize::new(0),
35        };
36        Self { inner: Arc::new(inner) }
37    }
38
39    /// Returns the network of the client
40    pub fn endpoint(&self) -> &str {
41        &self.inner.sequencer_endpoint
42    }
43
44    /// Returns the client
45    pub fn http_client(&self) -> &Client {
46        &self.inner.http_client
47    }
48
49    /// Returns the next id for the request
50    fn next_request_id(&self) -> usize {
51        self.inner.id.fetch_add(1, atomic::Ordering::SeqCst)
52    }
53
54    /// Helper function to get body of the request with the given params array.
55    fn request_body(&self, method: &str, params: Value) -> serde_json::Result<String> {
56        let request = json!({
57            "jsonrpc": "2.0",
58            "method": method,
59            "params": params,
60            "id": self.next_request_id()
61        });
62
63        serde_json::to_string(&request)
64    }
65
66    /// Sends a POST request to the sequencer endpoint.
67    async fn post_request(&self, body: String) -> Result<(), reqwest::Error> {
68        self.http_client()
69            .post(self.endpoint())
70            .header(reqwest::header::CONTENT_TYPE, "application/json")
71            .body(body)
72            .send()
73            .await?;
74        Ok(())
75    }
76
77    /// Forwards a transaction to the sequencer endpoint.
78    pub async fn forward_raw_transaction(&self, tx: &[u8]) -> Result<(), SequencerClientError> {
79        let body = self
80            .request_body("eth_sendRawTransaction", json!([format!("0x{}", hex::encode(tx))]))
81            .map_err(|_| {
82                warn!(
83                    target: "rpc::eth",
84                    "Failed to serialize transaction for forwarding to sequencer"
85                );
86                SequencerClientError::InvalidSequencerTransaction
87            })?;
88
89        self.post_request(body).await.inspect_err(|err| {
90            warn!(
91                target: "rpc::eth",
92                %err,
93                "Failed to forward transaction to sequencer",
94            );
95        })?;
96
97        Ok(())
98    }
99
100    /// Forwards a transaction conditional to the sequencer endpoint.
101    pub async fn forward_raw_transaction_conditional(
102        &self,
103        tx: &[u8],
104        condition: TransactionConditional,
105    ) -> Result<(), SequencerClientError> {
106        let params = json!([format!("0x{}", hex::encode(tx)), condition]);
107        let body =
108            self.request_body("eth_sendRawTransactionConditional", params).map_err(|_| {
109                warn!(
110                    target: "rpc::eth",
111                    "Failed to serialize transaction for forwarding to sequencer"
112                );
113                SequencerClientError::InvalidSequencerTransaction
114            })?;
115
116        self.post_request(body).await.inspect_err(|err| {
117            warn!(
118                target: "rpc::eth",
119                %err,
120                "Failed to forward transaction conditional for sequencer",
121            );
122        })?;
123        Ok(())
124    }
125}
126
127#[derive(Debug, Default)]
128struct SequencerClientInner {
129    /// The endpoint of the sequencer
130    sequencer_endpoint: String,
131    /// The HTTP client
132    http_client: Client,
133    /// Keeps track of unique request ids
134    id: AtomicUsize,
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use serde_json::json;
141
142    #[test]
143    fn test_body_str() {
144        let client = SequencerClient::new("http://localhost:8545");
145        let params = json!(["0x1234", {"block_number":10}]);
146
147        let body = client.request_body("eth_getBlockByNumber", params).unwrap();
148
149        assert_eq!(
150            body,
151            r#"{"id":0,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1234",{"block_number":10}]}"#
152        );
153
154        let condition = TransactionConditional::default();
155        let params = json!([format!("0x{}", hex::encode("abcd")), condition]);
156
157        let body = client.request_body("eth_sendRawTransactionConditional", params).unwrap();
158
159        assert_eq!(
160            body,
161            r#"{"id":1,"jsonrpc":"2.0","method":"eth_sendRawTransactionConditional","params":["0x61626364",{"knownAccounts":{}}]}"#
162        );
163    }
164}