arb_rpc/
arbtrace.rs

1//! `arbtrace_*` namespace — forwards pre-Nitro trace requests to a
2//! configured classic-node RPC endpoint.
3
4use std::sync::Arc;
5
6use jsonrpsee::{
7    core::{client::ClientT, RpcResult},
8    proc_macros::rpc,
9    types::{error::INTERNAL_ERROR_CODE, ErrorObject},
10};
11use jsonrpsee_http_client::{HttpClient, HttpClientBuilder};
12use parking_lot::Mutex;
13use serde_json::{self as json, value::RawValue, Value as JsonValue};
14use std::time::Duration;
15
16fn forwarding_not_configured() -> ErrorObject<'static> {
17    ErrorObject::owned(
18        INTERNAL_ERROR_CODE,
19        "arbtrace calls forwarding not configured",
20        None::<()>,
21    )
22}
23
24fn block_unsupported_by_classic(block_num: i64, genesis: u64) -> ErrorObject<'static> {
25    ErrorObject::owned(
26        INTERNAL_ERROR_CODE,
27        format!("block number {block_num} is not supported by classic node (> genesis {genesis})"),
28        None::<()>,
29    )
30}
31
32fn http_error(e: impl std::fmt::Display) -> ErrorObject<'static> {
33    ErrorObject::owned(
34        INTERNAL_ERROR_CODE,
35        format!("arbtrace forwarding failed: {e}"),
36        None::<()>,
37    )
38}
39
40#[derive(Debug, Clone, Default)]
41pub struct ArbTraceConfig {
42    /// URL of the pre-Nitro classic node to forward requests to.
43    pub fallback_client_url: Option<String>,
44    /// Timeout for forwarded RPC calls.
45    pub fallback_client_timeout: Option<Duration>,
46    /// Nitro genesis block number; requests past this are rejected
47    /// without hitting the classic node.
48    pub genesis_block_num: u64,
49}
50
51#[rpc(server, namespace = "arbtrace")]
52pub trait ArbTraceApi {
53    #[method(name = "call")]
54    async fn call(
55        &self,
56        call_args: Box<RawValue>,
57        trace_types: Box<RawValue>,
58        block_num_or_hash: Box<RawValue>,
59    ) -> RpcResult<JsonValue>;
60
61    #[method(name = "callMany")]
62    async fn call_many(
63        &self,
64        calls: Box<RawValue>,
65        block_num_or_hash: Box<RawValue>,
66    ) -> RpcResult<JsonValue>;
67
68    #[method(name = "replayBlockTransactions")]
69    async fn replay_block_transactions(
70        &self,
71        block_num_or_hash: Box<RawValue>,
72        trace_types: Box<RawValue>,
73    ) -> RpcResult<JsonValue>;
74
75    #[method(name = "replayTransaction")]
76    async fn replay_transaction(
77        &self,
78        tx_hash: Box<RawValue>,
79        trace_types: Box<RawValue>,
80    ) -> RpcResult<JsonValue>;
81
82    #[method(name = "transaction")]
83    async fn transaction(&self, tx_hash: Box<RawValue>) -> RpcResult<JsonValue>;
84
85    #[method(name = "get")]
86    async fn get(&self, tx_hash: Box<RawValue>, path: Box<RawValue>) -> RpcResult<JsonValue>;
87
88    #[method(name = "block")]
89    async fn block(&self, block_num_or_hash: Box<RawValue>) -> RpcResult<JsonValue>;
90
91    #[method(name = "filter")]
92    async fn filter(&self, filter: Box<RawValue>) -> RpcResult<JsonValue>;
93}
94
95/// Lazy HTTP client backed by `jsonrpsee-http-client`.
96pub struct ArbTraceHandler {
97    config: Arc<ArbTraceConfig>,
98    client: Mutex<Option<Arc<HttpClient>>>,
99}
100
101impl std::fmt::Debug for ArbTraceHandler {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.debug_struct("ArbTraceHandler")
104            .field("config", &self.config)
105            .finish_non_exhaustive()
106    }
107}
108
109impl Clone for ArbTraceHandler {
110    fn clone(&self) -> Self {
111        Self {
112            config: self.config.clone(),
113            client: Mutex::new(self.client.lock().clone()),
114        }
115    }
116}
117
118impl ArbTraceHandler {
119    pub fn new(config: ArbTraceConfig) -> Self {
120        Self {
121            config: Arc::new(config),
122            client: Mutex::new(None),
123        }
124    }
125
126    fn get_client(&self) -> Result<Arc<HttpClient>, ErrorObject<'static>> {
127        if let Some(c) = self.client.lock().as_ref() {
128            return Ok(c.clone());
129        }
130        let url = self
131            .config
132            .fallback_client_url
133            .as_ref()
134            .ok_or_else(forwarding_not_configured)?;
135        let mut builder = HttpClientBuilder::default();
136        if let Some(t) = self.config.fallback_client_timeout {
137            builder = builder.request_timeout(t);
138        }
139        let client = builder.build(url).map_err(http_error)?;
140        let arc = Arc::new(client);
141        *self.client.lock() = Some(arc.clone());
142        Ok(arc)
143    }
144
145    fn check_block_supported_by_classic(
146        &self,
147        block_num_or_hash: &RawValue,
148    ) -> Result<(), ErrorObject<'static>> {
149        let parsed: JsonValue = json::from_str(block_num_or_hash.get()).unwrap_or(JsonValue::Null);
150        if let Some(s) = parsed.as_str() {
151            if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
152                if let Ok(n) = i64::from_str_radix(hex, 16) {
153                    if n < 0 || (n as u64) > self.config.genesis_block_num {
154                        return Err(block_unsupported_by_classic(
155                            n,
156                            self.config.genesis_block_num,
157                        ));
158                    }
159                }
160            }
161        }
162        Ok(())
163    }
164
165    async fn forward(&self, method: &str, params: Vec<Box<RawValue>>) -> RpcResult<JsonValue> {
166        let client = self.get_client()?;
167        let mut array_params = jsonrpsee::core::params::ArrayParams::new();
168        for raw in params {
169            let v: JsonValue = serde_json::from_str(raw.get()).unwrap_or(JsonValue::Null);
170            array_params.insert(v).map_err(http_error)?;
171        }
172        let resp: JsonValue = client
173            .request(method, array_params)
174            .await
175            .map_err(http_error)?;
176        Ok(resp)
177    }
178}
179
180#[async_trait::async_trait]
181impl ArbTraceApiServer for ArbTraceHandler {
182    async fn call(
183        &self,
184        call_args: Box<RawValue>,
185        trace_types: Box<RawValue>,
186        block_num_or_hash: Box<RawValue>,
187    ) -> RpcResult<JsonValue> {
188        self.check_block_supported_by_classic(&block_num_or_hash)?;
189        self.forward(
190            "arbtrace_call",
191            vec![call_args, trace_types, block_num_or_hash],
192        )
193        .await
194    }
195
196    async fn call_many(
197        &self,
198        calls: Box<RawValue>,
199        block_num_or_hash: Box<RawValue>,
200    ) -> RpcResult<JsonValue> {
201        self.check_block_supported_by_classic(&block_num_or_hash)?;
202        self.forward("arbtrace_callMany", vec![calls, block_num_or_hash])
203            .await
204    }
205
206    async fn replay_block_transactions(
207        &self,
208        block_num_or_hash: Box<RawValue>,
209        trace_types: Box<RawValue>,
210    ) -> RpcResult<JsonValue> {
211        self.check_block_supported_by_classic(&block_num_or_hash)?;
212        self.forward(
213            "arbtrace_replayBlockTransactions",
214            vec![block_num_or_hash, trace_types],
215        )
216        .await
217    }
218
219    async fn replay_transaction(
220        &self,
221        tx_hash: Box<RawValue>,
222        trace_types: Box<RawValue>,
223    ) -> RpcResult<JsonValue> {
224        self.forward("arbtrace_replayTransaction", vec![tx_hash, trace_types])
225            .await
226    }
227
228    async fn transaction(&self, tx_hash: Box<RawValue>) -> RpcResult<JsonValue> {
229        self.forward("arbtrace_transaction", vec![tx_hash]).await
230    }
231
232    async fn get(&self, tx_hash: Box<RawValue>, path: Box<RawValue>) -> RpcResult<JsonValue> {
233        self.forward("arbtrace_get", vec![tx_hash, path]).await
234    }
235
236    async fn block(&self, block_num_or_hash: Box<RawValue>) -> RpcResult<JsonValue> {
237        self.check_block_supported_by_classic(&block_num_or_hash)?;
238        self.forward("arbtrace_block", vec![block_num_or_hash])
239            .await
240    }
241
242    async fn filter(&self, filter: Box<RawValue>) -> RpcResult<JsonValue> {
243        self.forward("arbtrace_filter", vec![filter]).await
244    }
245}