@@ -170,9 +170,38 @@ impl Network {
170
170
///
171
171
/// The Slot does not have to be a valid slot present in the blockchain.
172
172
#[ must_use]
173
- pub fn time_to_slot ( & self , _time : DateTime < Utc > ) -> Option < u64 > {
174
- // TODO: Implement this, for now just return None.
175
- None
173
+ pub fn time_to_slot ( & self , time : DateTime < Utc > ) -> Option < u64 > {
174
+ let genesis = self . genesis_values ( ) ;
175
+
176
+ let byron_start_time = i64:: try_from ( genesis. byron_known_time )
177
+ . map ( |time| DateTime :: < Utc > :: from_timestamp ( time, 0 ) )
178
+ . ok ( ) ??;
179
+ let shelley_start_time = i64:: try_from ( genesis. shelley_known_time )
180
+ . map ( |time| DateTime :: < Utc > :: from_timestamp ( time, 0 ) )
181
+ . ok ( ) ??;
182
+
183
+ // determine if the given time is in Byron or Shelley era.
184
+ if time < byron_start_time {
185
+ return None ;
186
+ }
187
+
188
+ if time < shelley_start_time {
189
+ // Byron era
190
+ let time_diff = time - byron_start_time;
191
+ let elapsed_slots = time_diff. num_seconds ( ) / i64:: from ( genesis. byron_slot_length ) ;
192
+
193
+ u64:: try_from ( elapsed_slots)
194
+ . map ( |elapsed_slots| Some ( genesis. byron_known_slot + elapsed_slots) )
195
+ . ok ( ) ?
196
+ } else {
197
+ // Shelley era
198
+ let time_diff = time - shelley_start_time;
199
+ let elapsed_slots = time_diff. num_seconds ( ) / i64:: from ( genesis. shelley_slot_length ) ;
200
+
201
+ u64:: try_from ( elapsed_slots)
202
+ . map ( |elapsed_slots| Some ( genesis. shelley_known_slot + elapsed_slots) )
203
+ . ok ( ) ?
204
+ }
176
205
}
177
206
}
178
207
@@ -191,6 +220,7 @@ mod tests {
191
220
use std:: str:: FromStr ;
192
221
193
222
use anyhow:: Ok ;
223
+ use chrono:: { TimeZone , Utc } ;
194
224
195
225
use super :: * ;
196
226
@@ -214,4 +244,153 @@ mod tests {
214
244
215
245
Ok ( ( ) )
216
246
}
247
+
248
+ #[ test]
249
+ fn test_time_to_slot_before_blockchain ( ) {
250
+ let network = Network :: Mainnet ;
251
+ let genesis = network. genesis_values ( ) ;
252
+
253
+ let before_blockchain = Utc
254
+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) - 1 , 0 )
255
+ . unwrap ( ) ;
256
+
257
+ assert_eq ! ( network. time_to_slot( before_blockchain) , None ) ;
258
+ }
259
+
260
+ #[ test]
261
+ fn test_time_to_slot_byron_era ( ) {
262
+ let network = Network :: Mainnet ;
263
+ let genesis = network. genesis_values ( ) ;
264
+
265
+ let byron_start_time = Utc
266
+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) , 0 )
267
+ . unwrap ( ) ;
268
+ let byron_slot_length = i64:: from ( genesis. byron_slot_length ) ;
269
+
270
+ // a time in the middle of the Byron era.
271
+ let time = byron_start_time + chrono:: Duration :: seconds ( byron_slot_length * 100 ) ;
272
+ let expected_slot = genesis. byron_known_slot + 100 ;
273
+
274
+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
275
+ }
276
+
277
+ #[ test]
278
+ fn test_time_to_slot_transition_to_shelley ( ) {
279
+ let network = Network :: Mainnet ;
280
+ let genesis = network. genesis_values ( ) ;
281
+
282
+ let shelley_start_time = Utc
283
+ . timestamp_opt ( i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) , 0 )
284
+ . unwrap ( ) ;
285
+ let byron_slot_length = i64:: from ( genesis. byron_slot_length ) ;
286
+
287
+ // a time just before Shelley era starts.
288
+ let time = shelley_start_time - chrono:: Duration :: seconds ( 1 ) ;
289
+ let elapsed_slots = ( time
290
+ - Utc
291
+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) , 0 )
292
+ . unwrap ( ) )
293
+ . num_seconds ( )
294
+ / byron_slot_length;
295
+ let expected_slot = genesis. byron_known_slot + u64:: try_from ( elapsed_slots) . unwrap ( ) ;
296
+
297
+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
298
+ }
299
+
300
+ #[ test]
301
+ fn test_time_to_slot_shelley_era ( ) {
302
+ let network = Network :: Mainnet ;
303
+ let genesis = network. genesis_values ( ) ;
304
+
305
+ let shelley_start_time = Utc
306
+ . timestamp_opt ( i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) , 0 )
307
+ . unwrap ( ) ;
308
+ let shelley_slot_length = i64:: from ( genesis. shelley_slot_length ) ;
309
+
310
+ // a time in the middle of the Shelley era.
311
+ let time = shelley_start_time + chrono:: Duration :: seconds ( shelley_slot_length * 200 ) ;
312
+ let expected_slot = genesis. shelley_known_slot + 200 ;
313
+
314
+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
315
+ }
316
+
317
+ #[ test]
318
+ fn test_slot_to_time_to_slot_consistency ( ) {
319
+ let network = Network :: Mainnet ;
320
+
321
+ // a few arbitrary slots in different ranges.
322
+ let slots_to_test = vec ! [ 0 , 10_000 , 1_000_000 , 50_000_000 ] ;
323
+
324
+ for slot in slots_to_test {
325
+ let time = network. slot_to_time ( slot) ;
326
+ let calculated_slot = network. time_to_slot ( time) ;
327
+
328
+ assert_eq ! ( calculated_slot, Some ( slot) , "Failed for slot: {slot}" ) ;
329
+ }
330
+ }
331
+
332
+ #[ test]
333
+ #[ allow( clippy:: panic) ]
334
+ fn test_time_to_slot_to_time_consistency ( ) {
335
+ let network = Network :: Mainnet ;
336
+ let genesis = network. genesis_values ( ) ;
337
+
338
+ // Byron, Shelley, and Conway.
339
+ let times_to_test = vec ! [
340
+ Utc . timestamp_opt( i64 :: try_from( genesis. byron_known_time) . unwrap( ) + 100 , 0 )
341
+ . unwrap( ) ,
342
+ Utc . timestamp_opt(
343
+ i64 :: try_from( genesis. shelley_known_time) . unwrap( ) + 1_000 ,
344
+ 0 ,
345
+ )
346
+ . unwrap( ) ,
347
+ Utc . timestamp_opt(
348
+ i64 :: try_from( genesis. shelley_known_time) . unwrap( ) + 10_000_000 ,
349
+ 0 ,
350
+ )
351
+ . unwrap( ) ,
352
+ ] ;
353
+
354
+ for time in times_to_test {
355
+ if let Some ( slot) = network. time_to_slot ( time) {
356
+ let calculated_time = network. slot_to_time ( slot) ;
357
+
358
+ assert_eq ! (
359
+ calculated_time. timestamp( ) ,
360
+ time. timestamp( ) ,
361
+ "Failed for time: {time}"
362
+ ) ;
363
+ } else {
364
+ panic ! ( "time_to_slot returned None for a valid time: {time}" ) ;
365
+ }
366
+ }
367
+ }
368
+
369
+ #[ test]
370
+ fn test_conway_era_time_to_slot_and_back ( ) {
371
+ let network = Network :: Mainnet ;
372
+ let genesis = network. genesis_values ( ) ;
373
+
374
+ // a very late time, far in the Conway era.
375
+ let conway_time = Utc
376
+ . timestamp_opt (
377
+ i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) + 20_000_000 ,
378
+ 0 ,
379
+ )
380
+ . unwrap ( ) ;
381
+
382
+ let slot = network. time_to_slot ( conway_time) ;
383
+ assert ! (
384
+ slot. is_some( ) ,
385
+ "Failed to calculate slot for Conway era time"
386
+ ) ;
387
+
388
+ let calculated_time = network. slot_to_time ( slot. unwrap ( ) ) ;
389
+
390
+ assert_eq ! (
391
+ calculated_time. timestamp( ) ,
392
+ conway_time. timestamp( ) ,
393
+ "Inconsistency for Conway era time"
394
+ ) ;
395
+ }
217
396
}
0 commit comments