init4_bin_base/utils/
calc.rs

1use crate::utils::from_env::FromEnv;
2
3/// A slot calculator, which can calculate the slot number for a given
4/// timestamp.
5#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize, FromEnv)]
6#[from_env(crate)]
7pub struct SlotCalculator {
8    /// The start timestamp.
9    #[from_env(
10        var = "START_TIMESTAMP",
11        desc = "The start timestamp of the chain in seconds"
12    )]
13    start_timestamp: u64,
14
15    /// This is the number of the slot containing the block which contains the
16    /// `start_timestamp`.
17    ///
18    /// This is needed for chains that contain a merge (like Ethereum Mainnet),
19    /// or for chains with missed slots at the start of the chain (like
20    /// Holesky).
21    #[from_env(
22        var = "SLOT_OFFSET",
23        desc = "The number of the slot containing the start timestamp"
24    )]
25    slot_offset: u64,
26
27    /// The slot duration (in seconds).
28    #[from_env(
29        var = "SLOT_DURATION",
30        desc = "The slot duration of the chain in seconds"
31    )]
32    slot_duration: u64,
33}
34
35impl SlotCalculator {
36    /// Creates a new slot calculator.
37    pub const fn new(start_timestamp: u64, slot_offset: u64, slot_duration: u64) -> Self {
38        Self {
39            start_timestamp,
40            slot_offset,
41            slot_duration,
42        }
43    }
44
45    /// Creates a new slot calculator for Holesky.
46    pub const fn holesky() -> Self {
47        // begin slot calculation for Holesky from block number 1, slot number 2, timestamp 1695902424
48        // because of a strange 324 second gap between block 0 and 1 which
49        // should have been 27 slots, but which is recorded as 2 slots in chain data
50        Self {
51            start_timestamp: 1695902424,
52            slot_offset: 2,
53            slot_duration: 12,
54        }
55    }
56
57    /// Creates a new slot calculator for Pecorino host network.
58    pub const fn pecorino_host() -> Self {
59        Self {
60            start_timestamp: 1740681556,
61            slot_offset: 0,
62            slot_duration: 12,
63        }
64    }
65
66    /// Creates a new slot calculator for Ethereum mainnet.
67    pub const fn mainnet() -> Self {
68        Self {
69            start_timestamp: 1663224179,
70            slot_offset: 4700013,
71            slot_duration: 12,
72        }
73    }
74
75    /// Calculates the slot for a given timestamp.
76    /// This only works for timestamps that are GEQ to the chain's start_timestamp.
77    pub const fn calculate_slot(&self, timestamp: u64) -> u64 {
78        let elapsed = timestamp - self.start_timestamp;
79        let slots = elapsed.div_ceil(self.slot_duration);
80        slots + self.slot_offset
81    }
82
83    /// Calculates how many seconds into the block window for a given timestamp.
84    pub const fn calculate_timepoint_within_slot(&self, timestamp: u64) -> u64 {
85        (timestamp - self.slot_utc_offset()) % self.slot_duration
86    }
87
88    /// Calculates the start and end timestamps for a given slot
89    pub const fn calculate_slot_window(&self, slot_number: u64) -> (u64, u64) {
90        let end_of_slot =
91            ((slot_number - self.slot_offset) * self.slot_duration) + self.start_timestamp;
92        let start_of_slot = end_of_slot - self.slot_duration;
93        (start_of_slot, end_of_slot)
94    }
95
96    /// The current slot number.
97    pub fn current_slot(&self) -> u64 {
98        self.calculate_slot(chrono::Utc::now().timestamp() as u64)
99    }
100
101    /// The current number of seconds into the block window.
102    pub fn current_timepoint_within_slot(&self) -> u64 {
103        self.calculate_timepoint_within_slot(chrono::Utc::now().timestamp() as u64)
104    }
105
106    /// The timestamp of the first PoS block in the chain.
107    pub const fn start_timestamp(&self) -> u64 {
108        self.start_timestamp
109    }
110
111    /// The slot number of the first PoS block in the chain.
112    pub const fn slot_offset(&self) -> u64 {
113        self.slot_offset
114    }
115
116    /// The slot duration, usually 12 seconds.
117    pub const fn slot_duration(&self) -> u64 {
118        self.slot_duration
119    }
120
121    /// The offset in seconds between UTC time and slot mining times
122    const fn slot_utc_offset(&self) -> u64 {
123        self.start_timestamp % self.slot_duration
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_basic_slot_calculations() {
133        let calculator = SlotCalculator::new(0, 0, 12);
134        assert_eq!(calculator.calculate_slot(0), 0);
135
136        assert_eq!(calculator.calculate_slot(1), 1);
137        assert_eq!(calculator.calculate_slot(11), 1);
138        assert_eq!(calculator.calculate_slot(12), 1);
139
140        assert_eq!(calculator.calculate_slot(13), 2);
141        assert_eq!(calculator.calculate_slot(23), 2);
142        assert_eq!(calculator.calculate_slot(24), 2);
143
144        assert_eq!(calculator.calculate_slot(25), 3);
145        assert_eq!(calculator.calculate_slot(35), 3);
146        assert_eq!(calculator.calculate_slot(36), 3);
147    }
148
149    #[test]
150    fn test_holesky_slot_calculations() {
151        let calculator = SlotCalculator::holesky();
152        // block 1 == slot 2 == timestamp 1695902424
153        // timestamp 1695902424 == slot 2
154        assert_eq!(calculator.calculate_slot(1695902424), 2);
155        // the next second, timestamp 1695902425 == slot 3
156        assert_eq!(calculator.calculate_slot(1695902425), 3);
157
158        // block 3557085 == slot 3919127 == timestamp 1742931924
159        // timestamp 1742931924 == slot 3919127
160        assert_eq!(calculator.calculate_slot(1742931924), 3919127);
161        // the next second, timestamp 1742931925 == slot 3919128
162        assert_eq!(calculator.calculate_slot(1742931925), 3919128);
163    }
164
165    #[test]
166    fn test_holesky_slot_timepoint_calculations() {
167        let calculator = SlotCalculator::holesky();
168        // calculate timepoint in slot
169        assert_eq!(calculator.calculate_timepoint_within_slot(1695902424), 0);
170        assert_eq!(calculator.calculate_timepoint_within_slot(1695902425), 1);
171        assert_eq!(calculator.calculate_timepoint_within_slot(1695902435), 11);
172        assert_eq!(calculator.calculate_timepoint_within_slot(1695902436), 0);
173    }
174
175    #[test]
176    fn test_holesky_slot_window() {
177        let calculator = SlotCalculator::holesky();
178        // calculate slot window
179        assert_eq!(
180            calculator.calculate_slot_window(2),
181            (1695902412, 1695902424)
182        );
183        assert_eq!(
184            calculator.calculate_slot_window(3),
185            (1695902424, 1695902436)
186        );
187    }
188
189    #[test]
190    fn test_mainnet_slot_calculations() {
191        let calculator = SlotCalculator::mainnet();
192        assert_eq!(calculator.calculate_slot(1663224179), 4700013);
193        assert_eq!(calculator.calculate_slot(1663224180), 4700014);
194
195        assert_eq!(calculator.calculate_slot(1738863035), 11003251);
196        assert_eq!(calculator.calculate_slot(1738866239), 11003518);
197        assert_eq!(calculator.calculate_slot(1738866227), 11003517);
198    }
199
200    #[test]
201    fn test_mainnet_slot_timepoint_calculations() {
202        let calculator = SlotCalculator::mainnet();
203        // calculate timepoint in slot
204        assert_eq!(calculator.calculate_timepoint_within_slot(1663224179), 0);
205        assert_eq!(calculator.calculate_timepoint_within_slot(1663224180), 1);
206        assert_eq!(calculator.calculate_timepoint_within_slot(1663224190), 11);
207        assert_eq!(calculator.calculate_timepoint_within_slot(1663224191), 0);
208    }
209
210    #[test]
211    fn test_ethereum_slot_window() {
212        let calculator = SlotCalculator::mainnet();
213        // calculate slot window
214        assert_eq!(
215            calculator.calculate_slot_window(4700013),
216            (1663224167, 1663224179)
217        );
218        assert_eq!(
219            calculator.calculate_slot_window(4700014),
220            (1663224179, 1663224191)
221        );
222    }
223}