mithril_common/entities/
epoch.rs

1use std::fmt::{Display, Formatter};
2use std::num::TryFromIntError;
3use std::ops::{Deref, DerefMut};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::entities::arithmetic_operation_wrapper::{
9    impl_add_to_wrapper, impl_partial_eq_to_wrapper, impl_sub_to_wrapper,
10};
11
12/// Epoch represents a Cardano epoch
13#[derive(
14    Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize, Hash, Eq, PartialOrd, Ord,
15)]
16pub struct Epoch(pub u64);
17
18impl Epoch {
19    /// The epoch offset used for signers stake distribution and verification keys retrieval.
20    pub const SIGNER_RETRIEVAL_OFFSET: i64 = -1;
21
22    /// The epoch offset used to retrieve the signers stake distribution and verification keys that's
23    /// currently being signed so it can be used in the next epoch.
24    pub const NEXT_SIGNER_RETRIEVAL_OFFSET: u64 = 0;
25
26    /// The epoch offset used for signers stake distribution and verification keys recording.
27    pub const SIGNER_RECORDING_OFFSET: u64 = 1;
28
29    /// The epoch offset used for aggregator epoch settings recording.
30    pub const EPOCH_SETTINGS_RECORDING_OFFSET: u64 = 2;
31
32    /// The epoch offset used to retrieve, given the epoch at which a signer registered, the epoch
33    /// at which the signer can send single signatures.
34    pub const SIGNER_SIGNING_OFFSET: u64 = 2;
35
36    /// The epoch offset used to retrieve the epoch at the end of which the snapshot of the stake distribution
37    /// was taken by the Cardano node and labeled as 'Mark' snapshot during the following epoch.
38    pub const CARDANO_STAKE_DISTRIBUTION_SNAPSHOT_OFFSET: u64 = 2;
39
40    /// The epoch offset used to retrieve the epoch at which a signer has registered to the leader aggregator.
41    pub const SIGNER_LEADER_SYNCHRONIZATION_OFFSET: u64 = 0;
42
43    /// Computes a new Epoch by applying an epoch offset.
44    ///
45    /// Will fail if the computed epoch is negative.
46    pub fn offset_by(&self, epoch_offset: i64) -> Result<Self, EpochError> {
47        let epoch_new = self.0 as i64 + epoch_offset;
48        if epoch_new < 0 {
49            return Err(EpochError::EpochOffset(self.0, epoch_offset));
50        }
51        Ok(Epoch(epoch_new as u64))
52    }
53
54    /// Apply the [retrieval offset][Self::SIGNER_RETRIEVAL_OFFSET] to this epoch
55    pub fn offset_to_signer_retrieval_epoch(&self) -> Result<Self, EpochError> {
56        self.offset_by(Self::SIGNER_RETRIEVAL_OFFSET)
57    }
58
59    /// Apply the [next signer retrieval offset][Self::NEXT_SIGNER_RETRIEVAL_OFFSET] to this epoch
60    pub fn offset_to_next_signer_retrieval_epoch(&self) -> Self {
61        *self + Self::NEXT_SIGNER_RETRIEVAL_OFFSET
62    }
63
64    /// Apply the [recording offset][Self::SIGNER_RECORDING_OFFSET] to this epoch
65    pub fn offset_to_recording_epoch(&self) -> Self {
66        *self + Self::SIGNER_RECORDING_OFFSET
67    }
68
69    /// Apply the [epoch settings recording offset][Self::EPOCH_SETTINGS_RECORDING_OFFSET] to this epoch
70    pub fn offset_to_epoch_settings_recording_epoch(&self) -> Self {
71        *self + Self::EPOCH_SETTINGS_RECORDING_OFFSET
72    }
73
74    /// Apply the [signer signing offset][Self::SIGNER_SIGNING_OFFSET] to this epoch
75    pub fn offset_to_signer_signing_offset(&self) -> Self {
76        *self + Self::SIGNER_SIGNING_OFFSET
77    }
78
79    /// Apply the [cardano stake distribution snapshot epoch offset][Self::CARDANO_STAKE_DISTRIBUTION_SNAPSHOT_OFFSET] to this epoch
80    pub fn offset_to_cardano_stake_distribution_snapshot_epoch(&self) -> Self {
81        *self + Self::CARDANO_STAKE_DISTRIBUTION_SNAPSHOT_OFFSET
82    }
83
84    /// Apply the [recording offset][Self::SIGNER_LEADER_SYNCHRONIZATION_OFFSET] to this epoch
85    pub fn offset_to_leader_synchronization_epoch(&self) -> Self {
86        *self + Self::SIGNER_LEADER_SYNCHRONIZATION_OFFSET
87    }
88
89    /// Computes the next Epoch
90    pub fn next(&self) -> Self {
91        *self + 1
92    }
93
94    /// Computes the previous Epoch
95    pub fn previous(&self) -> Result<Self, EpochError> {
96        self.offset_by(-1)
97    }
98
99    /// Check if there is a gap with another Epoch.
100    pub fn has_gap_with(&self, other: &Epoch) -> bool {
101        self.0.abs_diff(other.0) > 1
102    }
103}
104
105impl Deref for Epoch {
106    type Target = u64;
107
108    fn deref(&self) -> &Self::Target {
109        &self.0
110    }
111}
112
113impl DerefMut for Epoch {
114    fn deref_mut(&mut self) -> &mut Self::Target {
115        &mut self.0
116    }
117}
118
119impl_add_to_wrapper!(Epoch, u64);
120impl_sub_to_wrapper!(Epoch, u64);
121impl_partial_eq_to_wrapper!(Epoch, u64);
122
123impl Display for Epoch {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        write!(f, "{}", self.0)
126    }
127}
128
129impl TryInto<i64> for Epoch {
130    type Error = TryFromIntError;
131
132    fn try_into(self) -> Result<i64, Self::Error> {
133        self.0.try_into()
134    }
135}
136
137impl TryInto<i64> for &Epoch {
138    type Error = TryFromIntError;
139
140    fn try_into(self) -> Result<i64, Self::Error> {
141        self.0.try_into()
142    }
143}
144
145impl From<Epoch> for f64 {
146    fn from(value: Epoch) -> f64 {
147        value.0 as f64
148    }
149}
150
151/// EpochError is an error triggered by an [Epoch]
152#[derive(Error, Debug)]
153pub enum EpochError {
154    /// Error raised when the [computation of an epoch using an offset][Epoch::offset_by] fails.
155    #[error("epoch offset error")]
156    EpochOffset(u64, i64),
157}
158
159#[cfg(test)]
160mod tests {
161    use crate::entities::arithmetic_operation_wrapper::tests::test_op_assign;
162
163    use super::*;
164
165    #[test]
166    fn test_display() {
167        assert_eq!(format!("{}", Epoch(72)), "72");
168        assert_eq!(format!("{}", &Epoch(13224)), "13224");
169    }
170
171    #[test]
172    fn test_serialize() {
173        assert_eq!(serde_json::to_string(&Epoch(72)).unwrap(), "72");
174    }
175
176    #[test]
177    fn test_deserialize() {
178        let block_number: Epoch = serde_json::from_str("13224").unwrap();
179        assert_eq!(block_number, Epoch(13224));
180    }
181
182    #[test]
183    #[allow(clippy::op_ref)]
184    fn test_add() {
185        assert_eq!(Epoch(4), Epoch(1) + Epoch(3));
186        assert_eq!(Epoch(4), Epoch(1) + 3_u64);
187        assert_eq!(Epoch(4), Epoch(1) + &3_u64);
188
189        assert_eq!(Epoch(4), 3_u64 + Epoch(1));
190        assert_eq!(Epoch(4), 3_u64 + &Epoch(1));
191        assert_eq!(Epoch(4), &3_u64 + Epoch(1));
192        assert_eq!(Epoch(4), &3_u64 + &Epoch(1));
193
194        test_op_assign!(Epoch(1), +=, Epoch(3) => Epoch(4));
195        test_op_assign!(Epoch(1), +=, 3_u64 => Epoch(4));
196        test_op_assign!(Epoch(1), +=, &3_u64 => Epoch(4));
197
198        test_op_assign!(1_u64, +=, Epoch(3) => 4_u64);
199        test_op_assign!(1_u64, +=, &Epoch(3) => 4_u64);
200    }
201
202    #[test]
203    #[allow(clippy::op_ref)]
204    fn test_sub() {
205        assert_eq!(Epoch(8), Epoch(14) - Epoch(6));
206        assert_eq!(Epoch(8), Epoch(14) - 6_u64);
207        assert_eq!(Epoch(8), Epoch(14) - &6_u64);
208
209        assert_eq!(Epoch(8), 6_u64 - Epoch(14));
210        assert_eq!(Epoch(8), 6_u64 - &Epoch(14));
211        assert_eq!(Epoch(8), &6_u64 - Epoch(14));
212        assert_eq!(Epoch(8), &6_u64 - &Epoch(14));
213
214        test_op_assign!(Epoch(14), -=, Epoch(6) => Epoch(8));
215        test_op_assign!(Epoch(14), -=, 6_u64 => Epoch(8));
216        test_op_assign!(Epoch(14), -=, &6_u64 => Epoch(8));
217
218        test_op_assign!(14_u64, -=, Epoch(6) => 8_u64);
219        test_op_assign!(14_u64, -=, &Epoch(6) => 8_u64);
220    }
221
222    #[test]
223    fn saturating_sub() {
224        assert_eq!(Epoch(0), Epoch(1) - Epoch(5));
225        assert_eq!(Epoch(0), Epoch(1) - 5_u64);
226    }
227
228    #[test]
229    fn test_previous() {
230        assert_eq!(Epoch(2), Epoch(3).previous().unwrap());
231        assert!(Epoch(0).previous().is_err());
232    }
233
234    #[test]
235    fn test_next() {
236        assert_eq!(Epoch(4), Epoch(3).next());
237    }
238
239    #[test]
240    fn test_eq() {
241        assert_eq!(Epoch(1), Epoch(1));
242        assert_eq!(Epoch(2), &Epoch(2));
243        assert_eq!(&Epoch(3), Epoch(3));
244        assert_eq!(&Epoch(4), &Epoch(4));
245
246        assert_eq!(Epoch(5), 5);
247        assert_eq!(Epoch(6), &6);
248        assert_eq!(&Epoch(7), 7);
249        assert_eq!(&Epoch(8), &8);
250
251        assert_eq!(9, Epoch(9));
252        assert_eq!(10, &Epoch(10));
253        assert_eq!(&11, Epoch(11));
254        assert_eq!(&12, &Epoch(12));
255    }
256
257    #[test]
258    fn test_has_gap_ok() {
259        assert!(Epoch(3).has_gap_with(&Epoch(5)));
260        assert!(!Epoch(3).has_gap_with(&Epoch(4)));
261        assert!(!Epoch(3).has_gap_with(&Epoch(3)));
262        assert!(!Epoch(3).has_gap_with(&Epoch(2)));
263        assert!(Epoch(3).has_gap_with(&Epoch(0)));
264    }
265}