From 0602cee299077bfcae5accfd70f796abb8485447 Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 14:15:01 +0700 Subject: [PATCH 1/6] feat: initial time --- rust/cardano-chain-follower/src/network.rs | 28 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/rust/cardano-chain-follower/src/network.rs b/rust/cardano-chain-follower/src/network.rs index f7a6104e62..0b3654efae 100644 --- a/rust/cardano-chain-follower/src/network.rs +++ b/rust/cardano-chain-follower/src/network.rs @@ -170,9 +170,31 @@ 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) -> Option { - // TODO: Implement this, for now just return None. - None + pub fn time_to_slot(&self, time: DateTime) -> Option { + let genesis = self.genesis_values(); + + let byron_start_time = DateTime::::from_timestamp(genesis.byron_known_time as i64, 0)?; + let byron_slot_length = genesis.byron_slot_length as i64; + + let shelley_start_time = DateTime::::from_timestamp(genesis.shelley_known_time as i64, 0)?; + let shelley_slot_length = genesis.shelley_slot_length as i64; + + // 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() / byron_slot_length; + Some(genesis.byron_known_slot + elapsed_slots as u64) + } else { + // Shelley era + let time_diff = time - shelley_start_time; + let elapsed_slots = time_diff.num_seconds() / shelley_slot_length; + Some(genesis.shelley_known_slot + elapsed_slots as u64) + } } } From 4a4354de274967f63a1783b2967709196206fc52 Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 14:23:49 +0700 Subject: [PATCH 2/6] chore: fmtfix --- rust/cardano-chain-follower/src/network.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rust/cardano-chain-follower/src/network.rs b/rust/cardano-chain-follower/src/network.rs index 0b3654efae..4eae631440 100644 --- a/rust/cardano-chain-follower/src/network.rs +++ b/rust/cardano-chain-follower/src/network.rs @@ -174,10 +174,8 @@ impl Network { let genesis = self.genesis_values(); let byron_start_time = DateTime::::from_timestamp(genesis.byron_known_time as i64, 0)?; - let byron_slot_length = genesis.byron_slot_length as i64; - - let shelley_start_time = DateTime::::from_timestamp(genesis.shelley_known_time as i64, 0)?; - let shelley_slot_length = genesis.shelley_slot_length as i64; + let shelley_start_time = + DateTime::::from_timestamp(genesis.shelley_known_time as i64, 0)?; // determine if the given time is in Byron or Shelley era. if time < byron_start_time { @@ -187,12 +185,12 @@ impl Network { if time < shelley_start_time { // Byron era let time_diff = time - byron_start_time; - let elapsed_slots = time_diff.num_seconds() / byron_slot_length; + let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.byron_slot_length); Some(genesis.byron_known_slot + elapsed_slots as u64) } else { // Shelley era let time_diff = time - shelley_start_time; - let elapsed_slots = time_diff.num_seconds() / shelley_slot_length; + let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.shelley_slot_length); Some(genesis.shelley_known_slot + elapsed_slots as u64) } } From dadfa762c882f641a43d4699ef4dcb90a446b39c Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 14:51:06 +0700 Subject: [PATCH 3/6] feat: unit tests --- rust/cardano-chain-follower/src/network.rs | 93 ++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/rust/cardano-chain-follower/src/network.rs b/rust/cardano-chain-follower/src/network.rs index 4eae631440..f005ab306b 100644 --- a/rust/cardano-chain-follower/src/network.rs +++ b/rust/cardano-chain-follower/src/network.rs @@ -173,9 +173,12 @@ impl Network { pub fn time_to_slot(&self, time: DateTime) -> Option { let genesis = self.genesis_values(); - let byron_start_time = DateTime::::from_timestamp(genesis.byron_known_time as i64, 0)?; - let shelley_start_time = - DateTime::::from_timestamp(genesis.shelley_known_time as i64, 0)?; + let byron_start_time = i64::try_from(genesis.byron_known_time) + .map(|time| DateTime::::from_timestamp(time, 0)) + .ok()??; + let shelley_start_time = i64::try_from(genesis.shelley_known_time) + .map(|time| DateTime::::from_timestamp(time, 0)) + .ok()??; // determine if the given time is in Byron or Shelley era. if time < byron_start_time { @@ -186,12 +189,18 @@ impl Network { // Byron era let time_diff = time - byron_start_time; let elapsed_slots = time_diff.num_seconds() / i64::from(genesis.byron_slot_length); - Some(genesis.byron_known_slot + elapsed_slots as u64) + + 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); - Some(genesis.shelley_known_slot + elapsed_slots as u64) + + u64::try_from(elapsed_slots) + .map(|elapsed_slots| Some(genesis.shelley_known_slot + elapsed_slots)) + .ok()? } } } @@ -211,6 +220,7 @@ mod tests { use std::str::FromStr; use anyhow::Ok; + use chrono::{TimeZone, Utc}; use super::*; @@ -234,4 +244,77 @@ 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(); + + // Expect None for time before the blockchain started + 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; + + // Expect the correct slot for the given time + 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(); + + // Expect the slot to be in the Byron era + 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; + + // Expect the correct slot for the given time + assert_eq!(network.time_to_slot(time), Some(expected_slot)); + } } From 1b7f6534956daa5d7203f7ce12f1004a0b036717 Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 14:52:03 +0700 Subject: [PATCH 4/6] chore: clean comments --- rust/cardano-chain-follower/src/network.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/rust/cardano-chain-follower/src/network.rs b/rust/cardano-chain-follower/src/network.rs index f005ab306b..906cd71ca7 100644 --- a/rust/cardano-chain-follower/src/network.rs +++ b/rust/cardano-chain-follower/src/network.rs @@ -254,7 +254,6 @@ mod tests { .timestamp_opt(i64::try_from(genesis.byron_known_time).unwrap() - 1, 0) .unwrap(); - // Expect None for time before the blockchain started assert_eq!(network.time_to_slot(before_blockchain), None); } @@ -268,11 +267,10 @@ mod tests { .unwrap(); let byron_slot_length = i64::from(genesis.byron_slot_length); - // A time in the middle of the Byron era + // 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; - // Expect the correct slot for the given time assert_eq!(network.time_to_slot(time), Some(expected_slot)); } @@ -286,7 +284,7 @@ mod tests { .unwrap(); let byron_slot_length = i64::from(genesis.byron_slot_length); - // A time just before Shelley era starts + // a time just before Shelley era starts. let time = shelley_start_time - chrono::Duration::seconds(1); let elapsed_slots = (time - Utc @@ -296,7 +294,6 @@ mod tests { / byron_slot_length; let expected_slot = genesis.byron_known_slot + u64::try_from(elapsed_slots).unwrap(); - // Expect the slot to be in the Byron era assert_eq!(network.time_to_slot(time), Some(expected_slot)); } @@ -310,11 +307,10 @@ mod tests { .unwrap(); let shelley_slot_length = i64::from(genesis.shelley_slot_length); - // A time in the middle of the Shelley era + // 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; - // Expect the correct slot for the given time assert_eq!(network.time_to_slot(time), Some(expected_slot)); } } From 8e3fb4cc2b6cb0b16f978cddc19537d90a66beae Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 21:43:23 +0700 Subject: [PATCH 5/6] test: time consistent cases --- rust/cardano-chain-follower/src/network.rs | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/rust/cardano-chain-follower/src/network.rs b/rust/cardano-chain-follower/src/network.rs index 906cd71ca7..ddb0364b6d 100644 --- a/rust/cardano-chain-follower/src/network.rs +++ b/rust/cardano-chain-follower/src/network.rs @@ -313,4 +313,84 @@ mod tests { 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" + ); + } } From 30e9321f9ea8e23a40bf763cbf1534f2cc4499dc Mon Sep 17 00:00:00 2001 From: Apisit Ritreungroj Date: Wed, 27 Nov 2024 22:33:04 +0700 Subject: [PATCH 6/6] release: v0.0.5 --- rust/cardano-chain-follower/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/cardano-chain-follower/Cargo.toml b/rust/cardano-chain-follower/Cargo.toml index 78a1987656..f9171998c4 100644 --- a/rust/cardano-chain-follower/Cargo.toml +++ b/rust/cardano-chain-follower/Cargo.toml @@ -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