commonware_consensus/
types.rs

1//! Consensus types shared across the crate.
2//!
3//! This module defines the core types used throughout the consensus implementation:
4//!
5//! - [`Epoch`]: Represents a distinct segment of a contiguous sequence of views. When the validator
6//!   set changes, the epoch increments. Epochs provide reconfiguration boundaries for the consensus
7//!   protocol.
8//!
9//! - [`Height`]: Represents a sequential position in a chain or sequence.
10//!
11//! - [`View`]: A monotonically increasing counter within a single epoch, representing individual
12//!   consensus rounds. Views advance as the protocol progresses through proposals and votes.
13//!
14//! - [`Round`]: Combines an epoch and view into a single identifier for a consensus round.
15//!   Provides ordering across epoch boundaries.
16//!
17//! - [`Delta`]: A generic type representing offsets or durations for consensus types. Provides
18//!   type safety to prevent mixing epoch, height, and view deltas. Type aliases [`EpochDelta`],
19//!   [`HeightDelta`], and [`ViewDelta`] are provided for convenience.
20//!
21//! - [`Epocher`]: Mechanism for determining epoch boundaries.
22//!
23//! # Arithmetic Safety
24//!
25//! Arithmetic operations avoid silent errors. Only `next()` panics on overflow. All other
26//! operations either saturate or return `Option`.
27//!
28//! # Type Conversions
29//!
30//! Explicit type constructors (`Epoch::new()`, `View::new()`) are required to create instances
31//! from raw integers. Implicit conversions via, e.g. `From<u64>` are intentionally not provided
32//! to prevent accidental type misuse.
33
34use crate::{Epochable, Viewable};
35use bytes::{Buf, BufMut};
36use commonware_codec::{varint::UInt, EncodeSize, Error, Read, ReadExt, Write};
37use commonware_utils::sequence::U64;
38use std::{
39    fmt::{self, Display, Formatter},
40    marker::PhantomData,
41    num::NonZeroU64,
42};
43
44/// Represents a distinct segment of a contiguous sequence of views.
45///
46/// An epoch increments when the validator set changes, providing a reconfiguration boundary.
47/// All consensus operations within an epoch use the same validator set.
48#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
49#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
50pub struct Epoch(u64);
51
52impl Epoch {
53    /// Returns epoch zero.
54    pub const fn zero() -> Self {
55        Self(0)
56    }
57
58    /// Creates a new epoch from a u64 value.
59    pub const fn new(value: u64) -> Self {
60        Self(value)
61    }
62
63    /// Returns the underlying u64 value.
64    pub const fn get(self) -> u64 {
65        self.0
66    }
67
68    /// Returns true if this is epoch zero.
69    pub const fn is_zero(self) -> bool {
70        self.0 == 0
71    }
72
73    /// Returns the next epoch.
74    ///
75    /// # Panics
76    ///
77    /// Panics if the epoch would overflow u64::MAX. In practice, this is extremely unlikely
78    /// to occur during normal operation.
79    pub const fn next(self) -> Self {
80        Self(self.0.checked_add(1).expect("epoch overflow"))
81    }
82
83    /// Returns the previous epoch, or `None` if this is epoch zero.
84    ///
85    /// Unlike `Epoch::next()`, this returns an Option since reaching epoch zero
86    /// is common, whereas overflowing u64::MAX is not expected in normal
87    /// operation.
88    pub fn previous(self) -> Option<Self> {
89        self.0.checked_sub(1).map(Self)
90    }
91
92    /// Adds a delta to this epoch, saturating at u64::MAX.
93    pub const fn saturating_add(self, delta: EpochDelta) -> Self {
94        Self(self.0.saturating_add(delta.0))
95    }
96
97    /// Subtracts a delta from this epoch, returning `None` if it would underflow.
98    pub fn checked_sub(self, delta: EpochDelta) -> Option<Self> {
99        self.0.checked_sub(delta.0).map(Self)
100    }
101
102    /// Subtracts a delta from this epoch, saturating at zero.
103    pub const fn saturating_sub(self, delta: EpochDelta) -> Self {
104        Self(self.0.saturating_sub(delta.0))
105    }
106}
107
108impl Display for Epoch {
109    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
110        write!(f, "{}", self.0)
111    }
112}
113
114impl Read for Epoch {
115    type Cfg = ();
116
117    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
118        let value: u64 = UInt::read(buf)?.into();
119        Ok(Self(value))
120    }
121}
122
123impl Write for Epoch {
124    fn write(&self, buf: &mut impl BufMut) {
125        UInt(self.0).write(buf);
126    }
127}
128
129impl EncodeSize for Epoch {
130    fn encode_size(&self) -> usize {
131        UInt(self.0).encode_size()
132    }
133}
134
135impl From<Epoch> for U64 {
136    fn from(epoch: Epoch) -> Self {
137        Self::from(epoch.get())
138    }
139}
140
141/// Represents a sequential position in a chain or sequence.
142///
143/// Height is a monotonically increasing counter.
144#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
145#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
146pub struct Height(u64);
147
148impl Height {
149    /// Returns height zero.
150    pub const fn zero() -> Self {
151        Self(0)
152    }
153
154    /// Creates a new height from a u64 value.
155    pub const fn new(value: u64) -> Self {
156        Self(value)
157    }
158
159    /// Returns the underlying u64 value.
160    pub const fn get(self) -> u64 {
161        self.0
162    }
163
164    /// Returns true if this is height zero.
165    pub const fn is_zero(self) -> bool {
166        self.0 == 0
167    }
168
169    /// Returns the next height.
170    ///
171    /// # Panics
172    ///
173    /// Panics if the height would overflow u64::MAX. In practice, this is extremely unlikely
174    /// to occur during normal operation.
175    pub const fn next(self) -> Self {
176        Self(self.0.checked_add(1).expect("height overflow"))
177    }
178
179    /// Returns the previous height, or `None` if this is height zero.
180    ///
181    /// Unlike `Height::next()`, this returns an Option since reaching height zero
182    /// is common, whereas overflowing u64::MAX is not expected in normal
183    /// operation.
184    pub fn previous(self) -> Option<Self> {
185        self.0.checked_sub(1).map(Self)
186    }
187
188    /// Adds a height delta, saturating at u64::MAX.
189    pub const fn saturating_add(self, delta: HeightDelta) -> Self {
190        Self(self.0.saturating_add(delta.0))
191    }
192
193    /// Subtracts a height delta, saturating at zero.
194    pub const fn saturating_sub(self, delta: HeightDelta) -> Self {
195        Self(self.0.saturating_sub(delta.0))
196    }
197
198    /// Returns the delta from `other` to `self`, or `None` if `other > self`.
199    pub fn delta_from(self, other: Self) -> Option<HeightDelta> {
200        self.0.checked_sub(other.0).map(HeightDelta::new)
201    }
202
203    /// Returns an iterator over the range [start, end).
204    ///
205    /// If start >= end, returns an empty range.
206    pub const fn range(start: Self, end: Self) -> HeightRange {
207        HeightRange {
208            inner: start.get()..end.get(),
209        }
210    }
211}
212
213impl Display for Height {
214    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
215        write!(f, "{}", self.0)
216    }
217}
218
219impl Read for Height {
220    type Cfg = ();
221
222    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
223        let value: u64 = UInt::read(buf)?.into();
224        Ok(Self(value))
225    }
226}
227
228impl Write for Height {
229    fn write(&self, buf: &mut impl BufMut) {
230        UInt(self.0).write(buf);
231    }
232}
233
234impl EncodeSize for Height {
235    fn encode_size(&self) -> usize {
236        UInt(self.0).encode_size()
237    }
238}
239
240impl From<Height> for U64 {
241    fn from(height: Height) -> Self {
242        Self::from(height.get())
243    }
244}
245
246/// A monotonically increasing counter within a single epoch.
247///
248/// Views represent individual consensus rounds within an epoch. Each view corresponds to
249/// one attempt to reach consensus on a proposal.
250#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
252pub struct View(u64);
253
254impl View {
255    /// Returns view zero.
256    pub const fn zero() -> Self {
257        Self(0)
258    }
259
260    /// Creates a new view from a u64 value.
261    pub const fn new(value: u64) -> Self {
262        Self(value)
263    }
264
265    /// Returns the underlying u64 value.
266    pub const fn get(self) -> u64 {
267        self.0
268    }
269
270    /// Returns true if this is view zero.
271    pub const fn is_zero(self) -> bool {
272        self.0 == 0
273    }
274
275    /// Returns the next view.
276    ///
277    /// # Panics
278    ///
279    /// Panics if the view would overflow u64::MAX. In practice, this is extremely unlikely
280    /// to occur during normal operation.
281    pub const fn next(self) -> Self {
282        Self(self.0.checked_add(1).expect("view overflow"))
283    }
284
285    /// Returns the previous view, or `None` if this is view zero.
286    ///
287    /// Unlike `View::next()`, this returns an Option since reaching view zero
288    /// is common, whereas overflowing u64::MAX is not expected in normal
289    /// operation.
290    pub fn previous(self) -> Option<Self> {
291        self.0.checked_sub(1).map(Self)
292    }
293
294    /// Adds a view delta, saturating at u64::MAX.
295    pub const fn saturating_add(self, delta: ViewDelta) -> Self {
296        Self(self.0.saturating_add(delta.0))
297    }
298
299    /// Subtracts a view delta, saturating at zero.
300    pub const fn saturating_sub(self, delta: ViewDelta) -> Self {
301        Self(self.0.saturating_sub(delta.0))
302    }
303
304    /// Returns an iterator over the range [start, end).
305    ///
306    /// If start >= end, returns an empty range.
307    pub const fn range(start: Self, end: Self) -> ViewRange {
308        ViewRange {
309            inner: start.get()..end.get(),
310        }
311    }
312}
313
314impl Display for View {
315    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
316        write!(f, "{}", self.0)
317    }
318}
319
320impl Read for View {
321    type Cfg = ();
322
323    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
324        let value: u64 = UInt::read(buf)?.into();
325        Ok(Self(value))
326    }
327}
328
329impl Write for View {
330    fn write(&self, buf: &mut impl BufMut) {
331        UInt(self.0).write(buf);
332    }
333}
334
335impl EncodeSize for View {
336    fn encode_size(&self) -> usize {
337        UInt(self.0).encode_size()
338    }
339}
340
341impl From<View> for U64 {
342    fn from(view: View) -> Self {
343        Self::from(view.get())
344    }
345}
346
347/// A generic type representing offsets or durations for consensus types.
348///
349/// [`Delta<T>`] is semantically distinct from point-in-time types like [`Epoch`] or [`View`] -
350/// it represents a duration or distance rather than a specific moment.
351///
352/// For convenience, type aliases [`EpochDelta`] and [`ViewDelta`] are provided and should
353/// be preferred in most code.
354#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
355pub struct Delta<T>(u64, PhantomData<T>);
356
357impl<T> Delta<T> {
358    /// Returns a delta of zero.
359    pub const fn zero() -> Self {
360        Self(0, PhantomData)
361    }
362
363    /// Creates a new delta from a u64 value.
364    pub const fn new(value: u64) -> Self {
365        Self(value, PhantomData)
366    }
367
368    /// Returns the underlying u64 value.
369    pub const fn get(self) -> u64 {
370        self.0
371    }
372
373    /// Returns true if this delta is zero.
374    pub const fn is_zero(self) -> bool {
375        self.0 == 0
376    }
377}
378
379impl<T> Display for Delta<T> {
380    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
381        write!(f, "{}", self.0)
382    }
383}
384
385/// Type alias for epoch offsets and durations.
386///
387/// [`EpochDelta`] represents a distance between epochs or a duration measured in epochs.
388/// It is used for epoch arithmetic operations and defining epoch bounds for data retention.
389pub type EpochDelta = Delta<Epoch>;
390
391/// Type alias for height offsets and durations.
392///
393/// [`HeightDelta`] represents a distance between heights or a duration measured in heights.
394/// It is used for height arithmetic operations and defining height bounds for data retention.
395pub type HeightDelta = Delta<Height>;
396
397/// Type alias for view offsets and durations.
398///
399/// [`ViewDelta`] represents a distance between views or a duration measured in views.
400/// It is commonly used for timeouts, activity tracking windows, and view arithmetic.
401pub type ViewDelta = Delta<View>;
402
403/// A unique identifier combining epoch and view for a consensus round.
404///
405/// Round provides a total ordering across epoch boundaries, where rounds are
406/// ordered first by epoch, then by view within that epoch.
407#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
408#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
409pub struct Round {
410    epoch: Epoch,
411    view: View,
412}
413
414impl Round {
415    /// Creates a new round from an epoch and view.
416    pub const fn new(epoch: Epoch, view: View) -> Self {
417        Self { epoch, view }
418    }
419
420    /// Returns round zero, i.e. epoch zero and view zero.
421    pub const fn zero() -> Self {
422        Self::new(Epoch::zero(), View::zero())
423    }
424
425    /// Returns the epoch of this round.
426    pub const fn epoch(self) -> Epoch {
427        self.epoch
428    }
429
430    /// Returns the view of this round.
431    pub const fn view(self) -> View {
432        self.view
433    }
434}
435
436impl Epochable for Round {
437    fn epoch(&self) -> Epoch {
438        self.epoch
439    }
440}
441
442impl Viewable for Round {
443    fn view(&self) -> View {
444        self.view
445    }
446}
447
448impl From<(Epoch, View)> for Round {
449    fn from((epoch, view): (Epoch, View)) -> Self {
450        Self { epoch, view }
451    }
452}
453
454impl From<Round> for (Epoch, View) {
455    fn from(round: Round) -> Self {
456        (round.epoch, round.view)
457    }
458}
459
460/// Represents the relative position within an epoch.
461///
462/// Epochs are divided into two halves with a distinct midpoint.
463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464pub enum EpochPhase {
465    /// First half of the epoch (0 <= relative < length/2).
466    Early,
467    /// Exactly at the midpoint (relative == length/2).
468    Midpoint,
469    /// Second half of the epoch (length/2 < relative < length).
470    Late,
471}
472
473/// Information about an epoch relative to a specific height.
474#[derive(Clone, Copy, Debug, PartialEq, Eq)]
475pub struct EpochInfo {
476    epoch: Epoch,
477    height: Height,
478    first: Height,
479    last: Height,
480}
481
482impl EpochInfo {
483    /// Creates a new [`EpochInfo`].
484    pub const fn new(epoch: Epoch, height: Height, first: Height, last: Height) -> Self {
485        Self {
486            epoch,
487            height,
488            first,
489            last,
490        }
491    }
492
493    /// Returns the epoch.
494    pub const fn epoch(&self) -> Epoch {
495        self.epoch
496    }
497
498    /// Returns the queried height.
499    pub const fn height(&self) -> Height {
500        self.height
501    }
502
503    /// Returns the first block height in this epoch.
504    pub const fn first(&self) -> Height {
505        self.first
506    }
507
508    /// Returns the last block height in this epoch.
509    pub const fn last(&self) -> Height {
510        self.last
511    }
512
513    /// Returns the length of this epoch.
514    pub const fn length(&self) -> HeightDelta {
515        HeightDelta::new(self.last.get() - self.first.get() + 1)
516    }
517
518    /// Returns the relative position of the queried height within this epoch.
519    pub const fn relative(&self) -> Height {
520        Height::new(self.height.get() - self.first.get())
521    }
522
523    /// Returns the phase of the queried height within this epoch.
524    pub const fn phase(&self) -> EpochPhase {
525        let relative = self.relative().get();
526        let midpoint = self.length().get() / 2;
527
528        if relative < midpoint {
529            EpochPhase::Early
530        } else if relative == midpoint {
531            EpochPhase::Midpoint
532        } else {
533            EpochPhase::Late
534        }
535    }
536}
537
538/// Mechanism for determining epoch boundaries.
539pub trait Epocher: Clone + Send + Sync + 'static {
540    /// Returns the information about an epoch containing the given block height.
541    ///
542    /// Returns `None` if the height is not supported.
543    fn containing(&self, height: Height) -> Option<EpochInfo>;
544
545    /// Returns the first block height in the given epoch.
546    ///
547    /// Returns `None` if the epoch is not supported.
548    fn first(&self, epoch: Epoch) -> Option<Height>;
549
550    /// Returns the last block height in the given epoch.
551    ///
552    /// Returns `None` if the epoch is not supported.
553    fn last(&self, epoch: Epoch) -> Option<Height>;
554}
555
556/// Implementation of [`Epocher`] for fixed epoch lengths.
557#[derive(Clone, Debug, PartialEq, Eq)]
558pub struct FixedEpocher(u64);
559
560impl FixedEpocher {
561    /// Creates a new fixed epoch strategy.
562    ///
563    /// # Example
564    /// ```rust
565    /// # use commonware_consensus::types::FixedEpocher;
566    /// # use commonware_utils::NZU64;
567    /// let strategy = FixedEpocher::new(NZU64!(60_480));
568    /// ```
569    pub const fn new(length: NonZeroU64) -> Self {
570        Self(length.get())
571    }
572
573    /// Computes the first and last block height for an epoch, returning `None` if
574    /// either would overflow.
575    fn bounds(&self, epoch: Epoch) -> Option<(Height, Height)> {
576        let first = epoch.get().checked_mul(self.0)?;
577        let last = first.checked_add(self.0 - 1)?;
578        Some((Height::new(first), Height::new(last)))
579    }
580}
581
582impl Epocher for FixedEpocher {
583    fn containing(&self, height: Height) -> Option<EpochInfo> {
584        let epoch = Epoch::new(height.get() / self.0);
585        let (first, last) = self.bounds(epoch)?;
586        Some(EpochInfo::new(epoch, height, first, last))
587    }
588
589    fn first(&self, epoch: Epoch) -> Option<Height> {
590        self.bounds(epoch).map(|(first, _)| first)
591    }
592
593    fn last(&self, epoch: Epoch) -> Option<Height> {
594        self.bounds(epoch).map(|(_, last)| last)
595    }
596}
597
598impl Read for Round {
599    type Cfg = ();
600
601    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
602        Ok(Self {
603            epoch: Epoch::read(buf)?,
604            view: View::read(buf)?,
605        })
606    }
607}
608
609impl Write for Round {
610    fn write(&self, buf: &mut impl BufMut) {
611        self.epoch.write(buf);
612        self.view.write(buf);
613    }
614}
615
616impl EncodeSize for Round {
617    fn encode_size(&self) -> usize {
618        self.epoch.encode_size() + self.view.encode_size()
619    }
620}
621
622impl Display for Round {
623    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
624        write!(f, "({}, {})", self.epoch, self.view)
625    }
626}
627
628/// An iterator over a range of views.
629///
630/// Created by [`View::range`]. Iterates from start (inclusive) to end (exclusive).
631pub struct ViewRange {
632    inner: std::ops::Range<u64>,
633}
634
635impl Iterator for ViewRange {
636    type Item = View;
637
638    fn next(&mut self) -> Option<Self::Item> {
639        self.inner.next().map(View::new)
640    }
641
642    fn size_hint(&self) -> (usize, Option<usize>) {
643        self.inner.size_hint()
644    }
645}
646
647impl DoubleEndedIterator for ViewRange {
648    fn next_back(&mut self) -> Option<Self::Item> {
649        self.inner.next_back().map(View::new)
650    }
651}
652
653impl ExactSizeIterator for ViewRange {
654    fn len(&self) -> usize {
655        self.size_hint().0
656    }
657}
658
659/// An iterator over a range of heights.
660///
661/// Created by [`Height::range`]. Iterates from start (inclusive) to end (exclusive).
662pub struct HeightRange {
663    inner: std::ops::Range<u64>,
664}
665
666impl Iterator for HeightRange {
667    type Item = Height;
668
669    fn next(&mut self) -> Option<Self::Item> {
670        self.inner.next().map(Height::new)
671    }
672
673    fn size_hint(&self) -> (usize, Option<usize>) {
674        self.inner.size_hint()
675    }
676}
677
678impl DoubleEndedIterator for HeightRange {
679    fn next_back(&mut self) -> Option<Self::Item> {
680        self.inner.next_back().map(Height::new)
681    }
682}
683
684impl ExactSizeIterator for HeightRange {
685    fn len(&self) -> usize {
686        self.size_hint().0
687    }
688}
689
690/// Re-export [Participant] from commonware_utils for convenience.
691pub use commonware_utils::Participant;
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696    use commonware_codec::{DecodeExt, Encode, EncodeSize};
697    use commonware_utils::NZU64;
698
699    #[test]
700    fn test_epoch_constructors() {
701        assert_eq!(Epoch::zero().get(), 0);
702        assert_eq!(Epoch::new(42).get(), 42);
703        assert_eq!(Epoch::default().get(), 0);
704    }
705
706    #[test]
707    fn test_epoch_is_zero() {
708        assert!(Epoch::zero().is_zero());
709        assert!(Epoch::new(0).is_zero());
710        assert!(!Epoch::new(1).is_zero());
711        assert!(!Epoch::new(100).is_zero());
712    }
713
714    #[test]
715    fn test_epoch_next() {
716        assert_eq!(Epoch::zero().next().get(), 1);
717        assert_eq!(Epoch::new(5).next().get(), 6);
718        assert_eq!(Epoch::new(999).next().get(), 1000);
719    }
720
721    #[test]
722    #[should_panic(expected = "epoch overflow")]
723    fn test_epoch_next_overflow() {
724        Epoch::new(u64::MAX).next();
725    }
726
727    #[test]
728    fn test_epoch_previous() {
729        assert_eq!(Epoch::zero().previous(), None);
730        assert_eq!(Epoch::new(1).previous(), Some(Epoch::zero()));
731        assert_eq!(Epoch::new(5).previous(), Some(Epoch::new(4)));
732        assert_eq!(Epoch::new(1000).previous(), Some(Epoch::new(999)));
733    }
734
735    #[test]
736    fn test_epoch_saturating_add() {
737        assert_eq!(Epoch::zero().saturating_add(EpochDelta::new(5)).get(), 5);
738        assert_eq!(Epoch::new(10).saturating_add(EpochDelta::new(20)).get(), 30);
739        assert_eq!(
740            Epoch::new(u64::MAX)
741                .saturating_add(EpochDelta::new(1))
742                .get(),
743            u64::MAX
744        );
745        assert_eq!(
746            Epoch::new(u64::MAX - 5)
747                .saturating_add(EpochDelta::new(10))
748                .get(),
749            u64::MAX
750        );
751    }
752
753    #[test]
754    fn test_epoch_checked_sub() {
755        assert_eq!(
756            Epoch::new(10).checked_sub(EpochDelta::new(5)),
757            Some(Epoch::new(5))
758        );
759        assert_eq!(
760            Epoch::new(5).checked_sub(EpochDelta::new(5)),
761            Some(Epoch::zero())
762        );
763        assert_eq!(Epoch::new(5).checked_sub(EpochDelta::new(10)), None);
764        assert_eq!(Epoch::zero().checked_sub(EpochDelta::new(1)), None);
765    }
766
767    #[test]
768    fn test_epoch_saturating_sub() {
769        assert_eq!(Epoch::new(10).saturating_sub(EpochDelta::new(5)).get(), 5);
770        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(5)).get(), 0);
771        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(10)).get(), 0);
772        assert_eq!(Epoch::zero().saturating_sub(EpochDelta::new(100)).get(), 0);
773    }
774
775    #[test]
776    fn test_epoch_display() {
777        assert_eq!(format!("{}", Epoch::zero()), "0");
778        assert_eq!(format!("{}", Epoch::new(42)), "42");
779        assert_eq!(format!("{}", Epoch::new(1000)), "1000");
780    }
781
782    #[test]
783    fn test_epoch_ordering() {
784        assert!(Epoch::zero() < Epoch::new(1));
785        assert!(Epoch::new(5) < Epoch::new(10));
786        assert!(Epoch::new(10) > Epoch::new(5));
787        assert_eq!(Epoch::new(42), Epoch::new(42));
788    }
789
790    #[test]
791    fn test_epoch_encode_decode() {
792        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
793        for value in cases {
794            let epoch = Epoch::new(value);
795            let encoded = epoch.encode();
796            assert_eq!(encoded.len(), epoch.encode_size());
797            let decoded = Epoch::decode(encoded).unwrap();
798            assert_eq!(epoch, decoded);
799        }
800    }
801
802    #[test]
803    fn test_height_constructors() {
804        assert_eq!(Height::zero().get(), 0);
805        assert_eq!(Height::new(42).get(), 42);
806        assert_eq!(Height::new(100).get(), 100);
807        assert_eq!(Height::default().get(), 0);
808    }
809
810    #[test]
811    fn test_height_is_zero() {
812        assert!(Height::zero().is_zero());
813        assert!(Height::new(0).is_zero());
814        assert!(!Height::new(1).is_zero());
815        assert!(!Height::new(100).is_zero());
816    }
817
818    #[test]
819    fn test_height_next() {
820        assert_eq!(Height::zero().next().get(), 1);
821        assert_eq!(Height::new(5).next().get(), 6);
822        assert_eq!(Height::new(999).next().get(), 1000);
823    }
824
825    #[test]
826    #[should_panic(expected = "height overflow")]
827    fn test_height_next_overflow() {
828        Height::new(u64::MAX).next();
829    }
830
831    #[test]
832    fn test_height_previous() {
833        assert_eq!(Height::zero().previous(), None);
834        assert_eq!(Height::new(1).previous(), Some(Height::zero()));
835        assert_eq!(Height::new(5).previous(), Some(Height::new(4)));
836        assert_eq!(Height::new(1000).previous(), Some(Height::new(999)));
837    }
838
839    #[test]
840    fn test_height_saturating_add() {
841        let delta5 = HeightDelta::new(5);
842        let delta100 = HeightDelta::new(100);
843        assert_eq!(Height::zero().saturating_add(delta5).get(), 5);
844        assert_eq!(Height::new(10).saturating_add(delta100).get(), 110);
845        assert_eq!(
846            Height::new(u64::MAX)
847                .saturating_add(HeightDelta::new(1))
848                .get(),
849            u64::MAX
850        );
851    }
852
853    #[test]
854    fn test_height_saturating_sub() {
855        let delta5 = HeightDelta::new(5);
856        let delta100 = HeightDelta::new(100);
857        assert_eq!(Height::new(10).saturating_sub(delta5).get(), 5);
858        assert_eq!(Height::new(5).saturating_sub(delta5).get(), 0);
859        assert_eq!(Height::new(5).saturating_sub(delta100).get(), 0);
860        assert_eq!(Height::zero().saturating_sub(delta100).get(), 0);
861    }
862
863    #[test]
864    fn test_height_display() {
865        assert_eq!(format!("{}", Height::zero()), "0");
866        assert_eq!(format!("{}", Height::new(42)), "42");
867        assert_eq!(format!("{}", Height::new(1000)), "1000");
868    }
869
870    #[test]
871    fn test_height_ordering() {
872        assert!(Height::zero() < Height::new(1));
873        assert!(Height::new(5) < Height::new(10));
874        assert!(Height::new(10) > Height::new(5));
875        assert_eq!(Height::new(42), Height::new(42));
876    }
877
878    #[test]
879    fn test_height_encode_decode() {
880        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
881        for value in cases {
882            let height = Height::new(value);
883            let encoded = height.encode();
884            assert_eq!(encoded.len(), height.encode_size());
885            let decoded = Height::decode(encoded).unwrap();
886            assert_eq!(height, decoded);
887        }
888    }
889
890    #[test]
891    fn test_height_delta_from() {
892        assert_eq!(
893            Height::new(10).delta_from(Height::new(3)),
894            Some(HeightDelta::new(7))
895        );
896        assert_eq!(
897            Height::new(5).delta_from(Height::new(5)),
898            Some(HeightDelta::zero())
899        );
900        assert_eq!(Height::new(3).delta_from(Height::new(10)), None);
901        assert_eq!(Height::zero().delta_from(Height::new(1)), None);
902    }
903
904    #[test]
905    fn height_range_iterates() {
906        let collected: Vec<_> = Height::range(Height::new(3), Height::new(6))
907            .map(Height::get)
908            .collect();
909        assert_eq!(collected, vec![3, 4, 5]);
910    }
911
912    #[test]
913    fn height_range_empty() {
914        let collected: Vec<_> = Height::range(Height::new(5), Height::new(5)).collect();
915        assert_eq!(collected, vec![]);
916
917        let collected: Vec<_> = Height::range(Height::new(10), Height::new(5)).collect();
918        assert_eq!(collected, vec![]);
919    }
920
921    #[test]
922    fn height_range_single() {
923        let collected: Vec<_> = Height::range(Height::new(5), Height::new(6))
924            .map(Height::get)
925            .collect();
926        assert_eq!(collected, vec![5]);
927    }
928
929    #[test]
930    fn height_range_size_hint() {
931        let range = Height::range(Height::new(3), Height::new(10));
932        assert_eq!(range.size_hint(), (7, Some(7)));
933        assert_eq!(range.len(), 7);
934
935        let empty = Height::range(Height::new(5), Height::new(5));
936        assert_eq!(empty.size_hint(), (0, Some(0)));
937        assert_eq!(empty.len(), 0);
938    }
939
940    #[test]
941    fn height_range_rev() {
942        let collected: Vec<_> = Height::range(Height::new(3), Height::new(7))
943            .rev()
944            .map(Height::get)
945            .collect();
946        assert_eq!(collected, vec![6, 5, 4, 3]);
947    }
948
949    #[test]
950    fn height_range_double_ended() {
951        let mut range = Height::range(Height::new(5), Height::new(10));
952        assert_eq!(range.next(), Some(Height::new(5)));
953        assert_eq!(range.next_back(), Some(Height::new(9)));
954        assert_eq!(range.next(), Some(Height::new(6)));
955        assert_eq!(range.next_back(), Some(Height::new(8)));
956        assert_eq!(range.len(), 1);
957        assert_eq!(range.next(), Some(Height::new(7)));
958        assert_eq!(range.next(), None);
959        assert_eq!(range.next_back(), None);
960    }
961
962    #[test]
963    fn test_view_constructors() {
964        assert_eq!(View::zero().get(), 0);
965        assert_eq!(View::new(42).get(), 42);
966        assert_eq!(View::new(100).get(), 100);
967        assert_eq!(View::default().get(), 0);
968    }
969
970    #[test]
971    fn test_view_is_zero() {
972        assert!(View::zero().is_zero());
973        assert!(View::new(0).is_zero());
974        assert!(!View::new(1).is_zero());
975        assert!(!View::new(100).is_zero());
976    }
977
978    #[test]
979    fn test_view_next() {
980        assert_eq!(View::zero().next().get(), 1);
981        assert_eq!(View::new(5).next().get(), 6);
982        assert_eq!(View::new(999).next().get(), 1000);
983    }
984
985    #[test]
986    #[should_panic(expected = "view overflow")]
987    fn test_view_next_overflow() {
988        View::new(u64::MAX).next();
989    }
990
991    #[test]
992    fn test_view_previous() {
993        assert_eq!(View::zero().previous(), None);
994        assert_eq!(View::new(1).previous(), Some(View::zero()));
995        assert_eq!(View::new(5).previous(), Some(View::new(4)));
996        assert_eq!(View::new(1000).previous(), Some(View::new(999)));
997    }
998
999    #[test]
1000    fn test_view_saturating_add() {
1001        let delta5 = ViewDelta::new(5);
1002        let delta100 = ViewDelta::new(100);
1003        assert_eq!(View::zero().saturating_add(delta5).get(), 5);
1004        assert_eq!(View::new(10).saturating_add(delta100).get(), 110);
1005        assert_eq!(
1006            View::new(u64::MAX).saturating_add(ViewDelta::new(1)).get(),
1007            u64::MAX
1008        );
1009    }
1010
1011    #[test]
1012    fn test_view_saturating_sub() {
1013        let delta5 = ViewDelta::new(5);
1014        let delta100 = ViewDelta::new(100);
1015        assert_eq!(View::new(10).saturating_sub(delta5).get(), 5);
1016        assert_eq!(View::new(5).saturating_sub(delta5).get(), 0);
1017        assert_eq!(View::new(5).saturating_sub(delta100).get(), 0);
1018        assert_eq!(View::zero().saturating_sub(delta100).get(), 0);
1019    }
1020
1021    #[test]
1022    fn test_view_display() {
1023        assert_eq!(format!("{}", View::zero()), "0");
1024        assert_eq!(format!("{}", View::new(42)), "42");
1025        assert_eq!(format!("{}", View::new(1000)), "1000");
1026    }
1027
1028    #[test]
1029    fn test_view_ordering() {
1030        assert!(View::zero() < View::new(1));
1031        assert!(View::new(5) < View::new(10));
1032        assert!(View::new(10) > View::new(5));
1033        assert_eq!(View::new(42), View::new(42));
1034    }
1035
1036    #[test]
1037    fn test_view_encode_decode() {
1038        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
1039        for value in cases {
1040            let view = View::new(value);
1041            let encoded = view.encode();
1042            assert_eq!(encoded.len(), view.encode_size());
1043            let decoded = View::decode(encoded).unwrap();
1044            assert_eq!(view, decoded);
1045        }
1046    }
1047
1048    #[test]
1049    fn test_view_delta_constructors() {
1050        assert_eq!(ViewDelta::zero().get(), 0);
1051        assert_eq!(ViewDelta::new(42).get(), 42);
1052        assert_eq!(ViewDelta::new(100).get(), 100);
1053        assert_eq!(ViewDelta::default().get(), 0);
1054    }
1055
1056    #[test]
1057    fn test_view_delta_is_zero() {
1058        assert!(ViewDelta::zero().is_zero());
1059        assert!(ViewDelta::new(0).is_zero());
1060        assert!(!ViewDelta::new(1).is_zero());
1061        assert!(!ViewDelta::new(100).is_zero());
1062    }
1063
1064    #[test]
1065    fn test_view_delta_display() {
1066        assert_eq!(format!("{}", ViewDelta::zero()), "0");
1067        assert_eq!(format!("{}", ViewDelta::new(42)), "42");
1068        assert_eq!(format!("{}", ViewDelta::new(1000)), "1000");
1069    }
1070
1071    #[test]
1072    fn test_view_delta_ordering() {
1073        assert!(ViewDelta::zero() < ViewDelta::new(1));
1074        assert!(ViewDelta::new(5) < ViewDelta::new(10));
1075        assert!(ViewDelta::new(10) > ViewDelta::new(5));
1076        assert_eq!(ViewDelta::new(42), ViewDelta::new(42));
1077    }
1078
1079    #[test]
1080    fn test_round_cmp() {
1081        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(1), View::new(3)));
1082        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(2), View::new(1)));
1083    }
1084
1085    #[test]
1086    fn test_round_encode_decode_roundtrip() {
1087        let r: Round = (Epoch::new(42), View::new(1_000_000)).into();
1088        let encoded = r.encode();
1089        assert_eq!(encoded.len(), r.encode_size());
1090        let decoded = Round::decode(encoded).unwrap();
1091        assert_eq!(r, decoded);
1092    }
1093
1094    #[test]
1095    fn test_round_conversions() {
1096        let r: Round = (Epoch::new(5), View::new(6)).into();
1097        assert_eq!(r.epoch(), Epoch::new(5));
1098        assert_eq!(r.view(), View::new(6));
1099        let tuple: (Epoch, View) = r.into();
1100        assert_eq!(tuple, (Epoch::new(5), View::new(6)));
1101    }
1102
1103    #[test]
1104    fn test_round_new() {
1105        let r = Round::new(Epoch::new(10), View::new(20));
1106        assert_eq!(r.epoch(), Epoch::new(10));
1107        assert_eq!(r.view(), View::new(20));
1108
1109        let r2 = Round::new(Epoch::new(5), View::new(15));
1110        assert_eq!(r2.epoch(), Epoch::new(5));
1111        assert_eq!(r2.view(), View::new(15));
1112    }
1113
1114    #[test]
1115    fn test_round_display() {
1116        let r = Round::new(Epoch::new(5), View::new(100));
1117        assert_eq!(format!("{r}"), "(5, 100)");
1118    }
1119
1120    #[test]
1121    fn view_range_iterates() {
1122        let collected: Vec<_> = View::range(View::new(3), View::new(6))
1123            .map(View::get)
1124            .collect();
1125        assert_eq!(collected, vec![3, 4, 5]);
1126    }
1127
1128    #[test]
1129    fn view_range_empty() {
1130        let collected: Vec<_> = View::range(View::new(5), View::new(5)).collect();
1131        assert_eq!(collected, vec![]);
1132
1133        let collected: Vec<_> = View::range(View::new(10), View::new(5)).collect();
1134        assert_eq!(collected, vec![]);
1135    }
1136
1137    #[test]
1138    fn view_range_single() {
1139        let collected: Vec<_> = View::range(View::new(5), View::new(6))
1140            .map(View::get)
1141            .collect();
1142        assert_eq!(collected, vec![5]);
1143    }
1144
1145    #[test]
1146    fn view_range_size_hint() {
1147        let range = View::range(View::new(3), View::new(10));
1148        assert_eq!(range.size_hint(), (7, Some(7)));
1149        assert_eq!(range.len(), 7);
1150
1151        let empty = View::range(View::new(5), View::new(5));
1152        assert_eq!(empty.size_hint(), (0, Some(0)));
1153        assert_eq!(empty.len(), 0);
1154    }
1155
1156    #[test]
1157    fn view_range_collect() {
1158        let views: Vec<View> = View::range(View::new(0), View::new(3)).collect();
1159        assert_eq!(views, vec![View::zero(), View::new(1), View::new(2)]);
1160    }
1161
1162    #[test]
1163    fn view_range_iterator_next() {
1164        let mut range = View::range(View::new(5), View::new(8));
1165        assert_eq!(range.next(), Some(View::new(5)));
1166        assert_eq!(range.next(), Some(View::new(6)));
1167        assert_eq!(range.next(), Some(View::new(7)));
1168        assert_eq!(range.next(), None);
1169        assert_eq!(range.next(), None); // Multiple None
1170    }
1171
1172    #[test]
1173    fn view_range_exact_size_iterator() {
1174        let range = View::range(View::new(10), View::new(15));
1175        assert_eq!(range.len(), 5);
1176        assert_eq!(range.size_hint(), (5, Some(5)));
1177
1178        let mut range = View::range(View::new(10), View::new(15));
1179        assert_eq!(range.len(), 5);
1180        range.next();
1181        assert_eq!(range.len(), 4);
1182        range.next();
1183        assert_eq!(range.len(), 3);
1184    }
1185
1186    #[test]
1187    fn view_range_rev() {
1188        // Use .rev() to iterate in descending order
1189        let collected: Vec<_> = View::range(View::new(3), View::new(7))
1190            .rev()
1191            .map(View::get)
1192            .collect();
1193        assert_eq!(collected, vec![6, 5, 4, 3]);
1194    }
1195
1196    #[test]
1197    fn view_range_double_ended() {
1198        // Mixed next() and next_back() calls
1199        let mut range = View::range(View::new(5), View::new(10));
1200        assert_eq!(range.next(), Some(View::new(5)));
1201        assert_eq!(range.next_back(), Some(View::new(9)));
1202        assert_eq!(range.next(), Some(View::new(6)));
1203        assert_eq!(range.next_back(), Some(View::new(8)));
1204        assert_eq!(range.len(), 1);
1205        assert_eq!(range.next(), Some(View::new(7)));
1206        assert_eq!(range.next(), None);
1207        assert_eq!(range.next_back(), None);
1208    }
1209
1210    #[test]
1211    fn test_fixed_epoch_strategy() {
1212        let epocher = FixedEpocher::new(NZU64!(100));
1213
1214        // Test containing returns correct EpochInfo
1215        let bounds = epocher.containing(Height::zero()).unwrap();
1216        assert_eq!(bounds.epoch(), Epoch::new(0));
1217        assert_eq!(bounds.first(), Height::zero());
1218        assert_eq!(bounds.last(), Height::new(99));
1219        assert_eq!(bounds.length(), HeightDelta::new(100));
1220
1221        let bounds = epocher.containing(Height::new(99)).unwrap();
1222        assert_eq!(bounds.epoch(), Epoch::new(0));
1223
1224        let bounds = epocher.containing(Height::new(100)).unwrap();
1225        assert_eq!(bounds.epoch(), Epoch::new(1));
1226        assert_eq!(bounds.first(), Height::new(100));
1227        assert_eq!(bounds.last(), Height::new(199));
1228
1229        // Test first/last return correct boundaries
1230        assert_eq!(epocher.first(Epoch::new(0)), Some(Height::zero()));
1231        assert_eq!(epocher.last(Epoch::new(0)), Some(Height::new(99)));
1232        assert_eq!(epocher.first(Epoch::new(1)), Some(Height::new(100)));
1233        assert_eq!(epocher.last(Epoch::new(1)), Some(Height::new(199)));
1234        assert_eq!(epocher.first(Epoch::new(5)), Some(Height::new(500)));
1235        assert_eq!(epocher.last(Epoch::new(5)), Some(Height::new(599)));
1236    }
1237
1238    #[test]
1239    fn test_epoch_bounds_relative() {
1240        let epocher = FixedEpocher::new(NZU64!(100));
1241
1242        // Epoch 0: heights 0-99
1243        assert_eq!(
1244            epocher.containing(Height::zero()).unwrap().relative(),
1245            Height::zero()
1246        );
1247        assert_eq!(
1248            epocher.containing(Height::new(50)).unwrap().relative(),
1249            Height::new(50)
1250        );
1251        assert_eq!(
1252            epocher.containing(Height::new(99)).unwrap().relative(),
1253            Height::new(99)
1254        );
1255
1256        // Epoch 1: heights 100-199
1257        assert_eq!(
1258            epocher.containing(Height::new(100)).unwrap().relative(),
1259            Height::zero()
1260        );
1261        assert_eq!(
1262            epocher.containing(Height::new(150)).unwrap().relative(),
1263            Height::new(50)
1264        );
1265        assert_eq!(
1266            epocher.containing(Height::new(199)).unwrap().relative(),
1267            Height::new(99)
1268        );
1269
1270        // Epoch 5: heights 500-599
1271        assert_eq!(
1272            epocher.containing(Height::new(500)).unwrap().relative(),
1273            Height::zero()
1274        );
1275        assert_eq!(
1276            epocher.containing(Height::new(567)).unwrap().relative(),
1277            Height::new(67)
1278        );
1279        assert_eq!(
1280            epocher.containing(Height::new(599)).unwrap().relative(),
1281            Height::new(99)
1282        );
1283    }
1284
1285    #[test]
1286    fn test_epoch_bounds_phase() {
1287        // Test with epoch length of 30 (midpoint = 15)
1288        let epocher = FixedEpocher::new(NZU64!(30));
1289
1290        // Early phase: relative 0-14
1291        assert_eq!(
1292            epocher.containing(Height::zero()).unwrap().phase(),
1293            EpochPhase::Early
1294        );
1295        assert_eq!(
1296            epocher.containing(Height::new(14)).unwrap().phase(),
1297            EpochPhase::Early
1298        );
1299
1300        // Midpoint: relative 15
1301        assert_eq!(
1302            epocher.containing(Height::new(15)).unwrap().phase(),
1303            EpochPhase::Midpoint
1304        );
1305
1306        // Late phase: relative 16-29
1307        assert_eq!(
1308            epocher.containing(Height::new(16)).unwrap().phase(),
1309            EpochPhase::Late
1310        );
1311        assert_eq!(
1312            epocher.containing(Height::new(29)).unwrap().phase(),
1313            EpochPhase::Late
1314        );
1315
1316        // Second epoch starts at height 30
1317        assert_eq!(
1318            epocher.containing(Height::new(30)).unwrap().phase(),
1319            EpochPhase::Early
1320        );
1321        assert_eq!(
1322            epocher.containing(Height::new(44)).unwrap().phase(),
1323            EpochPhase::Early
1324        );
1325        assert_eq!(
1326            epocher.containing(Height::new(45)).unwrap().phase(),
1327            EpochPhase::Midpoint
1328        );
1329        assert_eq!(
1330            epocher.containing(Height::new(46)).unwrap().phase(),
1331            EpochPhase::Late
1332        );
1333
1334        // Test with epoch length 10 (midpoint = 5)
1335        let epocher = FixedEpocher::new(NZU64!(10));
1336        assert_eq!(
1337            epocher.containing(Height::zero()).unwrap().phase(),
1338            EpochPhase::Early
1339        );
1340        assert_eq!(
1341            epocher.containing(Height::new(4)).unwrap().phase(),
1342            EpochPhase::Early
1343        );
1344        assert_eq!(
1345            epocher.containing(Height::new(5)).unwrap().phase(),
1346            EpochPhase::Midpoint
1347        );
1348        assert_eq!(
1349            epocher.containing(Height::new(6)).unwrap().phase(),
1350            EpochPhase::Late
1351        );
1352        assert_eq!(
1353            epocher.containing(Height::new(9)).unwrap().phase(),
1354            EpochPhase::Late
1355        );
1356
1357        // Test with odd epoch length 11 (midpoint = 5 via integer division)
1358        let epocher = FixedEpocher::new(NZU64!(11));
1359        assert_eq!(
1360            epocher.containing(Height::zero()).unwrap().phase(),
1361            EpochPhase::Early
1362        );
1363        assert_eq!(
1364            epocher.containing(Height::new(4)).unwrap().phase(),
1365            EpochPhase::Early
1366        );
1367        assert_eq!(
1368            epocher.containing(Height::new(5)).unwrap().phase(),
1369            EpochPhase::Midpoint
1370        );
1371        assert_eq!(
1372            epocher.containing(Height::new(6)).unwrap().phase(),
1373            EpochPhase::Late
1374        );
1375        assert_eq!(
1376            epocher.containing(Height::new(10)).unwrap().phase(),
1377            EpochPhase::Late
1378        );
1379    }
1380
1381    #[test]
1382    fn test_fixed_epocher_overflow() {
1383        // Test that containing() returns None when last() would overflow
1384        let epocher = FixedEpocher::new(NZU64!(100));
1385
1386        // For epoch length 100:
1387        // - last valid epoch = (u64::MAX - 100 + 1) / 100 = 184467440737095515
1388        // - last valid first = 184467440737095515 * 100 = 18446744073709551500
1389        // - last valid last = 18446744073709551500 + 99 = 18446744073709551599
1390        // Heights 18446744073709551500 to 18446744073709551599 are in the last valid epoch
1391        // Height 18446744073709551600 onwards would be in an invalid epoch
1392
1393        // This height is in the last valid epoch
1394        let last_valid_first = Height::new(18446744073709551500u64);
1395        let last_valid_last = Height::new(18446744073709551599u64);
1396
1397        let result = epocher.containing(last_valid_first);
1398        assert!(result.is_some());
1399        let bounds = result.unwrap();
1400        assert_eq!(bounds.first(), last_valid_first);
1401        assert_eq!(bounds.last(), last_valid_last);
1402
1403        let result = epocher.containing(last_valid_last);
1404        assert!(result.is_some());
1405        assert_eq!(result.unwrap().last(), last_valid_last);
1406
1407        // This height would be in an epoch where last() overflows
1408        let overflow_height = last_valid_last.next();
1409        assert!(epocher.containing(overflow_height).is_none());
1410
1411        // u64::MAX is also in the overflow range
1412        assert!(epocher.containing(Height::new(u64::MAX)).is_none());
1413
1414        // Test the boundary more precisely with epoch length 2
1415        let epocher = FixedEpocher::new(NZU64!(2));
1416
1417        // u64::MAX - 1 is even, so epoch starts at u64::MAX - 1, last = u64::MAX
1418        let result = epocher.containing(Height::new(u64::MAX - 1));
1419        assert!(result.is_some());
1420        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
1421
1422        // u64::MAX is odd, epoch would start at u64::MAX - 1
1423        // first = u64::MAX - 1, last = first + 2 - 1 = u64::MAX (OK)
1424        let result = epocher.containing(Height::new(u64::MAX));
1425        assert!(result.is_some());
1426        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
1427
1428        // Test with epoch length 1 (every height is its own epoch)
1429        let epocher = FixedEpocher::new(NZU64!(1));
1430        let result = epocher.containing(Height::new(u64::MAX));
1431        assert!(result.is_some());
1432        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
1433
1434        // Test case where first overflows (covered by existing checked_mul)
1435        let epocher = FixedEpocher::new(NZU64!(u64::MAX));
1436        assert!(epocher.containing(Height::new(u64::MAX)).is_none());
1437
1438        // Test consistency: first(), last(), and containing() should agree on valid epochs
1439        let epocher = FixedEpocher::new(NZU64!(100));
1440        let last_valid_epoch = Epoch::new(184467440737095515);
1441        let first_invalid_epoch = Epoch::new(184467440737095516);
1442
1443        // For last valid epoch, all methods should return Some
1444        assert!(epocher.first(last_valid_epoch).is_some());
1445        assert!(epocher.last(last_valid_epoch).is_some());
1446        let first = epocher.first(last_valid_epoch).unwrap();
1447        assert!(epocher.containing(first).is_some());
1448        assert_eq!(
1449            epocher.containing(first).unwrap().last(),
1450            epocher.last(last_valid_epoch).unwrap()
1451        );
1452
1453        // For first invalid epoch, all methods should return None
1454        assert!(epocher.first(first_invalid_epoch).is_none());
1455        assert!(epocher.last(first_invalid_epoch).is_none());
1456        assert!(epocher.containing(last_valid_last.next()).is_none());
1457    }
1458
1459    #[cfg(feature = "arbitrary")]
1460    mod conformance {
1461        use super::*;
1462        use commonware_codec::conformance::CodecConformance;
1463
1464        commonware_conformance::conformance_tests! {
1465            CodecConformance<Epoch>,
1466            CodecConformance<Height>,
1467            CodecConformance<View>,
1468            CodecConformance<Round>,
1469        }
1470    }
1471}