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