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;
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	/// Returns the epoch number of `slot`
90	pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
91		self.slots_per_epoch.epoch_number(slot)
92	}
93
94	/// Returns the number of first slot of `epoch`
95	pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
96		self.slots_per_epoch.first_slot_number(epoch)
97	}
98
99	/// Returns the slot number that contains `timestamp`
100	pub fn slot_from_timestamp(&self, timestamp: u64) -> Slot {
101		Slot::from_timestamp(timestamp.into(), self.slot_duration)
102	}
103
104	/// Returns the start timestamp of `epoch`
105	pub fn epoch_start_time(&self, epoch: ScEpochNumber) -> Option<Timestamp> {
106		self.first_slot_number(epoch)
107			.timestamp(self.slot_duration)
108			.map(|s| Timestamp::from_unix_millis(s.as_millis()))
109	}
110}
111
112/// Returns the epoch number for `slot` given `slots_per_epoch`
113pub fn epoch_number(slot: Slot, slots_per_epoch: u32) -> ScEpochNumber {
114	ScEpochNumber(*slot / u64::from(slots_per_epoch))
115}
116
117/// Get the first slot number of the epoch `epoch`
118pub fn first_slot_number(epoch: ScEpochNumber, slots_per_epoch: u32) -> Slot {
119	Slot::from(epoch.0 * slots_per_epoch as u64)
120}
121
122/// Returns the number of `slot` within its epoch given `slots_per_epoch`
123pub fn slot_number_in_epoch(slot: Slot, slots_per_epoch: u32) -> u32 {
124	u32::try_from(slot.rem(u64::from(slots_per_epoch)))
125		.expect("slots_per_epoch is u32, thus any modulo reminder of it is also u32")
126}
127
128/// Checks whether `slot` is the last slot of its epoch given `slots_per_epoch`
129pub fn is_last_slot_of_an_epoch(slot: Slot, slots_per_epoch: u32) -> bool {
130	slot_number_in_epoch(slot, slots_per_epoch) == slots_per_epoch - 1
131}
132
133/// Error type returnes by epoch and slot handling functions in this crate
134pub enum Error {
135	/// Indicates that an integer overflow occured during epoch calculation
136	OverflowError,
137}
138
139sp_api::decl_runtime_apis! {
140	/// Runtime API serving slot configuration
141	pub trait SlotApi {
142		/// Returns the current slot configuration
143		fn slot_config() -> ScSlotConfig;
144	}
145}
146
147#[cfg(test)]
148mod tests {
149	use super::*;
150	use proptest::prelude::*;
151
152	prop_compose! {
153		fn arb_slot()(slot_number in 0..u64::MAX) -> Slot {
154			Slot::from(slot_number)
155		}
156	}
157
158	proptest! {
159		#[test]
160		fn slot_number_is_slot_modulo_slots_per_epoch(slot in arb_slot(), slots_per_epoch in 1..u32::MAX) {
161			let expected =u32::try_from(*slot % u64::from(slots_per_epoch)).unwrap();
162			assert_eq!(expected, slot_number_in_epoch(slot, slots_per_epoch))
163		}
164	}
165}