stratus/eth/primitives/
unix_time.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use std::ops::Deref;

use chrono::DateTime;
use chrono::Utc;
use display_json::DebugAsJson;
use ethereum_types::U256;
use fake::Dummy;
use fake::Faker;

use crate::alias::RevmU256;
use crate::ext::InfallibleExt;

#[derive(DebugAsJson, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct UnixTime(u64);

impl UnixTime {
    pub const ZERO: UnixTime = UnixTime(0u64);

    #[cfg(not(feature = "dev"))]
    pub fn now() -> Self {
        Self(Utc::now().timestamp() as u64)
    }

    #[cfg(feature = "dev")]
    pub fn now() -> Self {
        offset::now()
    }

    #[cfg(feature = "dev")]
    pub fn set_offset(current_block_timestamp: UnixTime, new_block_timestamp: UnixTime) -> anyhow::Result<()> {
        offset::set(current_block_timestamp, new_block_timestamp)
    }

    #[cfg(feature = "dev")]
    pub fn evm_set_next_block_timestamp_was_called() -> bool {
        offset::evm_set_next_block_timestamp_was_called()
    }
}

impl Deref for UnixTime {
    type Target = u64;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Dummy<Faker> for UnixTime {
    fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(_: &Faker, rng: &mut R) -> Self {
        rng.next_u64().into()
    }
}

// -----------------------------------------------------------------------------
// Conversions: Other -> Self
// -----------------------------------------------------------------------------

impl From<u64> for UnixTime {
    fn from(value: u64) -> Self {
        UnixTime(value)
    }
}

impl From<U256> for UnixTime {
    fn from(value: U256) -> Self {
        value.low_u64().into()
    }
}

// -----------------------------------------------------------------------------
// Conversions: Self -> Other
// -----------------------------------------------------------------------------

impl From<UnixTime> for RevmU256 {
    fn from(value: UnixTime) -> Self {
        Self::from_limbs([value.0, 0, 0, 0])
    }
}

impl From<UnixTime> for DateTime<Utc> {
    fn from(value: UnixTime) -> Self {
        DateTime::from_timestamp(value.0 as i64, 0).expect_infallible()
    }
}

#[cfg(feature = "dev")]
mod offset {
    use std::sync::atomic::AtomicBool;
    use std::sync::atomic::AtomicI64;
    use std::sync::atomic::AtomicU64;
    use std::sync::atomic::Ordering::Acquire;
    use std::sync::atomic::Ordering::SeqCst;

    use super::UnixTime;
    use super::Utc;

    /// Stores the time difference (in seconds) to apply to subsequent blocks
    /// This maintains relative time offsets after an explicit timestamp is used
    pub static NEW_TIMESTAMP_DIFF: AtomicI64 = AtomicI64::new(0);

    /// Stores the exact timestamp to use for the next block
    /// Only used once, then reset to 0
    pub static NEW_TIMESTAMP: AtomicU64 = AtomicU64::new(0);

    /// Indicates whether evm_setNextBlockTimestamp was called and hasn't been consumed yet
    pub static EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED: AtomicBool = AtomicBool::new(false);

    /// Stores the timestamp of the most recently processed block
    /// Used to ensure that block timestamps always increase monotonically
    pub static LAST_BLOCK_TIMESTAMP: AtomicI64 = AtomicI64::new(0);

    /// Sets the timestamp for the next block and calculates the offset for subsequent blocks
    ///
    /// # Scenarios:
    /// 1. Setting a future timestamp:
    ///    - current_block = 100, new_timestamp = 110
    ///    - diff = (110 - current_time) = +10
    ///    - Next block will be exactly 110
    ///    - Subsequent blocks will maintain the +10 offset from current time
    ///
    /// 2. Setting timestamp to 0 (reset):
    ///    - Stores the current block timestamp for reference
    ///    - Resets the time offset to 0
    ///    - Next block will be current_block + 1
    ///    - Subsequent blocks use current time
    ///
    /// 3. Setting a past timestamp (error):
    ///    - current_block = 100, new_timestamp = 90
    ///    - Returns error: "timestamp can't be before the latest block"
    pub fn set(current_block_timestamp: UnixTime, new_block_timestamp: UnixTime) -> anyhow::Result<()> {
        use crate::log_and_err;

        if *new_block_timestamp == 0 {
            LAST_BLOCK_TIMESTAMP.store(*current_block_timestamp as i64, SeqCst);
        }

        if *new_block_timestamp != 0 && *new_block_timestamp < *current_block_timestamp {
            return log_and_err!("timestamp can't be before the latest block");
        }

        let current_time = Utc::now().timestamp();
        let diff: i64 = if *new_block_timestamp == 0 {
            0
        } else {
            (*new_block_timestamp as i64).saturating_sub(current_time)
        };

        NEW_TIMESTAMP.store(*new_block_timestamp, SeqCst);
        NEW_TIMESTAMP_DIFF.store(diff, SeqCst);
        EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.store(true, SeqCst);
        Ok(())
    }

    /// Returns the timestamp for the current block based on various conditions.
    /// Ensures proper time progression by guaranteeing that each block's timestamp
    /// is at least 1 second greater than the previous block.
    ///
    /// # Behavior
    /// There are two main paths for timestamp generation:
    ///
    /// 1. When a specific timestamp was set (was_evm_timestamp_set = true):
    ///    - If new_timestamp is 0: Returns last_block_timestamp + 1
    ///    - If new_timestamp > 0: Returns exactly new_timestamp
    ///    - After this call, resets the timestamp flag and stored value
    ///
    /// 2. For subsequent blocks (was_evm_timestamp_set = false):
    ///    - If new_timestamp is set: Uses it as reference point
    ///    - Otherwise: Uses max(current_time + offset, last_block_timestamp)
    ///    - In both cases, adds 1 second to ensure progression
    ///
    /// The function always updates LAST_BLOCK_TIMESTAMP with the returned value
    /// to maintain the chain of increasing timestamps.
    pub fn now() -> UnixTime {
        let new_timestamp = NEW_TIMESTAMP.load(Acquire);
        let new_timestamp_diff = NEW_TIMESTAMP_DIFF.load(Acquire);
        let was_evm_timestamp_set = EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.load(Acquire);
        let last_block_timestamp = LAST_BLOCK_TIMESTAMP.load(Acquire);
        let current_time = Utc::now().timestamp();

        let result = if !was_evm_timestamp_set {
            let last_timestamp = if new_timestamp != 0 {
                new_timestamp as i64
            } else {
                std::cmp::max(current_time + new_timestamp_diff, last_block_timestamp)
            };

            UnixTime((last_timestamp + 1) as u64)
        } else {
            EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.store(false, SeqCst);
            NEW_TIMESTAMP.store(0, SeqCst);

            if new_timestamp == 0 {
                UnixTime((last_block_timestamp + 1) as u64)
            } else {
                UnixTime(new_timestamp)
            }
        };

        LAST_BLOCK_TIMESTAMP.store(*result as i64, SeqCst);
        result
    }

    /// Returns whether evm_setNextBlockTimestamp was called and hasn't been consumed yet
    pub fn evm_set_next_block_timestamp_was_called() -> bool {
        EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.load(Acquire)
    }
}