Skip to main content

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    /// Probe-roots addresses requested this round.
98    pub probe_roots_requested: usize,
99    /// Probe-roots addresses whose root could not be observed
100    /// (`RootProbeOutcome::root` is `None`).
101    pub probe_roots_failed: usize,
102    /// Discover calls issued this round.
103    pub discover_calls: usize,
104    /// Slots touched by this round's discover calls.
105    pub discovered_slots: usize,
106}
107
108impl ColdStartRunReport {
109    /// Fold one round's plan and results into the report.
110    ///
111    /// Plain field accumulation, no IO. `failed_slots` counts
112    /// [`FetchFailed`](crate::freshness::SlotFetch::FetchFailed) outcomes across
113    /// both the verify (`fetched`) and probe (`probed`) phases. Root probes are
114    /// not slots, so probe-roots counts are recorded per round
115    /// (`probe_roots_requested` / `probe_roots_failed`) and never fold into
116    /// `failed_slots` — mirroring how `probe_requested` / `probe_failed` carry
117    /// no dedicated run-level totals.
118    pub(crate) fn absorb_round(&mut self, plan: &ColdStartPlan, results: &ColdStartResults) {
119        self.rounds += 1;
120        let verify_failed = results
121            .fetched
122            .iter()
123            .filter(|o| matches!(o.fetch, SlotFetch::FetchFailed { .. }))
124            .count();
125        let probe_failed = results
126            .probed
127            .iter()
128            .filter(|o| matches!(o.fetch, SlotFetch::FetchFailed { .. }))
129            .count();
130        let probe_roots_failed = results
131            .probed_roots
132            .iter()
133            .filter(|o| o.root.is_none())
134            .count();
135        let discovered_slots: usize = results
136            .discovered
137            .iter()
138            .map(|d| d.access.slots.len())
139            .sum();
140        let discovered_accounts: usize = results
141            .discovered
142            .iter()
143            .map(|d| d.access.accounts.len())
144            .sum();
145
146        self.verified_slots += plan.verify.len();
147        self.changed_slots += results.verified.len();
148        self.failed_slots += verify_failed + probe_failed;
149        self.discovered_slots += discovered_slots;
150        self.discovered_accounts += discovered_accounts;
151
152        self.per_round.push(ColdStartRoundSummary {
153            verify_requested: plan.verify.len(),
154            verify_changed: results.verified.len(),
155            verify_failed,
156            probe_requested: plan.probe.len(),
157            probe_failed,
158            probe_roots_requested: plan.probe_roots.len(),
159            probe_roots_failed,
160            discover_calls: plan.discover.len(),
161            discovered_slots,
162        });
163    }
164}