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