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//! - [`View`]: A monotonically increasing counter within a single epoch, representing individual
10//!   consensus rounds. Views advance as the protocol progresses through proposals and votes.
11//!
12//! - [`Round`]: Combines an epoch and view into a single identifier for a consensus round.
13//!   Provides ordering across epoch boundaries.
14//!
15//! - [`Delta`]: A generic type representing offsets or durations for consensus types. Provides
16//!   type safety to prevent mixing epoch and view deltas. Type aliases [`EpochDelta`] and
17//!   [`ViewDelta`] are provided for convenience.
18//!
19//! - [`Epocher`]: Mechanism for determining epoch boundaries.
20//!
21//! # Arithmetic Safety
22//!
23//! Arithmetic operations avoid silent errors. Only `next()` panics on overflow. All other
24//! operations either saturate or return `Option`.
25//!
26//! # Type Conversions
27//!
28//! Explicit type constructors (`Epoch::new()`, `View::new()`) are required to create instances
29//! from raw integers. Implicit conversions via, e.g. `From<u64>` are intentionally not provided
30//! to prevent accidental type misuse.
31
32use crate::{Epochable, Viewable};
33use bytes::{Buf, BufMut};
34use commonware_codec::{varint::UInt, EncodeSize, Error, Read, ReadExt, Write};
35use commonware_utils::sequence::U64;
36use std::{
37    fmt::{self, Display, Formatter},
38    marker::PhantomData,
39    num::NonZeroU64,
40};
41
42/// Represents a distinct segment of a contiguous sequence of views.
43///
44/// An epoch increments when the validator set changes, providing a reconfiguration boundary.
45/// All consensus operations within an epoch use the same validator set.
46#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
47#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
48pub struct Epoch(u64);
49
50impl Epoch {
51    /// Returns epoch zero.
52    pub const fn zero() -> Self {
53        Self(0)
54    }
55
56    /// Creates a new epoch from a u64 value.
57    pub const fn new(value: u64) -> Self {
58        Self(value)
59    }
60
61    /// Returns the underlying u64 value.
62    pub const fn get(self) -> u64 {
63        self.0
64    }
65
66    /// Returns true if this is epoch zero.
67    pub const fn is_zero(self) -> bool {
68        self.0 == 0
69    }
70
71    /// Returns the next epoch.
72    ///
73    /// # Panics
74    ///
75    /// Panics if the epoch would overflow u64::MAX. In practice, this is extremely unlikely
76    /// to occur during normal operation.
77    pub const fn next(self) -> Self {
78        Self(self.0.checked_add(1).expect("epoch overflow"))
79    }
80
81    /// Returns the previous epoch, or `None` if this is epoch zero.
82    ///
83    /// Unlike `Epoch::next()`, this returns an Option since reaching epoch zero
84    /// is common, whereas overflowing u64::MAX is not expected in normal
85    /// operation.
86    pub fn previous(self) -> Option<Self> {
87        self.0.checked_sub(1).map(Self)
88    }
89
90    /// Adds a delta to this epoch, saturating at u64::MAX.
91    pub const fn saturating_add(self, delta: EpochDelta) -> Self {
92        Self(self.0.saturating_add(delta.0))
93    }
94
95    /// Subtracts a delta from this epoch, returning `None` if it would underflow.
96    pub fn checked_sub(self, delta: EpochDelta) -> Option<Self> {
97        self.0.checked_sub(delta.0).map(Self)
98    }
99
100    /// Subtracts a delta from this epoch, saturating at zero.
101    pub const fn saturating_sub(self, delta: EpochDelta) -> Self {
102        Self(self.0.saturating_sub(delta.0))
103    }
104}
105
106impl Display for Epoch {
107    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108        write!(f, "{}", self.0)
109    }
110}
111
112impl Read for Epoch {
113    type Cfg = ();
114
115    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
116        let value: u64 = UInt::read(buf)?.into();
117        Ok(Self(value))
118    }
119}
120
121impl Write for Epoch {
122    fn write(&self, buf: &mut impl BufMut) {
123        UInt(self.0).write(buf);
124    }
125}
126
127impl EncodeSize for Epoch {
128    fn encode_size(&self) -> usize {
129        UInt(self.0).encode_size()
130    }
131}
132
133impl From<Epoch> for U64 {
134    fn from(epoch: Epoch) -> Self {
135        Self::from(epoch.get())
136    }
137}
138
139/// A monotonically increasing counter within a single epoch.
140///
141/// Views represent individual consensus rounds within an epoch. Each view corresponds to
142/// one attempt to reach consensus on a proposal.
143#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
144#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
145pub struct View(u64);
146
147impl View {
148    /// Returns view zero.
149    pub const fn zero() -> Self {
150        Self(0)
151    }
152
153    /// Creates a new view from a u64 value.
154    pub const fn new(value: u64) -> Self {
155        Self(value)
156    }
157
158    /// Returns the underlying u64 value.
159    pub const fn get(self) -> u64 {
160        self.0
161    }
162
163    /// Returns true if this is view zero.
164    pub const fn is_zero(self) -> bool {
165        self.0 == 0
166    }
167
168    /// Returns the next view.
169    ///
170    /// # Panics
171    ///
172    /// Panics if the view would overflow u64::MAX. In practice, this is extremely unlikely
173    /// to occur during normal operation.
174    pub const fn next(self) -> Self {
175        Self(self.0.checked_add(1).expect("view overflow"))
176    }
177
178    /// Returns the previous view, or `None` if this is view zero.
179    ///
180    /// Unlike `View::next()`, this returns an Option since reaching view zero
181    /// is common, whereas overflowing u64::MAX is not expected in normal
182    /// operation.
183    pub fn previous(self) -> Option<Self> {
184        self.0.checked_sub(1).map(Self)
185    }
186
187    /// Adds a view delta, saturating at u64::MAX.
188    pub const fn saturating_add(self, delta: ViewDelta) -> Self {
189        Self(self.0.saturating_add(delta.0))
190    }
191
192    /// Subtracts a view delta, saturating at zero.
193    pub const fn saturating_sub(self, delta: ViewDelta) -> Self {
194        Self(self.0.saturating_sub(delta.0))
195    }
196
197    /// Returns an iterator over the range [start, end).
198    ///
199    /// If start >= end, returns an empty range.
200    pub const fn range(start: Self, end: Self) -> ViewRange {
201        ViewRange {
202            inner: start.get()..end.get(),
203        }
204    }
205}
206
207impl Display for View {
208    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
209        write!(f, "{}", self.0)
210    }
211}
212
213impl Read for View {
214    type Cfg = ();
215
216    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
217        let value: u64 = UInt::read(buf)?.into();
218        Ok(Self(value))
219    }
220}
221
222impl Write for View {
223    fn write(&self, buf: &mut impl BufMut) {
224        UInt(self.0).write(buf);
225    }
226}
227
228impl EncodeSize for View {
229    fn encode_size(&self) -> usize {
230        UInt(self.0).encode_size()
231    }
232}
233
234impl From<View> for U64 {
235    fn from(view: View) -> Self {
236        Self::from(view.get())
237    }
238}
239
240/// A generic type representing offsets or durations for consensus types.
241///
242/// [`Delta<T>`] is semantically distinct from point-in-time types like [`Epoch`] or [`View`] -
243/// it represents a duration or distance rather than a specific moment.
244///
245/// For convenience, type aliases [`EpochDelta`] and [`ViewDelta`] are provided and should
246/// be preferred in most code.
247#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
248pub struct Delta<T>(u64, PhantomData<T>);
249
250impl<T> Delta<T> {
251    /// Returns a delta of zero.
252    pub const fn zero() -> Self {
253        Self(0, PhantomData)
254    }
255
256    /// Creates a new delta from a u64 value.
257    pub const fn new(value: u64) -> Self {
258        Self(value, PhantomData)
259    }
260
261    /// Returns the underlying u64 value.
262    pub const fn get(self) -> u64 {
263        self.0
264    }
265
266    /// Returns true if this delta is zero.
267    pub const fn is_zero(self) -> bool {
268        self.0 == 0
269    }
270}
271
272impl<T> Display for Delta<T> {
273    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
274        write!(f, "{}", self.0)
275    }
276}
277
278/// Type alias for epoch offsets and durations.
279///
280/// [`EpochDelta`] represents a distance between epochs or a duration measured in epochs.
281/// It is used for epoch arithmetic operations and defining epoch bounds for data retention.
282pub type EpochDelta = Delta<Epoch>;
283
284/// Type alias for view offsets and durations.
285///
286/// [`ViewDelta`] represents a distance between views or a duration measured in views.
287/// It is commonly used for timeouts, activity tracking windows, and view arithmetic.
288pub type ViewDelta = Delta<View>;
289
290/// A unique identifier combining epoch and view for a consensus round.
291///
292/// Round provides a total ordering across epoch boundaries, where rounds are
293/// ordered first by epoch, then by view within that epoch.
294#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
295#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
296pub struct Round {
297    epoch: Epoch,
298    view: View,
299}
300
301impl Round {
302    /// Creates a new round from an epoch and view.
303    pub const fn new(epoch: Epoch, view: View) -> Self {
304        Self { epoch, view }
305    }
306
307    /// Returns round zero, i.e. epoch zero and view zero.
308    pub const fn zero() -> Self {
309        Self::new(Epoch::zero(), View::zero())
310    }
311
312    /// Returns the epoch of this round.
313    pub const fn epoch(self) -> Epoch {
314        self.epoch
315    }
316
317    /// Returns the view of this round.
318    pub const fn view(self) -> View {
319        self.view
320    }
321}
322
323impl Epochable for Round {
324    fn epoch(&self) -> Epoch {
325        self.epoch
326    }
327}
328
329impl Viewable for Round {
330    fn view(&self) -> View {
331        self.view
332    }
333}
334
335impl From<(Epoch, View)> for Round {
336    fn from((epoch, view): (Epoch, View)) -> Self {
337        Self { epoch, view }
338    }
339}
340
341impl From<Round> for (Epoch, View) {
342    fn from(round: Round) -> Self {
343        (round.epoch, round.view)
344    }
345}
346
347/// Represents the relative position within an epoch.
348///
349/// Epochs are divided into two halves with a distinct midpoint.
350#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351pub enum EpochPhase {
352    /// First half of the epoch (0 <= relative < length/2).
353    Early,
354    /// Exactly at the midpoint (relative == length/2).
355    Midpoint,
356    /// Second half of the epoch (length/2 < relative < length).
357    Late,
358}
359
360/// Information about an epoch relative to a specific height.
361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
362pub struct EpochInfo {
363    epoch: Epoch,
364    height: u64,
365    first: u64,
366    last: u64,
367}
368
369impl EpochInfo {
370    /// Creates a new [`EpochInfo`].
371    pub const fn new(epoch: Epoch, height: u64, first: u64, last: u64) -> Self {
372        Self {
373            epoch,
374            height,
375            first,
376            last,
377        }
378    }
379
380    /// Returns the epoch.
381    pub const fn epoch(&self) -> Epoch {
382        self.epoch
383    }
384
385    /// Returns the queried height.
386    pub const fn height(&self) -> u64 {
387        self.height
388    }
389
390    /// Returns the first block height in this epoch.
391    pub const fn first(&self) -> u64 {
392        self.first
393    }
394
395    /// Returns the last block height in this epoch.
396    pub const fn last(&self) -> u64 {
397        self.last
398    }
399
400    /// Returns the length of this epoch.
401    pub const fn length(&self) -> u64 {
402        self.last - self.first + 1
403    }
404
405    /// Returns the relative position of the queried height within this epoch.
406    pub const fn relative(&self) -> u64 {
407        self.height - self.first
408    }
409
410    /// Returns the phase of the queried height within this epoch.
411    pub const fn phase(&self) -> EpochPhase {
412        let relative = self.relative();
413        let midpoint = self.length() / 2;
414
415        if relative < midpoint {
416            EpochPhase::Early
417        } else if relative == midpoint {
418            EpochPhase::Midpoint
419        } else {
420            EpochPhase::Late
421        }
422    }
423}
424
425/// Mechanism for determining epoch boundaries.
426pub trait Epocher: Clone + Send + Sync + 'static {
427    /// Returns the information about an epoch containing the given block height.
428    ///
429    /// Returns `None` if the height is not supported.
430    fn containing(&self, height: u64) -> Option<EpochInfo>;
431
432    /// Returns the first block height in the given epoch.
433    ///
434    /// Returns `None` if the epoch is not supported.
435    fn first(&self, epoch: Epoch) -> Option<u64>;
436
437    /// Returns the last block height in the given epoch.
438    ///
439    /// Returns `None` if the epoch is not supported.
440    fn last(&self, epoch: Epoch) -> Option<u64>;
441}
442
443/// Implementation of [`Epocher`] for fixed epoch lengths.
444#[derive(Clone, Debug, PartialEq, Eq)]
445pub struct FixedEpocher(u64);
446
447impl FixedEpocher {
448    /// Creates a new fixed epoch strategy.
449    ///
450    /// # Example
451    /// ```rust
452    /// # use commonware_consensus::types::FixedEpocher;
453    /// # use commonware_utils::NZU64;
454    /// let strategy = FixedEpocher::new(NZU64!(60_480));
455    /// ```
456    pub const fn new(length: NonZeroU64) -> Self {
457        Self(length.get())
458    }
459
460    /// Computes the first and last block height for an epoch, returning `None` if
461    /// either would overflow.
462    fn bounds(&self, epoch: Epoch) -> Option<(u64, u64)> {
463        let first = epoch.get().checked_mul(self.0)?;
464        let last = first.checked_add(self.0 - 1)?;
465        Some((first, last))
466    }
467}
468
469impl Epocher for FixedEpocher {
470    fn containing(&self, height: u64) -> Option<EpochInfo> {
471        let epoch = Epoch::new(height / self.0);
472        let (first, last) = self.bounds(epoch)?;
473        Some(EpochInfo::new(epoch, height, first, last))
474    }
475
476    fn first(&self, epoch: Epoch) -> Option<u64> {
477        self.bounds(epoch).map(|(first, _)| first)
478    }
479
480    fn last(&self, epoch: Epoch) -> Option<u64> {
481        self.bounds(epoch).map(|(_, last)| last)
482    }
483}
484
485impl Read for Round {
486    type Cfg = ();
487
488    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
489        Ok(Self {
490            epoch: Epoch::read(buf)?,
491            view: View::read(buf)?,
492        })
493    }
494}
495
496impl Write for Round {
497    fn write(&self, buf: &mut impl BufMut) {
498        self.epoch.write(buf);
499        self.view.write(buf);
500    }
501}
502
503impl EncodeSize for Round {
504    fn encode_size(&self) -> usize {
505        self.epoch.encode_size() + self.view.encode_size()
506    }
507}
508
509impl Display for Round {
510    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
511        write!(f, "({}, {})", self.epoch, self.view)
512    }
513}
514
515/// An iterator over a range of views.
516///
517/// Created by [`View::range`]. Iterates from start (inclusive) to end (exclusive).
518pub struct ViewRange {
519    inner: std::ops::Range<u64>,
520}
521
522impl Iterator for ViewRange {
523    type Item = View;
524
525    fn next(&mut self) -> Option<Self::Item> {
526        self.inner.next().map(View::new)
527    }
528
529    fn size_hint(&self) -> (usize, Option<usize>) {
530        self.inner.size_hint()
531    }
532}
533
534impl DoubleEndedIterator for ViewRange {
535    fn next_back(&mut self) -> Option<Self::Item> {
536        self.inner.next_back().map(View::new)
537    }
538}
539
540impl ExactSizeIterator for ViewRange {
541    fn len(&self) -> usize {
542        self.size_hint().0
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    use super::*;
549    use commonware_codec::{DecodeExt, Encode, EncodeSize};
550    use commonware_utils::NZU64;
551
552    #[test]
553    fn test_epoch_constructors() {
554        assert_eq!(Epoch::zero().get(), 0);
555        assert_eq!(Epoch::new(42).get(), 42);
556        assert_eq!(Epoch::default().get(), 0);
557    }
558
559    #[test]
560    fn test_epoch_is_zero() {
561        assert!(Epoch::zero().is_zero());
562        assert!(Epoch::new(0).is_zero());
563        assert!(!Epoch::new(1).is_zero());
564        assert!(!Epoch::new(100).is_zero());
565    }
566
567    #[test]
568    fn test_epoch_next() {
569        assert_eq!(Epoch::zero().next().get(), 1);
570        assert_eq!(Epoch::new(5).next().get(), 6);
571        assert_eq!(Epoch::new(999).next().get(), 1000);
572    }
573
574    #[test]
575    #[should_panic(expected = "epoch overflow")]
576    fn test_epoch_next_overflow() {
577        Epoch::new(u64::MAX).next();
578    }
579
580    #[test]
581    fn test_epoch_previous() {
582        assert_eq!(Epoch::zero().previous(), None);
583        assert_eq!(Epoch::new(1).previous(), Some(Epoch::zero()));
584        assert_eq!(Epoch::new(5).previous(), Some(Epoch::new(4)));
585        assert_eq!(Epoch::new(1000).previous(), Some(Epoch::new(999)));
586    }
587
588    #[test]
589    fn test_epoch_saturating_add() {
590        assert_eq!(Epoch::zero().saturating_add(EpochDelta::new(5)).get(), 5);
591        assert_eq!(Epoch::new(10).saturating_add(EpochDelta::new(20)).get(), 30);
592        assert_eq!(
593            Epoch::new(u64::MAX)
594                .saturating_add(EpochDelta::new(1))
595                .get(),
596            u64::MAX
597        );
598        assert_eq!(
599            Epoch::new(u64::MAX - 5)
600                .saturating_add(EpochDelta::new(10))
601                .get(),
602            u64::MAX
603        );
604    }
605
606    #[test]
607    fn test_epoch_checked_sub() {
608        assert_eq!(
609            Epoch::new(10).checked_sub(EpochDelta::new(5)),
610            Some(Epoch::new(5))
611        );
612        assert_eq!(
613            Epoch::new(5).checked_sub(EpochDelta::new(5)),
614            Some(Epoch::zero())
615        );
616        assert_eq!(Epoch::new(5).checked_sub(EpochDelta::new(10)), None);
617        assert_eq!(Epoch::zero().checked_sub(EpochDelta::new(1)), None);
618    }
619
620    #[test]
621    fn test_epoch_saturating_sub() {
622        assert_eq!(Epoch::new(10).saturating_sub(EpochDelta::new(5)).get(), 5);
623        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(5)).get(), 0);
624        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(10)).get(), 0);
625        assert_eq!(Epoch::zero().saturating_sub(EpochDelta::new(100)).get(), 0);
626    }
627
628    #[test]
629    fn test_epoch_display() {
630        assert_eq!(format!("{}", Epoch::zero()), "0");
631        assert_eq!(format!("{}", Epoch::new(42)), "42");
632        assert_eq!(format!("{}", Epoch::new(1000)), "1000");
633    }
634
635    #[test]
636    fn test_epoch_ordering() {
637        assert!(Epoch::zero() < Epoch::new(1));
638        assert!(Epoch::new(5) < Epoch::new(10));
639        assert!(Epoch::new(10) > Epoch::new(5));
640        assert_eq!(Epoch::new(42), Epoch::new(42));
641    }
642
643    #[test]
644    fn test_epoch_encode_decode() {
645        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
646        for value in cases {
647            let epoch = Epoch::new(value);
648            let encoded = epoch.encode();
649            assert_eq!(encoded.len(), epoch.encode_size());
650            let decoded = Epoch::decode(encoded).unwrap();
651            assert_eq!(epoch, decoded);
652        }
653    }
654
655    #[test]
656    fn test_view_constructors() {
657        assert_eq!(View::zero().get(), 0);
658        assert_eq!(View::new(42).get(), 42);
659        assert_eq!(View::new(100).get(), 100);
660        assert_eq!(View::default().get(), 0);
661    }
662
663    #[test]
664    fn test_view_is_zero() {
665        assert!(View::zero().is_zero());
666        assert!(View::new(0).is_zero());
667        assert!(!View::new(1).is_zero());
668        assert!(!View::new(100).is_zero());
669    }
670
671    #[test]
672    fn test_view_next() {
673        assert_eq!(View::zero().next().get(), 1);
674        assert_eq!(View::new(5).next().get(), 6);
675        assert_eq!(View::new(999).next().get(), 1000);
676    }
677
678    #[test]
679    #[should_panic(expected = "view overflow")]
680    fn test_view_next_overflow() {
681        View::new(u64::MAX).next();
682    }
683
684    #[test]
685    fn test_view_previous() {
686        assert_eq!(View::zero().previous(), None);
687        assert_eq!(View::new(1).previous(), Some(View::zero()));
688        assert_eq!(View::new(5).previous(), Some(View::new(4)));
689        assert_eq!(View::new(1000).previous(), Some(View::new(999)));
690    }
691
692    #[test]
693    fn test_view_saturating_add() {
694        let delta5 = ViewDelta::new(5);
695        let delta100 = ViewDelta::new(100);
696        assert_eq!(View::zero().saturating_add(delta5).get(), 5);
697        assert_eq!(View::new(10).saturating_add(delta100).get(), 110);
698        assert_eq!(
699            View::new(u64::MAX).saturating_add(ViewDelta::new(1)).get(),
700            u64::MAX
701        );
702    }
703
704    #[test]
705    fn test_view_saturating_sub() {
706        let delta5 = ViewDelta::new(5);
707        let delta100 = ViewDelta::new(100);
708        assert_eq!(View::new(10).saturating_sub(delta5).get(), 5);
709        assert_eq!(View::new(5).saturating_sub(delta5).get(), 0);
710        assert_eq!(View::new(5).saturating_sub(delta100).get(), 0);
711        assert_eq!(View::zero().saturating_sub(delta100).get(), 0);
712    }
713
714    #[test]
715    fn test_view_display() {
716        assert_eq!(format!("{}", View::zero()), "0");
717        assert_eq!(format!("{}", View::new(42)), "42");
718        assert_eq!(format!("{}", View::new(1000)), "1000");
719    }
720
721    #[test]
722    fn test_view_ordering() {
723        assert!(View::zero() < View::new(1));
724        assert!(View::new(5) < View::new(10));
725        assert!(View::new(10) > View::new(5));
726        assert_eq!(View::new(42), View::new(42));
727    }
728
729    #[test]
730    fn test_view_encode_decode() {
731        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
732        for value in cases {
733            let view = View::new(value);
734            let encoded = view.encode();
735            assert_eq!(encoded.len(), view.encode_size());
736            let decoded = View::decode(encoded).unwrap();
737            assert_eq!(view, decoded);
738        }
739    }
740
741    #[test]
742    fn test_view_delta_constructors() {
743        assert_eq!(ViewDelta::zero().get(), 0);
744        assert_eq!(ViewDelta::new(42).get(), 42);
745        assert_eq!(ViewDelta::new(100).get(), 100);
746        assert_eq!(ViewDelta::default().get(), 0);
747    }
748
749    #[test]
750    fn test_view_delta_is_zero() {
751        assert!(ViewDelta::zero().is_zero());
752        assert!(ViewDelta::new(0).is_zero());
753        assert!(!ViewDelta::new(1).is_zero());
754        assert!(!ViewDelta::new(100).is_zero());
755    }
756
757    #[test]
758    fn test_view_delta_display() {
759        assert_eq!(format!("{}", ViewDelta::zero()), "0");
760        assert_eq!(format!("{}", ViewDelta::new(42)), "42");
761        assert_eq!(format!("{}", ViewDelta::new(1000)), "1000");
762    }
763
764    #[test]
765    fn test_view_delta_ordering() {
766        assert!(ViewDelta::zero() < ViewDelta::new(1));
767        assert!(ViewDelta::new(5) < ViewDelta::new(10));
768        assert!(ViewDelta::new(10) > ViewDelta::new(5));
769        assert_eq!(ViewDelta::new(42), ViewDelta::new(42));
770    }
771
772    #[test]
773    fn test_round_cmp() {
774        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(1), View::new(3)));
775        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(2), View::new(1)));
776    }
777
778    #[test]
779    fn test_round_encode_decode_roundtrip() {
780        let r: Round = (Epoch::new(42), View::new(1_000_000)).into();
781        let encoded = r.encode();
782        assert_eq!(encoded.len(), r.encode_size());
783        let decoded = Round::decode(encoded).unwrap();
784        assert_eq!(r, decoded);
785    }
786
787    #[test]
788    fn test_round_conversions() {
789        let r: Round = (Epoch::new(5), View::new(6)).into();
790        assert_eq!(r.epoch(), Epoch::new(5));
791        assert_eq!(r.view(), View::new(6));
792        let tuple: (Epoch, View) = r.into();
793        assert_eq!(tuple, (Epoch::new(5), View::new(6)));
794    }
795
796    #[test]
797    fn test_round_new() {
798        let r = Round::new(Epoch::new(10), View::new(20));
799        assert_eq!(r.epoch(), Epoch::new(10));
800        assert_eq!(r.view(), View::new(20));
801
802        let r2 = Round::new(Epoch::new(5), View::new(15));
803        assert_eq!(r2.epoch(), Epoch::new(5));
804        assert_eq!(r2.view(), View::new(15));
805    }
806
807    #[test]
808    fn test_round_display() {
809        let r = Round::new(Epoch::new(5), View::new(100));
810        assert_eq!(format!("{r}"), "(5, 100)");
811    }
812
813    #[test]
814    fn view_range_iterates() {
815        let collected: Vec<_> = View::range(View::new(3), View::new(6))
816            .map(View::get)
817            .collect();
818        assert_eq!(collected, vec![3, 4, 5]);
819    }
820
821    #[test]
822    fn view_range_empty() {
823        let collected: Vec<_> = View::range(View::new(5), View::new(5)).collect();
824        assert_eq!(collected, vec![]);
825
826        let collected: Vec<_> = View::range(View::new(10), View::new(5)).collect();
827        assert_eq!(collected, vec![]);
828    }
829
830    #[test]
831    fn view_range_single() {
832        let collected: Vec<_> = View::range(View::new(5), View::new(6))
833            .map(View::get)
834            .collect();
835        assert_eq!(collected, vec![5]);
836    }
837
838    #[test]
839    fn view_range_size_hint() {
840        let range = View::range(View::new(3), View::new(10));
841        assert_eq!(range.size_hint(), (7, Some(7)));
842        assert_eq!(range.len(), 7);
843
844        let empty = View::range(View::new(5), View::new(5));
845        assert_eq!(empty.size_hint(), (0, Some(0)));
846        assert_eq!(empty.len(), 0);
847    }
848
849    #[test]
850    fn view_range_collect() {
851        let views: Vec<View> = View::range(View::new(0), View::new(3)).collect();
852        assert_eq!(views, vec![View::zero(), View::new(1), View::new(2)]);
853    }
854
855    #[test]
856    fn view_range_iterator_next() {
857        let mut range = View::range(View::new(5), View::new(8));
858        assert_eq!(range.next(), Some(View::new(5)));
859        assert_eq!(range.next(), Some(View::new(6)));
860        assert_eq!(range.next(), Some(View::new(7)));
861        assert_eq!(range.next(), None);
862        assert_eq!(range.next(), None); // Multiple None
863    }
864
865    #[test]
866    fn view_range_exact_size_iterator() {
867        let range = View::range(View::new(10), View::new(15));
868        assert_eq!(range.len(), 5);
869        assert_eq!(range.size_hint(), (5, Some(5)));
870
871        let mut range = View::range(View::new(10), View::new(15));
872        assert_eq!(range.len(), 5);
873        range.next();
874        assert_eq!(range.len(), 4);
875        range.next();
876        assert_eq!(range.len(), 3);
877    }
878
879    #[test]
880    fn view_range_rev() {
881        // Use .rev() to iterate in descending order
882        let collected: Vec<_> = View::range(View::new(3), View::new(7))
883            .rev()
884            .map(View::get)
885            .collect();
886        assert_eq!(collected, vec![6, 5, 4, 3]);
887    }
888
889    #[test]
890    fn view_range_double_ended() {
891        // Mixed next() and next_back() calls
892        let mut range = View::range(View::new(5), View::new(10));
893        assert_eq!(range.next(), Some(View::new(5)));
894        assert_eq!(range.next_back(), Some(View::new(9)));
895        assert_eq!(range.next(), Some(View::new(6)));
896        assert_eq!(range.next_back(), Some(View::new(8)));
897        assert_eq!(range.len(), 1);
898        assert_eq!(range.next(), Some(View::new(7)));
899        assert_eq!(range.next(), None);
900        assert_eq!(range.next_back(), None);
901    }
902
903    #[test]
904    fn test_fixed_epoch_strategy() {
905        let epocher = FixedEpocher::new(NZU64!(100));
906
907        // Test containing returns correct EpochInfo
908        let bounds = epocher.containing(0).unwrap();
909        assert_eq!(bounds.epoch(), Epoch::new(0));
910        assert_eq!(bounds.first(), 0);
911        assert_eq!(bounds.last(), 99);
912        assert_eq!(bounds.length(), 100);
913
914        let bounds = epocher.containing(99).unwrap();
915        assert_eq!(bounds.epoch(), Epoch::new(0));
916
917        let bounds = epocher.containing(100).unwrap();
918        assert_eq!(bounds.epoch(), Epoch::new(1));
919        assert_eq!(bounds.first(), 100);
920        assert_eq!(bounds.last(), 199);
921
922        // Test first/last return correct boundaries
923        assert_eq!(epocher.first(Epoch::new(0)), Some(0));
924        assert_eq!(epocher.last(Epoch::new(0)), Some(99));
925        assert_eq!(epocher.first(Epoch::new(1)), Some(100));
926        assert_eq!(epocher.last(Epoch::new(1)), Some(199));
927        assert_eq!(epocher.first(Epoch::new(5)), Some(500));
928        assert_eq!(epocher.last(Epoch::new(5)), Some(599));
929    }
930
931    #[test]
932    fn test_epoch_bounds_relative() {
933        let epocher = FixedEpocher::new(NZU64!(100));
934
935        // Epoch 0: heights 0-99
936        assert_eq!(epocher.containing(0).unwrap().relative(), 0);
937        assert_eq!(epocher.containing(50).unwrap().relative(), 50);
938        assert_eq!(epocher.containing(99).unwrap().relative(), 99);
939
940        // Epoch 1: heights 100-199
941        assert_eq!(epocher.containing(100).unwrap().relative(), 0);
942        assert_eq!(epocher.containing(150).unwrap().relative(), 50);
943        assert_eq!(epocher.containing(199).unwrap().relative(), 99);
944
945        // Epoch 5: heights 500-599
946        assert_eq!(epocher.containing(500).unwrap().relative(), 0);
947        assert_eq!(epocher.containing(567).unwrap().relative(), 67);
948        assert_eq!(epocher.containing(599).unwrap().relative(), 99);
949    }
950
951    #[test]
952    fn test_epoch_bounds_phase() {
953        // Test with epoch length of 30 (midpoint = 15)
954        let epocher = FixedEpocher::new(NZU64!(30));
955
956        // Early phase: relative 0-14
957        assert_eq!(epocher.containing(0).unwrap().phase(), EpochPhase::Early);
958        assert_eq!(epocher.containing(14).unwrap().phase(), EpochPhase::Early);
959
960        // Midpoint: relative 15
961        assert_eq!(
962            epocher.containing(15).unwrap().phase(),
963            EpochPhase::Midpoint
964        );
965
966        // Late phase: relative 16-29
967        assert_eq!(epocher.containing(16).unwrap().phase(), EpochPhase::Late);
968        assert_eq!(epocher.containing(29).unwrap().phase(), EpochPhase::Late);
969
970        // Second epoch starts at height 30
971        assert_eq!(epocher.containing(30).unwrap().phase(), EpochPhase::Early);
972        assert_eq!(epocher.containing(44).unwrap().phase(), EpochPhase::Early);
973        assert_eq!(
974            epocher.containing(45).unwrap().phase(),
975            EpochPhase::Midpoint
976        );
977        assert_eq!(epocher.containing(46).unwrap().phase(), EpochPhase::Late);
978
979        // Test with epoch length 10 (midpoint = 5)
980        let epocher = FixedEpocher::new(NZU64!(10));
981        assert_eq!(epocher.containing(0).unwrap().phase(), EpochPhase::Early);
982        assert_eq!(epocher.containing(4).unwrap().phase(), EpochPhase::Early);
983        assert_eq!(epocher.containing(5).unwrap().phase(), EpochPhase::Midpoint);
984        assert_eq!(epocher.containing(6).unwrap().phase(), EpochPhase::Late);
985        assert_eq!(epocher.containing(9).unwrap().phase(), EpochPhase::Late);
986
987        // Test with odd epoch length 11 (midpoint = 5 via integer division)
988        let epocher = FixedEpocher::new(NZU64!(11));
989        assert_eq!(epocher.containing(0).unwrap().phase(), EpochPhase::Early);
990        assert_eq!(epocher.containing(4).unwrap().phase(), EpochPhase::Early);
991        assert_eq!(epocher.containing(5).unwrap().phase(), EpochPhase::Midpoint);
992        assert_eq!(epocher.containing(6).unwrap().phase(), EpochPhase::Late);
993        assert_eq!(epocher.containing(10).unwrap().phase(), EpochPhase::Late);
994    }
995
996    #[test]
997    fn test_fixed_epocher_overflow() {
998        // Test that containing() returns None when last() would overflow
999        let epocher = FixedEpocher::new(NZU64!(100));
1000
1001        // For epoch length 100:
1002        // - last valid epoch = (u64::MAX - 100 + 1) / 100 = 184467440737095515
1003        // - last valid first = 184467440737095515 * 100 = 18446744073709551500
1004        // - last valid last = 18446744073709551500 + 99 = 18446744073709551599
1005        // Heights 18446744073709551500 to 18446744073709551599 are in the last valid epoch
1006        // Height 18446744073709551600 onwards would be in an invalid epoch
1007
1008        // This height is in the last valid epoch
1009        let last_valid_first = 18446744073709551500u64;
1010        let last_valid_last = 18446744073709551599u64;
1011
1012        let result = epocher.containing(last_valid_first);
1013        assert!(result.is_some());
1014        let bounds = result.unwrap();
1015        assert_eq!(bounds.first(), last_valid_first);
1016        assert_eq!(bounds.last(), last_valid_last);
1017
1018        let result = epocher.containing(last_valid_last);
1019        assert!(result.is_some());
1020        assert_eq!(result.unwrap().last(), last_valid_last);
1021
1022        // This height would be in an epoch where last() overflows
1023        let overflow_height = last_valid_last + 1;
1024        assert!(epocher.containing(overflow_height).is_none());
1025
1026        // u64::MAX is also in the overflow range
1027        assert!(epocher.containing(u64::MAX).is_none());
1028
1029        // Test the boundary more precisely with epoch length 2
1030        let epocher = FixedEpocher::new(NZU64!(2));
1031
1032        // u64::MAX - 1 is even, so epoch starts at u64::MAX - 1, last = u64::MAX
1033        let result = epocher.containing(u64::MAX - 1);
1034        assert!(result.is_some());
1035        assert_eq!(result.unwrap().last(), u64::MAX);
1036
1037        // u64::MAX is odd, epoch would start at u64::MAX - 1
1038        // first = u64::MAX - 1, last = first + 2 - 1 = u64::MAX (OK)
1039        let result = epocher.containing(u64::MAX);
1040        assert!(result.is_some());
1041        assert_eq!(result.unwrap().last(), u64::MAX);
1042
1043        // Test with epoch length 1 (every height is its own epoch)
1044        let epocher = FixedEpocher::new(NZU64!(1));
1045        let result = epocher.containing(u64::MAX);
1046        assert!(result.is_some());
1047        assert_eq!(result.unwrap().last(), u64::MAX);
1048
1049        // Test case where first overflows (covered by existing checked_mul)
1050        let epocher = FixedEpocher::new(NZU64!(u64::MAX));
1051        assert!(epocher.containing(u64::MAX).is_none());
1052
1053        // Test consistency: first(), last(), and containing() should agree on valid epochs
1054        let epocher = FixedEpocher::new(NZU64!(100));
1055        let last_valid_epoch = Epoch::new(184467440737095515);
1056        let first_invalid_epoch = Epoch::new(184467440737095516);
1057
1058        // For last valid epoch, all methods should return Some
1059        assert!(epocher.first(last_valid_epoch).is_some());
1060        assert!(epocher.last(last_valid_epoch).is_some());
1061        let first = epocher.first(last_valid_epoch).unwrap();
1062        assert!(epocher.containing(first).is_some());
1063        assert_eq!(
1064            epocher.containing(first).unwrap().last(),
1065            epocher.last(last_valid_epoch).unwrap()
1066        );
1067
1068        // For first invalid epoch, all methods should return None
1069        assert!(epocher.first(first_invalid_epoch).is_none());
1070        assert!(epocher.last(first_invalid_epoch).is_none());
1071        assert!(epocher.containing(last_valid_last + 1).is_none());
1072    }
1073
1074    #[cfg(feature = "arbitrary")]
1075    mod conformance {
1076        use super::*;
1077        use commonware_codec::conformance::CodecConformance;
1078
1079        commonware_conformance::conformance_tests! {
1080            CodecConformance<Epoch>,
1081            CodecConformance<View>,
1082            CodecConformance<Round>,
1083        }
1084    }
1085}