init4_bin_base/utils/
calc.rs1use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar};
2use core::num;
3
4use super::from_env::EnvItemInfo;
5
6pub(crate) const START_TIMESTAMP: &str = "START_TIMESTAMP";
8pub(crate) const SLOT_OFFSET: &str = "SLOT_OFFSET";
9pub(crate) const SLOT_DURATION: &str = "SLOT_DURATION";
10
11#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
13pub enum SlotCalcEnvError {
14 #[error("error reading the start timestamp: {0}")]
16 StartTimestamp(num::ParseIntError),
17 #[error("error reading slot offset: {0}")]
19 SlotOffset(num::ParseIntError),
20 #[error("error reading slot duration: {0}")]
22 SlotDuration(num::ParseIntError),
23}
24
25#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28pub struct SlotCalculator {
29 start_timestamp: u64,
31
32 slot_offset: u64,
39
40 slot_duration: u64,
42}
43
44impl SlotCalculator {
45 pub const fn new(start_timestamp: u64, slot_offset: u64, slot_duration: u64) -> Self {
47 Self {
48 start_timestamp,
49 slot_offset,
50 slot_duration,
51 }
52 }
53
54 pub const fn holesky() -> Self {
56 Self {
60 start_timestamp: 1695902424,
61 slot_offset: 2,
62 slot_duration: 12,
63 }
64 }
65
66 pub const fn mainnet() -> Self {
68 Self {
69 start_timestamp: 1663224179,
70 slot_offset: 4700013,
71 slot_duration: 12,
72 }
73 }
74
75 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 pub const fn calculate_timepoint_within_slot(&self, timestamp: u64) -> u64 {
85 (timestamp - self.slot_utc_offset()) % self.slot_duration
86 }
87
88 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 pub fn current_slot(&self) -> u64 {
98 self.calculate_slot(chrono::Utc::now().timestamp() as u64)
99 }
100
101 pub fn current_timepoint_within_slot(&self) -> u64 {
103 self.calculate_timepoint_within_slot(chrono::Utc::now().timestamp() as u64)
104 }
105
106 pub const fn start_timestamp(&self) -> u64 {
108 self.start_timestamp
109 }
110
111 pub const fn slot_offset(&self) -> u64 {
113 self.slot_offset
114 }
115
116 pub const fn slot_duration(&self) -> u64 {
118 self.slot_duration
119 }
120
121 const fn slot_utc_offset(&self) -> u64 {
123 self.start_timestamp % self.slot_duration
124 }
125}
126
127impl FromEnv for SlotCalculator {
128 type Error = SlotCalcEnvError;
129
130 fn inventory() -> Vec<&'static EnvItemInfo> {
131 vec![
132 &EnvItemInfo {
133 var: START_TIMESTAMP,
134 description: "The start timestamp of the chain in seconds",
135 optional: false,
136 },
137 &EnvItemInfo {
138 var: SLOT_OFFSET,
139 description: "The slot offset of the chain in seconds",
140 optional: false,
141 },
142 &EnvItemInfo {
143 var: SLOT_DURATION,
144 description: "The slot duration of the chain in seconds",
145 optional: false,
146 },
147 ]
148 }
149
150 fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
151 let start_timestamp = u64::from_env_var(START_TIMESTAMP)
152 .map_err(|e| e.map(SlotCalcEnvError::StartTimestamp))?;
153 let slot_offset =
154 u64::from_env_var(SLOT_OFFSET).map_err(|e| e.map(SlotCalcEnvError::SlotOffset))?;
155
156 let slot_duration =
157 u64::from_env_var(SLOT_DURATION).map_err(|e| e.map(SlotCalcEnvError::SlotDuration))?;
158
159 Ok(Self::new(start_timestamp, slot_offset, slot_duration))
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_basic_slot_calculations() {
169 let calculator = SlotCalculator::new(0, 0, 12);
170 assert_eq!(calculator.calculate_slot(0), 0);
171
172 assert_eq!(calculator.calculate_slot(1), 1);
173 assert_eq!(calculator.calculate_slot(11), 1);
174 assert_eq!(calculator.calculate_slot(12), 1);
175
176 assert_eq!(calculator.calculate_slot(13), 2);
177 assert_eq!(calculator.calculate_slot(23), 2);
178 assert_eq!(calculator.calculate_slot(24), 2);
179
180 assert_eq!(calculator.calculate_slot(25), 3);
181 assert_eq!(calculator.calculate_slot(35), 3);
182 assert_eq!(calculator.calculate_slot(36), 3);
183 }
184
185 #[test]
186 fn test_holesky_slot_calculations() {
187 let calculator = SlotCalculator::holesky();
188 assert_eq!(calculator.calculate_slot(1695902424), 2);
191 assert_eq!(calculator.calculate_slot(1695902425), 3);
193
194 assert_eq!(calculator.calculate_slot(1742931924), 3919127);
197 assert_eq!(calculator.calculate_slot(1742931925), 3919128);
199 }
200
201 #[test]
202 fn test_holesky_slot_timepoint_calculations() {
203 let calculator = SlotCalculator::holesky();
204 assert_eq!(calculator.calculate_timepoint_within_slot(1695902424), 0);
206 assert_eq!(calculator.calculate_timepoint_within_slot(1695902425), 1);
207 assert_eq!(calculator.calculate_timepoint_within_slot(1695902435), 11);
208 assert_eq!(calculator.calculate_timepoint_within_slot(1695902436), 0);
209 }
210
211 #[test]
212 fn test_holesky_slot_window() {
213 let calculator = SlotCalculator::holesky();
214 assert_eq!(
216 calculator.calculate_slot_window(2),
217 (1695902412, 1695902424)
218 );
219 assert_eq!(
220 calculator.calculate_slot_window(3),
221 (1695902424, 1695902436)
222 );
223 }
224
225 #[test]
226 fn test_mainnet_slot_calculations() {
227 let calculator = SlotCalculator::mainnet();
228 assert_eq!(calculator.calculate_slot(1663224179), 4700013);
229 assert_eq!(calculator.calculate_slot(1663224180), 4700014);
230
231 assert_eq!(calculator.calculate_slot(1738863035), 11003251);
232 assert_eq!(calculator.calculate_slot(1738866239), 11003518);
233 assert_eq!(calculator.calculate_slot(1738866227), 11003517);
234 }
235
236 #[test]
237 fn test_mainnet_slot_timepoint_calculations() {
238 let calculator = SlotCalculator::mainnet();
239 assert_eq!(calculator.calculate_timepoint_within_slot(1663224179), 0);
241 assert_eq!(calculator.calculate_timepoint_within_slot(1663224180), 1);
242 assert_eq!(calculator.calculate_timepoint_within_slot(1663224190), 11);
243 assert_eq!(calculator.calculate_timepoint_within_slot(1663224191), 0);
244 }
245
246 #[test]
247 fn test_ethereum_slot_window() {
248 let calculator = SlotCalculator::mainnet();
249 assert_eq!(
251 calculator.calculate_slot_window(4700013),
252 (1663224167, 1663224179)
253 );
254 assert_eq!(
255 calculator.calculate_slot_window(4700014),
256 (1663224179, 1663224191)
257 );
258 }
259}