Skip to main content

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//! - [`TermLength`]: The number of consecutive views in which a leader remains stable (a "term").
22//!
23//! - [`Epocher`]: Mechanism for determining epoch boundaries.
24//!
25//! - [`coding::Commitment`]: A unique identifier combining a block digest, coding digest, context
26//!   hash, and encoded coding configuration. Used as the certificate payload for erasure-coded blocks.
27//!
28//! # Arithmetic Safety
29//!
30//! Arithmetic operations avoid silent errors. Only `next()`, `View::term_end()`, and
31//! `View::next_term_start()` panic on overflow. All other operations either saturate or
32//! return `Option`.
33//!
34//! # Type Conversions
35//!
36//! Explicit type constructors (`Epoch::new()`, `View::new()`) are required to create instances
37//! from raw integers. Implicit conversions via, e.g. `From<u64>` are intentionally not provided
38//! to prevent accidental type misuse.
39
40use crate::{Epochable, Viewable};
41use bytes::{Buf, BufMut};
42use commonware_codec::{EncodeSize, Error, Read, ReadExt, Write, varint::UInt};
43#[cfg(not(target_arch = "wasm32"))]
44use commonware_runtime::telemetry::traces::TracedExt;
45use commonware_utils::sequence::U64;
46use core::{
47    fmt::{self, Display, Formatter},
48    marker::PhantomData,
49    num::{NonZeroU32, NonZeroU64},
50    ops::RangeInclusive,
51};
52
53/// Represents a distinct segment of a contiguous sequence of views.
54///
55/// An epoch increments when the validator set changes, providing a reconfiguration boundary.
56/// All consensus operations within an epoch use the same validator set.
57#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
58#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
59pub struct Epoch(u64);
60
61impl Epoch {
62    /// Returns epoch zero.
63    pub const fn zero() -> Self {
64        Self(0)
65    }
66
67    /// Creates a new epoch from a u64 value.
68    pub const fn new(value: u64) -> Self {
69        Self(value)
70    }
71
72    /// Returns the underlying u64 value.
73    pub const fn get(self) -> u64 {
74        self.0
75    }
76
77    /// Returns true if this is epoch zero.
78    pub const fn is_zero(self) -> bool {
79        self.0 == 0
80    }
81
82    /// Returns the next epoch.
83    ///
84    /// # Panics
85    ///
86    /// Panics if the epoch would overflow u64::MAX. In practice, this is extremely unlikely
87    /// to occur during normal operation.
88    pub const fn next(self) -> Self {
89        Self(self.0.checked_add(1).expect("epoch overflow"))
90    }
91
92    /// Returns the previous epoch, or `None` if this is epoch zero.
93    ///
94    /// Unlike `Epoch::next()`, this returns an Option since reaching epoch zero
95    /// is common, whereas overflowing u64::MAX is not expected in normal
96    /// operation.
97    pub fn previous(self) -> Option<Self> {
98        self.0.checked_sub(1).map(Self)
99    }
100
101    /// Adds a delta to this epoch, saturating at u64::MAX.
102    pub const fn saturating_add(self, delta: EpochDelta) -> Self {
103        Self(self.0.saturating_add(delta.0))
104    }
105
106    /// Subtracts a delta from this epoch, returning `None` if it would underflow.
107    pub fn checked_sub(self, delta: EpochDelta) -> Option<Self> {
108        self.0.checked_sub(delta.0).map(Self)
109    }
110
111    /// Subtracts a delta from this epoch, saturating at zero.
112    pub const fn saturating_sub(self, delta: EpochDelta) -> Self {
113        Self(self.0.saturating_sub(delta.0))
114    }
115}
116
117impl Display for Epoch {
118    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119        write!(f, "{}", self.0)
120    }
121}
122
123impl Read for Epoch {
124    type Cfg = ();
125
126    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
127        let value: u64 = UInt::read(buf)?.into();
128        Ok(Self(value))
129    }
130}
131
132impl Write for Epoch {
133    fn write(&self, buf: &mut impl BufMut) {
134        UInt(self.0).write(buf);
135    }
136}
137
138impl EncodeSize for Epoch {
139    fn encode_size(&self) -> usize {
140        UInt(self.0).encode_size()
141    }
142}
143
144impl From<Epoch> for U64 {
145    fn from(epoch: Epoch) -> Self {
146        Self::from(epoch.get())
147    }
148}
149
150/// Represents a sequential position in a chain or sequence.
151///
152/// Height is a monotonically increasing counter. Height zero is the genesis block.
153#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
154#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
155pub struct Height(u64);
156
157impl Height {
158    /// Returns height zero.
159    pub const fn zero() -> Self {
160        Self(0)
161    }
162
163    /// Creates a new height from a u64 value.
164    pub const fn new(value: u64) -> Self {
165        Self(value)
166    }
167
168    /// Returns the underlying u64 value.
169    pub const fn get(self) -> u64 {
170        self.0
171    }
172
173    /// Returns true if this is height zero.
174    pub const fn is_zero(self) -> bool {
175        self.0 == 0
176    }
177
178    /// Returns the next height.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the height would overflow u64::MAX. In practice, this is extremely unlikely
183    /// to occur during normal operation.
184    pub const fn next(self) -> Self {
185        Self(self.0.checked_add(1).expect("height overflow"))
186    }
187
188    /// Returns the previous height, or `None` if this is height zero.
189    ///
190    /// Unlike `Height::next()`, this returns an Option since reaching height zero
191    /// is common, whereas overflowing u64::MAX is not expected in normal
192    /// operation.
193    pub fn previous(self) -> Option<Self> {
194        self.0.checked_sub(1).map(Self)
195    }
196
197    /// Adds a height delta, saturating at u64::MAX.
198    pub const fn saturating_add(self, delta: HeightDelta) -> Self {
199        Self(self.0.saturating_add(delta.0))
200    }
201
202    /// Subtracts a height delta, saturating at zero.
203    pub const fn saturating_sub(self, delta: HeightDelta) -> Self {
204        Self(self.0.saturating_sub(delta.0))
205    }
206
207    /// Returns the delta from `other` to `self`, or `None` if `other > self`.
208    pub fn delta_from(self, other: Self) -> Option<HeightDelta> {
209        self.0.checked_sub(other.0).map(HeightDelta::new)
210    }
211
212    /// Returns an iterator over the range [start, end).
213    ///
214    /// If start >= end, returns an empty range.
215    pub const fn range(start: Self, end: Self) -> HeightRange {
216        HeightRange {
217            inner: start.get()..end.get(),
218        }
219    }
220}
221
222impl Display for Height {
223    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
224        write!(f, "{}", self.0)
225    }
226}
227
228impl Read for Height {
229    type Cfg = ();
230
231    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
232        let value: u64 = UInt::read(buf)?.into();
233        Ok(Self(value))
234    }
235}
236
237impl Write for Height {
238    fn write(&self, buf: &mut impl BufMut) {
239        UInt(self.0).write(buf);
240    }
241}
242
243impl EncodeSize for Height {
244    fn encode_size(&self) -> usize {
245        UInt(self.0).encode_size()
246    }
247}
248
249impl From<Height> for U64 {
250    fn from(height: Height) -> Self {
251        Self::from(height.get())
252    }
253}
254
255/// A monotonically increasing counter within a single epoch.
256///
257/// Views represent individual consensus rounds within an epoch. Each view corresponds to
258/// one attempt to reach consensus on a proposal.
259#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
260#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
261pub struct View(u64);
262
263impl View {
264    /// Returns view zero.
265    pub const fn zero() -> Self {
266        Self(0)
267    }
268
269    /// Creates a new view from a u64 value.
270    pub const fn new(value: u64) -> Self {
271        Self(value)
272    }
273
274    /// Returns the underlying u64 value.
275    pub const fn get(self) -> u64 {
276        self.0
277    }
278
279    /// Returns true if this is view zero.
280    pub const fn is_zero(self) -> bool {
281        self.0 == 0
282    }
283
284    /// Returns the next view.
285    ///
286    /// # Panics
287    ///
288    /// Panics if the view would overflow u64::MAX. In practice, this is extremely unlikely
289    /// to occur during normal operation.
290    pub const fn next(self) -> Self {
291        Self(self.0.checked_add(1).expect("view overflow"))
292    }
293
294    /// Returns the previous view, or `None` if this is view zero.
295    ///
296    /// Unlike `View::next()`, this returns an Option since reaching view zero
297    /// is common, whereas overflowing u64::MAX is not expected in normal
298    /// operation.
299    pub fn previous(self) -> Option<Self> {
300        self.0.checked_sub(1).map(Self)
301    }
302
303    /// Adds a view delta, saturating at u64::MAX.
304    pub const fn saturating_add(self, delta: ViewDelta) -> Self {
305        Self(self.0.saturating_add(delta.0))
306    }
307
308    /// Subtracts a view delta, saturating at zero.
309    pub const fn saturating_sub(self, delta: ViewDelta) -> Self {
310        Self(self.0.saturating_sub(delta.0))
311    }
312
313    /// Returns an iterator over the range [start, end).
314    ///
315    /// If start >= end, returns an empty range.
316    pub const fn range(start: Self, end: Self) -> ViewRange {
317        ViewRange {
318            inner: start.get()..end.get(),
319        }
320    }
321
322    /// Returns the first view of the term containing this view.
323    ///
324    /// Terms group consecutive views so that the same leader serves for
325    /// `term_length` views. View 0 (genesis) is its own term. For views >= 1,
326    /// term boundaries are: [1, term_length], [term_length+1, 2*term_length], ...
327    ///
328    /// When `term_length` is 1, every view is its own term (no grouping).
329    pub const fn term_start(self, term_length: TermLength) -> Self {
330        let term_length = term_length.get();
331        let Self(view) = self;
332        if view == 0 {
333            return self;
334        }
335        // Cannot overflow: base is at most view - 1.
336        let base = (view - 1) / term_length * term_length;
337        Self(base).next()
338    }
339
340    /// Returns whether this view is the first view of its term.
341    pub const fn is_term_start(self, term_length: TermLength) -> bool {
342        let start = self.term_start(term_length);
343        self.get() == start.get()
344    }
345
346    /// Returns whether this view shares a term with `other`.
347    pub const fn same_term(self, other: Self, term_length: TermLength) -> bool {
348        let start = self.term_start(term_length);
349        let other_start = other.term_start(term_length);
350        start.get() == other_start.get()
351    }
352
353    /// Returns the last view of the term containing this view.
354    ///
355    /// See [`term_start`](View::term_start) for term boundary semantics.
356    ///
357    /// When `term_length` is 1, returns `self`.
358    pub const fn term_end(self, term_length: TermLength) -> Self {
359        if self.0 == 0 {
360            return self;
361        }
362        let end = self
363            .term_start(term_length)
364            .get()
365            .checked_add(term_length.get() - 1)
366            .expect("view term_end overflow");
367        Self(end)
368    }
369
370    /// Returns the first view of the term that follows this view's term.
371    ///
372    /// When `term_length` is 1, returns `self.next()`.
373    pub const fn next_term_start(self, term_length: TermLength) -> Self {
374        self.term_end(term_length).next()
375    }
376
377    /// Returns the index of the term containing this view.
378    ///
379    /// View 0 (genesis) is its own term with index 0; terms of later views
380    /// are numbered from 1. When `term_length` is 1, the index equals the
381    /// view.
382    pub const fn term_index(self, term_length: TermLength) -> u64 {
383        self.get().div_ceil(term_length.get())
384    }
385
386    /// Returns whether a nullification at this view covers `view`.
387    ///
388    /// A nullification covers the view it was created for and the rest of that
389    /// view's term.
390    pub const fn covers(self, view: Self, term_length: TermLength) -> bool {
391        self.get() <= view.get() && self.same_term(view, term_length)
392    }
393
394    /// Returns the range of views whose nullifications cover this view.
395    ///
396    /// The inverse of [`covers`](Self::covers): a nullification covers the
397    /// rest of its term, so this view is covered by a nullification at any
398    /// view in `[term_start, self]`.
399    pub const fn covering_range(self, term_length: TermLength) -> RangeInclusive<Self> {
400        self.term_start(term_length)..=self
401    }
402
403    /// Returns whether `pending` is an acceptable view relative to this view
404    /// when future views are bounded.
405    ///
406    /// Views at or below this view are always acceptable (callers enforce any
407    /// lower bound separately). Beyond that, only the next view and the first
408    /// view of the next term are acceptable: the only views this view can
409    /// directly advance into (a nullification of the current view skips to
410    /// the latter). When `term_length` is 1 the two views are the same.
411    ///
412    /// This bound exists to limit memory committed to unverified messages
413    /// (like votes) from future views. It should not be applied to
414    /// self-certifying artifacts (like certificates), which may arrive from
415    /// arbitrarily far ahead and let a lagging participant fast-forward.
416    pub const fn admits(self, pending: Self, term_length: TermLength) -> bool {
417        if pending.get() <= self.get() || pending.get() == self.next().get() {
418            return true;
419        }
420        // Equivalent to `pending == self.next_term_start(term_length)`, but
421        // stated as a property of `pending` so it stays total: computing the
422        // next term start can overflow near `u64::MAX`, where the correct
423        // answer is simply that no representable view starts the next term.
424        // Cannot underflow: pending is above self, so it is at least 1.
425        pending.is_term_start(term_length) && self.same_term(Self(pending.get() - 1), term_length)
426    }
427}
428
429impl Display for View {
430    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
431        write!(f, "{}", self.0)
432    }
433}
434
435#[cfg(not(target_arch = "wasm32"))]
436impl TracedExt for Epoch {
437    fn traced(self) -> i64 {
438        self.0.traced()
439    }
440}
441
442#[cfg(not(target_arch = "wasm32"))]
443impl TracedExt for Height {
444    fn traced(self) -> i64 {
445        self.0.traced()
446    }
447}
448
449#[cfg(not(target_arch = "wasm32"))]
450impl TracedExt for View {
451    fn traced(self) -> i64 {
452        self.0.traced()
453    }
454}
455
456impl Read for View {
457    type Cfg = ();
458
459    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
460        let value: u64 = UInt::read(buf)?.into();
461        Ok(Self(value))
462    }
463}
464
465impl Write for View {
466    fn write(&self, buf: &mut impl BufMut) {
467        UInt(self.0).write(buf);
468    }
469}
470
471impl EncodeSize for View {
472    fn encode_size(&self) -> usize {
473        UInt(self.0).encode_size()
474    }
475}
476
477impl From<View> for U64 {
478    fn from(view: View) -> Self {
479        Self::from(view.get())
480    }
481}
482
483/// A generic type representing offsets or durations for consensus types.
484///
485/// [`Delta<T>`] is semantically distinct from point-in-time types like [`Epoch`] or [`View`] -
486/// it represents a duration or distance rather than a specific moment.
487///
488/// For convenience, type aliases [`EpochDelta`] and [`ViewDelta`] are provided and should
489/// be preferred in most code.
490#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
491pub struct Delta<T>(u64, PhantomData<T>);
492
493impl<T> Delta<T> {
494    /// Returns a delta of zero.
495    pub const fn zero() -> Self {
496        Self(0, PhantomData)
497    }
498
499    /// Creates a new delta from a u64 value.
500    pub const fn new(value: u64) -> Self {
501        Self(value, PhantomData)
502    }
503
504    /// Returns the underlying u64 value.
505    pub const fn get(self) -> u64 {
506        self.0
507    }
508
509    /// Returns true if this delta is zero.
510    pub const fn is_zero(self) -> bool {
511        self.0 == 0
512    }
513}
514
515impl<T> Display for Delta<T> {
516    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
517        write!(f, "{}", self.0)
518    }
519}
520
521/// Type alias for epoch offsets and durations.
522///
523/// [`EpochDelta`] represents a distance between epochs or a duration measured in epochs.
524/// It is used for epoch arithmetic operations and defining epoch bounds for data retention.
525pub type EpochDelta = Delta<Epoch>;
526
527/// Type alias for height offsets and durations.
528///
529/// [`HeightDelta`] represents a distance between heights or a duration measured in heights.
530/// It is used for height arithmetic operations and defining height bounds for data retention.
531pub type HeightDelta = Delta<Height>;
532
533/// Type alias for view offsets and durations.
534///
535/// [`ViewDelta`] represents a distance between views or a duration measured in views.
536/// It is commonly used for timeouts, activity tracking windows, and view arithmetic.
537pub type ViewDelta = Delta<View>;
538
539/// Number of consecutive views in which a leader remains stable (a "term").
540///
541/// When the term length is 1, every view is its own term and each view has an
542/// independently elected leader. When greater than 1, views are grouped into
543/// terms and the same leader serves for every view in the term.
544///
545/// Unlike [`ViewDelta`], which represents an offset added to or subtracted from
546/// a view, a term length is a period that partitions the view space. It is
547/// always non-zero.
548///
549/// # Consensus-Critical
550///
551/// The term length is consensus-critical configuration (like the namespace or
552/// participant set): it is local, is not carried by any vote or certificate,
553/// and nothing in the protocol detects a mismatch. All participants must
554/// configure the same value. Term boundaries determine which views a
555/// nullification covers, leader election, and when finalize votes are
556/// withheld, so mismatched participants silently disagree on view transitions
557/// and vote safety without producing any fault evidence. Only change the term
558/// length when all participants change it together (e.g., at an epoch
559/// boundary).
560///
561/// Longer terms also widen the window of unverified votes a participant may
562/// buffer while finalization stalls: votes are accepted for any view between
563/// the highest finalized view and the current view, and the current view
564/// advances by up to a full term per nullification.
565#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
566pub struct TermLength(u32);
567
568impl TermLength {
569    /// The maximum term length. Lengths are stored as a `u32`, bounding term
570    /// arithmetic (like [`View::next_term_start`]) away from `u64` overflow
571    /// for any realistic view.
572    pub const MAX: Self = Self(u32::MAX);
573
574    /// A term length of one view (every view has an independently elected leader).
575    pub const ONE: Self = Self(1);
576
577    /// Creates a new term length.
578    pub const fn new(length: NonZeroU32) -> Self {
579        Self(length.get())
580    }
581
582    /// Returns the number of views per term.
583    pub const fn get(self) -> u64 {
584        self.0 as u64
585    }
586}
587
588impl Default for TermLength {
589    fn default() -> Self {
590        Self::ONE
591    }
592}
593
594#[cfg(feature = "arbitrary")]
595impl arbitrary::Arbitrary<'_> for TermLength {
596    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
597        Ok(Self(u.int_in_range(1..=u32::MAX)?))
598    }
599}
600
601impl Display for TermLength {
602    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
603        write!(f, "{}", self.0)
604    }
605}
606
607/// A unique identifier combining epoch and view for a consensus round.
608///
609/// Round provides a total ordering across epoch boundaries, where rounds are
610/// ordered first by epoch, then by view within that epoch.
611#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
612#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
613pub struct Round {
614    epoch: Epoch,
615    view: View,
616}
617
618impl Round {
619    /// Creates a new round from an epoch and view.
620    pub const fn new(epoch: Epoch, view: View) -> Self {
621        Self { epoch, view }
622    }
623
624    /// Returns round zero, i.e. epoch zero and view zero.
625    pub const fn zero() -> Self {
626        Self::new(Epoch::zero(), View::zero())
627    }
628
629    /// Returns the epoch of this round.
630    pub const fn epoch(self) -> Epoch {
631        self.epoch
632    }
633
634    /// Returns the view of this round.
635    pub const fn view(self) -> View {
636        self.view
637    }
638}
639
640impl Epochable for Round {
641    fn epoch(&self) -> Epoch {
642        self.epoch
643    }
644}
645
646impl Viewable for Round {
647    fn view(&self) -> View {
648        self.view
649    }
650}
651
652impl From<(Epoch, View)> for Round {
653    fn from((epoch, view): (Epoch, View)) -> Self {
654        Self { epoch, view }
655    }
656}
657
658impl From<Round> for (Epoch, View) {
659    fn from(round: Round) -> Self {
660        (round.epoch, round.view)
661    }
662}
663
664/// Represents the relative position within an epoch.
665///
666/// Epochs are divided into two halves with a distinct midpoint.
667#[derive(Clone, Copy, Debug, PartialEq, Eq)]
668pub enum EpochPhase {
669    /// First half of the epoch (0 <= relative < length/2).
670    Early,
671    /// Exactly at the midpoint (relative == length/2).
672    Midpoint,
673    /// Second half of the epoch (length/2 < relative < length).
674    Late,
675}
676
677/// Information about an epoch relative to a specific height.
678#[derive(Clone, Copy, Debug, PartialEq, Eq)]
679pub struct EpochInfo {
680    epoch: Epoch,
681    height: Height,
682    first: Height,
683    last: Height,
684}
685
686impl EpochInfo {
687    /// Creates a new [`EpochInfo`].
688    pub const fn new(epoch: Epoch, height: Height, first: Height, last: Height) -> Self {
689        Self {
690            epoch,
691            height,
692            first,
693            last,
694        }
695    }
696
697    /// Returns the epoch.
698    pub const fn epoch(&self) -> Epoch {
699        self.epoch
700    }
701
702    /// Returns the queried height.
703    pub const fn height(&self) -> Height {
704        self.height
705    }
706
707    /// Returns the first block height in this epoch.
708    pub const fn first(&self) -> Height {
709        self.first
710    }
711
712    /// Returns the last block height in this epoch.
713    pub const fn last(&self) -> Height {
714        self.last
715    }
716
717    /// Returns the length of this epoch.
718    pub const fn length(&self) -> HeightDelta {
719        HeightDelta::new(self.last.get() - self.first.get() + 1)
720    }
721
722    /// Returns the relative position of the queried height within this epoch.
723    pub const fn relative(&self) -> Height {
724        Height::new(self.height.get() - self.first.get())
725    }
726
727    /// Returns the phase of the queried height within this epoch.
728    pub const fn phase(&self) -> EpochPhase {
729        let relative = self.relative().get();
730        let midpoint = self.length().get() / 2;
731
732        if relative < midpoint {
733            EpochPhase::Early
734        } else if relative == midpoint {
735            EpochPhase::Midpoint
736        } else {
737            EpochPhase::Late
738        }
739    }
740}
741
742/// Mechanism for determining epoch boundaries.
743///
744/// Genesis is not produced by any epoch, so every epoch must contain at least one
745/// height above [`Height::zero`].
746pub trait Epocher: Clone + Send + Sync + 'static {
747    /// Returns the information about an epoch containing the given block height.
748    ///
749    /// Returns `None` if the height is not supported.
750    fn containing(&self, height: Height) -> Option<EpochInfo>;
751
752    /// Returns the first block height in the given epoch.
753    ///
754    /// Returns `None` if the epoch is not supported.
755    fn first(&self, epoch: Epoch) -> Option<Height>;
756
757    /// Returns the last block height in the given epoch.
758    ///
759    /// Returns `None` if the epoch is not supported.
760    fn last(&self, epoch: Epoch) -> Option<Height>;
761}
762
763/// Implementation of [`Epocher`] for fixed epoch lengths.
764///
765/// Epoch `e` spans heights `e * length..(e + 1) * length`, so epoch zero includes
766/// genesis.
767#[derive(Clone, Debug, PartialEq, Eq)]
768pub struct FixedEpocher(u64);
769
770impl FixedEpocher {
771    /// Creates a new fixed epoch strategy.
772    ///
773    /// # Panics
774    ///
775    /// Panics if `length` is one, since epoch zero would contain only genesis.
776    ///
777    /// # Example
778    /// ```rust
779    /// # use commonware_consensus::types::FixedEpocher;
780    /// # use commonware_utils::NZU64;
781    /// let strategy = FixedEpocher::new(NZU64!(60_480));
782    /// ```
783    pub const fn new(length: NonZeroU64) -> Self {
784        assert!(length.get() > 1, "epoch length must exceed one");
785        Self(length.get())
786    }
787
788    /// Computes the first and last block height for an epoch, returning `None` if
789    /// either would overflow.
790    fn bounds(&self, epoch: Epoch) -> Option<(Height, Height)> {
791        let first = epoch.get().checked_mul(self.0)?;
792        let last = first.checked_add(self.0 - 1)?;
793        Some((Height::new(first), Height::new(last)))
794    }
795
796    /// Returns the midpoint block height in the given epoch.
797    ///
798    /// Returns `None` if the epoch is not supported.
799    pub fn midpoint(&self, epoch: Epoch) -> Option<Height> {
800        let (first, _) = self.bounds(epoch)?;
801        first.get().checked_add(self.0 / 2).map(Height::new)
802    }
803}
804
805impl Epocher for FixedEpocher {
806    fn containing(&self, height: Height) -> Option<EpochInfo> {
807        let epoch = Epoch::new(height.get() / self.0);
808        let (first, last) = self.bounds(epoch)?;
809        Some(EpochInfo::new(epoch, height, first, last))
810    }
811
812    fn first(&self, epoch: Epoch) -> Option<Height> {
813        self.bounds(epoch).map(|(first, _)| first)
814    }
815
816    fn last(&self, epoch: Epoch) -> Option<Height> {
817        self.bounds(epoch).map(|(_, last)| last)
818    }
819}
820
821impl Read for Round {
822    type Cfg = ();
823
824    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
825        Ok(Self {
826            epoch: Epoch::read(buf)?,
827            view: View::read(buf)?,
828        })
829    }
830}
831
832impl Write for Round {
833    fn write(&self, buf: &mut impl BufMut) {
834        self.epoch.write(buf);
835        self.view.write(buf);
836    }
837}
838
839impl EncodeSize for Round {
840    fn encode_size(&self) -> usize {
841        self.epoch.encode_size() + self.view.encode_size()
842    }
843}
844
845impl Display for Round {
846    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
847        write!(f, "({}, {})", self.epoch, self.view)
848    }
849}
850
851/// An iterator over a range of views.
852///
853/// Created by [`View::range`]. Iterates from start (inclusive) to end (exclusive).
854pub struct ViewRange {
855    inner: std::ops::Range<u64>,
856}
857
858impl Iterator for ViewRange {
859    type Item = View;
860
861    fn next(&mut self) -> Option<Self::Item> {
862        self.inner.next().map(View::new)
863    }
864
865    fn size_hint(&self) -> (usize, Option<usize>) {
866        self.inner.size_hint()
867    }
868}
869
870impl DoubleEndedIterator for ViewRange {
871    fn next_back(&mut self) -> Option<Self::Item> {
872        self.inner.next_back().map(View::new)
873    }
874}
875
876impl ExactSizeIterator for ViewRange {
877    fn len(&self) -> usize {
878        self.size_hint().0
879    }
880}
881
882/// An iterator over a range of heights.
883///
884/// Created by [`Height::range`]. Iterates from start (inclusive) to end (exclusive).
885pub struct HeightRange {
886    inner: std::ops::Range<u64>,
887}
888
889impl Iterator for HeightRange {
890    type Item = Height;
891
892    fn next(&mut self) -> Option<Self::Item> {
893        self.inner.next().map(Height::new)
894    }
895
896    fn size_hint(&self) -> (usize, Option<usize>) {
897        self.inner.size_hint()
898    }
899}
900
901impl DoubleEndedIterator for HeightRange {
902    fn next_back(&mut self) -> Option<Self::Item> {
903        self.inner.next_back().map(Height::new)
904    }
905}
906
907impl ExactSizeIterator for HeightRange {
908    fn len(&self) -> usize {
909        self.size_hint().0
910    }
911}
912
913/// Re-export [Participant] from commonware_utils for convenience.
914pub use commonware_utils::Participant;
915
916commonware_macros::stability_scope!(ALPHA {
917    pub mod coding {
918        //! Types and utilities for working with [`Commitment`]s.
919
920        use commonware_codec::{Encode, FixedArray, FixedSize, Read, ReadExt, Write};
921        use commonware_coding::{Config as CodingConfig, Scheme};
922        use commonware_cryptography::{Digest, Digestible, Hasher};
923        use commonware_math::algebra::Random;
924        use commonware_utils::{Array, NZU16, Span};
925        use core::{
926            cmp::Ordering,
927            hash::{Hash, Hasher as StdHasher},
928            marker::PhantomData,
929            num::NonZeroU16,
930            ops::Deref,
931        };
932        use rand_core::CryptoRng;
933
934        /// The fixed wire width reserved for each digest field in a [`Commitment`].
935        ///
936        /// A concrete width keeps the representation independent of `B`, `C`, and `H`.
937        /// Stable Rust cannot use their associated sizes in the backing array length.
938        pub const COMMITMENT_DIGEST_SIZE: usize = 32;
939
940        /// The encoded size of a [`Commitment`].
941        pub const COMMITMENT_SIZE: usize = 3 * COMMITMENT_DIGEST_SIZE + CodingConfig::SIZE;
942
943        /// A [`Digest`] containing a coding commitment, encoded [`CodingConfig`], and context hash.
944        ///
945        /// ```text
946        /// 0                   32                  64                  96            100
947        /// +-------------------+-------------------+-------------------+---------------+
948        /// | block digest      | coding root       | context digest    | coding config |
949        /// +-------------------+-------------------+-------------------+---------------+
950        /// ```
951        ///
952        /// Each digest occupies [`COMMITMENT_DIGEST_SIZE`] bytes. Any unused bytes at the end of
953        /// a digest field are zero.
954        ///
955        /// Each field is parsed as its declared type on deserialization, so the accessors on a
956        /// successfully decoded [`Commitment`] never fail.
957        #[derive(FixedArray)]
958        #[fixed_array(bytes([u8; COMMITMENT_SIZE]))]
959        pub struct Commitment<B, C, H>([u8; COMMITMENT_SIZE], PhantomData<(B, C, H)>);
960
961        impl<B, C, H> Clone for Commitment<B, C, H> {
962            fn clone(&self) -> Self {
963                *self
964            }
965        }
966
967        impl<B, C, H> Copy for Commitment<B, C, H> {}
968
969        impl<B, C, H> PartialEq for Commitment<B, C, H> {
970            fn eq(&self, other: &Self) -> bool {
971                self.0 == other.0
972            }
973        }
974
975        impl<B, C, H> Eq for Commitment<B, C, H> {}
976
977        impl<B, C, H> PartialOrd for Commitment<B, C, H> {
978            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
979                Some(self.cmp(other))
980            }
981        }
982
983        impl<B, C, H> Ord for Commitment<B, C, H> {
984            fn cmp(&self, other: &Self) -> Ordering {
985                self.0.cmp(&other.0)
986            }
987        }
988
989        impl<B, C, H> Hash for Commitment<B, C, H> {
990            fn hash<S: StdHasher>(&self, state: &mut S) {
991                self.0.hash(state);
992            }
993        }
994
995        impl<B: Digestible, C: Scheme, H: Hasher> Commitment<B, C, H> {
996            const BLOCK_OFFSET: usize = 0;
997            const ROOT_OFFSET: usize = Self::BLOCK_OFFSET + COMMITMENT_DIGEST_SIZE;
998            const CONTEXT_OFFSET: usize = Self::ROOT_OFFSET + COMMITMENT_DIGEST_SIZE;
999            const CONFIG_OFFSET: usize = Self::CONTEXT_OFFSET + COMMITMENT_DIGEST_SIZE;
1000
1001            /// Returns the block [`Digest`] from this [`Commitment`].
1002            pub fn block(&self) -> B::Digest {
1003                self.field(Self::BLOCK_OFFSET)
1004            }
1005
1006            /// Returns the coding root [`Digest`] from this [`Commitment`].
1007            pub fn root(&self) -> C::Commitment {
1008                self.field(Self::ROOT_OFFSET)
1009            }
1010
1011            /// Returns the context [`Digest`] from this [`Commitment`].
1012            pub fn context(&self) -> H::Digest {
1013                self.field(Self::CONTEXT_OFFSET)
1014            }
1015
1016            /// Extracts the [`CodingConfig`] from this [`Commitment`].
1017            pub fn config(&self) -> CodingConfig {
1018                self.field(Self::CONFIG_OFFSET)
1019            }
1020
1021            fn field<T: ReadExt + FixedSize>(&self, offset: usize) -> T {
1022                T::read(&mut &self.0[offset..offset + T::SIZE])
1023                    .expect("fields are validated on decode and typed construction")
1024            }
1025
1026            /// Validates a typed digest field and its canonical zero padding.
1027            fn validate_field<T: ReadExt + FixedSize>(
1028                bytes: &[u8],
1029                offset: usize,
1030                reason: &'static str,
1031            ) -> Result<(), commonware_codec::Error> {
1032                let field_end = offset + T::SIZE;
1033                let padding_end = offset + COMMITMENT_DIGEST_SIZE;
1034                T::read(&mut &bytes[offset..field_end])
1035                    .map_err(|_| commonware_codec::Error::Invalid("Commitment", reason))?;
1036                if bytes[field_end..padding_end].iter().any(|byte| *byte != 0) {
1037                    return Err(commonware_codec::Error::Invalid(
1038                        "Commitment",
1039                        "non-zero digest padding",
1040                    ));
1041                }
1042                Ok(())
1043            }
1044
1045            /// Ensures each typed digest fits its fixed-width wire field.
1046            const fn assert_layout() {
1047                assert!(
1048                    B::Digest::SIZE <= COMMITMENT_DIGEST_SIZE,
1049                    "block digest exceeds commitment field size"
1050                );
1051                assert!(
1052                    C::Commitment::SIZE <= COMMITMENT_DIGEST_SIZE,
1053                    "coding root exceeds commitment field size"
1054                );
1055                assert!(
1056                    H::Digest::SIZE <= COMMITMENT_DIGEST_SIZE,
1057                    "context digest exceeds commitment field size"
1058                );
1059            }
1060        }
1061
1062        impl<B: Digestible, C: Scheme, H: Hasher> Random for Commitment<B, C, H> {
1063            fn random(mut rng: impl CryptoRng) -> Self {
1064                let one = NZU16!(1);
1065                let shards = rng.next_u32();
1066                let config = CodingConfig {
1067                    minimum_shards: NonZeroU16::new(shards as u16).unwrap_or(one),
1068                    extra_shards: NonZeroU16::new((shards >> 16) as u16).unwrap_or(one),
1069                };
1070                Self::from((
1071                    B::Digest::random(&mut rng),
1072                    C::Commitment::random(&mut rng),
1073                    H::Digest::random(&mut rng),
1074                    config,
1075                ))
1076            }
1077        }
1078
1079        impl<B: Digestible, C: Scheme, H: Hasher> Digest for Commitment<B, C, H> {
1080            /// The all-zero sentinel. Its config bytes are not a valid
1081            /// [`CodingConfig`], so accessors must not be called on it.
1082            const EMPTY: Self = {
1083                Self::assert_layout();
1084                Self([0u8; COMMITMENT_SIZE], PhantomData)
1085            };
1086        }
1087
1088        impl<B: Digestible, C: Scheme, H: Hasher> Write for Commitment<B, C, H> {
1089            fn write(&self, buf: &mut impl bytes::BufMut) {
1090                buf.put_slice(self.as_ref());
1091            }
1092        }
1093
1094        impl<B: Digestible, C: Scheme, H: Hasher> FixedSize for Commitment<B, C, H> {
1095            const SIZE: usize = COMMITMENT_SIZE;
1096        }
1097
1098        impl<B: Digestible, C: Scheme, H: Hasher> Read for Commitment<B, C, H> {
1099            type Cfg = ();
1100
1101            fn read_cfg(
1102                buf: &mut impl bytes::Buf,
1103                _cfg: &Self::Cfg,
1104            ) -> Result<Self, commonware_codec::Error> {
1105                const { Self::assert_layout() };
1106                let arr = <[u8; COMMITMENT_SIZE]>::read(buf)?;
1107
1108                Self::validate_field::<B::Digest>(
1109                    &arr,
1110                    Self::BLOCK_OFFSET,
1111                    "invalid block digest",
1112                )?;
1113                Self::validate_field::<C::Commitment>(
1114                    &arr,
1115                    Self::ROOT_OFFSET,
1116                    "invalid coding root",
1117                )?;
1118                Self::validate_field::<H::Digest>(
1119                    &arr,
1120                    Self::CONTEXT_OFFSET,
1121                    "invalid context digest",
1122                )?;
1123                let mut cursor = &arr[Self::CONFIG_OFFSET..];
1124                CodingConfig::read(&mut cursor).map_err(|_| {
1125                    commonware_codec::Error::Invalid("Commitment", "invalid embedded CodingConfig")
1126                })?;
1127
1128                Ok(Self(arr, PhantomData))
1129            }
1130        }
1131
1132        impl<B: Digestible, C: Scheme, H: Hasher> AsRef<[u8]> for Commitment<B, C, H> {
1133            fn as_ref(&self) -> &[u8] {
1134                &self.0
1135            }
1136        }
1137
1138        impl<B: Digestible, C: Scheme, H: Hasher> Deref for Commitment<B, C, H> {
1139            type Target = [u8];
1140
1141            fn deref(&self) -> &Self::Target {
1142                self.as_ref()
1143            }
1144        }
1145
1146        impl<B: Digestible, C: Scheme, H: Hasher> core::fmt::Display for Commitment<B, C, H> {
1147            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1148                write!(f, "{}", commonware_formatting::Hex(self.as_ref()))
1149            }
1150        }
1151
1152        impl<B: Digestible, C: Scheme, H: Hasher> core::fmt::Debug for Commitment<B, C, H> {
1153            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1154                write!(f, "{}", commonware_formatting::Hex(self.as_ref()))
1155            }
1156        }
1157
1158        impl<B: Digestible, C: Scheme, H: Hasher> Default for Commitment<B, C, H> {
1159            fn default() -> Self {
1160                Self::EMPTY
1161            }
1162        }
1163
1164        impl<B: Digestible, C: Scheme, H: Hasher>
1165            From<(B::Digest, C::Commitment, H::Digest, CodingConfig)> for Commitment<B, C, H>
1166        {
1167            fn from(
1168                (block, root, context, config): (B::Digest, C::Commitment, H::Digest, CodingConfig),
1169            ) -> Self {
1170                const { Self::assert_layout() };
1171
1172                let mut buf = [0u8; COMMITMENT_SIZE];
1173                buf[Self::BLOCK_OFFSET..Self::BLOCK_OFFSET + B::Digest::SIZE]
1174                    .copy_from_slice(&block);
1175                buf[Self::ROOT_OFFSET..Self::ROOT_OFFSET + C::Commitment::SIZE]
1176                    .copy_from_slice(&root);
1177                buf[Self::CONTEXT_OFFSET..Self::CONTEXT_OFFSET + H::Digest::SIZE]
1178                    .copy_from_slice(&context);
1179                buf[Self::CONFIG_OFFSET..].copy_from_slice(&config.encode());
1180                Self(buf, PhantomData)
1181            }
1182        }
1183
1184        impl<B: Digestible, C: Scheme, H: Hasher> Span for Commitment<B, C, H> {}
1185
1186        impl<B: Digestible, C: Scheme, H: Hasher> Array for Commitment<B, C, H> {}
1187
1188        #[cfg(feature = "arbitrary")]
1189        impl<B, C, H> arbitrary::Arbitrary<'_> for Commitment<B, C, H>
1190        where
1191            B: Digestible,
1192            B::Digest: for<'a> arbitrary::Arbitrary<'a>,
1193            C: Scheme,
1194            C::Commitment: for<'a> arbitrary::Arbitrary<'a>,
1195            H: Hasher,
1196            H::Digest: for<'a> arbitrary::Arbitrary<'a>,
1197        {
1198            fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1199                Ok(Self::from((
1200                    B::Digest::arbitrary(u)?,
1201                    C::Commitment::arbitrary(u)?,
1202                    H::Digest::arbitrary(u)?,
1203                    CodingConfig::arbitrary(u)?,
1204                )))
1205            }
1206        }
1207    }
1208});
1209
1210#[cfg(test)]
1211mod tests {
1212    use super::*;
1213    use crate::types::coding::{COMMITMENT_SIZE, Commitment};
1214    use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize};
1215    use commonware_coding::{Config as CodingConfig, ReedSolomon};
1216    use commonware_cryptography::{Digest as DigestTrait, Digestible, Hasher};
1217    use commonware_math::algebra::Random;
1218    use commonware_utils::{Array, NZU16, NZU64, Span, test_rng};
1219    use std::{marker::PhantomData, ops::Deref};
1220
1221    #[derive(Clone)]
1222    struct TestBlock<D>(PhantomData<D>);
1223
1224    impl<D: DigestTrait> Digestible for TestBlock<D> {
1225        type Digest = D;
1226
1227        fn digest(&self) -> Self::Digest {
1228            unreachable!("test block is only used to bind commitment digest types")
1229        }
1230    }
1231
1232    #[derive(Clone)]
1233    struct TestHasher<D>(PhantomData<D>);
1234
1235    impl<D> Default for TestHasher<D> {
1236        fn default() -> Self {
1237            Self(PhantomData)
1238        }
1239    }
1240
1241    impl<D: DigestTrait> Hasher for TestHasher<D> {
1242        type Digest = D;
1243
1244        fn hash(_parts: &[&[u8]]) -> Self::Digest {
1245            D::EMPTY
1246        }
1247
1248        fn hash_pair(_left: &[&[u8]], _right: &[&[u8]]) -> (Self::Digest, Self::Digest) {
1249            (D::EMPTY, D::EMPTY)
1250        }
1251
1252        fn update(&mut self, _message: &[u8]) -> &mut Self {
1253            self
1254        }
1255
1256        fn finalize(self) -> (Self, Self::Digest) {
1257            (self, D::EMPTY)
1258        }
1259    }
1260
1261    #[test]
1262    fn test_epoch_constructors() {
1263        assert_eq!(Epoch::zero().get(), 0);
1264        assert_eq!(Epoch::new(42).get(), 42);
1265        assert_eq!(Epoch::default().get(), 0);
1266    }
1267
1268    #[test]
1269    fn test_epoch_is_zero() {
1270        assert!(Epoch::zero().is_zero());
1271        assert!(Epoch::new(0).is_zero());
1272        assert!(!Epoch::new(1).is_zero());
1273        assert!(!Epoch::new(100).is_zero());
1274    }
1275
1276    #[test]
1277    fn test_epoch_next() {
1278        assert_eq!(Epoch::zero().next().get(), 1);
1279        assert_eq!(Epoch::new(5).next().get(), 6);
1280        assert_eq!(Epoch::new(999).next().get(), 1000);
1281    }
1282
1283    #[test]
1284    #[should_panic(expected = "epoch overflow")]
1285    fn test_epoch_next_overflow() {
1286        Epoch::new(u64::MAX).next();
1287    }
1288
1289    #[test]
1290    fn test_epoch_previous() {
1291        assert_eq!(Epoch::zero().previous(), None);
1292        assert_eq!(Epoch::new(1).previous(), Some(Epoch::zero()));
1293        assert_eq!(Epoch::new(5).previous(), Some(Epoch::new(4)));
1294        assert_eq!(Epoch::new(1000).previous(), Some(Epoch::new(999)));
1295    }
1296
1297    #[test]
1298    fn test_epoch_saturating_add() {
1299        assert_eq!(Epoch::zero().saturating_add(EpochDelta::new(5)).get(), 5);
1300        assert_eq!(Epoch::new(10).saturating_add(EpochDelta::new(20)).get(), 30);
1301        assert_eq!(
1302            Epoch::new(u64::MAX)
1303                .saturating_add(EpochDelta::new(1))
1304                .get(),
1305            u64::MAX
1306        );
1307        assert_eq!(
1308            Epoch::new(u64::MAX - 5)
1309                .saturating_add(EpochDelta::new(10))
1310                .get(),
1311            u64::MAX
1312        );
1313    }
1314
1315    #[test]
1316    fn test_epoch_checked_sub() {
1317        assert_eq!(
1318            Epoch::new(10).checked_sub(EpochDelta::new(5)),
1319            Some(Epoch::new(5))
1320        );
1321        assert_eq!(
1322            Epoch::new(5).checked_sub(EpochDelta::new(5)),
1323            Some(Epoch::zero())
1324        );
1325        assert_eq!(Epoch::new(5).checked_sub(EpochDelta::new(10)), None);
1326        assert_eq!(Epoch::zero().checked_sub(EpochDelta::new(1)), None);
1327    }
1328
1329    #[test]
1330    fn test_epoch_saturating_sub() {
1331        assert_eq!(Epoch::new(10).saturating_sub(EpochDelta::new(5)).get(), 5);
1332        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(5)).get(), 0);
1333        assert_eq!(Epoch::new(5).saturating_sub(EpochDelta::new(10)).get(), 0);
1334        assert_eq!(Epoch::zero().saturating_sub(EpochDelta::new(100)).get(), 0);
1335    }
1336
1337    #[test]
1338    fn test_epoch_display() {
1339        assert_eq!(format!("{}", Epoch::zero()), "0");
1340        assert_eq!(format!("{}", Epoch::new(42)), "42");
1341        assert_eq!(format!("{}", Epoch::new(1000)), "1000");
1342    }
1343
1344    #[test]
1345    fn test_epoch_ordering() {
1346        assert!(Epoch::zero() < Epoch::new(1));
1347        assert!(Epoch::new(5) < Epoch::new(10));
1348        assert!(Epoch::new(10) > Epoch::new(5));
1349        assert_eq!(Epoch::new(42), Epoch::new(42));
1350    }
1351
1352    #[test]
1353    fn test_epoch_encode_decode() {
1354        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
1355        for value in cases {
1356            let epoch = Epoch::new(value);
1357            let encoded = epoch.encode();
1358            assert_eq!(encoded.len(), epoch.encode_size());
1359            let decoded = Epoch::decode(encoded).unwrap();
1360            assert_eq!(epoch, decoded);
1361        }
1362    }
1363
1364    #[test]
1365    fn test_height_constructors() {
1366        assert_eq!(Height::zero().get(), 0);
1367        assert_eq!(Height::new(42).get(), 42);
1368        assert_eq!(Height::new(100).get(), 100);
1369        assert_eq!(Height::default().get(), 0);
1370    }
1371
1372    #[test]
1373    fn test_height_is_zero() {
1374        assert!(Height::zero().is_zero());
1375        assert!(Height::new(0).is_zero());
1376        assert!(!Height::new(1).is_zero());
1377        assert!(!Height::new(100).is_zero());
1378    }
1379
1380    #[test]
1381    fn test_height_next() {
1382        assert_eq!(Height::zero().next().get(), 1);
1383        assert_eq!(Height::new(5).next().get(), 6);
1384        assert_eq!(Height::new(999).next().get(), 1000);
1385    }
1386
1387    #[test]
1388    #[should_panic(expected = "height overflow")]
1389    fn test_height_next_overflow() {
1390        Height::new(u64::MAX).next();
1391    }
1392
1393    #[test]
1394    fn test_height_previous() {
1395        assert_eq!(Height::zero().previous(), None);
1396        assert_eq!(Height::new(1).previous(), Some(Height::zero()));
1397        assert_eq!(Height::new(5).previous(), Some(Height::new(4)));
1398        assert_eq!(Height::new(1000).previous(), Some(Height::new(999)));
1399    }
1400
1401    #[test]
1402    fn test_height_saturating_add() {
1403        let delta5 = HeightDelta::new(5);
1404        let delta100 = HeightDelta::new(100);
1405        assert_eq!(Height::zero().saturating_add(delta5).get(), 5);
1406        assert_eq!(Height::new(10).saturating_add(delta100).get(), 110);
1407        assert_eq!(
1408            Height::new(u64::MAX)
1409                .saturating_add(HeightDelta::new(1))
1410                .get(),
1411            u64::MAX
1412        );
1413    }
1414
1415    #[test]
1416    fn test_height_saturating_sub() {
1417        let delta5 = HeightDelta::new(5);
1418        let delta100 = HeightDelta::new(100);
1419        assert_eq!(Height::new(10).saturating_sub(delta5).get(), 5);
1420        assert_eq!(Height::new(5).saturating_sub(delta5).get(), 0);
1421        assert_eq!(Height::new(5).saturating_sub(delta100).get(), 0);
1422        assert_eq!(Height::zero().saturating_sub(delta100).get(), 0);
1423    }
1424
1425    #[test]
1426    fn test_height_display() {
1427        assert_eq!(format!("{}", Height::zero()), "0");
1428        assert_eq!(format!("{}", Height::new(42)), "42");
1429        assert_eq!(format!("{}", Height::new(1000)), "1000");
1430    }
1431
1432    #[test]
1433    fn test_height_ordering() {
1434        assert!(Height::zero() < Height::new(1));
1435        assert!(Height::new(5) < Height::new(10));
1436        assert!(Height::new(10) > Height::new(5));
1437        assert_eq!(Height::new(42), Height::new(42));
1438    }
1439
1440    #[test]
1441    fn test_height_encode_decode() {
1442        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
1443        for value in cases {
1444            let height = Height::new(value);
1445            let encoded = height.encode();
1446            assert_eq!(encoded.len(), height.encode_size());
1447            let decoded = Height::decode(encoded).unwrap();
1448            assert_eq!(height, decoded);
1449        }
1450    }
1451
1452    #[test]
1453    fn test_height_delta_from() {
1454        assert_eq!(
1455            Height::new(10).delta_from(Height::new(3)),
1456            Some(HeightDelta::new(7))
1457        );
1458        assert_eq!(
1459            Height::new(5).delta_from(Height::new(5)),
1460            Some(HeightDelta::zero())
1461        );
1462        assert_eq!(Height::new(3).delta_from(Height::new(10)), None);
1463        assert_eq!(Height::zero().delta_from(Height::new(1)), None);
1464    }
1465
1466    #[test]
1467    fn height_range_iterates() {
1468        let collected: Vec<_> = Height::range(Height::new(3), Height::new(6))
1469            .map(Height::get)
1470            .collect();
1471        assert_eq!(collected, vec![3, 4, 5]);
1472    }
1473
1474    #[test]
1475    fn height_range_empty() {
1476        let collected: Vec<_> = Height::range(Height::new(5), Height::new(5)).collect();
1477        assert_eq!(collected, vec![]);
1478
1479        let collected: Vec<_> = Height::range(Height::new(10), Height::new(5)).collect();
1480        assert_eq!(collected, vec![]);
1481    }
1482
1483    #[test]
1484    fn height_range_single() {
1485        let collected: Vec<_> = Height::range(Height::new(5), Height::new(6))
1486            .map(Height::get)
1487            .collect();
1488        assert_eq!(collected, vec![5]);
1489    }
1490
1491    #[test]
1492    fn height_range_size_hint() {
1493        let range = Height::range(Height::new(3), Height::new(10));
1494        assert_eq!(range.size_hint(), (7, Some(7)));
1495        assert_eq!(range.len(), 7);
1496
1497        let empty = Height::range(Height::new(5), Height::new(5));
1498        assert_eq!(empty.size_hint(), (0, Some(0)));
1499        assert_eq!(empty.len(), 0);
1500    }
1501
1502    #[test]
1503    fn height_range_rev() {
1504        let collected: Vec<_> = Height::range(Height::new(3), Height::new(7))
1505            .rev()
1506            .map(Height::get)
1507            .collect();
1508        assert_eq!(collected, vec![6, 5, 4, 3]);
1509    }
1510
1511    #[test]
1512    fn height_range_double_ended() {
1513        let mut range = Height::range(Height::new(5), Height::new(10));
1514        assert_eq!(range.next(), Some(Height::new(5)));
1515        assert_eq!(range.next_back(), Some(Height::new(9)));
1516        assert_eq!(range.next(), Some(Height::new(6)));
1517        assert_eq!(range.next_back(), Some(Height::new(8)));
1518        assert_eq!(range.len(), 1);
1519        assert_eq!(range.next(), Some(Height::new(7)));
1520        assert_eq!(range.next(), None);
1521        assert_eq!(range.next_back(), None);
1522    }
1523
1524    #[test]
1525    fn test_view_constructors() {
1526        assert_eq!(View::zero().get(), 0);
1527        assert_eq!(View::new(42).get(), 42);
1528        assert_eq!(View::new(100).get(), 100);
1529        assert_eq!(View::default().get(), 0);
1530    }
1531
1532    #[test]
1533    fn test_view_is_zero() {
1534        assert!(View::zero().is_zero());
1535        assert!(View::new(0).is_zero());
1536        assert!(!View::new(1).is_zero());
1537        assert!(!View::new(100).is_zero());
1538    }
1539
1540    #[test]
1541    fn test_view_next() {
1542        assert_eq!(View::zero().next().get(), 1);
1543        assert_eq!(View::new(5).next().get(), 6);
1544        assert_eq!(View::new(999).next().get(), 1000);
1545    }
1546
1547    #[test]
1548    #[should_panic(expected = "view overflow")]
1549    fn test_view_next_overflow() {
1550        View::new(u64::MAX).next();
1551    }
1552
1553    #[test]
1554    fn test_view_previous() {
1555        assert_eq!(View::zero().previous(), None);
1556        assert_eq!(View::new(1).previous(), Some(View::zero()));
1557        assert_eq!(View::new(5).previous(), Some(View::new(4)));
1558        assert_eq!(View::new(1000).previous(), Some(View::new(999)));
1559    }
1560
1561    #[test]
1562    fn test_view_saturating_add() {
1563        let delta5 = ViewDelta::new(5);
1564        let delta100 = ViewDelta::new(100);
1565        assert_eq!(View::zero().saturating_add(delta5).get(), 5);
1566        assert_eq!(View::new(10).saturating_add(delta100).get(), 110);
1567        assert_eq!(
1568            View::new(u64::MAX).saturating_add(ViewDelta::new(1)).get(),
1569            u64::MAX
1570        );
1571    }
1572
1573    #[test]
1574    fn test_view_saturating_sub() {
1575        let delta5 = ViewDelta::new(5);
1576        let delta100 = ViewDelta::new(100);
1577        assert_eq!(View::new(10).saturating_sub(delta5).get(), 5);
1578        assert_eq!(View::new(5).saturating_sub(delta5).get(), 0);
1579        assert_eq!(View::new(5).saturating_sub(delta100).get(), 0);
1580        assert_eq!(View::zero().saturating_sub(delta100).get(), 0);
1581    }
1582
1583    #[test]
1584    fn test_view_display() {
1585        assert_eq!(format!("{}", View::zero()), "0");
1586        assert_eq!(format!("{}", View::new(42)), "42");
1587        assert_eq!(format!("{}", View::new(1000)), "1000");
1588    }
1589
1590    #[test]
1591    fn test_view_ordering() {
1592        assert!(View::zero() < View::new(1));
1593        assert!(View::new(5) < View::new(10));
1594        assert!(View::new(10) > View::new(5));
1595        assert_eq!(View::new(42), View::new(42));
1596    }
1597
1598    #[test]
1599    fn test_view_encode_decode() {
1600        let cases = vec![0u64, 1, 127, 128, 255, 256, u64::MAX];
1601        for value in cases {
1602            let view = View::new(value);
1603            let encoded = view.encode();
1604            assert_eq!(encoded.len(), view.encode_size());
1605            let decoded = View::decode(encoded).unwrap();
1606            assert_eq!(view, decoded);
1607        }
1608    }
1609
1610    #[test]
1611    fn test_view_term_start() {
1612        let cases = [
1613            (0, 5, 0),
1614            (1, 1, 1),
1615            (5, 1, 5),
1616            (6, 1, 6),
1617            (7, 1, 7),
1618            (1, 5, 1),
1619            (5, 5, 1),
1620            (6, 5, 6),
1621            (10, 5, 6),
1622            (11, 5, 11),
1623            (12, 3, 10),
1624        ];
1625        for (view, term_length, expected) in cases {
1626            assert_eq!(
1627                View::new(view).term_start(TermLength::new(commonware_utils::NZU32!(term_length))),
1628                View::new(expected),
1629                "view={view}, term_length={term_length}"
1630            );
1631        }
1632    }
1633
1634    #[test]
1635    fn test_view_term_end() {
1636        let cases = [
1637            (0, 5, 0),
1638            (1, 1, 1),
1639            (5, 1, 5),
1640            (1, 5, 5),
1641            (5, 5, 5),
1642            (6, 5, 10),
1643            (10, 5, 10),
1644            (11, 5, 15),
1645            (12, 3, 12),
1646        ];
1647        for (view, term_length, expected) in cases {
1648            assert_eq!(
1649                View::new(view).term_end(TermLength::new(commonware_utils::NZU32!(term_length))),
1650                View::new(expected),
1651                "view={view}, term_length={term_length}"
1652            );
1653        }
1654    }
1655
1656    #[test]
1657    fn test_view_is_term_start() {
1658        let cases = [
1659            (0, 1, true),
1660            (1, 1, true),
1661            (5, 1, true),
1662            (1, 5, true),
1663            (5, 5, false),
1664            (6, 5, true),
1665            (10, 5, false),
1666            (11, 5, true),
1667        ];
1668        for (view, term_length, expected) in cases {
1669            assert_eq!(
1670                View::new(view)
1671                    .is_term_start(TermLength::new(commonware_utils::NZU32!(term_length))),
1672                expected,
1673                "view={view}, term_length={term_length}"
1674            );
1675        }
1676    }
1677
1678    #[test]
1679    fn test_view_same_term() {
1680        let cases = [
1681            (0, 0, 1, true),
1682            (0, 0, 5, true),
1683            (0, 1, 5, false),
1684            (0, 5, 5, false),
1685            (1, 1, 1, true),
1686            (1, 2, 5, true),
1687            (1, 5, 5, true),
1688            (5, 6, 5, false),
1689            (6, 10, 5, true),
1690            (10, 11, 5, false),
1691            (11, 15, 5, true),
1692        ];
1693        for (a, b, term_length, expected) in cases {
1694            assert_eq!(
1695                View::new(a).same_term(
1696                    View::new(b),
1697                    TermLength::new(commonware_utils::NZU32!(term_length))
1698                ),
1699                expected,
1700                "a={a}, b={b}, term_length={term_length}"
1701            );
1702        }
1703    }
1704
1705    #[test]
1706    fn test_view_next_term_start() {
1707        let cases = [
1708            (0, 1, 1),
1709            (5, 1, 6),
1710            (1, 5, 6),
1711            (5, 5, 6),
1712            (6, 5, 11),
1713            (10, 5, 11),
1714            (11, 5, 16),
1715            (12, 3, 13),
1716        ];
1717        for (view, term_length, expected) in cases {
1718            assert_eq!(
1719                View::new(view)
1720                    .next_term_start(TermLength::new(commonware_utils::NZU32!(term_length))),
1721                View::new(expected),
1722                "view={view}, term_length={term_length}"
1723            );
1724        }
1725    }
1726
1727    #[test]
1728    fn test_view_term_index() {
1729        let cases = [
1730            (0, 1, 0),
1731            (1, 1, 1),
1732            (5, 1, 5),
1733            (0, 5, 0),
1734            (1, 5, 1),
1735            (5, 5, 1),
1736            (6, 5, 2),
1737            (10, 5, 2),
1738            (11, 5, 3),
1739        ];
1740        for (view, term_length, expected) in cases {
1741            assert_eq!(
1742                View::new(view).term_index(TermLength::new(commonware_utils::NZU32!(term_length))),
1743                expected,
1744                "view={view}, term_length={term_length}"
1745            );
1746        }
1747    }
1748
1749    #[test]
1750    fn test_view_covers() {
1751        let cases = [
1752            (0, 0, 5, true),
1753            (0, 3, 5, false),
1754            (1, 0, 5, false),
1755            (1, 1, 1, true),
1756            (1, 2, 1, false),
1757            (2, 1, 1, false),
1758            (6, 6, 5, true),
1759            (6, 8, 5, true),
1760            (6, 10, 5, true),
1761            (6, 11, 5, false),
1762            (8, 6, 5, false),
1763            (6, 5, 5, false),
1764        ];
1765        for (nullified, view, term_length, expected) in cases {
1766            assert_eq!(
1767                View::new(nullified).covers(
1768                    View::new(view),
1769                    TermLength::new(commonware_utils::NZU32!(term_length))
1770                ),
1771                expected,
1772                "nullified={nullified}, view={view}, term_length={term_length}"
1773            );
1774        }
1775    }
1776
1777    #[test]
1778    fn test_view_admits() {
1779        let cases = [
1780            (0, 0, 5, true),
1781            (0, 1, 5, true),
1782            (0, 2, 5, false),
1783            (0, 5, 5, false),
1784            (5, 4, 1, true),
1785            (5, 5, 1, true),
1786            (5, 6, 1, true),
1787            (5, 7, 1, false),
1788            (6, 7, 5, true),
1789            (6, 11, 5, true),
1790            (6, 8, 5, false),
1791            (6, 12, 5, false),
1792            (10, 11, 5, true),
1793            (10, 12, 5, false),
1794        ];
1795        for (current, pending, term_length, expected) in cases {
1796            assert_eq!(
1797                View::new(current).admits(
1798                    View::new(pending),
1799                    TermLength::new(commonware_utils::NZU32!(term_length))
1800                ),
1801                expected,
1802                "current={current}, pending={pending}, term_length={term_length}"
1803            );
1804        }
1805    }
1806
1807    #[test]
1808    #[should_panic(expected = "view term_end overflow")]
1809    fn test_view_term_end_overflow_panics() {
1810        let _ = View::new(u64::MAX).term_end(TermLength::new(commonware_utils::NZU32!(2)));
1811    }
1812
1813    #[test]
1814    #[should_panic(expected = "view overflow")]
1815    fn test_view_next_term_start_overflow_panics() {
1816        let _ = View::new(u64::MAX).next_term_start(TermLength::ONE);
1817    }
1818
1819    #[test]
1820    fn test_view_admits_near_max_does_not_panic() {
1821        let term_length = TermLength::new(commonware_utils::NZU32!(5));
1822        // The next term start overflows, so only lower views and the
1823        // successor are admitted.
1824        let current = View::new(u64::MAX - 2);
1825        assert!(current.admits(View::new(0), term_length));
1826        assert!(current.admits(View::new(u64::MAX - 1), term_length));
1827        assert!(!current.admits(View::new(u64::MAX), term_length));
1828    }
1829
1830    #[test]
1831    fn test_view_delta_constructors() {
1832        assert_eq!(ViewDelta::zero().get(), 0);
1833        assert_eq!(ViewDelta::new(42).get(), 42);
1834        assert_eq!(ViewDelta::new(100).get(), 100);
1835        assert_eq!(ViewDelta::default().get(), 0);
1836    }
1837
1838    #[test]
1839    fn test_view_delta_is_zero() {
1840        assert!(ViewDelta::zero().is_zero());
1841        assert!(ViewDelta::new(0).is_zero());
1842        assert!(!ViewDelta::new(1).is_zero());
1843        assert!(!ViewDelta::new(100).is_zero());
1844    }
1845
1846    #[test]
1847    fn test_view_delta_display() {
1848        assert_eq!(format!("{}", ViewDelta::zero()), "0");
1849        assert_eq!(format!("{}", ViewDelta::new(42)), "42");
1850        assert_eq!(format!("{}", ViewDelta::new(1000)), "1000");
1851    }
1852
1853    #[test]
1854    fn test_view_delta_ordering() {
1855        assert!(ViewDelta::zero() < ViewDelta::new(1));
1856        assert!(ViewDelta::new(5) < ViewDelta::new(10));
1857        assert!(ViewDelta::new(10) > ViewDelta::new(5));
1858        assert_eq!(ViewDelta::new(42), ViewDelta::new(42));
1859    }
1860
1861    #[test]
1862    fn test_round_cmp() {
1863        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(1), View::new(3)));
1864        assert!(Round::new(Epoch::new(1), View::new(2)) < Round::new(Epoch::new(2), View::new(1)));
1865    }
1866
1867    #[test]
1868    fn test_round_encode_decode_roundtrip() {
1869        let r: Round = (Epoch::new(42), View::new(1_000_000)).into();
1870        let encoded = r.encode();
1871        assert_eq!(encoded.len(), r.encode_size());
1872        let decoded = Round::decode(encoded).unwrap();
1873        assert_eq!(r, decoded);
1874    }
1875
1876    #[test]
1877    fn test_round_conversions() {
1878        let r: Round = (Epoch::new(5), View::new(6)).into();
1879        assert_eq!(r.epoch(), Epoch::new(5));
1880        assert_eq!(r.view(), View::new(6));
1881        let tuple: (Epoch, View) = r.into();
1882        assert_eq!(tuple, (Epoch::new(5), View::new(6)));
1883    }
1884
1885    #[test]
1886    fn test_round_new() {
1887        let r = Round::new(Epoch::new(10), View::new(20));
1888        assert_eq!(r.epoch(), Epoch::new(10));
1889        assert_eq!(r.view(), View::new(20));
1890
1891        let r2 = Round::new(Epoch::new(5), View::new(15));
1892        assert_eq!(r2.epoch(), Epoch::new(5));
1893        assert_eq!(r2.view(), View::new(15));
1894    }
1895
1896    #[test]
1897    fn test_round_display() {
1898        let r = Round::new(Epoch::new(5), View::new(100));
1899        assert_eq!(format!("{r}"), "(5, 100)");
1900    }
1901
1902    #[test]
1903    fn view_range_iterates() {
1904        let collected: Vec<_> = View::range(View::new(3), View::new(6))
1905            .map(View::get)
1906            .collect();
1907        assert_eq!(collected, vec![3, 4, 5]);
1908    }
1909
1910    #[test]
1911    fn view_range_empty() {
1912        let collected: Vec<_> = View::range(View::new(5), View::new(5)).collect();
1913        assert_eq!(collected, vec![]);
1914
1915        let collected: Vec<_> = View::range(View::new(10), View::new(5)).collect();
1916        assert_eq!(collected, vec![]);
1917    }
1918
1919    #[test]
1920    fn view_range_single() {
1921        let collected: Vec<_> = View::range(View::new(5), View::new(6))
1922            .map(View::get)
1923            .collect();
1924        assert_eq!(collected, vec![5]);
1925    }
1926
1927    #[test]
1928    fn view_range_size_hint() {
1929        let range = View::range(View::new(3), View::new(10));
1930        assert_eq!(range.size_hint(), (7, Some(7)));
1931        assert_eq!(range.len(), 7);
1932
1933        let empty = View::range(View::new(5), View::new(5));
1934        assert_eq!(empty.size_hint(), (0, Some(0)));
1935        assert_eq!(empty.len(), 0);
1936    }
1937
1938    #[test]
1939    fn view_range_collect() {
1940        let views: Vec<View> = View::range(View::new(0), View::new(3)).collect();
1941        assert_eq!(views, vec![View::zero(), View::new(1), View::new(2)]);
1942    }
1943
1944    #[test]
1945    fn view_range_iterator_next() {
1946        let mut range = View::range(View::new(5), View::new(8));
1947        assert_eq!(range.next(), Some(View::new(5)));
1948        assert_eq!(range.next(), Some(View::new(6)));
1949        assert_eq!(range.next(), Some(View::new(7)));
1950        assert_eq!(range.next(), None);
1951        assert_eq!(range.next(), None); // Multiple None
1952    }
1953
1954    #[test]
1955    fn view_range_exact_size_iterator() {
1956        let range = View::range(View::new(10), View::new(15));
1957        assert_eq!(range.len(), 5);
1958        assert_eq!(range.size_hint(), (5, Some(5)));
1959
1960        let mut range = View::range(View::new(10), View::new(15));
1961        assert_eq!(range.len(), 5);
1962        range.next();
1963        assert_eq!(range.len(), 4);
1964        range.next();
1965        assert_eq!(range.len(), 3);
1966    }
1967
1968    #[test]
1969    fn view_range_rev() {
1970        // Use .rev() to iterate in descending order
1971        let collected: Vec<_> = View::range(View::new(3), View::new(7))
1972            .rev()
1973            .map(View::get)
1974            .collect();
1975        assert_eq!(collected, vec![6, 5, 4, 3]);
1976    }
1977
1978    #[test]
1979    fn view_range_double_ended() {
1980        // Mixed next() and next_back() calls
1981        let mut range = View::range(View::new(5), View::new(10));
1982        assert_eq!(range.next(), Some(View::new(5)));
1983        assert_eq!(range.next_back(), Some(View::new(9)));
1984        assert_eq!(range.next(), Some(View::new(6)));
1985        assert_eq!(range.next_back(), Some(View::new(8)));
1986        assert_eq!(range.len(), 1);
1987        assert_eq!(range.next(), Some(View::new(7)));
1988        assert_eq!(range.next(), None);
1989        assert_eq!(range.next_back(), None);
1990    }
1991
1992    #[test]
1993    fn test_fixed_epoch_strategy() {
1994        let epocher = FixedEpocher::new(NZU64!(100));
1995
1996        // Test containing returns correct EpochInfo
1997        let bounds = epocher.containing(Height::zero()).unwrap();
1998        assert_eq!(bounds.epoch(), Epoch::new(0));
1999        assert_eq!(bounds.first(), Height::zero());
2000        assert_eq!(bounds.last(), Height::new(99));
2001        assert_eq!(bounds.length(), HeightDelta::new(100));
2002
2003        let bounds = epocher.containing(Height::new(99)).unwrap();
2004        assert_eq!(bounds.epoch(), Epoch::new(0));
2005
2006        let bounds = epocher.containing(Height::new(100)).unwrap();
2007        assert_eq!(bounds.epoch(), Epoch::new(1));
2008        assert_eq!(bounds.first(), Height::new(100));
2009        assert_eq!(bounds.last(), Height::new(199));
2010
2011        // Test first/last return correct boundaries
2012        assert_eq!(epocher.first(Epoch::new(0)), Some(Height::zero()));
2013        assert_eq!(epocher.last(Epoch::new(0)), Some(Height::new(99)));
2014        assert_eq!(epocher.first(Epoch::new(1)), Some(Height::new(100)));
2015        assert_eq!(epocher.last(Epoch::new(1)), Some(Height::new(199)));
2016        assert_eq!(epocher.first(Epoch::new(5)), Some(Height::new(500)));
2017        assert_eq!(epocher.last(Epoch::new(5)), Some(Height::new(599)));
2018    }
2019
2020    #[test]
2021    fn test_epoch_bounds_relative() {
2022        let epocher = FixedEpocher::new(NZU64!(100));
2023
2024        // Epoch 0: heights 0-99
2025        assert_eq!(
2026            epocher.containing(Height::zero()).unwrap().relative(),
2027            Height::zero()
2028        );
2029        assert_eq!(
2030            epocher.containing(Height::new(50)).unwrap().relative(),
2031            Height::new(50)
2032        );
2033        assert_eq!(
2034            epocher.containing(Height::new(99)).unwrap().relative(),
2035            Height::new(99)
2036        );
2037
2038        // Epoch 1: heights 100-199
2039        assert_eq!(
2040            epocher.containing(Height::new(100)).unwrap().relative(),
2041            Height::zero()
2042        );
2043        assert_eq!(
2044            epocher.containing(Height::new(150)).unwrap().relative(),
2045            Height::new(50)
2046        );
2047        assert_eq!(
2048            epocher.containing(Height::new(199)).unwrap().relative(),
2049            Height::new(99)
2050        );
2051
2052        // Epoch 5: heights 500-599
2053        assert_eq!(
2054            epocher.containing(Height::new(500)).unwrap().relative(),
2055            Height::zero()
2056        );
2057        assert_eq!(
2058            epocher.containing(Height::new(567)).unwrap().relative(),
2059            Height::new(67)
2060        );
2061        assert_eq!(
2062            epocher.containing(Height::new(599)).unwrap().relative(),
2063            Height::new(99)
2064        );
2065    }
2066
2067    #[test]
2068    fn test_epoch_bounds_phase() {
2069        // Test with epoch length of 30 (midpoint = 15)
2070        let epocher = FixedEpocher::new(NZU64!(30));
2071
2072        // Early phase: relative 0-14
2073        assert_eq!(
2074            epocher.containing(Height::zero()).unwrap().phase(),
2075            EpochPhase::Early
2076        );
2077        assert_eq!(
2078            epocher.containing(Height::new(14)).unwrap().phase(),
2079            EpochPhase::Early
2080        );
2081
2082        // Midpoint: relative 15
2083        assert_eq!(
2084            epocher.containing(Height::new(15)).unwrap().phase(),
2085            EpochPhase::Midpoint
2086        );
2087
2088        // Late phase: relative 16-29
2089        assert_eq!(
2090            epocher.containing(Height::new(16)).unwrap().phase(),
2091            EpochPhase::Late
2092        );
2093        assert_eq!(
2094            epocher.containing(Height::new(29)).unwrap().phase(),
2095            EpochPhase::Late
2096        );
2097
2098        // Second epoch starts at height 30
2099        assert_eq!(
2100            epocher.containing(Height::new(30)).unwrap().phase(),
2101            EpochPhase::Early
2102        );
2103        assert_eq!(
2104            epocher.containing(Height::new(44)).unwrap().phase(),
2105            EpochPhase::Early
2106        );
2107        assert_eq!(
2108            epocher.containing(Height::new(45)).unwrap().phase(),
2109            EpochPhase::Midpoint
2110        );
2111        assert_eq!(
2112            epocher.containing(Height::new(46)).unwrap().phase(),
2113            EpochPhase::Late
2114        );
2115
2116        // Test with epoch length 10 (midpoint = 5)
2117        let epocher = FixedEpocher::new(NZU64!(10));
2118        assert_eq!(
2119            epocher.containing(Height::zero()).unwrap().phase(),
2120            EpochPhase::Early
2121        );
2122        assert_eq!(
2123            epocher.containing(Height::new(4)).unwrap().phase(),
2124            EpochPhase::Early
2125        );
2126        assert_eq!(
2127            epocher.containing(Height::new(5)).unwrap().phase(),
2128            EpochPhase::Midpoint
2129        );
2130        assert_eq!(
2131            epocher.containing(Height::new(6)).unwrap().phase(),
2132            EpochPhase::Late
2133        );
2134        assert_eq!(
2135            epocher.containing(Height::new(9)).unwrap().phase(),
2136            EpochPhase::Late
2137        );
2138
2139        // Test with odd epoch length 11 (midpoint = 5 via integer division)
2140        let epocher = FixedEpocher::new(NZU64!(11));
2141        assert_eq!(
2142            epocher.containing(Height::zero()).unwrap().phase(),
2143            EpochPhase::Early
2144        );
2145        assert_eq!(
2146            epocher.containing(Height::new(4)).unwrap().phase(),
2147            EpochPhase::Early
2148        );
2149        assert_eq!(
2150            epocher.containing(Height::new(5)).unwrap().phase(),
2151            EpochPhase::Midpoint
2152        );
2153        assert_eq!(
2154            epocher.containing(Height::new(6)).unwrap().phase(),
2155            EpochPhase::Late
2156        );
2157        assert_eq!(
2158            epocher.containing(Height::new(10)).unwrap().phase(),
2159            EpochPhase::Late
2160        );
2161    }
2162
2163    #[test]
2164    #[should_panic(expected = "epoch length must exceed one")]
2165    fn test_fixed_epocher_rejects_length_one() {
2166        let _ = FixedEpocher::new(NZU64!(1));
2167    }
2168
2169    #[test]
2170    fn test_fixed_epocher_overflow() {
2171        // Test that containing() returns None when last() would overflow
2172        let epocher = FixedEpocher::new(NZU64!(100));
2173
2174        // For epoch length 100:
2175        // - last valid epoch = (u64::MAX - 100 + 1) / 100 = 184467440737095515
2176        // - last valid first = 184467440737095515 * 100 = 18446744073709551500
2177        // - last valid last = 18446744073709551500 + 99 = 18446744073709551599
2178        // Heights 18446744073709551500 to 18446744073709551599 are in the last valid epoch
2179        // Height 18446744073709551600 onwards would be in an invalid epoch
2180
2181        // This height is in the last valid epoch
2182        let last_valid_first = Height::new(18446744073709551500u64);
2183        let last_valid_last = Height::new(18446744073709551599u64);
2184
2185        let result = epocher.containing(last_valid_first);
2186        assert!(result.is_some());
2187        let bounds = result.unwrap();
2188        assert_eq!(bounds.first(), last_valid_first);
2189        assert_eq!(bounds.last(), last_valid_last);
2190
2191        let result = epocher.containing(last_valid_last);
2192        assert!(result.is_some());
2193        assert_eq!(result.unwrap().last(), last_valid_last);
2194
2195        // This height would be in an epoch where last() overflows
2196        let overflow_height = last_valid_last.next();
2197        assert!(epocher.containing(overflow_height).is_none());
2198
2199        // u64::MAX is also in the overflow range
2200        assert!(epocher.containing(Height::new(u64::MAX)).is_none());
2201
2202        // Test the boundary more precisely with epoch length 2
2203        let epocher = FixedEpocher::new(NZU64!(2));
2204
2205        // u64::MAX - 1 is even, so epoch starts at u64::MAX - 1, last = u64::MAX
2206        let result = epocher.containing(Height::new(u64::MAX - 1));
2207        assert!(result.is_some());
2208        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
2209
2210        // u64::MAX is odd, epoch would start at u64::MAX - 1
2211        // first = u64::MAX - 1, last = first + 2 - 1 = u64::MAX (OK)
2212        let result = epocher.containing(Height::new(u64::MAX));
2213        assert!(result.is_some());
2214        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
2215
2216        // Test with the smallest epoch length (the final epoch ends exactly at u64::MAX)
2217        let epocher = FixedEpocher::new(NZU64!(2));
2218        let result = epocher.containing(Height::new(u64::MAX));
2219        assert!(result.is_some());
2220        assert_eq!(result.unwrap().last(), Height::new(u64::MAX));
2221
2222        // Test case where first overflows (covered by existing checked_mul)
2223        let epocher = FixedEpocher::new(NZU64!(u64::MAX));
2224        assert!(epocher.containing(Height::new(u64::MAX)).is_none());
2225
2226        // Test consistency: first(), last(), and containing() should agree on valid epochs
2227        let epocher = FixedEpocher::new(NZU64!(100));
2228        let last_valid_epoch = Epoch::new(184467440737095515);
2229        let first_invalid_epoch = Epoch::new(184467440737095516);
2230
2231        // For last valid epoch, all methods should return Some
2232        assert!(epocher.first(last_valid_epoch).is_some());
2233        assert!(epocher.last(last_valid_epoch).is_some());
2234        let first = epocher.first(last_valid_epoch).unwrap();
2235        assert!(epocher.containing(first).is_some());
2236        assert_eq!(
2237            epocher.containing(first).unwrap().last(),
2238            epocher.last(last_valid_epoch).unwrap()
2239        );
2240
2241        // For first invalid epoch, all methods should return None
2242        assert!(epocher.first(first_invalid_epoch).is_none());
2243        assert!(epocher.last(first_invalid_epoch).is_none());
2244        assert!(epocher.containing(last_valid_last.next()).is_none());
2245    }
2246
2247    #[test]
2248    fn test_coding_commitment_fallible_digest() {
2249        #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2250        struct Digest([u8; Self::SIZE]);
2251
2252        impl Random for Digest {
2253            fn random(mut rng: impl rand_core::CryptoRng) -> Self {
2254                let mut buf = [0u8; Self::SIZE];
2255                rng.fill_bytes(&mut buf);
2256                Self(buf)
2257            }
2258        }
2259
2260        impl commonware_cryptography::Digest for Digest {
2261            const EMPTY: Self = Self([0u8; Self::SIZE]);
2262        }
2263
2264        impl Write for Digest {
2265            fn write(&self, buf: &mut impl BufMut) {
2266                buf.put_slice(&self.0);
2267            }
2268        }
2269
2270        impl FixedSize for Digest {
2271            const SIZE: usize = 32;
2272        }
2273
2274        impl Read for Digest {
2275            type Cfg = ();
2276
2277            fn read_cfg(
2278                _: &mut impl bytes::Buf,
2279                _: &Self::Cfg,
2280            ) -> Result<Self, commonware_codec::Error> {
2281                Err(commonware_codec::Error::Invalid(
2282                    "Digest",
2283                    "read not implemented",
2284                ))
2285            }
2286        }
2287
2288        impl AsRef<[u8]> for Digest {
2289            fn as_ref(&self) -> &[u8] {
2290                &self.0
2291            }
2292        }
2293
2294        impl Deref for Digest {
2295            type Target = [u8];
2296
2297            fn deref(&self) -> &Self::Target {
2298                &self.0
2299            }
2300        }
2301
2302        impl core::fmt::Display for Digest {
2303            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2304                write!(f, "{}", commonware_formatting::Hex(self.as_ref()))
2305            }
2306        }
2307
2308        impl core::fmt::Debug for Digest {
2309            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2310                write!(f, "Digest({})", commonware_formatting::Hex(self.as_ref()))
2311            }
2312        }
2313
2314        impl Span for Digest {}
2315        impl Array for Digest {}
2316
2317        let digest = Digest::random(test_rng());
2318        let config = CodingConfig {
2319            minimum_shards: NZU16!(1),
2320            extra_shards: NZU16!(1),
2321        };
2322        type Sha256Digest = commonware_cryptography::sha256::Digest;
2323        type InvalidBlockCommitment =
2324            Commitment<TestBlock<Digest>, ReedSolomon<TestHasher<Digest>>, TestHasher<Digest>>;
2325        let commitment = InvalidBlockCommitment::from((digest, digest, digest, config));
2326        assert!(InvalidBlockCommitment::decode(commitment.encode()).is_err());
2327
2328        type InvalidRootCommitment = Commitment<
2329            TestBlock<Sha256Digest>,
2330            ReedSolomon<TestHasher<Digest>>,
2331            TestHasher<Sha256Digest>,
2332        >;
2333        let commitment =
2334            InvalidRootCommitment::from((Sha256Digest::EMPTY, digest, Sha256Digest::EMPTY, config));
2335        assert!(InvalidRootCommitment::decode(commitment.encode()).is_err());
2336
2337        type InvalidContextCommitment = Commitment<
2338            TestBlock<Sha256Digest>,
2339            ReedSolomon<TestHasher<Sha256Digest>>,
2340            TestHasher<Digest>,
2341        >;
2342        let commitment = InvalidContextCommitment::from((
2343            Sha256Digest::EMPTY,
2344            Sha256Digest::EMPTY,
2345            digest,
2346            config,
2347        ));
2348        assert!(InvalidContextCommitment::decode(commitment.encode()).is_err());
2349    }
2350
2351    #[test]
2352    fn test_coding_commitment_supports_short_digest_types() {
2353        type CrcCommitment = Commitment<
2354            TestBlock<commonware_cryptography::crc32::Digest>,
2355            ReedSolomon<commonware_cryptography::Crc32>,
2356            commonware_cryptography::Crc32,
2357        >;
2358
2359        let block = commonware_cryptography::crc32::Digest::from(1);
2360        let root = commonware_cryptography::crc32::Digest::from(2);
2361        let context = commonware_cryptography::crc32::Digest::from(3);
2362        let config = CodingConfig {
2363            minimum_shards: NZU16!(1),
2364            extra_shards: NZU16!(1),
2365        };
2366        let commitment = CrcCommitment::from((block, root, context, config));
2367
2368        assert_eq!(CrcCommitment::SIZE, COMMITMENT_SIZE);
2369        assert_eq!(commitment.encode().len(), COMMITMENT_SIZE);
2370
2371        let decoded = CrcCommitment::decode(commitment.encode()).unwrap();
2372        assert_eq!(decoded.block(), block);
2373        assert_eq!(decoded.root(), root);
2374        assert_eq!(decoded.context(), context);
2375        assert_eq!(decoded.config(), config);
2376    }
2377
2378    #[test]
2379    fn test_coding_commitment_rejects_non_zero_digest_padding() {
2380        type CrcCommitment = Commitment<
2381            TestBlock<commonware_cryptography::crc32::Digest>,
2382            ReedSolomon<commonware_cryptography::Crc32>,
2383            commonware_cryptography::Crc32,
2384        >;
2385
2386        let config = CodingConfig {
2387            minimum_shards: NZU16!(1),
2388            extra_shards: NZU16!(1),
2389        };
2390        let commitment = CrcCommitment::from((
2391            commonware_cryptography::crc32::Digest::from(1),
2392            commonware_cryptography::crc32::Digest::from(2),
2393            commonware_cryptography::crc32::Digest::from(3),
2394            config,
2395        ));
2396        let encoded = commitment.encode();
2397        for offset in [
2398            commonware_cryptography::crc32::Digest::SIZE,
2399            32 + commonware_cryptography::crc32::Digest::SIZE,
2400            64 + commonware_cryptography::crc32::Digest::SIZE,
2401        ] {
2402            let mut malformed = encoded.to_vec();
2403            malformed[offset] = 1;
2404            assert!(CrcCommitment::decode(malformed.as_ref()).is_err());
2405        }
2406    }
2407
2408    #[cfg(feature = "arbitrary")]
2409    mod conformance {
2410        use super::{coding::Commitment, *};
2411        use commonware_codec::conformance::CodecConformance;
2412        use commonware_cryptography::sha256::{Digest as Sha256Digest, Sha256};
2413
2414        type TestCommitment = Commitment<TestBlock<Sha256Digest>, ReedSolomon<Sha256>, Sha256>;
2415
2416        commonware_conformance::conformance_tests! {
2417            CodecConformance<Epoch>,
2418            CodecConformance<Height>,
2419            CodecConformance<View>,
2420            CodecConformance<Round>,
2421            CodecConformance<TestCommitment>,
2422        }
2423    }
2424}