@@ -22,7 +22,10 @@ impl SlotCalculator {
2222
2323 /// Creates a new slot calculator for Holesky.
2424 pub const fn holesky ( ) -> Self {
25- Self { start_timestamp : 1695902400 , slot_offset : 0 , slot_duration : 12 }
25+ // begin slot calculation for Holesky from block number 1, slot number 2, timestamp 1695902424
26+ // because of a strange 324 second gap between block 0 and 1 which
27+ // should have been 27 slots, but which is recorded as 2 slots in chain data
28+ Self { start_timestamp : 1695902424 , slot_offset : 2 , slot_duration : 12 }
2629 }
2730
2831 /// Creates a new slot calculator for Ethereum mainnet.
@@ -31,12 +34,36 @@ impl SlotCalculator {
3134 }
3235
3336 /// Calculates the slot for a given timestamp.
37+ /// This only works for timestamps that are GEQ to the chain's start_timestamp.
3438 pub const fn calculate_slot ( & self , timestamp : u64 ) -> u64 {
3539 let elapsed = timestamp - self . start_timestamp ;
36- let slots = elapsed. saturating_div ( self . slot_duration ) ;
40+ let slots = elapsed. div_ceil ( self . slot_duration ) ;
3741 slots + self . slot_offset
3842 }
3943
44+ /// Calculates how many seconds into the block window for a given timestamp.
45+ pub const fn calculate_timepoint_within_slot ( & self , timestamp : u64 ) -> u64 {
46+ ( timestamp - self . slot_utc_offset ( ) ) % self . slot_duration
47+ }
48+
49+ /// Calculates the start and end timestamps for a given slot
50+ pub const fn calculate_slot_window ( & self , slot_number : u64 ) -> ( u64 , u64 ) {
51+ let end_of_slot =
52+ ( ( slot_number - self . slot_offset ) * self . slot_duration ) + self . start_timestamp ;
53+ let start_of_slot = end_of_slot - self . slot_duration ;
54+ ( start_of_slot, end_of_slot)
55+ }
56+
57+ /// The current slot number.
58+ pub fn current_slot ( & self ) -> u64 {
59+ self . calculate_slot ( chrono:: Utc :: now ( ) . timestamp ( ) as u64 )
60+ }
61+
62+ /// The current number of seconds into the block window.
63+ pub fn current_timepoint_within_slot ( & self ) -> u64 {
64+ self . calculate_timepoint_within_slot ( chrono:: Utc :: now ( ) . timestamp ( ) as u64 )
65+ }
66+
4067 /// The timestamp of the first PoS block in the chain.
4168 pub const fn start_timestamp ( & self ) -> u64 {
4269 self . start_timestamp
@@ -51,6 +78,11 @@ impl SlotCalculator {
5178 pub const fn slot_duration ( & self ) -> u64 {
5279 self . slot_duration
5380 }
81+
82+ /// The offset in seconds between UTC time and slot mining times
83+ const fn slot_utc_offset ( & self ) -> u64 {
84+ self . start_timestamp % self . slot_duration
85+ }
5486}
5587
5688#[ cfg( test) ]
@@ -61,25 +93,80 @@ mod tests {
6193 fn test_basic_slot_calculations ( ) {
6294 let calculator = SlotCalculator :: new ( 0 , 0 , 12 ) ;
6395 assert_eq ! ( calculator. calculate_slot( 0 ) , 0 ) ;
96+
97+ assert_eq ! ( calculator. calculate_slot( 1 ) , 1 ) ;
98+ assert_eq ! ( calculator. calculate_slot( 11 ) , 1 ) ;
6499 assert_eq ! ( calculator. calculate_slot( 12 ) , 1 ) ;
65- assert_eq ! ( calculator. calculate_slot( 35 ) , 2 ) ;
100+
101+ assert_eq ! ( calculator. calculate_slot( 13 ) , 2 ) ;
102+ assert_eq ! ( calculator. calculate_slot( 23 ) , 2 ) ;
103+ assert_eq ! ( calculator. calculate_slot( 24 ) , 2 ) ;
104+
105+ assert_eq ! ( calculator. calculate_slot( 25 ) , 3 ) ;
106+ assert_eq ! ( calculator. calculate_slot( 35 ) , 3 ) ;
66107 assert_eq ! ( calculator. calculate_slot( 36 ) , 3 ) ;
67108 }
68109
69110 #[ test]
70111 fn test_holesky_slot_calculations ( ) {
71112 let calculator = SlotCalculator :: holesky ( ) ;
72- assert_eq ! ( calculator. calculate_slot( 1695902400 ) , 0 ) ;
73- assert_eq ! ( calculator. calculate_slot( 1695902412 ) , 1 ) ;
74- assert_eq ! ( calculator. calculate_slot( 1695902416 ) , 1 ) ;
75- assert_eq ! ( calculator. calculate_slot( 1738866996 ) , 3580383 ) ;
113+ // block 1 == slot 2 == timestamp 1695902424
114+ // timestamp 1695902424 == slot 2
115+ assert_eq ! ( calculator. calculate_slot( 1695902424 ) , 2 ) ;
116+ // the next second, timestamp 1695902425 == slot 3
117+ assert_eq ! ( calculator. calculate_slot( 1695902425 ) , 3 ) ;
118+
119+ // block 3557085 == slot 3919127 == timestamp 1742931924
120+ // timestamp 1742931924 == slot 3919127
121+ assert_eq ! ( calculator. calculate_slot( 1742931924 ) , 3919127 ) ;
122+ // the next second, timestamp 1742931925 == slot 3919128
123+ assert_eq ! ( calculator. calculate_slot( 1742931925 ) , 3919128 ) ;
124+ }
125+
126+ #[ test]
127+ fn test_holesky_slot_timepoint_calculations ( ) {
128+ let calculator = SlotCalculator :: holesky ( ) ;
129+ // calculate timepoint in slot
130+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1695902424 ) , 0 ) ;
131+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1695902425 ) , 1 ) ;
132+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1695902435 ) , 11 ) ;
133+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1695902436 ) , 0 ) ;
134+ }
135+
136+ #[ test]
137+ fn test_holesky_slot_window ( ) {
138+ let calculator = SlotCalculator :: holesky ( ) ;
139+ // calculate slot window
140+ assert_eq ! ( calculator. calculate_slot_window( 2 ) , ( 1695902412 , 1695902424 ) ) ;
141+ assert_eq ! ( calculator. calculate_slot_window( 3 ) , ( 1695902424 , 1695902436 ) ) ;
76142 }
77143
78144 #[ test]
79145 fn test_mainnet_slot_calculations ( ) {
80146 let calculator = SlotCalculator :: mainnet ( ) ;
147+ assert_eq ! ( calculator. calculate_slot( 1663224179 ) , 4700013 ) ;
148+ assert_eq ! ( calculator. calculate_slot( 1663224180 ) , 4700014 ) ;
149+
81150 assert_eq ! ( calculator. calculate_slot( 1738863035 ) , 11003251 ) ;
82151 assert_eq ! ( calculator. calculate_slot( 1738866239 ) , 11003518 ) ;
83152 assert_eq ! ( calculator. calculate_slot( 1738866227 ) , 11003517 ) ;
84153 }
154+
155+ #[ test]
156+ fn test_mainnet_slot_timepoint_calculations ( ) {
157+ let calculator = SlotCalculator :: mainnet ( ) ;
158+ // calculate timepoint in slot
159+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1663224179 ) , 0 ) ;
160+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1663224180 ) , 1 ) ;
161+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1663224190 ) , 11 ) ;
162+ assert_eq ! ( calculator. calculate_timepoint_within_slot( 1663224191 ) , 0 ) ;
163+ }
164+
165+ #[ test]
166+ fn test_ethereum_slot_window ( ) {
167+ let calculator = SlotCalculator :: mainnet ( ) ;
168+ // calculate slot window
169+ assert_eq ! ( calculator. calculate_slot_window( 4700013 ) , ( 1663224167 , 1663224179 ) ) ;
170+ assert_eq ! ( calculator. calculate_slot_window( 4700014 ) , ( 1663224179 , 1663224191 ) ) ;
171+ }
85172}
0 commit comments