tempo_commonware_node/epoch/
mod.rs

1//! Epoch logic used by tempo.
2//!
3//! All logic is written with the assumption that there are at least 3 heights
4//! per epoch. Having less heights per epoch will not immediately break the
5//! logic, but it might lead to strange behavior and is not supported.
6//!
7//! Note that either way, 3 blocks per epoch is a highly unreasonable number.
8
9pub(crate) mod manager;
10mod scheme_provider;
11
12use commonware_consensus::types::Epoch;
13pub(crate) use manager::ingress::{Enter, Exit};
14pub(crate) use scheme_provider::SchemeProvider;
15
16/// The relative position of in an epoch.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub(crate) enum RelativePosition {
19    FirstHalf,
20    Middle,
21    SecondHalf,
22}
23
24/// Returns the relative position of `height` in an epoch given `epoch_length`.
25///
26/// This function is written under the assumption that a height `h` belongs to
27/// epoch `E` if `(E*epoch_length) <= h <= (E+1)*epoch_length-1`. For example,
28/// for `epoch_length == 1000`, epoch `E=0` includes blocks 0 to 999, epoch
29/// `E=1` includes 1000 to 1999, and so on.
30///
31/// For epoch length 1000, we have the following cases:
32///
33/// 1. heights 0 to 499, 1000 to 1499, etc: first half.
34/// 2. heights 500, 1500, etc: middle.
35/// 3. heights 501 to 999, 1501 to 1999, etc: second half.
36///
37/// # Panics
38///
39/// Panics if `epoch_length = 0`.
40pub(crate) fn relative_position(height: u64, epoch_length: u64) -> RelativePosition {
41    let mid_point = epoch_length / 2;
42
43    let height_finite_field = height.rem_euclid(epoch_length);
44
45    match height_finite_field.cmp(&mid_point) {
46        std::cmp::Ordering::Less => RelativePosition::FirstHalf,
47        std::cmp::Ordering::Equal => RelativePosition::Middle,
48        std::cmp::Ordering::Greater => RelativePosition::SecondHalf,
49    }
50}
51
52/// Returns `Some(epoch)` if `height` is the last block in an epoch of `epoch_length`.
53///
54/// # Panics
55///
56/// Panics if `epoch_length = 0` and `height = 0`.
57pub(crate) fn is_first_block_in_epoch(epoch_length: u64, height: u64) -> Option<Epoch> {
58    // NOTE: `commonware_consensus::utils::epoch` pancis on epoch_length = 0,
59    // but `u64::is_multiple_of = true` only if both values are 0.
60    height
61        .is_multiple_of(epoch_length)
62        .then(|| commonware_consensus::utils::epoch(epoch_length, height))
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::epoch::is_first_block_in_epoch;
68
69    use super::{RelativePosition, relative_position};
70
71    #[track_caller]
72    fn assert_relative_position(expected: RelativePosition, height: u64, epoch_length: u64) {
73        assert_eq!(expected, relative_position(height, epoch_length),);
74    }
75
76    #[test]
77    fn height_falls_into_correct_part_of_epoch() {
78        use RelativePosition::*;
79
80        assert_relative_position(FirstHalf, 0, 100);
81        assert_relative_position(FirstHalf, 1, 100);
82        assert_relative_position(Middle, 50, 100);
83        assert_relative_position(SecondHalf, 51, 100);
84        assert_relative_position(SecondHalf, 99, 100);
85
86        assert_relative_position(FirstHalf, 100, 100);
87        assert_relative_position(FirstHalf, 101, 100);
88        assert_relative_position(Middle, 150, 100);
89        assert_relative_position(SecondHalf, 151, 100);
90        assert_relative_position(SecondHalf, 199, 100);
91
92        assert_relative_position(FirstHalf, 200, 100);
93
94        assert_relative_position(FirstHalf, 0, 99);
95        assert_relative_position(FirstHalf, 1, 99);
96        assert_relative_position(Middle, 49, 99);
97        assert_relative_position(SecondHalf, 50, 99);
98        assert_relative_position(SecondHalf, 51, 99);
99        assert_relative_position(SecondHalf, 98, 99);
100
101        assert_relative_position(FirstHalf, 99, 99);
102        assert_relative_position(FirstHalf, 100, 99);
103        assert_relative_position(Middle, 148, 99);
104        assert_relative_position(SecondHalf, 149, 99);
105        assert_relative_position(SecondHalf, 197, 99);
106
107        assert_relative_position(FirstHalf, 198, 99);
108
109        assert_relative_position(FirstHalf, 9, 199);
110        assert_relative_position(FirstHalf, 1, 199);
111        assert_relative_position(Middle, 99, 199);
112        assert_relative_position(SecondHalf, 100, 199);
113        assert_relative_position(SecondHalf, 101, 199);
114        assert_relative_position(SecondHalf, 198, 199);
115
116        assert_relative_position(FirstHalf, 199, 199);
117    }
118
119    #[should_panic]
120    #[test]
121    fn is_first_block_in_epoch_panics_on_epoch_length_0_height_0() {
122        is_first_block_in_epoch(0, 0);
123    }
124
125    #[test]
126    fn is_first_block_in_epoch_identifies_first_block() {
127        assert_eq!(is_first_block_in_epoch(10, 0), Some(0));
128        assert_eq!(is_first_block_in_epoch(10, 10), Some(1));
129        assert_eq!(is_first_block_in_epoch(10, 20), Some(2));
130        assert_eq!(is_first_block_in_epoch(5, 215), Some(43));
131    }
132
133    #[test]
134    fn is_first_block_in_epoch_returns_none_when_not_first_block() {
135        assert_eq!(is_first_block_in_epoch(10, 1), None);
136        assert_eq!(is_first_block_in_epoch(10, 9), None);
137        assert_eq!(is_first_block_in_epoch(10, 18), None);
138    }
139}