evm_fork_cache/cold_start/config.rs
1//! Cold-start run configuration and the accumulated run report.
2
3use alloy_primitives::B256;
4
5use crate::cold_start::plan::ColdStartPlan;
6use crate::cold_start::results::ColdStartResults;
7use crate::freshness::SlotFetch;
8
9/// Configuration for a cold-start run.
10///
11/// `Default` is hand-written (`max_rounds: 8`, `pin: ColdStartPin::CachePinned`)
12/// rather than derived, because a derived `max_rounds: 0` would trip
13/// [`RoundBudgetExceeded`](crate::cold_start::ColdStartError::RoundBudgetExceeded)
14/// before any round ran. `max_rounds >= 1` is a precondition.
15#[derive(Clone, Debug)]
16pub struct ColdStartConfig {
17 /// Hard upper bound on **executed** rounds. A planner that returns `Continue`
18 /// past this bound yields
19 /// [`RoundBudgetExceeded`](crate::cold_start::ColdStartError::RoundBudgetExceeded).
20 /// Must be `>= 1`.
21 pub max_rounds: usize,
22 /// Block-pinning policy for the run's reads.
23 pub pin: ColdStartPin,
24}
25
26impl Default for ColdStartConfig {
27 fn default() -> Self {
28 Self {
29 max_rounds: 8,
30 pin: ColdStartPin::CachePinned,
31 }
32 }
33}
34
35/// How the driver pins the block for the run's reads.
36#[derive(Clone, Debug)]
37pub enum ColdStartPin {
38 /// Use the cache's currently-pinned block (`self.block`) for every round.
39 CachePinned,
40 /// Pin every round to an explicit hash for the whole run (reorg-stable cold
41 /// start), restoring the prior block on completion. With
42 /// `require_canonical: true`, the provider rejects a reorged hash so the run
43 /// fails fast.
44 Hash {
45 /// Block number associated with the hash (advisory).
46 number: u64,
47 /// Block hash to pin all reads to.
48 hash: B256,
49 /// Whether the provider must reject a non-canonical hash.
50 require_canonical: bool,
51 },
52}
53
54/// Summary of a completed cold-start run, returned by
55/// [`run_cold_start`](crate::cache::EvmCache::run_cold_start) on success.
56///
57/// Accumulated round-by-round as the run progresses. Note `run_cold_start`
58/// returns this report **only on the `Ok` path**: on a hard error the partial
59/// round is folded in before the error is returned, but the report itself is
60/// then dropped (the `Err` carries only the cause). To observe partial progress
61/// on failure, drive rounds yourself via
62/// [`execute_cold_start_round`](crate::cache::EvmCache::execute_cold_start_round),
63/// which always returns a [`RoundOutcome`](crate::cold_start::RoundOutcome).
64#[derive(Clone, Debug, Default)]
65pub struct ColdStartRunReport {
66 /// Number of rounds executed.
67 pub rounds: usize,
68 /// Total verify slots requested across all rounds.
69 pub verified_slots: usize,
70 /// Total slots that changed and were injected.
71 pub changed_slots: usize,
72 /// Total accounts touched by discover calls, summed across calls and rounds
73 /// (not de-duplicated — the same account touched twice counts twice).
74 pub discovered_accounts: usize,
75 /// Total slots touched by discover calls, summed across calls and rounds
76 /// (not de-duplicated — the same slot touched twice counts twice).
77 pub discovered_slots: usize,
78 /// Total verify + probe slots whose fetch failed.
79 pub failed_slots: usize,
80 /// One summary per round, in execution order.
81 pub per_round: Vec<ColdStartRoundSummary>,
82}
83
84/// Per-round breakdown recorded in [`ColdStartRunReport::per_round`].
85#[derive(Clone, Debug, Default)]
86pub struct ColdStartRoundSummary {
87 /// Verify slots requested this round.
88 pub verify_requested: usize,
89 /// Verify slots that changed and were injected.
90 pub verify_changed: usize,
91 /// Verify slots whose fetch failed.
92 pub verify_failed: usize,
93 /// Probe slots requested this round.
94 pub probe_requested: usize,
95 /// Probe slots whose fetch failed.
96 pub probe_failed: usize,
97 /// Discover calls issued this round.
98 pub discover_calls: usize,
99 /// Slots touched by this round's discover calls.
100 pub discovered_slots: usize,
101}
102
103impl ColdStartRunReport {
104 /// Fold one round's plan and results into the report.
105 ///
106 /// Plain field accumulation, no IO. `failed_slots` counts
107 /// [`FetchFailed`](crate::freshness::SlotFetch::FetchFailed) outcomes across
108 /// both the verify (`fetched`) and probe (`probed`) phases.
109 pub(crate) fn absorb_round(&mut self, plan: &ColdStartPlan, results: &ColdStartResults) {
110 self.rounds += 1;
111 let verify_failed = results
112 .fetched
113 .iter()
114 .filter(|o| matches!(o.fetch, SlotFetch::FetchFailed { .. }))
115 .count();
116 let probe_failed = results
117 .probed
118 .iter()
119 .filter(|o| matches!(o.fetch, SlotFetch::FetchFailed { .. }))
120 .count();
121 let discovered_slots: usize = results
122 .discovered
123 .iter()
124 .map(|d| d.access.slots.len())
125 .sum();
126 let discovered_accounts: usize = results
127 .discovered
128 .iter()
129 .map(|d| d.access.accounts.len())
130 .sum();
131
132 self.verified_slots += plan.verify.len();
133 self.changed_slots += results.verified.len();
134 self.failed_slots += verify_failed + probe_failed;
135 self.discovered_slots += discovered_slots;
136 self.discovered_accounts += discovered_accounts;
137
138 self.per_round.push(ColdStartRoundSummary {
139 verify_requested: plan.verify.len(),
140 verify_changed: results.verified.len(),
141 verify_failed,
142 probe_requested: plan.probe.len(),
143 probe_failed,
144 discover_calls: plan.discover.len(),
145 discovered_slots,
146 });
147 }
148}