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/// Returns the `Utc` datetime for the given seconds since unix epoch
9pub fn utc_from_secs(secs: u64) -> DateTime<Utc> {
10    DateTime::from_timestamp(secs as i64, 0).unwrap_or(DateTime::<Utc>::MAX_UTC)
11}
12
13/// Manages block time
14#[derive(Clone, Debug)]
15pub struct TimeManager {
16    state: Arc<RwLock<TimeState>>,
17}
18
19/// Timestamp controls that must be read and committed atomically.
20#[derive(Debug, Default)]
21struct TimeState {
22    offset: i128,
23    offset_reset_generation: u64,
24    last_timestamp: u64,
25    next_exact_timestamp: Option<TimestampOverride>,
26    interval: Option<u64>,
27    next_override_generation: u64,
28}
29
30#[derive(Clone, Copy, Debug)]
31struct TimestampOverride {
32    timestamp: u64,
33    generation: u64,
34}
35
36/// A timestamp prepared for a candidate block but not yet committed.
37#[derive(Clone, Copy, Debug)]
38pub(crate) struct PendingBlockTimestamp {
39    pub(crate) timestamp: u64,
40    exact_generation: Option<u64>,
41    prepared_offset: i128,
42    offset_reset_generation: u64,
43    next_offset: Option<i128>,
44}
45
46/// A temporary additive time increase that can be rolled back after failed manual mining.
47#[derive(Clone, Copy, Debug)]
48pub(crate) struct PendingTimeIncrease {
49    seconds: u64,
50    offset_reset_generation: u64,
51}
52
53impl TimeManager {
54    pub fn new(start_timestamp: u64) -> Self {
55        let time_manager = Self { state: Default::default() };
56        time_manager.reset(start_timestamp);
57        time_manager
58    }
59
60    /// Resets the current time manager to the given timestamp, resetting the offsets and
61    /// next block timestamp option
62    pub fn reset(&self, start_timestamp: u64) {
63        let current = duration_since_unix_epoch().as_secs() as i128;
64        let mut state = self.state.write();
65        state.last_timestamp = start_timestamp;
66        state.offset = (start_timestamp as i128) - current;
67        state.offset_reset_generation = state.offset_reset_generation.wrapping_add(1);
68        state.next_exact_timestamp = None;
69        state.next_override_generation = state.next_override_generation.wrapping_add(1);
70    }
71
72    pub fn offset(&self) -> i128 {
73        self.state.read().offset
74    }
75
76    /// Adds the given `offset` to the already tracked offset and returns the result
77    fn add_offset(&self, offset: i128) -> i128 {
78        let mut state = self.state.write();
79        let next = state.offset.saturating_add(offset);
80        trace!(target: "time", "adding timestamp offset={}, total={}", offset, next);
81        state.offset = next;
82        next
83    }
84
85    /// Jumps forward in time by the given seconds
86    ///
87    /// This will apply a permanent offset to the natural UNIX Epoch timestamp
88    pub fn increase_time(&self, seconds: u64) -> i128 {
89        self.add_offset(seconds as i128)
90    }
91
92    /// Applies a temporary increase that can be rolled back if mining fails.
93    pub(crate) fn apply_time_increase(&self, seconds: u64) -> PendingTimeIncrease {
94        let mut state = self.state.write();
95        state.offset = state.offset.saturating_add(seconds as i128);
96        PendingTimeIncrease { seconds, offset_reset_generation: state.offset_reset_generation }
97    }
98
99    /// Reverts a temporary increase unless an absolute time reset superseded it.
100    pub(crate) fn revert_time_increase(&self, pending: PendingTimeIncrease) {
101        let mut state = self.state.write();
102        if state.offset_reset_generation == pending.offset_reset_generation {
103            state.offset = state.offset.saturating_sub(pending.seconds as i128);
104        }
105    }
106
107    /// Sets the exact timestamp to use in the next block
108    /// Fails if it's before (or at the same time) the last timestamp
109    pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), BlockchainError> {
110        trace!(target: "time", "override next timestamp {}", timestamp);
111        let mut state = self.state.write();
112        if timestamp < state.last_timestamp {
113            return Err(BlockchainError::TimestampError(format!(
114                "{timestamp} is lower than previous block's timestamp"
115            )));
116        }
117        state.next_override_generation = state.next_override_generation.wrapping_add(1);
118        state.next_exact_timestamp =
119            Some(TimestampOverride { timestamp, generation: state.next_override_generation });
120        Ok(())
121    }
122
123    /// Sets an interval to use when computing the next timestamp
124    ///
125    /// If an interval already exists, this will update the interval, otherwise a new interval will
126    /// be set starting with the current timestamp.
127    pub fn set_block_timestamp_interval(&self, interval: u64) {
128        trace!(target: "time", "set interval {}", interval);
129        self.state.write().interval = Some(interval);
130    }
131
132    /// Returns the configured block timestamp interval.
133    pub(crate) fn block_timestamp_interval(&self) -> Option<u64> {
134        self.state.read().interval
135    }
136
137    /// Removes the interval if it exists
138    pub fn remove_block_timestamp_interval(&self) -> bool {
139        if self.state.write().interval.take().is_some() {
140            trace!(target: "time", "removed interval");
141            true
142        } else {
143            false
144        }
145    }
146
147    /// Computes the next timestamp without updating internals
148    fn compute_next_timestamp(
149        state: &TimeState,
150        current: i128,
151    ) -> (u64, Option<u64>, Option<i128>) {
152        let exact_timestamp = state.next_exact_timestamp;
153        let last_timestamp = state.last_timestamp;
154
155        let (mut next_timestamp, update_offset) = if let Some(next) = exact_timestamp {
156            (next.timestamp, true)
157        } else if let Some(interval) = state.interval {
158            (last_timestamp.saturating_add(interval), false)
159        } else {
160            (current.saturating_add(state.offset) as u64, false)
161        };
162        // Ensures that the timestamp is always increasing
163        if next_timestamp < last_timestamp {
164            next_timestamp = last_timestamp + 1;
165        }
166        let next_offset = update_offset.then_some((next_timestamp as i128) - current);
167        (next_timestamp, exact_timestamp.map(|exact| exact.generation), next_offset)
168    }
169
170    /// Prepares the next timestamp without consuming a one-shot override.
171    pub(crate) fn prepare_next_timestamp(&self) -> PendingBlockTimestamp {
172        let current = duration_since_unix_epoch().as_secs() as i128;
173        let state = self.state.read();
174        let (timestamp, exact_generation, next_offset) =
175            Self::compute_next_timestamp(&state, current);
176        PendingBlockTimestamp {
177            timestamp,
178            exact_generation,
179            prepared_offset: state.offset,
180            offset_reset_generation: state.offset_reset_generation,
181            next_offset,
182        }
183    }
184
185    /// Commits a timestamp after its candidate block finalized successfully.
186    pub(crate) fn commit_next_timestamp(&self, pending: PendingBlockTimestamp) {
187        let mut state = self.state.write();
188        if pending.exact_generation.is_some_and(|generation| {
189            state.next_exact_timestamp.is_some_and(|exact| exact.generation == generation)
190        }) {
191            state.next_exact_timestamp = None;
192        }
193        if let Some(next_offset) = pending.next_offset
194            && state.offset_reset_generation == pending.offset_reset_generation
195        {
196            let concurrent_offset = state.offset.saturating_sub(pending.prepared_offset);
197            state.offset = next_offset.saturating_add(concurrent_offset);
198        }
199        state.last_timestamp = pending.timestamp;
200    }
201
202    /// Returns the current timestamp and updates the underlying offset and interval accordingly
203    pub fn next_timestamp(&self) -> u64 {
204        let pending = self.prepare_next_timestamp();
205        self.commit_next_timestamp(pending);
206        pending.timestamp
207    }
208
209    /// Returns the current timestamp for a call that does _not_ update the value
210    pub fn current_call_timestamp(&self) -> u64 {
211        self.prepare_next_timestamp().timestamp
212    }
213}
214
215/// Returns the current duration since unix epoch.
216pub fn duration_since_unix_epoch() -> Duration {
217    use std::time::SystemTime;
218    let now = SystemTime::now();
219    now.duration_since(SystemTime::UNIX_EPOCH)
220        .unwrap_or_else(|err| panic!("Current time {now:?} is invalid: {err:?}"))
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn candidate_consumes_only_its_timestamp_override() {
229        let time = TimeManager::new(1);
230        time.set_next_block_timestamp(100).unwrap();
231        let pending = time.prepare_next_timestamp();
232
233        time.set_next_block_timestamp(100).unwrap();
234        time.commit_next_timestamp(pending);
235
236        let state = time.state.read();
237        assert_eq!(state.last_timestamp, 100);
238        assert_eq!(state.next_exact_timestamp.unwrap().timestamp, 100);
239    }
240
241    #[test]
242    fn candidate_commit_preserves_concurrent_time_increase() {
243        let time = TimeManager::new(1);
244        time.set_next_block_timestamp(100).unwrap();
245        let pending = time.prepare_next_timestamp();
246
247        time.increase_time(10);
248        time.commit_next_timestamp(pending);
249
250        let state = time.state.read();
251        assert_eq!(state.last_timestamp, 100);
252        assert_eq!(state.offset, pending.next_offset.unwrap() + 10);
253    }
254
255    #[test]
256    fn candidate_commit_preserves_concurrent_time_reset() {
257        let time = TimeManager::new(1);
258        time.set_next_block_timestamp(100).unwrap();
259        let pending = time.prepare_next_timestamp();
260
261        time.reset(1_000);
262        let reset_offset = time.offset();
263        time.commit_next_timestamp(pending);
264
265        let state = time.state.read();
266        assert_eq!(state.last_timestamp, 100);
267        assert_eq!(state.offset, reset_offset);
268    }
269
270    #[test]
271    fn failed_temporary_increase_preserves_concurrent_time_reset() {
272        let time = TimeManager::new(1);
273        let pending = time.apply_time_increase(60);
274
275        time.reset(1_000);
276        let reset_offset = time.offset();
277        time.revert_time_increase(pending);
278
279        assert_eq!(time.offset(), reset_offset);
280    }
281}