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