anvil_zksync_common/
resolver.rs

1//! Resolving the selectors (both method & event) with external database.
2use 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
19/// The standard request timeout for API requests
20const REQ_TIMEOUT: Duration = Duration::from_secs(15);
21
22/// How many request can time out before we decide this is a spurious connection
23const MAX_TIMEDOUT_REQ: usize = 4usize;
24
25/// A client that can request API data from `https://api.openchain.xyz`
26#[derive(Debug, Clone)]
27pub struct SignEthClient {
28    inner: reqwest::Client,
29    /// Whether the connection is spurious, or API is down
30    spurious_connection: Arc<AtomicBool>,
31    /// How many requests timed out
32    timedout_requests: Arc<AtomicUsize>,
33    /// Max allowed request that can time out
34    max_timedout_requests: usize,
35    /// Cache for network data.
36    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    /// Creates a new client with default settings
59    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            // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry
97            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    /// Returns whether the connection was marked as spurious
116    fn is_spurious(&self) -> bool {
117        self.spurious_connection.load(Ordering::Relaxed)
118    }
119
120    /// Marks the connection as spurious
121    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    /// Decodes the given function or event selector using api.openchain.xyz
133    pub async fn decode_selector(
134        &self,
135        selector: &str,
136        selector_type: SelectorType,
137    ) -> eyre::Result<Option<String>> {
138        // exit early if spurious connection
139        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        // using openchain signature database over 4byte
160        // see https://github.com/foundry-rs/foundry/issues/1672
161        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        // If the search returns null, we should default to using the selector
186        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    /// Decodes the given function, error or event selectors using OpenChain.
205    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        // exit early if spurious connection
231        self.ensure_not_spurious()?;
232
233        let expected_len = match selector_type {
234            SelectorType::Function | SelectorType::Error => 10, // 0x + hex(4bytes)
235            SelectorType::Event => 66,                          // 0x + hex(32bytes)
236        };
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    /// Fetches a function signature given the selector using api.openchain.xyz
296    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}
317/// Fetches a function signature given the selector using api.openchain.xyz
318pub async fn decode_function_selector(selector: &str) -> eyre::Result<Option<String>> {
319    let client = SignEthClient::new();
320    {
321        // Check cache
322        if let Some(resolved_selector) = client
323            .as_ref()
324            .unwrap() // Safe to do as client is created within this function
325            .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() // Safe to do as client is created within this function
339        .decode_function_selector(selector)
340        .await;
341
342    if let Ok(result) = &result {
343        client
344            .as_ref()
345            .unwrap() // Safe to do as client is created within this function
346            .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        // Check cache
361        if let Some(resolved_selector) = client
362            .as_ref()
363            .unwrap() // Safe to do as client is created within this function
364            .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() // Safe to do as client is created within this function
385            .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}