Skip to content

Commit

Permalink
feat(rust/cardano-chain-follower): Implement time_to_slot function …
Browse files Browse the repository at this point in the history
…for `Network` (#91)

* feat: initial time

* chore: fmtfix

* feat: unit tests

* chore: clean comments

* test: time consistent cases

* release: v0.0.5
  • Loading branch information
apskhem authored Nov 28, 2024
1 parent 3910b60 commit dbe3d4c
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 4 deletions.
2 changes: 1 addition & 1 deletion rust/cardano-chain-follower/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cardano-chain-follower"
version = "0.0.4"
version = "0.0.5"
edition.workspace = true
authors.workspace = true
homepage.workspace = true
Expand Down
185 changes: 182 additions & 3 deletions rust/cardano-chain-follower/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,38 @@ impl Network {
///
/// The Slot does not have to be a valid slot present in the blockchain.
#[must_use]
pub fn time_to_slot(&self, _time: DateTime<Utc>) -> Option<u64> {
// TODO: Implement this, for now just return None.
None
pub fn time_to_slot(&self, time: DateTime<Utc>) -> Option<u64> {
let genesis = self.genesis_values();

let byron_start_time = i64::try_from(genesis.byron_known_time)
.map(|time| DateTime::<Utc>::from_timestamp(time, 0))
.ok()??;
let shelley_start_time = i64::try_from(genesis.shelley_known_time)
.map(|time| DateTime::<Utc>::from_timestamp(time, 0))
.ok()??;

// determine if the given time is in Byron or Shelley era.
if time < byron_start_time {
return None;
}

if time < shelley_start_time {
// Byron era
let time_diff = time - byron_start_time;
let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.byron_slot_length);

u64::try_from(elapsed_slots)
.map(|elapsed_slots| Some(genesis.byron_known_slot + elapsed_slots))
.ok()?
} else {
// Shelley era
let time_diff = time - shelley_start_time;
let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.shelley_slot_length);

u64::try_from(elapsed_slots)
.map(|elapsed_slots| Some(genesis.shelley_known_slot + elapsed_slots))
.ok()?
}
}
}

Expand All @@ -191,6 +220,7 @@ mod tests {
use std::str::FromStr;

use anyhow::Ok;
use chrono::{TimeZone, Utc};

use super::*;

Expand All @@ -214,4 +244,153 @@ mod tests {

Ok(())
}

#[test]
fn test_time_to_slot_before_blockchain() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

let before_blockchain = Utc
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap() - 1, 0)
.unwrap();

assert_eq!(network.time_to_slot(before_blockchain), None);
}

#[test]
fn test_time_to_slot_byron_era() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

let byron_start_time = Utc
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap(), 0)
.unwrap();
let byron_slot_length = i64::from(genesis.byron_slot_length);

// a time in the middle of the Byron era.
let time = byron_start_time + chrono::Duration::seconds(byron_slot_length * 100);
let expected_slot = genesis.byron_known_slot + 100;

assert_eq!(network.time_to_slot(time), Some(expected_slot));
}

#[test]
fn test_time_to_slot_transition_to_shelley() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

let shelley_start_time = Utc
.timestamp_opt(i64::try_from(genesis.shelley_known_time).unwrap(), 0)
.unwrap();
let byron_slot_length = i64::from(genesis.byron_slot_length);

// a time just before Shelley era starts.
let time = shelley_start_time - chrono::Duration::seconds(1);
let elapsed_slots = (time
- Utc
.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap(), 0)
.unwrap())
.num_seconds()
/ byron_slot_length;
let expected_slot = genesis.byron_known_slot + u64::try_from(elapsed_slots).unwrap();

assert_eq!(network.time_to_slot(time), Some(expected_slot));
}

#[test]
fn test_time_to_slot_shelley_era() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

let shelley_start_time = Utc
.timestamp_opt(i64::try_from(genesis.shelley_known_time).unwrap(), 0)
.unwrap();
let shelley_slot_length = i64::from(genesis.shelley_slot_length);

// a time in the middle of the Shelley era.
let time = shelley_start_time + chrono::Duration::seconds(shelley_slot_length * 200);
let expected_slot = genesis.shelley_known_slot + 200;

assert_eq!(network.time_to_slot(time), Some(expected_slot));
}

#[test]
fn test_slot_to_time_to_slot_consistency() {
let network = Network::Mainnet;

// a few arbitrary slots in different ranges.
let slots_to_test = vec![0, 10_000, 1_000_000, 50_000_000];

for slot in slots_to_test {
let time = network.slot_to_time(slot);
let calculated_slot = network.time_to_slot(time);

assert_eq!(calculated_slot, Some(slot), "Failed for slot: {slot}");
}
}

#[test]
#[allow(clippy::panic)]
fn test_time_to_slot_to_time_consistency() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

// Byron, Shelley, and Conway.
let times_to_test = vec![
Utc.timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap() + 100, 0)
.unwrap(),
Utc.timestamp_opt(
i64::try_from(genesis.shelley_known_time).unwrap() + 1_000,
0,
)
.unwrap(),
Utc.timestamp_opt(
i64::try_from(genesis.shelley_known_time).unwrap() + 10_000_000,
0,
)
.unwrap(),
];

for time in times_to_test {
if let Some(slot) = network.time_to_slot(time) {
let calculated_time = network.slot_to_time(slot);

assert_eq!(
calculated_time.timestamp(),
time.timestamp(),
"Failed for time: {time}"
);
} else {
panic!("time_to_slot returned None for a valid time: {time}");
}
}
}

#[test]
fn test_conway_era_time_to_slot_and_back() {
let network = Network::Mainnet;
let genesis = network.genesis_values();

// a very late time, far in the Conway era.
let conway_time = Utc
.timestamp_opt(
i64::try_from(genesis.shelley_known_time).unwrap() + 20_000_000,
0,
)
.unwrap();

let slot = network.time_to_slot(conway_time);
assert!(
slot.is_some(),
"Failed to calculate slot for Conway era time"
);

let calculated_time = network.slot_to_time(slot.unwrap());

assert_eq!(
calculated_time.timestamp(),
conway_time.timestamp(),
"Inconsistency for Conway era time"
);
}
}

0 comments on commit dbe3d4c

Please sign in to comment.