diff --git a/src/perms/builders.rs b/src/perms/builders.rs index 3cc2fa7..5079954 100644 --- a/src/perms/builders.rs +++ b/src/perms/builders.rs @@ -141,7 +141,11 @@ impl Builders { /// Get the index of the builder that is allowed to sign a block for a /// particular timestamp. pub fn index(&self, timestamp: u64) -> u64 { - self.config.calc().calculate_slot(timestamp) % self.builders.len() as u64 + self.config + .calc() + .time_to_slot(timestamp) + .expect("host chain has started") + % self.builders.len() as u64 } /// Get the index of the builder that is allowed to sign a block at the @@ -157,7 +161,10 @@ impl Builders { /// Check the query bounds for the current timestamp. fn check_query_bounds(&self) -> Result<(), BuilderPermissionError> { - let current_slot_time = self.calc().current_timepoint_within_slot(); + let current_slot_time = self + .calc() + .current_point_within_slot() + .expect("host chain has started"); if current_slot_time < self.config.block_query_start() { return Err(BuilderPermissionError::ActionAttemptTooEarly); } diff --git a/src/perms/middleware.rs b/src/perms/middleware.rs index 0cb899d..279090c 100644 --- a/src/perms/middleware.rs +++ b/src/perms/middleware.rs @@ -162,8 +162,11 @@ where permissioned_builder = this.builders.current_builder().sub(), requesting_builder = tracing::field::Empty, current_slot = this.builders.calc().current_slot(), - current_timepoint_within_slot = - this.builders.calc().current_timepoint_within_slot(), + current_timepoint_within_slot = this + .builders + .calc() + .current_point_within_slot() + .expect("host chain has started"), permissioning_error = tracing::field::Empty, ); diff --git a/src/utils/calc.rs b/src/utils/calc.rs index 2cb280a..cef68ac 100644 --- a/src/utils/calc.rs +++ b/src/utils/calc.rs @@ -73,34 +73,67 @@ impl SlotCalculator { } /// Calculates the slot for a given timestamp. - /// This only works for timestamps that are GEQ to the chain's start_timestamp. - pub const fn calculate_slot(&self, timestamp: u64) -> u64 { - let elapsed = timestamp - self.start_timestamp; + /// + /// Returns `None` if the timestamp is before the chain's start timestamp. + pub const fn time_to_slot(&self, timestamp: u64) -> Option { + let Some(elapsed) = timestamp.checked_sub(self.start_timestamp) else { + return None; + }; let slots = elapsed.div_ceil(self.slot_duration); - slots + self.slot_offset + Some(slots + self.slot_offset) + } + + /// Calculates how many seconds a given timestamp is into its containing + /// slot. + /// + /// Returns `None` if the timestamp is before the chain's start. + pub const fn point_within_slot(&self, timestamp: u64) -> Option { + let Some(offset) = timestamp.checked_sub(self.slot_utc_offset()) else { + return None; + }; + Some(offset % self.slot_duration) } - /// Calculates how many seconds into the block window for a given timestamp. - pub const fn calculate_timepoint_within_slot(&self, timestamp: u64) -> u64 { - (timestamp - self.slot_utc_offset()) % self.slot_duration + /// Calculates how many seconds a given timestamp is into a given slot. + /// Returns `None` if the timestamp is not within the slot. + pub const fn checked_point_within_slot(&self, slot: u64, timestamp: u64) -> Option { + let calculated = self.time_to_slot(timestamp); + if calculated.is_none() || calculated.unwrap() != slot { + return None; + } + self.point_within_slot(timestamp) } /// Calculates the start and end timestamps for a given slot - pub const fn calculate_slot_window(&self, slot_number: u64) -> (u64, u64) { + pub const fn slot_window(&self, slot_number: u64) -> std::ops::Range { let end_of_slot = ((slot_number - self.slot_offset) * self.slot_duration) + self.start_timestamp; let start_of_slot = end_of_slot - self.slot_duration; - (start_of_slot, end_of_slot) + start_of_slot..end_of_slot + } + + /// Calculates the slot window for the slot that corresponds to the given + /// timestamp. + /// + /// Returns `None` if the timestamp is before the chain's start timestamp. + pub const fn slot_window_for_timestamp(&self, timestamp: u64) -> Option> { + let Some(slot) = self.time_to_slot(timestamp) else { + return None; + }; + Some(self.slot_window(slot)) } /// The current slot number. - pub fn current_slot(&self) -> u64 { - self.calculate_slot(chrono::Utc::now().timestamp() as u64) + /// + /// Returns `None` if the current time is before the chain's start + /// timestamp. + pub fn current_slot(&self) -> Option { + self.time_to_slot(chrono::Utc::now().timestamp() as u64) } - /// The current number of seconds into the block window. - pub fn current_timepoint_within_slot(&self) -> u64 { - self.calculate_timepoint_within_slot(chrono::Utc::now().timestamp() as u64) + /// The current number of seconds into the slot. + pub fn current_point_within_slot(&self) -> Option { + self.point_within_slot(chrono::Utc::now().timestamp() as u64) } /// The timestamp of the first PoS block in the chain. @@ -130,94 +163,124 @@ mod tests { #[test] fn test_basic_slot_calculations() { - let calculator = SlotCalculator::new(0, 0, 12); - assert_eq!(calculator.calculate_slot(0), 0); + let calculator = SlotCalculator::new(12, 0, 12); + assert_eq!(calculator.time_to_slot(0), None); - assert_eq!(calculator.calculate_slot(1), 1); - assert_eq!(calculator.calculate_slot(11), 1); - assert_eq!(calculator.calculate_slot(12), 1); + assert_eq!(calculator.time_to_slot(1), Some(13)); + assert_eq!(calculator.time_to_slot(11), Some(13)); + assert_eq!(calculator.time_to_slot(12), Some(13)); - assert_eq!(calculator.calculate_slot(13), 2); - assert_eq!(calculator.calculate_slot(23), 2); - assert_eq!(calculator.calculate_slot(24), 2); + assert_eq!(calculator.time_to_slot(13), Some(14)); + assert_eq!(calculator.time_to_slot(23), Some(14)); + assert_eq!(calculator.time_to_slot(24), Some(14)); - assert_eq!(calculator.calculate_slot(25), 3); - assert_eq!(calculator.calculate_slot(35), 3); - assert_eq!(calculator.calculate_slot(36), 3); + assert_eq!(calculator.time_to_slot(25), Some(15)); + assert_eq!(calculator.time_to_slot(35), Some(15)); + assert_eq!(calculator.time_to_slot(36), Some(15)); } #[test] fn test_holesky_slot_calculations() { let calculator = SlotCalculator::holesky(); + + // Just before the start timestamp + let just_before = calculator.start_timestamp - 1; + assert_eq!(calculator.time_to_slot(just_before), None); + + // Timestamp 17 + assert_eq!(calculator.time_to_slot(17), None); + // block 1 == slot 2 == timestamp 1695902424 // timestamp 1695902424 == slot 2 - assert_eq!(calculator.calculate_slot(1695902424), 2); + assert_eq!(calculator.time_to_slot(1695902424), Some(2)); // the next second, timestamp 1695902425 == slot 3 - assert_eq!(calculator.calculate_slot(1695902425), 3); + assert_eq!(calculator.time_to_slot(1695902425), Some(3)); // block 3557085 == slot 3919127 == timestamp 1742931924 // timestamp 1742931924 == slot 3919127 - assert_eq!(calculator.calculate_slot(1742931924), 3919127); + assert_eq!(calculator.time_to_slot(1742931924), Some(3919127)); // the next second, timestamp 1742931925 == slot 3919128 - assert_eq!(calculator.calculate_slot(1742931925), 3919128); + assert_eq!(calculator.time_to_slot(1742931925), Some(3919128)); } #[test] fn test_holesky_slot_timepoint_calculations() { let calculator = SlotCalculator::holesky(); // calculate timepoint in slot - assert_eq!(calculator.calculate_timepoint_within_slot(1695902424), 0); - assert_eq!(calculator.calculate_timepoint_within_slot(1695902425), 1); - assert_eq!(calculator.calculate_timepoint_within_slot(1695902435), 11); - assert_eq!(calculator.calculate_timepoint_within_slot(1695902436), 0); + assert_eq!(calculator.point_within_slot(1695902424), Some(0)); + assert_eq!(calculator.point_within_slot(1695902425), Some(1)); + assert_eq!(calculator.point_within_slot(1695902435), Some(11)); + assert_eq!(calculator.point_within_slot(1695902436), Some(0)); } #[test] fn test_holesky_slot_window() { let calculator = SlotCalculator::holesky(); // calculate slot window - assert_eq!( - calculator.calculate_slot_window(2), - (1695902412, 1695902424) - ); - assert_eq!( - calculator.calculate_slot_window(3), - (1695902424, 1695902436) - ); + assert_eq!(calculator.slot_window(2), 1695902412..1695902424); + assert_eq!(calculator.slot_window(3), 1695902424..1695902436); } #[test] fn test_mainnet_slot_calculations() { let calculator = SlotCalculator::mainnet(); - assert_eq!(calculator.calculate_slot(1663224179), 4700013); - assert_eq!(calculator.calculate_slot(1663224180), 4700014); - assert_eq!(calculator.calculate_slot(1738863035), 11003251); - assert_eq!(calculator.calculate_slot(1738866239), 11003518); - assert_eq!(calculator.calculate_slot(1738866227), 11003517); + // Just before the start timestamp + let just_before = calculator.start_timestamp - 1; + assert_eq!(calculator.time_to_slot(just_before), None); + + // Timestamp 17 + assert_eq!(calculator.time_to_slot(17), None); + + assert_eq!(calculator.time_to_slot(1663224179), Some(4700013)); + assert_eq!(calculator.time_to_slot(1663224180), Some(4700014)); + + assert_eq!(calculator.time_to_slot(1738863035), Some(11003251)); + assert_eq!(calculator.time_to_slot(1738866239), Some(11003518)); + assert_eq!(calculator.time_to_slot(1738866227), Some(11003517)); } #[test] fn test_mainnet_slot_timepoint_calculations() { let calculator = SlotCalculator::mainnet(); // calculate timepoint in slot - assert_eq!(calculator.calculate_timepoint_within_slot(1663224179), 0); - assert_eq!(calculator.calculate_timepoint_within_slot(1663224180), 1); - assert_eq!(calculator.calculate_timepoint_within_slot(1663224190), 11); - assert_eq!(calculator.calculate_timepoint_within_slot(1663224191), 0); + assert_eq!(calculator.point_within_slot(1663224179), Some(0)); + assert_eq!(calculator.point_within_slot(1663224180), Some(1)); + assert_eq!(calculator.point_within_slot(1663224190), Some(11)); + assert_eq!(calculator.point_within_slot(1663224191), Some(0)); } #[test] fn test_ethereum_slot_window() { let calculator = SlotCalculator::mainnet(); // calculate slot window - assert_eq!( - calculator.calculate_slot_window(4700013), - (1663224167, 1663224179) - ); - assert_eq!( - calculator.calculate_slot_window(4700014), - (1663224179, 1663224191) - ); + assert_eq!(calculator.slot_window(4700013), (1663224167..1663224179)); + assert_eq!(calculator.slot_window(4700014), (1663224179..1663224191)); + } + + #[test] + fn slot_boundaries() { + let calculator = SlotCalculator::new(12, 0, 12); + + // Check the boundaries of slots + assert_eq!(calculator.time_to_slot(0), None); + assert_eq!(calculator.time_to_slot(11), None); + assert_eq!(calculator.time_to_slot(12), Some(0)); + assert_eq!(calculator.time_to_slot(13), Some(0)); + assert_eq!(calculator.time_to_slot(23), Some(1)); + assert_eq!(calculator.time_to_slot(24), Some(1)); + assert_eq!(calculator.time_to_slot(25), Some(1)); + assert_eq!(calculator.time_to_slot(35), Some(2)); + + let calculator = SlotCalculator::new(12, 1, 12); + + assert_eq!(calculator.time_to_slot(0), None); + assert_eq!(calculator.time_to_slot(11), None); + assert_eq!(calculator.time_to_slot(12), Some(1)); + assert_eq!(calculator.time_to_slot(13), Some(1)); + assert_eq!(calculator.time_to_slot(23), Some(1)); + assert_eq!(calculator.time_to_slot(24), Some(2)); + assert_eq!(calculator.time_to_slot(25), Some(2)); + assert_eq!(calculator.time_to_slot(35), Some(3)); } }