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()
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum TimePrecision {
15    /// Make time manager return the timestamp in seconds.
16    Seconds,
17    /// Make the time manager return the timestamp in milliseconds.
18    Milliseconds,
19}
20
21/// Manages block time
22#[derive(Clone, Debug)]
23pub struct TimeManager {
24    /// tracks the overall applied timestamp offset
25    offset: Arc<RwLock<i128>>,
26    /// The timestamp of the last block header
27    last_timestamp: Arc<RwLock<u64>>,
28    /// Contains the next timestamp to use
29    /// if this is set then the next time `[TimeManager::current_timestamp()]` is called this value
30    /// will be taken and returned. After which the `offset` will be updated accordingly
31    next_exact_timestamp: Arc<RwLock<Option<u64>>>,
32    /// The interval to use when determining the next block's timestamp
33    interval: Arc<RwLock<Option<u64>>>,
34    precision: TimePrecision,
35}
36
37impl TimeManager {
38    pub fn new(start_timestamp: u64) -> Self {
39        let time_manager = Self {
40            last_timestamp: Default::default(),
41            offset: Default::default(),
42            next_exact_timestamp: Default::default(),
43            interval: Default::default(),
44            precision: TimePrecision::Seconds,
45        };
46        time_manager.reset(start_timestamp);
47        time_manager
48    }
49
50    pub fn new_with_milliseconds(start_timestamp: u64) -> Self {
51        let time_manager = Self {
52            last_timestamp: Default::default(),
53            offset: Default::default(),
54            next_exact_timestamp: Default::default(),
55            interval: Default::default(),
56            precision: TimePrecision::Milliseconds,
57        };
58        let start_timestamp = start_timestamp.saturating_div(1000);
59        time_manager.reset(start_timestamp);
60        time_manager
61    }
62
63    /// Converts a value from milliseconds to the manager's `precision`.
64    fn convert_from_milliseconds(&self, value: u64) -> u64 {
65        match self.precision {
66            TimePrecision::Seconds => value.saturating_div(1000),
67            TimePrecision::Milliseconds => value,
68        }
69    }
70
71    /// Resets the current time manager to the given timestamp, resetting the offsets and
72    /// next block timestamp option
73    pub fn reset(&self, start_timestamp: u64) {
74        let current = duration_since_unix_epoch().as_millis() as i128;
75        let start_timestamp = to_milliseconds(start_timestamp);
76        *self.last_timestamp.write() = start_timestamp;
77        *self.offset.write() = (start_timestamp as i128) - current;
78        self.next_exact_timestamp.write().take();
79    }
80
81    pub fn offset(&self) -> i128 {
82        *self.offset.read()
83    }
84
85    /// Adds the given `offset` to the already tracked offset and returns the result
86    fn add_offset(&self, offset: i128) -> i128 {
87        let mut current = self.offset.write();
88        let next = current.saturating_add(offset);
89        trace!(target: "time", "adding timestamp offset={}, total={}", offset, next);
90        *current = next;
91        next
92    }
93
94    /// Jumps forward in time by the given seconds
95    ///
96    /// This will apply a permanent offset to the natural UNIX Epoch timestamp
97    pub fn increase_time(&self, seconds: u64) -> i128 {
98        self.add_offset(to_milliseconds(seconds) as i128)
99    }
100
101    /// Sets the exact timestamp to use in the next block
102    /// Fails if it's before (or at the same time) the last timestamp
103    pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), BlockchainError> {
104        trace!(target: "time", "override next timestamp {}", timestamp);
105        let timestamp = to_milliseconds(timestamp);
106        if timestamp < *self.last_timestamp.read() {
107            return Err(BlockchainError::TimestampError(format!(
108                "{} is lower than previous block's timestamp",
109                self.convert_from_milliseconds(timestamp)
110            )));
111        }
112        self.next_exact_timestamp.write().replace(timestamp);
113        Ok(())
114    }
115
116    /// Sets an interval to use when computing the next timestamp
117    ///
118    /// If an interval already exists, this will update the interval, otherwise a new interval will
119    /// be set starting with the current timestamp.
120    pub fn set_block_timestamp_interval(&self, interval: u64) {
121        trace!(target: "time", "set interval {}", interval);
122        let interval = to_milliseconds(interval);
123        self.interval.write().replace(interval);
124    }
125
126    /// Removes the interval if it exists
127    pub fn remove_block_timestamp_interval(&self) -> bool {
128        if self.interval.write().take().is_some() {
129            trace!(target: "time", "removed interval");
130            true
131        } else {
132            false
133        }
134    }
135
136    /// Computes the next timestamp without updating internals
137    fn compute_next_timestamp(&self) -> (u64, Option<i128>) {
138        let current = duration_since_unix_epoch().as_millis() as i128;
139        let last_timestamp = *self.last_timestamp.read();
140
141        let (mut next_timestamp, update_offset) =
142            if let Some(next) = *self.next_exact_timestamp.read() {
143                (next, true)
144            } else if let Some(interval) = *self.interval.read() {
145                (last_timestamp.saturating_add(interval), false)
146            } else {
147                (current.saturating_add(self.offset()) as u64, false)
148            };
149        // Ensures that the timestamp is always increasing
150        if next_timestamp < last_timestamp {
151            next_timestamp = last_timestamp + to_milliseconds(1);
152        }
153        let next_offset = update_offset.then_some((next_timestamp as i128) - current);
154        (next_timestamp, next_offset)
155    }
156
157    /// Returns the current timestamp and updates the underlying offset and interval accordingly
158    pub fn next_timestamp(&self) -> u64 {
159        let (next_timestamp, next_offset) = self.compute_next_timestamp();
160        // Make sure we reset the `next_exact_timestamp`
161        self.next_exact_timestamp.write().take();
162        if let Some(next_offset) = next_offset {
163            *self.offset.write() = next_offset;
164        }
165        *self.last_timestamp.write() = next_timestamp;
166        self.convert_from_milliseconds(next_timestamp)
167    }
168
169    /// Returns the current timestamp for a call that does _not_ update the value
170    pub fn current_call_timestamp(&self) -> u64 {
171        let (next_timestamp, _) = self.compute_next_timestamp();
172        self.convert_from_milliseconds(next_timestamp)
173    }
174}
175
176/// Returns the current duration since unix epoch.
177pub fn duration_since_unix_epoch() -> Duration {
178    use std::time::SystemTime;
179    let now = SystemTime::now();
180    now.duration_since(SystemTime::UNIX_EPOCH)
181        .unwrap_or_else(|err| panic!("Current time {now:?} is invalid: {err:?}"))
182}
183
184/// Converts a value from the manager's `precision` to milliseconds.
185fn to_milliseconds(value: u64) -> u64 {
186    value.saturating_mul(1000)
187}