sidechain_slots/
lib.rs

1//! This crate provides types and logic for handling Partner Chain slots.
2//!
3//! Partner Chain slots are grouped into epochs of equal length.
4#![cfg_attr(not(feature = "std"), no_std)]
5#![deny(missing_docs)]
6
7#[cfg(feature = "std")]
8pub mod runtime_api_client;
9
10use core::{ops::Rem, time::Duration};
11use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
12use scale_info::TypeInfo;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use sidechain_domain::{ScEpochNumber, ScSlotNumber};
16pub use sp_consensus_slots::{Slot, SlotDuration};
17use sp_core::offchain::Timestamp;
18
19/// Number of slots in each Partner Chain epoch
20///
21/// This value should satisfy the following property:
22/// > `main_chain_epoch_duration` % (`slots_per_epoch` * `slot_duration`)  == 0
23/// that is, Cardano main chain's epoch boundaries should always coincide with
24/// a Partner Chain epoch boundary, or in other words PC epochs should perfectly
25/// cover a Cardano epoch.
26#[derive(Clone, Copy, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub struct SlotsPerEpoch(pub u32);
29
30/// Default number of slots per epoch.
31///
32/// This value is used by [SlotsPerEpoch::read_from_env] when no other value is set in the environment.
33pub fn default_slots_per_epoch() -> u32 {
34	60
35}
36
37impl Default for SlotsPerEpoch {
38	/// Set to 60 to maintain backwards compatibility with existing chains.
39	fn default() -> Self {
40		SlotsPerEpoch(60)
41	}
42}
43
44impl SlotsPerEpoch {
45	/// Reads [SlotsPerEpoch] from the environment variable `SLOTS_PER_EPOCH` with default.
46	#[cfg(all(feature = "std", feature = "serde"))]
47	pub fn read_from_env() -> Result<Self, envy::Error> {
48		#[derive(Serialize, Deserialize)]
49		struct SlotsPerEpochEnvConfig {
50			#[serde(default = "default_slots_per_epoch")]
51			slots_per_epoch: u32,
52		}
53
54		let raw = envy::from_env::<SlotsPerEpochEnvConfig>()?;
55		Ok(Self(raw.slots_per_epoch))
56	}
57
58	/// Returns the epoch number of `slot`
59	pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
60		epoch_number(slot, self.0)
61	}
62
63	/// Returns the epoch number of `slot`
64	pub fn epoch_number_from_sc_slot(&self, slot: ScSlotNumber) -> ScEpochNumber {
65		epoch_number(Slot::from(slot.0), self.0)
66	}
67
68	/// Returns the number of first slot in `epoch`
69	pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
70		first_slot_number(epoch, self.0)
71	}
72
73	/// Returns the number of `slot` within its epoch
74	pub fn slot_number_in_epoch(&self, slot: Slot) -> u32 {
75		slot_number_in_epoch(slot, self.0)
76	}
77}
78
79/// Slot and epoch configuration for a Partner Chain
80#[derive(Clone, Debug, Encode, Decode, TypeInfo)]
81pub struct ScSlotConfig {
82	/// Number of slots per Partner Chain epoch
83	pub slots_per_epoch: SlotsPerEpoch,
84	/// Duration of a single Partner Chain slot
85	pub slot_duration: SlotDuration,
86}
87
88impl ScSlotConfig {
89	#[cfg(feature = "std")]
90	/// Returns epoch duration
91	pub fn epoch_duration(&self) -> Duration {
92		self.slot_duration.as_duration() * self.slots_per_epoch.0
93	}
94
95	/// Returns the epoch number of `slot`
96	pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
97		self.slots_per_epoch.epoch_number(slot)
98	}
99
100	/// Returns the number of first slot of `epoch`
101	pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
102		self.slots_per_epoch.first_slot_number(epoch)
103	}
104
105	/// Returns the slot number that contains `timestamp`
106	pub fn slot_from_timestamp(&self, timestamp: u64) -> Slot {
107		Slot::from_timestamp(timestamp.into(), self.slot_duration)
108	}
109
110	/// Returns the start timestamp of `slot`
111	pub fn slot_start_time(&self, slot: Slot) -> Timestamp {
112		Timestamp::from_unix_millis(self.slot_duration.as_millis() * u64::from(slot))
113	}
114
115	/// Returns the start timestamp of `epoch`
116	pub fn epoch_start_time(&self, epoch: ScEpochNumber) -> Option<Timestamp> {
117		self.first_slot_number(epoch)
118			.timestamp(self.slot_duration)
119			.map(|s| Timestamp::from_unix_millis(s.as_millis()))
120	}
121}
122
123/// Returns the epoch number for `slot` given `slots_per_epoch`
124pub fn epoch_number(slot: Slot, slots_per_epoch: u32) -> ScEpochNumber {
125	ScEpochNumber(*slot / u64::from(slots_per_epoch))
126}
127
128/// Get the first slot number of the epoch `epoch`
129pub fn first_slot_number(epoch: ScEpochNumber, slots_per_epoch: u32) -> Slot {
130	Slot::from(epoch.0 * slots_per_epoch as u64)
131}
132
133/// Returns the number of `slot` within its epoch given `slots_per_epoch`
134pub fn slot_number_in_epoch(slot: Slot, slots_per_epoch: u32) -> u32 {
135	u32::try_from(slot.rem(u64::from(slots_per_epoch)))
136		.expect("slots_per_epoch is u32, thus any modulo reminder of it is also u32")
137}
138
139/// Checks whether `slot` is the last slot of its epoch given `slots_per_epoch`
140pub fn is_last_slot_of_an_epoch(slot: Slot, slots_per_epoch: u32) -> bool {
141	slot_number_in_epoch(slot, slots_per_epoch) == slots_per_epoch - 1
142}
143
144/// Error type returnes by epoch and slot handling functions in this crate
145pub enum Error {
146	/// Indicates that an integer overflow occured during epoch calculation
147	OverflowError,
148}
149
150sp_api::decl_runtime_apis! {
151	/// Runtime API serving slot configuration
152	pub trait SlotApi {
153		/// Returns the current slot configuration
154		fn slot_config() -> ScSlotConfig;
155	}
156}
157
158#[cfg(test)]
159mod tests {
160	use super::*;
161	use proptest::prelude::*;
162
163	prop_compose! {
164		fn arb_slot()(slot_number in 0..u64::MAX) -> Slot {
165			Slot::from(slot_number)
166		}
167	}
168
169	proptest! {
170		#[test]
171		fn slot_number_is_slot_modulo_slots_per_epoch(slot in arb_slot(), slots_per_epoch in 1..u32::MAX) {
172			let expected =u32::try_from(*slot % u64::from(slots_per_epoch)).unwrap();
173			assert_eq!(expected, slot_number_in_epoch(slot, slots_per_epoch))
174		}
175	}
176}