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