use crate::types::{Height, Round}; use bytes::{Buf, BufMut, Bytes}; use commonware_actor::mailbox::{self, Overflow, Policy, Sender}; use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write}; use commonware_cryptography::Digest; use commonware_resolver::{p2p::Producer, Consumer, Delivery, Fetch as ResolverFetch}; use commonware_runtime::Metrics; use commonware_utils::{channel::oneshot, Span}; use std::{ collections::VecDeque, fmt::{Debug, Display}, hash::{Hash, Hasher}, num::NonZeroUsize, sync::mpsc::TryRecvError, }; /// The subject of a backfill request. const BLOCK_REQUEST: u8 = 0; const FINALIZED_REQUEST: u8 = 1; const NOTARIZED_REQUEST: u8 = 2; /// Messages sent from the resolver's [Consumer]/[Producer] implementation /// to the marshal actor. pub(crate) enum Message { /// A request to deliver a value for a given key. Deliver { /// The delivery metadata attached to the resolved value. delivery: Delivery, Annotation>, /// The value being delivered. value: Bytes, /// A channel to send the result of the delivery. response: oneshot::Sender, }, /// A request to produce a value for a given key. Produce { /// The key of the value to produce. key: Key, /// A channel to send the produced value. response: oneshot::Sender, }, } impl Message { /// Returns true if the requester has stopped waiting for this response. pub(crate) fn response_closed(&self) -> bool { match self { Self::Deliver { response, .. } => response.is_closed(), Self::Produce { response, .. } => response.is_closed(), } } } /// Pending resolver handler messages retained after the mailbox fills. pub(crate) struct Pending(VecDeque>); impl Default for Pending { fn default() -> Self { Self(VecDeque::new()) } } impl Overflow> for Pending { fn is_empty(&self) -> bool { self.0.is_empty() } fn drain(&mut self, mut push: F) where F: FnMut(Message) -> Option>, { while let Some(message) = self.0.pop_front() { if message.response_closed() { continue; } if let Some(message) = push(message) { self.0.push_front(message); break; } } } } impl Policy for Message { type Overflow = Pending; fn handle(overflow: &mut Self::Overflow, message: Self) { if message.response_closed() { return; } overflow.0.push_back(message); } } /// A handler that forwards requests from the resolver to the marshal actor. /// /// This struct implements the [Consumer] and [Producer] traits from the /// resolver, and acts as a bridge to the main actor loop. #[derive(Clone)] pub struct Handler { sender: Sender>, } impl Handler { /// Creates a new handler. pub(crate) const fn new(sender: Sender>) -> Self { Self { sender } } } /// Creates a resolver receiver and handler pair. pub fn init(metrics: impl Metrics, capacity: NonZeroUsize) -> (Receiver, Handler) { let (sender, receiver) = mailbox::new(metrics, capacity); (Receiver::new(receiver), Handler::new(sender)) } /// Receiver for resolver handler messages. pub struct Receiver { inner: mailbox::Receiver>, } impl Receiver { pub(crate) const fn new(inner: mailbox::Receiver>) -> Self { Self { inner } } pub(crate) async fn recv(&mut self) -> Option> { self.inner.recv().await } pub(crate) fn try_recv(&mut self) -> Result, TryRecvError> { self.inner.try_recv() } } impl Consumer for Handler { type Key = Key; type Value = Bytes; type Subscriber = Annotation; fn deliver( &mut self, delivery: Delivery, value: Self::Value, ) -> oneshot::Receiver { let (response, receiver) = oneshot::channel(); let _ = self.sender.enqueue(Message::Deliver { delivery, value, response, }); receiver } } impl Producer for Handler { type Key = Key; fn produce(&mut self, key: Self::Key) -> oneshot::Receiver { let (response, receiver) = oneshot::channel(); let _ = self.sender.enqueue(Message::Produce { key, response }); receiver } } /// Local processing annotation for a resolved key. /// /// The resolver key is the peer-visible lookup. An annotation is local /// metadata attached to that lookup so marshal can decide how to process the /// response after validating it against the key. It is not part of peer /// response validity. Multiple local annotations may share one peer key when /// they depend on the same block. /// /// [`Notarization`](Annotation::Notarization) carries round-bound local /// context. [`Certified`](Annotation::Certified) and /// [`Finalized`](Annotation::Finalized) describe how block-bearing responses /// should be processed locally. /// /// This storage role is part of the annotation because a [`Key::Block`] /// only names the peer-visible commitment. The same block-shaped response may /// need to update different local stores depending on whether it was fetched /// for a certified chain or for the finalized chain. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Annotation { /// A notarization requested by round. Notarization { round: Round }, /// A block requested by commitment for a certified chain. /// /// The expected height is local pruning metadata and should only be /// supplied when the caller has a validated height bound. It must not make /// a commitment-matching response invalid, and certified storage uses the /// fetched block's decoded height. Certified { height: Height }, /// A block requested by commitment for the finalized chain. Finalized(Finalized), } /// Metadata for a finalized block requested by commitment. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Finalized { /// The finalized height is known before the request. ByHeight { height: Height }, /// Only the finalization round is known before the request. /// /// This happens when a finalization names the block commitment but not the /// block height. ByRound { round: Round }, } /// A raw resolver key for backfilling data. #[derive(Clone, Copy)] pub enum Key { /// Fetch a block by consensus commitment. Block(D), Finalized { height: Height, }, Notarized { round: Round, }, } impl Key { /// The subject of the request. const fn subject(&self) -> u8 { match self { Self::Block(_) => BLOCK_REQUEST, Self::Finalized { .. } => FINALIZED_REQUEST, Self::Notarized { .. } => NOTARIZED_REQUEST, } } } /// A valid marshal backfill fetch request. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum RequestKind { /// Fetch a notarized proposal for a round. Notarized { round: Round }, /// Fetch a finalization for a height. Finalized { height: Height }, /// Fetch a certified-chain block by commitment. CertifiedBlock { commitment: D, height: Height }, /// Fetch a finalized-chain block by commitment when its height is known. FinalizedBlockByHeight { commitment: D, height: Height }, /// Fetch a finalized-chain block by commitment when only its finalization round is known. FinalizedBlockByRound { commitment: D, round: Round }, } /// A marshal backfill fetch with a request and local processing annotation that match. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Request { kind: RequestKind, } impl Request { /// Fetch a notarized proposal for `round`. pub const fn notarized(round: Round) -> Self { Self { kind: RequestKind::Notarized { round }, } } /// Fetch a finalization for `height`. pub const fn finalized(height: Height) -> Self { Self { kind: RequestKind::Finalized { height }, } } /// Fetch a certified-chain block by commitment. pub const fn certified_block(commitment: D, height: Height) -> Self { Self { kind: RequestKind::CertifiedBlock { commitment, height }, } } /// Fetch a finalized-chain block by commitment when its height is known. pub const fn finalized_block_by_height(commitment: D, height: Height) -> Self { Self { kind: RequestKind::FinalizedBlockByHeight { commitment, height }, } } /// Fetch a finalized-chain block by commitment when only its finalization round is known. pub const fn finalized_block_by_round(commitment: D, round: Round) -> Self { Self { kind: RequestKind::FinalizedBlockByRound { commitment, round }, } } pub(crate) fn above_height_floor(&self, floor: Height) -> bool { match self.kind { RequestKind::Finalized { height } | RequestKind::CertifiedBlock { height, .. } | RequestKind::FinalizedBlockByHeight { height, .. } => height > floor, RequestKind::Notarized { .. } | RequestKind::FinalizedBlockByRound { .. } => true, } } pub(crate) fn above_round_floor(&self, floor: Round) -> bool { match self.kind { RequestKind::Notarized { round } | RequestKind::FinalizedBlockByRound { round, .. } => { round > floor } RequestKind::Finalized { .. } | RequestKind::CertifiedBlock { .. } | RequestKind::FinalizedBlockByHeight { .. } => true, } } pub(crate) const fn into_inner(self) -> ResolverFetch, Annotation> { match self.kind { RequestKind::Notarized { round } => ResolverFetch { key: Key::Notarized { round }, subscriber: Annotation::Notarization { round }, }, RequestKind::Finalized { height } => ResolverFetch { key: Key::Finalized { height }, subscriber: Annotation::Finalized(Finalized::ByHeight { height }), }, RequestKind::CertifiedBlock { commitment, height } => ResolverFetch { key: Key::Block(commitment), subscriber: Annotation::Certified { height }, }, RequestKind::FinalizedBlockByHeight { commitment, height } => ResolverFetch { key: Key::Block(commitment), subscriber: Annotation::Finalized(Finalized::ByHeight { height }), }, RequestKind::FinalizedBlockByRound { commitment, round } => ResolverFetch { key: Key::Block(commitment), subscriber: Annotation::Finalized(Finalized::ByRound { round }), }, } } } impl From> for ResolverFetch, Annotation> { fn from(fetch: Request) -> Self { fetch.into_inner() } } /// Returns a predicate that keeps resolver requests above the processed height floor. /// /// Unrelated requests are retained. Height-bound requests are pruned once the /// processed height reaches them. pub(crate) fn above_height_floor( height: Height, ) -> impl Fn(&Key, &Annotation) -> bool + Send + 'static { move |request, annotation| match (request, annotation) { (Key::Finalized { height: requested }, _) => *requested > height, ( Key::Block(_), Annotation::Certified { height: requested } | Annotation::Finalized(Finalized::ByHeight { height: requested }), ) => *requested > height, _ => true, } } /// Returns a predicate that keeps resolver requests above the processed round floor. /// /// Unrelated requests are retained. Round-bound requests are pruned once the /// processed round reaches them. pub(crate) fn above_round_floor( round: Round, ) -> impl Fn(&Key, &Annotation) -> bool + Send + 'static { move |request, annotation| match (request, annotation) { (Key::Notarized { round: requested }, _) => *requested > round, (Key::Block(_), Annotation::Finalized(Finalized::ByRound { round: requested })) => { *requested > round } _ => true, } } impl Write for Key { fn write(&self, buf: &mut impl BufMut) { self.subject().write(buf); match self { Self::Block(commitment) => commitment.write(buf), Self::Finalized { height } => height.write(buf), Self::Notarized { round } => round.write(buf), } } } impl Read for Key { type Cfg = (); fn read_cfg(buf: &mut impl Buf, _: &()) -> Result { let request = match u8::read(buf)? { BLOCK_REQUEST => Self::Block(D::read(buf)?), FINALIZED_REQUEST => Self::Finalized { height: Height::read(buf)?, }, NOTARIZED_REQUEST => Self::Notarized { round: Round::read(buf)?, }, i => return Err(CodecError::InvalidEnum(i)), }; Ok(request) } } impl EncodeSize for Key { fn encode_size(&self) -> usize { 1 + match self { Self::Block(commitment) => commitment.encode_size(), Self::Finalized { height } => height.encode_size(), Self::Notarized { round } => round.encode_size(), } } } impl Span for Key {} impl PartialEq for Key { fn eq(&self, other: &Self) -> bool { match (&self, &other) { (Self::Block(a), Self::Block(b)) => a == b, (Self::Finalized { height: a }, Self::Finalized { height: b }) => a == b, (Self::Notarized { round: a }, Self::Notarized { round: b }) => a == b, _ => false, } } } impl Eq for Key {} impl Ord for Key { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match (&self, &other) { (Self::Block(a), Self::Block(b)) => a.cmp(b), (Self::Finalized { height: a }, Self::Finalized { height: b }) => a.cmp(b), (Self::Notarized { round: a }, Self::Notarized { round: b }) => a.cmp(b), (a, b) => a.subject().cmp(&b.subject()), } } } impl PartialOrd for Key { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for Key { fn hash(&self, state: &mut H) { self.subject().hash(state); match self { Self::Block(commitment) => commitment.hash(state), Self::Finalized { height } => height.hash(state), Self::Notarized { round } => round.hash(state), } } } impl Display for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Block(commitment) => write!(f, "Block({commitment:?})"), Self::Finalized { height } => write!(f, "Finalized({height:?})"), Self::Notarized { round } => write!(f, "Notarized({round:?})"), } } } impl Debug for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Block(commitment) => write!(f, "Block({commitment:?})"), Self::Finalized { height } => write!(f, "Finalized({height:?})"), Self::Notarized { round } => write!(f, "Notarized({round:?})"), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Key where D: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let choice = u.int_in_range(0..=2)?; match choice { 0 => Ok(Self::Block(u.arbitrary()?)), 1 => Ok(Self::Finalized { height: u.arbitrary()?, }), 2 => Ok(Self::Notarized { round: u.arbitrary()?, }), _ => unreachable!(), } } } #[cfg(test)] mod tests { use super::*; use crate::types::{Epoch, View}; use commonware_codec::{Encode, ReadExt}; use commonware_cryptography::{ sha256::{Digest as Sha256Digest, Sha256}, Hasher as _, }; use std::collections::BTreeSet; type D = Sha256Digest; #[test] fn handler_drain_skips_closed_responses() { let mut overflow = Pending::::default(); let (closed_response, closed_receiver) = oneshot::channel(); Message::handle( &mut overflow, Message::Produce { key: Key::Finalized { height: Height::new(1), }, response: closed_response, }, ); drop(closed_receiver); let (open_response, _open_receiver) = oneshot::channel(); Message::handle( &mut overflow, Message::Produce { key: Key::Finalized { height: Height::new(2), }, response: open_response, }, ); let mut messages = Vec::new(); Overflow::drain(&mut overflow, |message| { messages.push(message); None }); assert_eq!(messages.len(), 1); assert!(matches!( messages.pop(), Some(Message::Produce { key: Key::Finalized { height }, .. }) if height == Height::new(2) )); } #[test] fn test_cross_variant_hash_differs() { use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; fn hash_of(t: &T) -> u64 { let mut h = DefaultHasher::new(); t.hash(&mut h); h.finish() } let finalized = Key::::Finalized { height: Height::new(1), }; let notarized = Key::::Notarized { round: Round::new(Epoch::new(0), View::new(1)), }; assert_ne!(hash_of(&finalized), hash_of(¬arized)); } #[test] fn test_subject_block_encoding() { let commitment = Sha256::hash(b"test"); let request = Key::::Block(commitment); // Test encoding let encoded = request.encode(); assert_eq!(encoded.len(), 33); // 1 byte for enum variant + 32 bytes for commitment assert_eq!(encoded[0], 0); // Block variant // Test decoding let mut buf = encoded.as_ref(); let decoded = Key::::read(&mut buf).unwrap(); assert_eq!(request, decoded); assert_eq!(decoded, Key::Block(commitment)); } #[test] fn test_subject_finalized_encoding() { let height = Height::new(12345u64); let request = Key::::Finalized { height }; // Test encoding let encoded = request.encode(); assert_eq!(encoded[0], 1); // Finalized variant // Test decoding let mut buf = encoded.as_ref(); let decoded = Key::::read(&mut buf).unwrap(); assert_eq!(request, decoded); assert_eq!(decoded, Key::Finalized { height }); } #[test] fn test_subject_notarized_encoding() { let round = Round::new(Epoch::new(67890), View::new(12345)); let request = Key::::Notarized { round }; // Test encoding let encoded = request.encode(); assert_eq!(encoded[0], 2); // Notarized variant // Test decoding let mut buf = encoded.as_ref(); let decoded = Key::::read(&mut buf).unwrap(); assert_eq!(request, decoded); assert_eq!(decoded, Key::Notarized { round }); } #[test] fn test_subject_decode_rejects_invalid_enum_tag() { let bad = [3u8]; let mut buf = bad.as_ref(); assert!(matches!( Key::::read(&mut buf), Err(CodecError::InvalidEnum(3)) )); } #[test] fn test_subject_hash() { use std::collections::HashSet; let r1 = Key::::Finalized { height: Height::new(100), }; let r2 = Key::::Finalized { height: Height::new(100), }; let r3 = Key::::Finalized { height: Height::new(200), }; let mut set = HashSet::new(); set.insert(r1); assert!(!set.insert(r2)); // Should not insert duplicate assert!(set.insert(r3)); // Should insert different value } #[test] fn test_height_floor_predicate() { let floor = Height::new(100); let higher_finalized = Key::::Finalized { height: Height::new(200), }; let notarized = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(150)), }; let block = Key::::Block(Sha256::hash(b"block")); let stale_finalized = Annotation::Finalized(Finalized::ByHeight { height: Height::new(100), }); let fresh_certified = Annotation::Certified { height: Height::new(101), }; let stale_certified = Annotation::Certified { height: Height::new(100), }; let predicate = above_height_floor(floor); assert!(predicate( &higher_finalized, &Annotation::Finalized(Finalized::ByHeight { height: Height::new(200), }) )); assert!(predicate( ¬arized, &Annotation::Notarization { round: Round::new(Epoch::new(333), View::new(150)), } )); assert!(predicate(&block, &fresh_certified)); let same_height = Key::::Finalized { height: Height::new(100), }; assert!(!predicate( &same_height, &Annotation::Finalized(Finalized::ByHeight { height: Height::new(100), }) )); assert!(!predicate(&block, &stale_finalized)); assert!(!predicate(&block, &stale_certified)); } #[test] fn test_round_floor_predicate() { let floor = Round::new(Epoch::new(1), View::new(10)); let block = Key::::Block(Sha256::hash(b"block")); let higher_notarized = Key::::Notarized { round: Round::new(Epoch::new(1), View::new(11)), }; let same_notarized = Key::::Notarized { round: Round::new(Epoch::new(1), View::new(10)), }; let finalized = Key::::Finalized { height: Height::new(100), }; let predicate = above_round_floor(floor); assert!(predicate( &higher_notarized, &Annotation::Notarization { round: Round::new(Epoch::new(1), View::new(11)), } )); assert!(predicate( &finalized, &Annotation::Finalized(Finalized::ByHeight { height: Height::new(100), }) )); assert!(predicate( &block, &Annotation::Finalized(Finalized::ByRound { round: Round::new(Epoch::new(1), View::new(11)), }) )); assert!(!predicate( &same_notarized, &Annotation::Notarization { round: Round::new(Epoch::new(1), View::new(10)), } )); assert!(!predicate( &block, &Annotation::Finalized(Finalized::ByRound { round: Round::new(Epoch::new(1), View::new(10)), }) )); } #[test] fn test_encode_size() { let commitment = Sha256::hash(&[0u8; 32]); let r1 = Key::::Block(commitment); let r2 = Key::::Finalized { height: Height::new(u64::MAX), }; let r3 = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(0)), }; // Verify encode_size matches actual encoded length assert_eq!(r1.encode_size(), r1.encode().len()); assert_eq!(r2.encode_size(), r2.encode().len()); assert_eq!(r3.encode_size(), r3.encode().len()); } #[test] fn test_request_ord_same_variant() { // Test ordering within the same variant let commitment1 = Sha256::hash(b"test1"); let commitment2 = Sha256::hash(b"test2"); let block1 = Key::::Block(commitment1); let block2 = Key::::Block(commitment2); // Block ordering depends on commitment ordering if commitment1 < commitment2 { assert!(block1 < block2); assert!(block2 > block1); } else { assert!(block1 > block2); assert!(block2 < block1); } // Finalized ordering by height let fin1 = Key::::Finalized { height: Height::new(100), }; let fin2 = Key::::Finalized { height: Height::new(200), }; let fin3 = Key::::Finalized { height: Height::new(200), }; assert!(fin1 < fin2); assert!(fin2 > fin1); assert_eq!(fin2.cmp(&fin3), std::cmp::Ordering::Equal); // Notarized ordering by view let not1 = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(50)), }; let not2 = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(150)), }; let not3 = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(150)), }; assert!(not1 < not2); assert!(not2 > not1); assert_eq!(not2.cmp(¬3), std::cmp::Ordering::Equal); } #[test] fn test_request_ord_cross_variant() { let commitment = Sha256::hash(b"test"); let block = Key::::Block(commitment); let finalized = Key::::Finalized { height: Height::new(100), }; let notarized = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(200)), }; // Block < Finalized < Notarized assert!(block < finalized); assert!(block < notarized); assert!(finalized < notarized); assert!(finalized > block); assert!(notarized > block); assert!(notarized > finalized); // Test all combinations assert_eq!(block.cmp(&finalized), std::cmp::Ordering::Less); assert_eq!(block.cmp(¬arized), std::cmp::Ordering::Less); assert_eq!(finalized.cmp(¬arized), std::cmp::Ordering::Less); assert_eq!(finalized.cmp(&block), std::cmp::Ordering::Greater); assert_eq!(notarized.cmp(&block), std::cmp::Ordering::Greater); assert_eq!(notarized.cmp(&finalized), std::cmp::Ordering::Greater); } #[test] fn test_request_partial_ord() { let commitment1 = Sha256::hash(b"test1"); let commitment2 = Sha256::hash(b"test2"); let block1 = Key::::Block(commitment1); let block2 = Key::::Block(commitment2); let finalized = Key::::Finalized { height: Height::new(100), }; let notarized = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(200)), }; // PartialOrd should always return Some assert!(block1.partial_cmp(&block2).is_some()); assert!(block1.partial_cmp(&finalized).is_some()); assert!(finalized.partial_cmp(¬arized).is_some()); // Verify consistency with Ord assert_eq!( block1.partial_cmp(&finalized), Some(std::cmp::Ordering::Less) ); assert_eq!( finalized.partial_cmp(¬arized), Some(std::cmp::Ordering::Less) ); assert_eq!( notarized.partial_cmp(&block1), Some(std::cmp::Ordering::Greater) ); } #[test] fn test_request_ord_sorting() { let commitment1 = Sha256::hash(b"a"); let commitment2 = Sha256::hash(b"b"); let commitment3 = Sha256::hash(b"c"); let requests = vec![ Key::::Notarized { round: Round::new(Epoch::new(333), View::new(300)), }, Key::::Block(commitment2), Key::::Finalized { height: Height::new(200), }, Key::::Block(commitment1), Key::::Notarized { round: Round::new(Epoch::new(333), View::new(250)), }, Key::::Finalized { height: Height::new(100), }, Key::::Block(commitment3), ]; // Sort using BTreeSet (uses Ord) let sorted: Vec<_> = requests .into_iter() .collect::>() .into_iter() .collect(); // Verify order: all Blocks first (sorted by commitment), then Finalized (by height), then Notarized (by view) assert_eq!(sorted.len(), 7); // Check that all blocks come first assert!(matches!(sorted[0], Key::::Block(_))); assert!(matches!(sorted[1], Key::::Block(_))); assert!(matches!(sorted[2], Key::::Block(_))); // Check that finalized come next assert_eq!( sorted[3], Key::::Finalized { height: Height::new(100) } ); assert_eq!( sorted[4], Key::::Finalized { height: Height::new(200) } ); // Check that notarized come last assert_eq!( sorted[5], Key::::Notarized { round: Round::new(Epoch::new(333), View::new(250)) } ); assert_eq!( sorted[6], Key::::Notarized { round: Round::new(Epoch::new(333), View::new(300)) } ); } #[test] fn test_request_ord_edge_cases() { // Test with extreme values let min_finalized = Key::::Finalized { height: Height::new(0), }; let max_finalized = Key::::Finalized { height: Height::new(u64::MAX), }; let min_notarized = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(0)), }; let max_notarized = Key::::Notarized { round: Round::new(Epoch::new(333), View::new(u64::MAX)), }; assert!(min_finalized < max_finalized); assert!(min_notarized < max_notarized); assert!(max_finalized < min_notarized); // Test self-comparison let commitment = Sha256::hash(b"self"); let block = Key::::Block(commitment); assert_eq!(block.cmp(&block), std::cmp::Ordering::Equal); assert_eq!(min_finalized.cmp(&min_finalized), std::cmp::Ordering::Equal); assert_eq!(max_notarized.cmp(&max_notarized), std::cmp::Ordering::Equal); } #[cfg(feature = "arbitrary")] mod conformance { use super::*; use commonware_codec::conformance::CodecConformance; commonware_conformance::conformance_tests! { CodecConformance> } } }