tycho_simulation/evm/traces/
signatures.rs

1use std::{
2    collections::{hash_map::Entry, HashMap},
3    time::Duration,
4};
5
6use alloy::{
7    json_abi::{Event, Function},
8    primitives::{Selector, B256},
9};
10use reqwest::Client;
11use serde::Deserialize;
12use tokio::{sync::RwLock, time::sleep};
13
14/// Response from 4byte.directory API for function signatures
15#[derive(Debug, Deserialize)]
16struct FourByteResponse {
17    results: Vec<FunctionSignature>,
18}
19
20#[derive(Debug, Deserialize)]
21struct FunctionSignature {
22    text_signature: String,
23}
24
25/// Response from 4byte.directory API for event signatures  
26#[derive(Debug, Deserialize)]
27struct EventSignatureResponse {
28    results: Vec<EventSignature>,
29}
30
31#[derive(Debug, Deserialize)]
32struct EventSignature {
33    text_signature: String,
34}
35
36/// Signature identifier that uses 4byte.directory
37pub struct SignaturesIdentifier {
38    client: Client,
39    /// Cache for function signatures
40    function_cache: RwLock<HashMap<Selector, Function>>,
41    /// Cache for event signatures
42    event_cache: RwLock<HashMap<B256, Event>>,
43    /// Whether to work in offline mode
44    offline: bool,
45}
46
47impl SignaturesIdentifier {
48    /// Create a new signature identifier
49    pub fn new(offline: bool) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
50        let client = Client::builder()
51            .timeout(Duration::from_secs(10))
52            .build()?;
53
54        Ok(Self {
55            client,
56            function_cache: RwLock::new(HashMap::new()),
57            event_cache: RwLock::new(HashMap::new()),
58            offline,
59        })
60    }
61
62    /// Identify a function by its selector
63    pub async fn identify_function(&self, selector: Selector) -> Option<Function> {
64        // Check cache first
65        {
66            let lock_guard = self.function_cache.read().await;
67            if let Some(func) = lock_guard.get(&selector) {
68                return Some(func.clone());
69            }
70        }
71
72        // Don't make network calls if offline
73        if self.offline {
74            return None;
75        }
76
77        // Query 4byte.directory
78        let hex_selector = format!("0x{}", hex::encode(selector.as_slice()));
79        let url = format!(
80            "https://www.4byte.directory/api/v1/signatures/?hex_signature={}",
81            hex_selector
82        );
83
84        match self.fetch_with_retry(&url).await {
85            Ok(response) => {
86                if let Ok(data) = response
87                    .json::<FourByteResponse>()
88                    .await
89                {
90                    if let Some(sig) = data.results.first() {
91                        if let Ok(function) = Function::parse(&sig.text_signature) {
92                            let mut lock_guard = self.function_cache.write().await;
93                            lock_guard.insert(selector, function.clone());
94                            return Some(function);
95                        }
96                    }
97                }
98            }
99            Err(e) => {
100                tracing::warn!(target: "traces::signatures", "failed to fetch function signature: {}", e);
101            }
102        }
103
104        None
105    }
106
107    /// Identify an event by its topic hash
108    pub async fn identify_event(&self, topic: B256) -> Option<Event> {
109        // Check cache first
110        {
111            let cache_guard = self.event_cache.read().await;
112            if let Some(event) = cache_guard.get(&topic) {
113                return Some(event.clone());
114            }
115        }
116
117        // Don't make network calls if offline
118        if self.offline {
119            return None;
120        }
121
122        // Query 4byte.directory for event signatures
123        let hex_topic = format!("0x{}", hex::encode(topic.as_slice()));
124        let url = format!(
125            "https://www.4byte.directory/api/v1/event-signatures/?hex_signature={}",
126            hex_topic
127        );
128
129        match self.fetch_with_retry(&url).await {
130            Ok(response) => {
131                if let Ok(data) = response
132                    .json::<EventSignatureResponse>()
133                    .await
134                {
135                    if let Some(sig) = data.results.first() {
136                        if let Ok(event) = Event::parse(&sig.text_signature) {
137                            let mut cache_guard = self.event_cache.write().await;
138                            cache_guard.insert(topic, event.clone());
139                            return Some(event);
140                        }
141                    }
142                }
143            }
144            Err(e) => {
145                tracing::warn!(target: "traces::signatures", "failed to fetch event signature: {}", e);
146            }
147        }
148
149        None
150    }
151
152    /// Identify multiple signatures at once
153    pub async fn identify_batch(
154        &self,
155        selectors: &[SelectorKind],
156    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
157        if self.offline {
158            return Ok(());
159        }
160
161        // Split into functions and events
162        let functions: Vec<_> = selectors
163            .iter()
164            .filter_map(|s| match s {
165                SelectorKind::Function(sel) => Some(*sel),
166                _ => None,
167            })
168            .collect();
169
170        let events: Vec<_> = selectors
171            .iter()
172            .filter_map(|s| match s {
173                SelectorKind::Event(topic) => Some(*topic),
174                _ => None,
175            })
176            .collect();
177
178        // Fetch functions
179        {
180            let mut cache_guard = self.function_cache.write().await;
181            for selector in functions {
182                if let Entry::Vacant(e) = cache_guard.entry(selector) {
183                    if let Some(func) = self.identify_function(selector).await {
184                        e.insert(func);
185                    }
186                    // Rate limiting - don't overwhelm 4byte.directory
187                    sleep(Duration::from_millis(100)).await;
188                }
189            }
190        }
191
192        // Fetch events
193        {
194            let mut cache_guard = self.event_cache.write().await;
195            for topic in events {
196                if let Entry::Vacant(e) = cache_guard.entry(topic) {
197                    if let Some(event) = self.identify_event(topic).await {
198                        e.insert(event);
199                    }
200                    // Rate limiting
201                    sleep(Duration::from_millis(100)).await;
202                }
203            }
204        }
205
206        Ok(())
207    }
208
209    /// Fetch with basic retry logic
210    async fn fetch_with_retry(&self, url: &str) -> Result<reqwest::Response, reqwest::Error> {
211        let mut retries = 3;
212        loop {
213            match self.client.get(url).send().await {
214                Ok(response) => {
215                    if response.status().is_success() {
216                        return Ok(response);
217                    } else if response.status().as_u16() == 429 && retries > 0 {
218                        // Rate limited, wait and retry
219                        sleep(Duration::from_secs(1)).await;
220                        retries -= 1;
221                        continue;
222                    } else {
223                        return Ok(response);
224                    }
225                }
226                Err(_e) if retries > 0 => {
227                    sleep(Duration::from_millis(500)).await;
228                    retries -= 1;
229                    continue;
230                }
231                Err(e) => return Err(e),
232            }
233        }
234    }
235}
236
237/// Selector kind for batch identification
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum SelectorKind {
240    /// Function selector (4 bytes)
241    Function(Selector),
242    /// Event topic hash (32 bytes)
243    Event(B256),
244}