anvil_zksync_common/
resolver.rs

1//! Resolving the selectors (both method & event) with external database.
2use super::sh_warn;
3use once_cell::sync::Lazy;
4use serde::Deserialize;
5use std::{
6    collections::HashMap,
7    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
8};
9
10static SELECTOR_DATABASE_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup";
11
12/// How many request can time out before we decide this is a spurious connection
13const MAX_TIMEDOUT_REQ: usize = 4usize;
14
15/// A client that can request API data from `https://api.openchain.xyz`
16/// Does not perform any caching and should not be used directly.
17/// Use `SignaturesIdentifier` instead.
18#[derive(Debug, Default)]
19pub struct SignEthClient {
20    /// Whether the connection is spurious, or API is down
21    spurious_connection: AtomicBool,
22    /// How many requests timed out
23    timedout_requests: AtomicUsize,
24}
25
26#[derive(Deserialize)]
27struct KnownAbi {
28    abi: String,
29    name: String,
30}
31
32static KNOWN_SIGNATURES: Lazy<HashMap<String, String>> = Lazy::new(|| {
33    let json_value = serde_json::from_slice(include_bytes!("data/abi_map.json")).unwrap();
34    let pairs: Vec<KnownAbi> = serde_json::from_value(json_value).unwrap();
35
36    pairs
37        .into_iter()
38        .map(|entry| (entry.abi, entry.name))
39        .collect()
40});
41
42impl SignEthClient {
43    /// Creates a new client with default settings
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Convenience method for making a GET request
49    async fn get(&self, url: &str) -> eyre::Result<String> {
50        let resp = reqwest::get(url).await.inspect_err(|e| {
51            self.on_reqwest_err(e);
52        })?;
53        let text = resp.text().await.inspect_err(|e| {
54            self.on_reqwest_err(e);
55        })?;
56        Ok(text)
57    }
58
59    fn on_reqwest_err(&self, err: &reqwest::Error) {
60        fn is_connectivity_err(err: &reqwest::Error) -> bool {
61            if err.is_timeout() || err.is_connect() {
62                return true;
63            }
64            // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry
65            if let Some(status) = err.status() {
66                let code = status.as_u16();
67                if (500..600).contains(&code) {
68                    return true;
69                }
70            }
71            false
72        }
73
74        if is_connectivity_err(err) {
75            sh_warn!("spurious network detected for api.openchain.xyz");
76            let previous = self.timedout_requests.fetch_add(1, Ordering::Relaxed);
77            if previous >= MAX_TIMEDOUT_REQ {
78                self.set_spurious();
79            }
80        }
81    }
82
83    /// Returns whether the connection was marked as spurious
84    fn is_spurious(&self) -> bool {
85        self.spurious_connection.load(Ordering::Relaxed)
86    }
87
88    /// Marks the connection as spurious
89    fn set_spurious(&self) {
90        self.spurious_connection.store(true, Ordering::Relaxed);
91        tracing::warn!(
92            "Connection to {SELECTOR_DATABASE_URL} is spurious, further requests will fail."
93        );
94    }
95
96    /// Decodes the given function or event selector using api.openchain.xyz
97    pub async fn decode_selector(
98        &self,
99        selector: &str,
100        selector_type: SelectorType,
101    ) -> eyre::Result<Option<String>> {
102        // exit early if spurious connection
103        eyre::ensure!(!self.is_spurious(), "Spurious connection detected");
104
105        #[derive(Deserialize)]
106        struct Decoded {
107            name: String,
108            filtered: bool,
109        }
110
111        #[derive(Deserialize)]
112        struct ApiResult {
113            event: HashMap<String, Option<Vec<Decoded>>>,
114            function: HashMap<String, Option<Vec<Decoded>>>,
115        }
116
117        #[derive(Deserialize)]
118        struct ApiResponse {
119            ok: bool,
120            result: ApiResult,
121        }
122
123        // using openchain signature database over 4byte
124        // see https://github.com/foundry-rs/foundry/issues/1672
125        let url = match selector_type {
126            SelectorType::Function | SelectorType::Error => {
127                format!("{SELECTOR_DATABASE_URL}?function={selector}&filter=true")
128            }
129            SelectorType::Event => format!("{SELECTOR_DATABASE_URL}?event={selector}&filter=true"),
130        };
131
132        let res = self.get(&url).await?;
133        let api_response = match serde_json::from_str::<ApiResponse>(&res) {
134            Ok(inner) => inner,
135            Err(err) => {
136                eyre::bail!("Could not decode response:\n {res}.\nError: {err}")
137            }
138        };
139
140        if !api_response.ok {
141            eyre::bail!("Failed to decode:\n {res}")
142        }
143
144        let decoded = match selector_type {
145            SelectorType::Function | SelectorType::Error => api_response.result.function,
146            SelectorType::Event => api_response.result.event,
147        };
148
149        // If the search returns null, we should default to using the selector
150        let default_decoded = vec![Decoded {
151            name: selector.to_string(),
152            filtered: false,
153        }];
154
155        Ok(decoded
156            .get(selector)
157            .ok_or(eyre::eyre!("No signature found"))?
158            .as_ref()
159            .unwrap_or(&default_decoded)
160            .iter()
161            .filter(|d| !d.filtered)
162            .map(|d| d.name.clone())
163            .collect::<Vec<String>>()
164            .first()
165            .cloned())
166    }
167
168    /// Decodes the given function, error or event selectors using OpenChain.
169    pub async fn decode_selectors(
170        &self,
171        selector_type: SelectorType,
172        selectors: impl IntoIterator<Item = impl Into<String>>,
173    ) -> eyre::Result<Vec<Option<Vec<String>>>> {
174        let selectors: Vec<String> = selectors
175            .into_iter()
176            .map(Into::into)
177            .map(|s| s.to_lowercase())
178            .map(|s| {
179                if s.starts_with("0x") {
180                    s
181                } else {
182                    format!("0x{s}")
183                }
184            })
185            .collect();
186
187        if selectors.is_empty() {
188            return Ok(vec![]);
189        }
190
191        tracing::debug!(len = selectors.len(), "decoding selectors");
192        tracing::trace!(?selectors, "decoding selectors");
193
194        // exit early if spurious connection
195        eyre::ensure!(!self.is_spurious(), "Spurious connection detected");
196
197        let expected_len = match selector_type {
198            SelectorType::Function | SelectorType::Error => 10, // 0x + hex(4bytes)
199            SelectorType::Event => 66,                          // 0x + hex(32bytes)
200        };
201        if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) {
202            eyre::bail!(
203                "Invalid selector {s}: expected {expected_len} characters (including 0x prefix)."
204            )
205        }
206
207        #[derive(Deserialize)]
208        struct Decoded {
209            name: String,
210        }
211
212        #[derive(Deserialize)]
213        struct ApiResult {
214            event: HashMap<String, Option<Vec<Decoded>>>,
215            function: HashMap<String, Option<Vec<Decoded>>>,
216        }
217
218        #[derive(Deserialize)]
219        struct ApiResponse {
220            ok: bool,
221            result: ApiResult,
222        }
223
224        let url = format!(
225            "{SELECTOR_DATABASE_URL}?{ltype}={selectors_str}",
226            ltype = match selector_type {
227                SelectorType::Function | SelectorType::Error => "function",
228                SelectorType::Event => "event",
229            },
230            selectors_str = selectors.join(",")
231        );
232
233        let res = self.get(&url).await?;
234        let api_response = match serde_json::from_str::<ApiResponse>(&res) {
235            Ok(inner) => inner,
236            Err(err) => {
237                eyre::bail!("Could not decode response:\n {res}.\nError: {err}")
238            }
239        };
240
241        if !api_response.ok {
242            eyre::bail!("Failed to decode:\n {res}")
243        }
244
245        let decoded = match selector_type {
246            SelectorType::Function | SelectorType::Error => api_response.result.function,
247            SelectorType::Event => api_response.result.event,
248        };
249
250        Ok(selectors
251            .into_iter()
252            .map(|selector| match decoded.get(&selector) {
253                Some(Some(r)) => Some(r.iter().map(|d| d.name.clone()).collect()),
254                _ => None,
255            })
256            .collect())
257    }
258
259    /// Fetches a function signature given the selector using api.openchain.xyz
260    pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result<Option<String>> {
261        let prefixed_selector = format!("0x{}", selector.strip_prefix("0x").unwrap_or(selector));
262        if prefixed_selector.len() != 10 {
263            eyre::bail!("Invalid selector: expected 8 characters (excluding 0x prefix), got {} characters (including 0x prefix).", prefixed_selector.len())
264        }
265
266        if let Some(r) = KNOWN_SIGNATURES.get(&prefixed_selector) {
267            return Ok(Some(r.clone()));
268        }
269
270        self.decode_selector(&prefixed_selector[..10], SelectorType::Function)
271            .await
272    }
273}
274
275#[derive(Clone, Copy)]
276pub enum SelectorType {
277    Function,
278    Event,
279    Error,
280}