arb_rpc/
arbtrace.rs

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