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