commonware_consensus/
utils.rs

1//! Utility functions for consensus.
2
3use crate::types::Epoch;
4
5/// Returns the epoch the given height belongs to.
6///
7/// Epochs are organized as follows:
8///
9/// ```txt
10/// 0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
11/// 1: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
12/// 2: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
13/// ...
14/// ```
15///
16/// Epoch length is defined in number of blocks. Panics if `epoch_length` is
17/// zero.
18#[inline]
19pub fn epoch(epoch_length: u64, height: u64) -> Epoch {
20    assert!(epoch_length > 0);
21    height / epoch_length
22}
23
24/// Returns the last block height for the given epoch.
25///
26/// Epoch length is defined in number of blocks. Panics if `epoch_length` is
27/// zero or if overflow occurs.
28#[inline]
29pub fn last_block_in_epoch(epoch_length: u64, epoch: Epoch) -> u64 {
30    assert!(epoch_length > 0);
31
32    // (epoch + 1) * epoch_length - 1
33    epoch
34        .checked_add(1)
35        .and_then(|next_epoch| next_epoch.checked_mul(epoch_length))
36        .unwrap()
37        - 1
38}
39
40/// Returns `Some(epoch)` if the height is the last block in the epoch, `None` otherwise.
41///
42/// Epoch length is defined in number of blocks. Panics if `epoch_length` is
43/// zero.
44#[inline]
45pub fn is_last_block_in_epoch(epoch_length: u64, height: u64) -> Option<Epoch> {
46    assert!(epoch_length > 0);
47
48    // Check if the height is the last block in the epoch.
49    if height % epoch_length != epoch_length - 1 {
50        return None;
51    }
52
53    // Return the epoch that the block belongs to.
54    Some(height / epoch_length)
55}
56
57/// Returns the position of `height` within its epoch (starting at zero).
58///
59/// Epoch length is defined in number of blocks. Panics if `epoch_length` is
60/// zero.
61#[inline]
62pub fn relative_height_in_epoch(epoch_length: u64, height: u64) -> u64 {
63    assert!(epoch_length > 0);
64    height % epoch_length
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn epoch_returns_expected_epoch() {
73        assert_eq!(epoch(10, 0), 0);
74        assert_eq!(epoch(10, 9), 0);
75        assert_eq!(epoch(10, 10), 1);
76        assert_eq!(epoch(5, 42), 8);
77    }
78
79    #[test]
80    fn last_block_in_epoch_returns_last_height() {
81        assert_eq!(last_block_in_epoch(1, 0), 0);
82        assert_eq!(last_block_in_epoch(10, 0), 9);
83        assert_eq!(last_block_in_epoch(10, 1), 19);
84        assert_eq!(last_block_in_epoch(5, 42), 214);
85    }
86
87    #[test]
88    fn is_last_block_in_epoch_identifies_last_block() {
89        assert_eq!(is_last_block_in_epoch(10, 9), Some(0));
90        assert_eq!(is_last_block_in_epoch(10, 19), Some(1));
91        assert_eq!(is_last_block_in_epoch(5, 214), Some(42));
92    }
93
94    #[test]
95    fn is_last_block_in_epoch_returns_none_when_not_last_block() {
96        assert_eq!(is_last_block_in_epoch(10, 0), None);
97        assert_eq!(is_last_block_in_epoch(10, 5), None);
98        assert_eq!(is_last_block_in_epoch(10, 18), None);
99    }
100
101    #[test]
102    fn relative_height_in_epoch_returns_expected_offset() {
103        assert_eq!(relative_height_in_epoch(10, 0), 0);
104        assert_eq!(relative_height_in_epoch(10, 9), 9);
105        assert_eq!(relative_height_in_epoch(10, 10), 0);
106        assert_eq!(relative_height_in_epoch(5, 42), 2);
107    }
108}