tycho_simulation/evm/traces/
signatures.rs1use 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#[derive(Debug, Deserialize)]
16struct FourByteResponse {
17 results: Vec<FunctionSignature>,
18}
19
20#[derive(Debug, Deserialize)]
21struct FunctionSignature {
22 text_signature: String,
23}
24
25#[derive(Debug, Deserialize)]
27struct EventSignatureResponse {
28 results: Vec<EventSignature>,
29}
30
31#[derive(Debug, Deserialize)]
32struct EventSignature {
33 text_signature: String,
34}
35
36pub struct SignaturesIdentifier {
38 client: Client,
39 function_cache: RwLock<HashMap<Selector, Function>>,
41 event_cache: RwLock<HashMap<B256, Event>>,
43 offline: bool,
45}
46
47impl SignaturesIdentifier {
48 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 pub async fn identify_function(&self, selector: Selector) -> Option<Function> {
64 {
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 if self.offline {
74 return None;
75 }
76
77 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 pub async fn identify_event(&self, topic: B256) -> Option<Event> {
109 {
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 if self.offline {
119 return None;
120 }
121
122 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 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 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 {
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 sleep(Duration::from_millis(100)).await;
188 }
189 }
190 }
191
192 {
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 sleep(Duration::from_millis(100)).await;
202 }
203 }
204 }
205
206 Ok(())
207 }
208
209 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum SelectorKind {
240 Function(Selector),
242 Event(B256),
244}