reth_optimism_rpc/
sequencer.rs
1use 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#[derive(Debug, Clone)]
18pub struct SequencerClient {
19 inner: Arc<SequencerClientInner>,
20}
21
22impl SequencerClient {
23 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 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 pub fn endpoint(&self) -> &str {
41 &self.inner.sequencer_endpoint
42 }
43
44 pub fn http_client(&self) -> &Client {
46 &self.inner.http_client
47 }
48
49 fn next_request_id(&self) -> usize {
51 self.inner.id.fetch_add(1, atomic::Ordering::SeqCst)
52 }
53
54 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 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 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 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 sequencer_endpoint: String,
131 http_client: Client,
133 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}