1use std::collections::{HashMap, HashSet};
16use std::path::Path;
17
18use alloy_primitives::{Address, U256};
19use anyhow::{Context as _, Result};
20use serde::{Deserialize, Serialize};
21use tracing::{debug, info, warn};
22
23use crate::StorageAccessList;
24use crate::cache::{EvmCache, versioned};
25
26const PREFETCH_REGISTRY_MAGIC: &[u8; 8] = b"EFC-PFRG";
27const PREFETCH_REGISTRY_VERSION: u32 = 1;
28
29#[derive(Debug, Default, Clone, Serialize, Deserialize)]
35pub struct PrefetchRegistry {
36 phases: HashMap<String, StorageAccessList>,
38 keyed_phases: HashMap<String, HashMap<Address, StorageAccessList>>,
41}
42
43impl PrefetchRegistry {
44 pub fn load(path: &Path) -> Self {
52 match std::fs::read(path) {
53 Ok(data) => {
54 if let Some(registry) = versioned::decode::<PrefetchRegistry>(
55 &data,
56 PREFETCH_REGISTRY_MAGIC,
57 PREFETCH_REGISTRY_VERSION,
58 "prefetch registry",
59 ) {
60 let phase_count = registry.phases.len();
61 let keyed_phase_count = registry.keyed_phases.len();
62 let total_slots: usize = registry
63 .phases
64 .values()
65 .map(|al| al.slots.len())
66 .sum::<usize>()
67 + registry
68 .keyed_phases
69 .values()
70 .flat_map(|m| m.values())
71 .map(|al| al.slots.len())
72 .sum::<usize>();
73 info!(
74 phases = phase_count,
75 keyed_phases = keyed_phase_count,
76 total_slots,
77 "Loaded prefetch registry"
78 );
79 registry
80 } else {
81 warn!("Prefetch registry cache miss, starting fresh");
82 Self::default()
83 }
84 }
85 Err(_) => {
86 debug!("No prefetch registry file found, starting fresh");
87 Self::default()
88 }
89 }
90 }
91
92 pub fn save(&self, path: &Path) -> Result<()> {
98 if let Some(parent) = path.parent() {
99 std::fs::create_dir_all(parent).with_context(|| {
100 format!("failed to create prefetch registry directory {parent:?}")
101 })?;
102 }
103 let data = versioned::encode(
104 PREFETCH_REGISTRY_MAGIC,
105 PREFETCH_REGISTRY_VERSION,
106 self,
107 "prefetch registry",
108 )?;
109 std::fs::write(path, data)
110 .with_context(|| format!("failed to persist prefetch registry to {path:?}"))?;
111
112 let total_slots: usize = self.phases.values().map(|al| al.slots.len()).sum::<usize>()
113 + self
114 .keyed_phases
115 .values()
116 .flat_map(|m| m.values())
117 .map(|al| al.slots.len())
118 .sum::<usize>();
119 debug!(total_slots, "Saved prefetch registry");
120 Ok(())
121 }
122
123 pub fn record(&mut self, phase: &str, access_list: StorageAccessList) {
152 self.phases.insert(phase.to_string(), access_list);
153 }
154
155 pub fn record_keyed(&mut self, phase: &str, key: Address, access_list: StorageAccessList) {
162 self.keyed_phases
163 .entry(phase.to_string())
164 .or_default()
165 .insert(key, access_list);
166 }
167
168 pub fn prefetch_phase(&self, phase: &str, cache: &mut EvmCache) -> (usize, usize) {
172 let Some(access_list) = self.phases.get(phase) else {
173 debug!(phase, "No prefetch data for phase");
174 return (0, 0);
175 };
176
177 if access_list.slots.is_empty() {
178 return (0, 0);
179 }
180
181 batch_prefetch(cache, access_list.slots.iter().copied(), phase)
182 }
183
184 pub fn prefetch_keyed(
191 &self,
192 phase: &str,
193 keys: &[Address],
194 cache: &mut EvmCache,
195 exclude: &HashSet<(Address, U256)>,
196 ) -> (usize, usize) {
197 let Some(keyed_map) = self.keyed_phases.get(phase) else {
198 debug!(phase, "No keyed prefetch data for phase");
199 return (0, 0);
200 };
201
202 let slots: HashSet<(Address, U256)> = keys
203 .iter()
204 .filter_map(|addr| keyed_map.get(addr))
205 .flat_map(|al| al.slots.iter().copied())
206 .filter(|slot| !exclude.contains(slot))
207 .collect();
208
209 if slots.is_empty() {
210 debug!(
211 phase,
212 keys = keys.len(),
213 excluded = exclude.len(),
214 "All keyed slots excluded or empty"
215 );
216 return (0, 0);
217 }
218
219 batch_prefetch(cache, slots.into_iter(), phase)
220 }
221
222 pub fn phase_slots(&self, phase: &str) -> HashSet<(Address, U256)> {
229 self.phases
230 .get(phase)
231 .map(|al| al.slots.clone())
232 .unwrap_or_default()
233 }
234}
235
236fn batch_prefetch(
246 cache: &mut EvmCache,
247 slots: impl Iterator<Item = (Address, U256)>,
248 phase: &str,
249) -> (usize, usize) {
250 let requests: Vec<(Address, U256)> = slots.collect();
251 if requests.is_empty() {
252 return (0, 0);
253 }
254
255 let fetcher = match cache.storage_batch_fetcher().cloned() {
256 Some(f) => f,
257 None => {
258 debug!(
259 "No batch fetcher available, skipping prefetch for {}",
260 phase
261 );
262 return (0, 0);
263 }
264 };
265
266 let start = std::time::Instant::now();
267 let total_requested = requests.len();
268 let results = fetcher(requests, None);
270
271 let mut successes: Vec<(Address, U256, U256)> = Vec::with_capacity(results.len());
272 let mut errors = 0usize;
273 for (addr, slot, result) in results {
274 match result {
275 Ok(value) => successes.push((addr, slot, value)),
276 Err(_) => errors += 1,
277 }
278 }
279
280 let fetched = successes.len();
281 cache.inject_storage_batch(&successes);
282
283 let ms = start.elapsed().as_millis() as u64;
284 info!(
285 phase,
286 slots_requested = total_requested,
287 slots_fetched = fetched,
288 errors,
289 prefetch_ms = ms,
290 "Prefetch complete"
291 );
292
293 (fetched, errors)
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_registry_record_and_phase_slots() {
302 let mut registry = PrefetchRegistry::default();
303 let mut al = StorageAccessList::default();
304 let addr = Address::repeat_byte(0x01);
305 al.slots.insert((addr, U256::from(1)));
306 al.slots.insert((addr, U256::from(2)));
307
308 registry.record("pool_refresh", al);
309
310 let slots = registry.phase_slots("pool_refresh");
311 assert_eq!(slots.len(), 2);
312 assert!(slots.contains(&(addr, U256::from(1))));
313 assert!(slots.contains(&(addr, U256::from(2))));
314
315 assert!(registry.phase_slots("nonexistent").is_empty());
317 }
318
319 #[test]
320 fn test_registry_record_keyed() {
321 let mut registry = PrefetchRegistry::default();
322 let key_a = Address::repeat_byte(0x01);
323 let key_b = Address::repeat_byte(0x02);
324
325 let mut al1 = StorageAccessList::default();
326 al1.slots.insert((key_a, U256::from(10)));
327
328 let mut al2 = StorageAccessList::default();
329 al2.slots.insert((key_b, U256::from(20)));
330
331 registry.record_keyed("per_target", key_a, al1);
332 registry.record_keyed("per_target", key_b, al2);
333
334 let map = registry.keyed_phases.get("per_target").unwrap();
336 assert_eq!(map.len(), 2);
337 assert!(
338 map.get(&key_a)
339 .unwrap()
340 .slots
341 .contains(&(key_a, U256::from(10)))
342 );
343 }
344
345 #[test]
346 fn test_save_load_round_trip() {
347 let dir = std::env::temp_dir().join("evm_fork_cache_test_prefetch_registry");
348 let path = dir.join("test_registry.bin");
349 let _ = std::fs::remove_file(&path);
350
351 let mut registry = PrefetchRegistry::default();
352 let addr = Address::repeat_byte(0xAA);
353 let mut al = StorageAccessList::default();
354 al.slots.insert((addr, U256::from(42)));
355 al.accounts.insert(addr);
356 registry.record("test_phase", al);
357
358 let key = Address::repeat_byte(0xBB);
359 let mut sal = StorageAccessList::default();
360 sal.slots.insert((key, U256::from(99)));
361 registry.record_keyed("per_target", key, sal);
362
363 registry.save(&path).expect("save registry");
364 let data = std::fs::read(&path).expect("read saved registry");
365 assert!(
366 data.starts_with(b"EFC-PFRG"),
367 "prefetch registry files must carry a magic/version header"
368 );
369
370 let loaded = PrefetchRegistry::load(&path);
371 assert_eq!(loaded.phases.len(), 1);
372 assert!(
373 loaded.phases["test_phase"]
374 .slots
375 .contains(&(addr, U256::from(42)))
376 );
377 assert_eq!(loaded.keyed_phases.len(), 1);
378 assert!(
379 loaded.keyed_phases["per_target"]
380 .get(&key)
381 .unwrap()
382 .slots
383 .contains(&(key, U256::from(99)))
384 );
385
386 let _ = std::fs::remove_file(&path);
387 let _ = std::fs::remove_dir(&dir);
388 }
389
390 #[test]
391 fn save_reports_write_failures() {
392 let dir = std::env::temp_dir().join("evm_fork_cache_test_prefetch_registry_write_error");
393 let _ = std::fs::remove_dir_all(&dir);
394 let _ = std::fs::remove_file(&dir);
395 std::fs::write(&dir, b"not a directory").expect("create file path conflict");
396
397 let registry = PrefetchRegistry::default();
398 let path = dir.join("registry.bin");
399 let err = registry
400 .save(&path)
401 .expect_err("save must report write failure");
402 assert!(
403 err.to_string().contains("directory") || err.to_string().contains("Not a directory"),
404 "unexpected error: {err:#}"
405 );
406
407 let _ = std::fs::remove_file(&dir);
408 }
409
410 #[test]
411 fn legacy_raw_bincode_loads_as_default() {
412 let dir = std::env::temp_dir().join("evm_fork_cache_test_prefetch_registry_legacy");
413 let path = dir.join("legacy_registry.bin");
414 let _ = std::fs::remove_dir_all(&dir);
415 std::fs::create_dir_all(&dir).expect("create temp dir");
416
417 let mut registry = PrefetchRegistry::default();
418 let addr = Address::repeat_byte(0xAA);
419 let mut al = StorageAccessList::default();
420 al.slots.insert((addr, U256::from(42)));
421 registry.record("legacy_phase", al);
422 let legacy = bincode::serialize(®istry).expect("serialize legacy registry");
423 std::fs::write(&path, legacy).expect("write legacy registry");
424
425 let loaded = PrefetchRegistry::load(&path);
426 assert!(
427 loaded.phases.is_empty() && loaded.keyed_phases.is_empty(),
428 "legacy raw bincode must be treated as a cache miss"
429 );
430
431 let _ = std::fs::remove_dir_all(&dir);
432 }
433
434 #[test]
435 fn test_load_missing_file_returns_default() {
436 let path = std::path::Path::new("/tmp/nonexistent_prefetch_registry.bin");
437 let registry = PrefetchRegistry::load(path);
438 assert!(registry.phases.is_empty());
439 assert!(registry.keyed_phases.is_empty());
440 }
441
442 #[test]
443 fn test_record_replaces_existing() {
444 let mut registry = PrefetchRegistry::default();
445 let addr = Address::repeat_byte(0x01);
446
447 let mut al1 = StorageAccessList::default();
448 al1.slots.insert((addr, U256::from(1)));
449 registry.record("phase", al1);
450
451 let mut al2 = StorageAccessList::default();
452 al2.slots.insert((addr, U256::from(2)));
453 registry.record("phase", al2);
454
455 let slots = registry.phase_slots("phase");
456 assert_eq!(slots.len(), 1);
457 assert!(slots.contains(&(addr, U256::from(2))));
458 }
459}