Skip to main content

anvil/eth/backend/
time.rs

1//! Manages the block time
2
3use crate::eth::error::BlockchainError;
4use chrono::{DateTime, Utc};
5use parking_lot::RwLock;
6use std::{sync::Arc, time::Duration};
7
8/// Manages block time
9#[derive(Clone, Debug)]
10pub struct TimeManager {
11    state: Arc<RwLock<TimeState>>,
12}
13
14/// Timestamp controls that must be read and committed atomically.
15#[derive(Debug, Default)]
16struct TimeState {
17    offset: i128,
18    /// Explicit increases not yet committed, separate from the wall-clock offset.
19    time_increase: u64,
20    offset_reset_generation: u64,
21    last_timestamp: u64,
22    last_block_wall_time: u64,
23    next_exact_timestamp: Option<TimestampOverride>,
24    interval: Option<u64>,
25    next_override_generation: u64,
26}
27
28#[derive(Clone, Copy, Debug)]
29struct TimestampOverride {
30    timestamp: u64,
31    generation: u64,
32}
33
34/// A timestamp prepared for a candidate block but not yet committed.
35#[derive(Clone, Copy, Debug)]
36pub(crate) struct PendingBlockTimestamp {
37    pub(crate) timestamp: u64,
38    exact_generation: Option<u64>,
39    prepared_offset: i128,
40    offset_reset_generation: u64,
41    next_offset: Option<i128>,
42    time_increase: u64,
43}
44
45/// Time controls captured by an in-memory state snapshot.
46#[derive(Clone, Copy, Debug)]
47pub(crate) struct TimeSnapshot {
48    offset: i128,
49    last_timestamp: u64,
50    next_block_timestamp: Option<u64>,
51    time_increase: u64,
52}
53
54/// A temporary additive time increase that can be rolled back after failed manual mining.
55#[derive(Clone, Copy, Debug)]
56pub(crate) struct PendingTimeIncrease {
57    seconds: u64,
58    offset_reset_generation: u64,
59}
60
61impl TimeManager {
62    pub fn new(start_timestamp: u64) -> Self {
63        let time_manager = Self { state: Default::default() };
64        time_manager.reset(start_timestamp);
65        time_manager
66    }
67
68    /// Resets the current time manager to the given timestamp, resetting the offsets and
69    /// next block timestamp option
70    pub fn reset(&self, start_timestamp: u64) {
71        self.reset_timestamp(start_timestamp, true, None, None, 0);
72    }
73
74    /// Sets the current timestamp without changing when the current head was installed.
75    pub fn set_time(&self, timestamp: u64) {
76        self.reset_timestamp(timestamp, false, None, None, 0);
77    }
78
79    /// Restores the timestamp and offset captured by a state snapshot.
80    pub(crate) fn restore(&self, snapshot: TimeSnapshot) {
81        self.reset_timestamp(
82            snapshot.last_timestamp,
83            true,
84            Some(snapshot.offset),
85            snapshot.next_block_timestamp,
86            snapshot.time_increase,
87        );
88    }
89
90    fn reset_timestamp(
91        &self,
92        start_timestamp: u64,
93        mark_new_head: bool,
94        offset: Option<i128>,
95        next_block_timestamp: Option<u64>,
96        time_increase: u64,
97    ) {
98        let current = duration_since_unix_epoch();
99        let mut state = self.state.write();
100        state.last_timestamp = start_timestamp;
101        state.time_increase = time_increase;
102        if mark_new_head {
103            state.last_block_wall_time = current.as_millis().try_into().unwrap_or(u64::MAX);
104        }
105        state.offset =
106            offset.unwrap_or_else(|| (start_timestamp as i128) - current.as_secs() as i128);
107        state.offset_reset_generation = state.offset_reset_generation.wrapping_add(1);
108        state.next_override_generation = state.next_override_generation.wrapping_add(1);
109        state.next_exact_timestamp = next_block_timestamp.map(|timestamp| TimestampOverride {
110            timestamp,
111            generation: state.next_override_generation,
112        });
113    }
114
115    pub fn offset(&self) -> i128 {
116        self.state.read().offset
117    }
118
119    pub(crate) fn snapshot(&self) -> TimeSnapshot {
120        let state = self.state.read();
121        TimeSnapshot {
122            offset: state.offset,
123            last_timestamp: state.last_timestamp,
124            next_block_timestamp: state.next_exact_timestamp.map(|override_| override_.timestamp),
125            time_increase: state.time_increase,
126        }
127    }
128
129    /// Returns the UNIX wall time in milliseconds when the current head was installed.
130    pub fn last_block_wall_time(&self) -> u64 {
131        self.state.read().last_block_wall_time
132    }
133
134    /// Records that a new latest block was created.
135    pub(crate) fn mark_block_created(&self) {
136        self.state.write().last_block_wall_time =
137            duration_since_unix_epoch().as_millis().try_into().unwrap_or(u64::MAX);
138    }
139
140    /// Adds the given `offset` to the already tracked offset and returns the result
141    fn add_offset(&self, offset: u64) -> i128 {
142        let mut state = self.state.write();
143        let next = state.offset.saturating_add(offset as i128);
144        trace!(target: "time", "adding timestamp offset={}, total={}", offset, next);
145        state.offset = next;
146        state.time_increase = state.time_increase.saturating_add(offset);
147        next
148    }
149
150    /// Jumps forward in time by the given seconds
151    ///
152    /// This will apply a permanent offset to the natural UNIX Epoch timestamp
153    pub fn increase_time(&self, seconds: u64) -> i128 {
154        self.add_offset(seconds)
155    }
156
157    /// Applies a temporary increase that can be rolled back if mining fails.
158    pub(crate) fn apply_time_increase(&self, seconds: u64) -> PendingTimeIncrease {
159        let mut state = self.state.write();
160        state.offset = state.offset.saturating_add(seconds as i128);
161        state.time_increase = state.time_increase.saturating_add(seconds);
162        PendingTimeIncrease { seconds, offset_reset_generation: state.offset_reset_generation }
163    }
164
165    /// Reverts a temporary increase unless an absolute time reset superseded it.
166    pub(crate) fn revert_time_increase(&self, pending: PendingTimeIncrease) {
167        let mut state = self.state.write();
168        if state.offset_reset_generation == pending.offset_reset_generation {
169            state.offset = state.offset.saturating_sub(pending.seconds as i128);
170            state.time_increase = state.time_increase.saturating_sub(pending.seconds);
171        }
172    }
173
174    /// Sets the exact timestamp to use in the next block
175    /// Fails if it's before (or at the same time) the last timestamp
176    pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), BlockchainError> {
177        trace!(target: "time", "override next timestamp {}", timestamp);
178        let mut state = self.state.write();
179        if timestamp < state.last_timestamp {
180            return Err(BlockchainError::TimestampError(format!(
181                "{timestamp} is lower than previous block's timestamp"
182            )));
183        }
184        state.next_override_generation = state.next_override_generation.wrapping_add(1);
185        state.next_exact_timestamp =
186            Some(TimestampOverride { timestamp, generation: state.next_override_generation });
187        Ok(())
188    }
189
190    /// Sets an interval to use when computing the next timestamp
191    ///
192    /// If an interval already exists, this will update the interval, otherwise a new interval will
193    /// be set starting with the current timestamp.
194    pub fn set_block_timestamp_interval(&self, interval: u64) {
195        trace!(target: "time", "set interval {}", interval);
196        self.state.write().interval = Some(interval);
197    }
198
199    /// Returns the configured block timestamp interval.
200    pub(crate) fn block_timestamp_interval(&self) -> Option<u64> {
201        self.state.read().interval
202    }
203
204    /// Removes the interval if it exists
205    pub fn remove_block_timestamp_interval(&self) -> bool {
206        if self.state.write().interval.take().is_some() {
207            trace!(target: "time", "removed interval");
208            true
209        } else {
210            false
211        }
212    }
213
214    /// Computes the next timestamp without updating internals
215    fn compute_next_timestamp(
216        state: &TimeState,
217        current: i128,
218    ) -> (u64, Option<u64>, Option<i128>) {
219        let exact_timestamp = state.next_exact_timestamp;
220        let last_timestamp = state.last_timestamp;
221
222        let (mut next_timestamp, update_offset) = if let Some(next) = exact_timestamp {
223            (next.timestamp, true)
224        } else if let Some(interval) = state.interval {
225            (last_timestamp.saturating_add(interval), false)
226        } else {
227            (current.saturating_add(state.offset) as u64, false)
228        };
229        // Ensures that the timestamp is always increasing
230        if next_timestamp < last_timestamp {
231            next_timestamp = last_timestamp + 1;
232        }
233        let next_offset = update_offset.then_some((next_timestamp as i128) - current);
234        (next_timestamp, exact_timestamp.map(|exact| exact.generation), next_offset)
235    }
236
237    /// Prepares the next timestamp without consuming a one-shot override.
238    pub(crate) fn prepare_next_timestamp(&self) -> PendingBlockTimestamp {
239        self.prepare_next_timestamp_inner(None)
240    }
241
242    /// Prepares a logical block clock, using explicit controls instead of elapsed wall time.
243    ///
244    /// `increment` is the whole-second carry supplied by the chain's block schedule. The
245    /// minimum keeps a sub-second rollover monotonic even when an override repeats the parent.
246    #[cfg(any(feature = "base", test))]
247    pub(crate) fn prepare_next_timestamp_with_increment(
248        &self,
249        increment: u64,
250        minimum: u64,
251    ) -> PendingBlockTimestamp {
252        self.prepare_next_timestamp_inner(Some((increment, minimum)))
253    }
254
255    fn prepare_next_timestamp_inner(&self, schedule: Option<(u64, u64)>) -> PendingBlockTimestamp {
256        let current = duration_since_unix_epoch().as_secs() as i128;
257        let state = self.state.read();
258        let (timestamp, exact_generation, next_offset) =
259            if let Some((increment, minimum)) = schedule {
260                let timestamp = state
261                    .next_exact_timestamp
262                    .map_or_else(
263                        || {
264                            state
265                                .last_timestamp
266                                .saturating_add(state.interval.unwrap_or(increment))
267                                .saturating_add(state.time_increase)
268                        },
269                        |next| next.timestamp,
270                    )
271                    .max(minimum);
272                (
273                    timestamp,
274                    state.next_exact_timestamp.map(|next| next.generation),
275                    Some(timestamp as i128 - current),
276                )
277            } else {
278                Self::compute_next_timestamp(&state, current)
279            };
280        PendingBlockTimestamp {
281            timestamp,
282            exact_generation,
283            prepared_offset: state.offset,
284            offset_reset_generation: state.offset_reset_generation,
285            next_offset,
286            time_increase: state.time_increase,
287        }
288    }
289
290    /// Commits a timestamp after its candidate block finalized successfully.
291    pub(crate) fn commit_next_timestamp(&self, pending: PendingBlockTimestamp) {
292        let mut state = self.state.write();
293        if pending.exact_generation.is_some_and(|generation| {
294            state.next_exact_timestamp.is_some_and(|exact| exact.generation == generation)
295        }) {
296            state.next_exact_timestamp = None;
297        }
298        if let Some(next_offset) = pending.next_offset
299            && state.offset_reset_generation == pending.offset_reset_generation
300        {
301            let concurrent_offset = state.offset.saturating_sub(pending.prepared_offset);
302            state.offset = next_offset.saturating_add(concurrent_offset);
303        }
304        if state.offset_reset_generation == pending.offset_reset_generation {
305            state.time_increase = state.time_increase.saturating_sub(pending.time_increase);
306            state.last_timestamp = pending.timestamp;
307        }
308    }
309
310    /// Returns the current timestamp and updates the underlying offset and interval accordingly
311    pub fn next_timestamp(&self) -> u64 {
312        let pending = self.prepare_next_timestamp();
313        self.commit_next_timestamp(pending);
314        pending.timestamp
315    }
316
317    /// Returns the current timestamp for a call that does _not_ update the value
318    pub fn current_call_timestamp(&self) -> u64 {
319        self.prepare_next_timestamp().timestamp
320    }
321}
322
323/// Returns the `Utc` datetime for the given seconds since unix epoch
324pub fn utc_from_secs(secs: u64) -> DateTime<Utc> {
325    DateTime::from_timestamp(secs as i64, 0).unwrap_or(DateTime::<Utc>::MAX_UTC)
326}
327
328/// Returns the current duration since unix epoch.
329pub fn duration_since_unix_epoch() -> Duration {
330    use std::time::SystemTime;
331    let now = SystemTime::now();
332    now.duration_since(SystemTime::UNIX_EPOCH)
333        .unwrap_or_else(|err| panic!("Current time {now:?} is invalid: {err:?}"))
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn candidate_consumes_only_its_timestamp_override() {
342        let time = TimeManager::new(1);
343        time.set_next_block_timestamp(100).unwrap();
344        let pending = time.prepare_next_timestamp();
345
346        time.set_next_block_timestamp(100).unwrap();
347        time.commit_next_timestamp(pending);
348
349        let state = time.state.read();
350        assert_eq!(state.last_timestamp, 100);
351        assert_eq!(state.next_exact_timestamp.unwrap().timestamp, 100);
352    }
353
354    #[test]
355    fn candidate_commit_preserves_concurrent_time_increase() {
356        let time = TimeManager::new(1);
357        time.set_next_block_timestamp(100).unwrap();
358        let pending = time.prepare_next_timestamp();
359
360        time.increase_time(10);
361        time.commit_next_timestamp(pending);
362
363        let state = time.state.read();
364        assert_eq!(state.last_timestamp, 100);
365        assert_eq!(state.offset, pending.next_offset.unwrap() + 10);
366    }
367
368    #[test]
369    fn candidate_commit_preserves_concurrent_time_reset() {
370        let time = TimeManager::new(1);
371        time.set_next_block_timestamp(100).unwrap();
372        let pending = time.prepare_next_timestamp();
373
374        time.reset(1_000);
375        let reset_offset = time.offset();
376        time.commit_next_timestamp(pending);
377
378        let state = time.state.read();
379        assert_eq!(state.last_timestamp, 1_000);
380        assert_eq!(state.offset, reset_offset);
381    }
382
383    #[test]
384    fn failed_temporary_increase_preserves_concurrent_time_reset() {
385        let time = TimeManager::new(1);
386        let pending = time.apply_time_increase(60);
387
388        time.reset(1_000);
389        let reset_offset = time.offset();
390        time.revert_time_increase(pending);
391
392        assert_eq!(time.offset(), reset_offset);
393    }
394
395    #[test]
396    fn logical_clock_ignores_wall_time_and_preserves_explicit_controls() {
397        let time = TimeManager::new(100);
398        // Move the wall-clock fallback without issuing an explicit time increase.
399        time.state.write().offset += 1_000;
400        assert!(time.current_call_timestamp() >= 1_100);
401        assert_eq!(time.prepare_next_timestamp_with_increment(0, 100).timestamp, 100);
402
403        time.increase_time(10);
404        let pending = time.prepare_next_timestamp_with_increment(0, 100);
405        assert_eq!(pending.timestamp, 110);
406        assert_eq!(time.prepare_next_timestamp_with_increment(0, 100).timestamp, 110);
407        time.increase_time(7);
408        time.commit_next_timestamp(pending);
409        let pending = time.prepare_next_timestamp_with_increment(1, 111);
410        assert_eq!(pending.timestamp, 118);
411        time.commit_next_timestamp(pending);
412        assert_eq!(time.prepare_next_timestamp_with_increment(0, 118).timestamp, 118);
413
414        time.set_block_timestamp_interval(2);
415        assert_eq!(time.prepare_next_timestamp_with_increment(0, 118).timestamp, 120);
416        time.set_next_block_timestamp(200).unwrap();
417        let pending = time.prepare_next_timestamp_with_increment(0, 118);
418        assert_eq!(pending.timestamp, 200);
419        time.set_next_block_timestamp(300).unwrap();
420        time.commit_next_timestamp(pending);
421        assert_eq!(time.prepare_next_timestamp_with_increment(0, 200).timestamp, 300);
422    }
423
424    #[test]
425    fn logical_clock_restores_pending_increases_and_absolute_resets() {
426        let time = TimeManager::new(100);
427        time.set_time(500);
428        time.increase_time(10);
429        let snapshot = time.snapshot();
430        time.commit_next_timestamp(time.prepare_next_timestamp_with_increment(0, 100));
431        time.restore(snapshot);
432        assert_eq!(time.prepare_next_timestamp_with_increment(0, 100).timestamp, 510);
433
434        let pending = time.prepare_next_timestamp_with_increment(0, 100);
435        time.set_time(1_000);
436        time.increase_time(20);
437        time.commit_next_timestamp(pending);
438        assert_eq!(time.prepare_next_timestamp_with_increment(0, 510).timestamp, 1_020);
439    }
440
441    #[test]
442    fn logical_clock_reverts_failed_manual_time_increase() {
443        let time = TimeManager::new(100);
444        let increase = time.apply_time_increase(60);
445        assert_eq!(time.prepare_next_timestamp_with_increment(0, 100).timestamp, 160);
446        time.revert_time_increase(increase);
447        assert_eq!(time.prepare_next_timestamp_with_increment(0, 100).timestamp, 100);
448        time.set_next_block_timestamp(100).unwrap();
449        assert_eq!(time.prepare_next_timestamp_with_increment(1, 101).timestamp, 101);
450    }
451}