1use super::{cache::Cache, cache::CacheConfig, sh_warn};
3use lazy_static::lazy_static;
4use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
5use serde::Deserialize;
6use std::iter::FromIterator;
7use std::{
8 collections::HashMap,
9 sync::{
10 atomic::{AtomicBool, AtomicUsize, Ordering},
11 Arc,
12 },
13 time::Duration,
14};
15use tokio::sync::RwLock;
16
17static SELECTOR_DATABASE_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup";
18
19const REQ_TIMEOUT: Duration = Duration::from_secs(15);
21
22const MAX_TIMEDOUT_REQ: usize = 4usize;
24
25#[derive(Debug, Clone)]
27pub struct SignEthClient {
28 inner: reqwest::Client,
29 spurious_connection: Arc<AtomicBool>,
31 timedout_requests: Arc<AtomicUsize>,
33 max_timedout_requests: usize,
35 pub(crate) cache: Arc<RwLock<Cache>>,
37}
38
39#[derive(Deserialize)]
40pub struct KnownAbi {
41 abi: String,
42 name: String,
43}
44
45lazy_static! {
46 static ref KNOWN_SIGNATURES: HashMap<String, String> = {
47 let json_value = serde_json::from_slice(include_bytes!("data/abi_map.json")).unwrap();
48 let pairs: Vec<KnownAbi> = serde_json::from_value(json_value).unwrap();
49
50 pairs
51 .into_iter()
52 .map(|entry| (entry.abi, entry.name))
53 .collect()
54 };
55}
56
57impl SignEthClient {
58 pub fn new() -> reqwest::Result<Self> {
60 let inner = reqwest::Client::builder()
61 .default_headers(HeaderMap::from_iter([(
62 HeaderName::from_static("user-agent"),
63 HeaderValue::from_static("zksync"),
64 )]))
65 .timeout(REQ_TIMEOUT)
66 .build()?;
67 Ok(Self {
68 inner,
69 spurious_connection: Arc::new(Default::default()),
70 timedout_requests: Arc::new(Default::default()),
71 max_timedout_requests: MAX_TIMEDOUT_REQ,
72 cache: Arc::new(RwLock::new(Cache::new(CacheConfig::default()))),
73 })
74 }
75
76 async fn get_text(&self, url: &str) -> reqwest::Result<String> {
77 self.inner
78 .get(url)
79 .send()
80 .await
81 .inspect_err(|err| {
82 self.on_reqwest_err(err);
83 })?
84 .text()
85 .await
86 .inspect_err(|err| {
87 self.on_reqwest_err(err);
88 })
89 }
90
91 fn on_reqwest_err(&self, err: &reqwest::Error) {
92 fn is_connectivity_err(err: &reqwest::Error) -> bool {
93 if err.is_timeout() || err.is_connect() {
94 return true;
95 }
96 if let Some(status) = err.status() {
98 let code = status.as_u16();
99 if (500..600).contains(&code) {
100 return true;
101 }
102 }
103 false
104 }
105
106 if is_connectivity_err(err) {
107 sh_warn!("spurious network detected for api.openchain.xyz");
108 let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst);
109 if previous >= self.max_timedout_requests {
110 self.set_spurious();
111 }
112 }
113 }
114
115 fn is_spurious(&self) -> bool {
117 self.spurious_connection.load(Ordering::Relaxed)
118 }
119
120 fn set_spurious(&self) {
122 self.spurious_connection.store(true, Ordering::Relaxed)
123 }
124
125 fn ensure_not_spurious(&self) -> eyre::Result<()> {
126 if self.is_spurious() {
127 eyre::bail!("Spurious connection detected")
128 }
129 Ok(())
130 }
131
132 pub async fn decode_selector(
134 &self,
135 selector: &str,
136 selector_type: SelectorType,
137 ) -> eyre::Result<Option<String>> {
138 self.ensure_not_spurious()?;
140
141 #[derive(Deserialize)]
142 struct Decoded {
143 name: String,
144 filtered: bool,
145 }
146
147 #[derive(Deserialize)]
148 struct ApiResult {
149 event: HashMap<String, Option<Vec<Decoded>>>,
150 function: HashMap<String, Option<Vec<Decoded>>>,
151 }
152
153 #[derive(Deserialize)]
154 struct ApiResponse {
155 ok: bool,
156 result: ApiResult,
157 }
158
159 let url = match selector_type {
162 SelectorType::Function | SelectorType::Error => {
163 format!("{SELECTOR_DATABASE_URL}?function={selector}&filter=true")
164 }
165 SelectorType::Event => format!("{SELECTOR_DATABASE_URL}?event={selector}&filter=true"),
166 };
167
168 let res = self.get_text(&url).await?;
169 let api_response = match serde_json::from_str::<ApiResponse>(&res) {
170 Ok(inner) => inner,
171 Err(err) => {
172 eyre::bail!("Could not decode response:\n {res}.\nError: {err}")
173 }
174 };
175
176 if !api_response.ok {
177 eyre::bail!("Failed to decode:\n {res}")
178 }
179
180 let decoded = match selector_type {
181 SelectorType::Function | SelectorType::Error => api_response.result.function,
182 SelectorType::Event => api_response.result.event,
183 };
184
185 let default_decoded = vec![Decoded {
187 name: selector.to_string(),
188 filtered: false,
189 }];
190
191 Ok(decoded
192 .get(selector)
193 .ok_or(eyre::eyre!("No signature found"))?
194 .as_ref()
195 .unwrap_or(&default_decoded)
196 .iter()
197 .filter(|d| !d.filtered)
198 .map(|d| d.name.clone())
199 .collect::<Vec<String>>()
200 .first()
201 .cloned())
202 }
203
204 pub async fn decode_selectors(
206 &self,
207 selector_type: SelectorType,
208 selectors: impl IntoIterator<Item = impl Into<String>>,
209 ) -> eyre::Result<Vec<Option<Vec<String>>>> {
210 let selectors: Vec<String> = selectors
211 .into_iter()
212 .map(Into::into)
213 .map(|s| s.to_lowercase())
214 .map(|s| {
215 if s.starts_with("0x") {
216 s
217 } else {
218 format!("0x{s}")
219 }
220 })
221 .collect();
222
223 if selectors.is_empty() {
224 return Ok(vec![]);
225 }
226
227 tracing::debug!(len = selectors.len(), "decoding selectors");
228 tracing::trace!(?selectors, "decoding selectors");
229
230 self.ensure_not_spurious()?;
232
233 let expected_len = match selector_type {
234 SelectorType::Function | SelectorType::Error => 10, SelectorType::Event => 66, };
237 if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) {
238 eyre::bail!(
239 "Invalid selector {s}: expected {expected_len} characters (including 0x prefix)."
240 )
241 }
242
243 #[derive(Deserialize)]
244 struct Decoded {
245 name: String,
246 }
247
248 #[derive(Deserialize)]
249 struct ApiResult {
250 event: HashMap<String, Option<Vec<Decoded>>>,
251 function: HashMap<String, Option<Vec<Decoded>>>,
252 }
253
254 #[derive(Deserialize)]
255 struct ApiResponse {
256 ok: bool,
257 result: ApiResult,
258 }
259
260 let url = format!(
261 "{SELECTOR_DATABASE_URL}?{ltype}={selectors_str}",
262 ltype = match selector_type {
263 SelectorType::Function | SelectorType::Error => "function",
264 SelectorType::Event => "event",
265 },
266 selectors_str = selectors.join(",")
267 );
268
269 let res = self.get_text(&url).await?;
270 let api_response = match serde_json::from_str::<ApiResponse>(&res) {
271 Ok(inner) => inner,
272 Err(err) => {
273 eyre::bail!("Could not decode response:\n {res}.\nError: {err}")
274 }
275 };
276
277 if !api_response.ok {
278 eyre::bail!("Failed to decode:\n {res}")
279 }
280
281 let decoded = match selector_type {
282 SelectorType::Function | SelectorType::Error => api_response.result.function,
283 SelectorType::Event => api_response.result.event,
284 };
285
286 Ok(selectors
287 .into_iter()
288 .map(|selector| match decoded.get(&selector) {
289 Some(Some(r)) => Some(r.iter().map(|d| d.name.clone()).collect()),
290 _ => None,
291 })
292 .collect())
293 }
294
295 pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result<Option<String>> {
297 let prefixed_selector = format!("0x{}", selector.strip_prefix("0x").unwrap_or(selector));
298 if prefixed_selector.len() != 10 {
299 eyre::bail!("Invalid selector: expected 8 characters (excluding 0x prefix), got {} characters (including 0x prefix).", prefixed_selector.len())
300 }
301
302 if let Some(r) = KNOWN_SIGNATURES.get(&prefixed_selector) {
303 return Ok(Some(r.clone()));
304 }
305
306 self.decode_selector(&prefixed_selector[..10], SelectorType::Function)
307 .await
308 }
309}
310
311#[derive(Clone, Copy)]
312pub enum SelectorType {
313 Function,
314 Event,
315 Error,
316}
317pub async fn decode_function_selector(selector: &str) -> eyre::Result<Option<String>> {
319 let client = SignEthClient::new();
320 {
321 if let Some(resolved_selector) = client
323 .as_ref()
324 .unwrap() .cache
326 .read()
327 .await
328 .get_resolver_selector(&(selector.to_string()))
329 {
330 tracing::debug!("Using cached function selector for {selector}");
331 return Ok(Some(resolved_selector.clone()));
332 }
333 }
334
335 tracing::debug!("Making external request to resolve function selector for {selector}");
336 let result = client
337 .as_ref()
338 .unwrap() .decode_function_selector(selector)
340 .await;
341
342 if let Ok(result) = &result {
343 client
344 .as_ref()
345 .unwrap() .cache
347 .write()
348 .await
349 .insert_resolver_selector(
350 selector.to_string(),
351 result.clone().unwrap_or_else(|| "".to_string()),
352 );
353 }
354 result
355}
356
357pub async fn decode_event_selector(selector: &str) -> eyre::Result<Option<String>> {
358 let client = SignEthClient::new();
359 {
360 if let Some(resolved_selector) = client
362 .as_ref()
363 .unwrap() .cache
365 .read()
366 .await
367 .get_resolver_selector(&(selector.to_string()))
368 {
369 tracing::debug!("Using cached event selector for {selector}");
370 return Ok(Some(resolved_selector.clone()));
371 }
372 }
373
374 tracing::debug!("Making external request to resolve event selector for {selector}");
375 let result = client
376 .as_ref()
377 .unwrap()
378 .decode_selector(selector, SelectorType::Event)
379 .await;
380
381 if let Ok(result) = &result {
382 client
383 .as_ref()
384 .unwrap() .cache
386 .write()
387 .await
388 .insert_resolver_selector(
389 selector.to_string(),
390 result.clone().unwrap_or_else(|| "".to_string()),
391 );
392 }
393 result
394}