Skip to main content

commonware_consensus/marshal/resolver/
handler.rs

1use crate::types::{Height, Round};
2use bytes::{Buf, BufMut, Bytes};
3use commonware_actor::mailbox::{self, Overflow, Policy, Sender};
4use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write};
5use commonware_cryptography::Digest;
6use commonware_resolver::{p2p::Producer, Consumer, Delivery, Fetch as ResolverFetch};
7use commonware_runtime::Metrics;
8use commonware_utils::{channel::oneshot, Span};
9use std::{
10    collections::VecDeque,
11    fmt::{Debug, Display},
12    hash::{Hash, Hasher},
13    num::NonZeroUsize,
14    sync::mpsc::TryRecvError,
15};
16
17/// The subject of a backfill request.
18const BLOCK_REQUEST: u8 = 0;
19const FINALIZED_REQUEST: u8 = 1;
20const NOTARIZED_REQUEST: u8 = 2;
21
22/// Messages sent from the resolver's [Consumer]/[Producer] implementation
23/// to the marshal actor.
24pub(crate) enum Message<D: Digest> {
25    /// A request to deliver a value for a given key.
26    Deliver {
27        /// The delivery metadata attached to the resolved value.
28        delivery: Delivery<Key<D>, Annotation>,
29        /// The value being delivered.
30        value: Bytes,
31        /// A channel to send the result of the delivery.
32        response: oneshot::Sender<bool>,
33    },
34    /// A request to produce a value for a given key.
35    Produce {
36        /// The key of the value to produce.
37        key: Key<D>,
38        /// A channel to send the produced value.
39        response: oneshot::Sender<Bytes>,
40    },
41}
42
43impl<D: Digest> Message<D> {
44    /// Returns true if the requester has stopped waiting for this response.
45    pub(crate) fn response_closed(&self) -> bool {
46        match self {
47            Self::Deliver { response, .. } => response.is_closed(),
48            Self::Produce { response, .. } => response.is_closed(),
49        }
50    }
51}
52
53/// Pending resolver handler messages retained after the mailbox fills.
54pub(crate) struct Pending<D: Digest>(VecDeque<Message<D>>);
55
56impl<D: Digest> Default for Pending<D> {
57    fn default() -> Self {
58        Self(VecDeque::new())
59    }
60}
61
62impl<D: Digest> Overflow<Message<D>> for Pending<D> {
63    fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    fn drain<F>(&mut self, mut push: F)
68    where
69        F: FnMut(Message<D>) -> Option<Message<D>>,
70    {
71        while let Some(message) = self.0.pop_front() {
72            if message.response_closed() {
73                continue;
74            }
75
76            if let Some(message) = push(message) {
77                self.0.push_front(message);
78                break;
79            }
80        }
81    }
82}
83
84impl<D: Digest> Policy for Message<D> {
85    type Overflow = Pending<D>;
86
87    fn handle(overflow: &mut Self::Overflow, message: Self) {
88        if message.response_closed() {
89            return;
90        }
91        overflow.0.push_back(message);
92    }
93}
94
95/// A handler that forwards requests from the resolver to the marshal actor.
96///
97/// This struct implements the [Consumer] and [Producer] traits from the
98/// resolver, and acts as a bridge to the main actor loop.
99#[derive(Clone)]
100pub struct Handler<D: Digest> {
101    sender: Sender<Message<D>>,
102}
103
104impl<D: Digest> Handler<D> {
105    /// Creates a new handler.
106    pub(crate) const fn new(sender: Sender<Message<D>>) -> Self {
107        Self { sender }
108    }
109}
110
111/// Creates a resolver receiver and handler pair.
112pub fn init<D: Digest>(metrics: impl Metrics, capacity: NonZeroUsize) -> (Receiver<D>, Handler<D>) {
113    let (sender, receiver) = mailbox::new(metrics, capacity);
114    (Receiver::new(receiver), Handler::new(sender))
115}
116
117/// Receiver for resolver handler messages.
118pub struct Receiver<D: Digest> {
119    inner: mailbox::Receiver<Message<D>>,
120}
121
122impl<D: Digest> Receiver<D> {
123    pub(crate) const fn new(inner: mailbox::Receiver<Message<D>>) -> Self {
124        Self { inner }
125    }
126
127    pub(crate) async fn recv(&mut self) -> Option<Message<D>> {
128        self.inner.recv().await
129    }
130
131    pub(crate) fn try_recv(&mut self) -> Result<Message<D>, TryRecvError> {
132        self.inner.try_recv()
133    }
134}
135
136impl<D: Digest> Consumer for Handler<D> {
137    type Key = Key<D>;
138    type Value = Bytes;
139    type Subscriber = Annotation;
140
141    fn deliver(
142        &mut self,
143        delivery: Delivery<Self::Key, Self::Subscriber>,
144        value: Self::Value,
145    ) -> oneshot::Receiver<bool> {
146        let (response, receiver) = oneshot::channel();
147        let _ = self.sender.enqueue(Message::Deliver {
148            delivery,
149            value,
150            response,
151        });
152        receiver
153    }
154}
155
156impl<D: Digest> Producer for Handler<D> {
157    type Key = Key<D>;
158
159    fn produce(&mut self, key: Self::Key) -> oneshot::Receiver<Bytes> {
160        let (response, receiver) = oneshot::channel();
161        let _ = self.sender.enqueue(Message::Produce { key, response });
162        receiver
163    }
164}
165
166/// Local processing annotation for a resolved key.
167///
168/// The resolver key is the peer-visible lookup. An annotation is local
169/// metadata attached to that lookup so marshal can decide how to process the
170/// response after validating it against the key. It is not part of peer
171/// response validity. Multiple local annotations may share one peer key when
172/// they depend on the same block.
173///
174/// [`Notarization`](Annotation::Notarization) carries round-bound local
175/// context. [`Certified`](Annotation::Certified) and
176/// [`Finalized`](Annotation::Finalized) describe how block-bearing responses
177/// should be processed locally.
178///
179/// This storage role is part of the annotation because a [`Key::Block`]
180/// only names the peer-visible commitment. The same block-shaped response may
181/// need to update different local stores depending on whether it was fetched
182/// for a certified chain or for the finalized chain.
183#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
184pub enum Annotation {
185    /// A notarization requested by round.
186    Notarization { round: Round },
187    /// A block requested by commitment for a certified chain.
188    ///
189    /// The expected height is local pruning metadata and should only be
190    /// supplied when the caller has a validated height bound. It must not make
191    /// a commitment-matching response invalid, and certified storage uses the
192    /// fetched block's decoded height.
193    Certified { height: Height },
194    /// A block requested by commitment for the finalized chain.
195    Finalized(Finalized),
196}
197
198/// Metadata for a finalized block requested by commitment.
199#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
200pub enum Finalized {
201    /// The finalized height is known before the request.
202    ByHeight { height: Height },
203    /// Only the finalization round is known before the request.
204    ///
205    /// This happens when a finalization names the block commitment but not the
206    /// block height.
207    ByRound { round: Round },
208}
209
210/// A raw resolver key for backfilling data.
211#[derive(Clone, Copy)]
212pub enum Key<D: Digest> {
213    /// Fetch a block by consensus commitment.
214    Block(D),
215    Finalized {
216        height: Height,
217    },
218    Notarized {
219        round: Round,
220    },
221}
222
223impl<D: Digest> Key<D> {
224    /// The subject of the request.
225    const fn subject(&self) -> u8 {
226        match self {
227            Self::Block(_) => BLOCK_REQUEST,
228            Self::Finalized { .. } => FINALIZED_REQUEST,
229            Self::Notarized { .. } => NOTARIZED_REQUEST,
230        }
231    }
232}
233
234/// A valid marshal backfill fetch request.
235#[derive(Clone, Copy, Debug, Eq, PartialEq)]
236pub(crate) enum RequestKind<D: Digest> {
237    /// Fetch a notarized proposal for a round.
238    Notarized { round: Round },
239    /// Fetch a finalization for a height.
240    Finalized { height: Height },
241    /// Fetch a certified-chain block by commitment.
242    CertifiedBlock { commitment: D, height: Height },
243    /// Fetch a finalized-chain block by commitment when its height is known.
244    FinalizedBlockByHeight { commitment: D, height: Height },
245    /// Fetch a finalized-chain block by commitment when only its finalization round is known.
246    FinalizedBlockByRound { commitment: D, round: Round },
247}
248
249/// A marshal backfill fetch with a request and local processing annotation that match.
250#[derive(Clone, Copy, Debug, Eq, PartialEq)]
251pub struct Request<D: Digest> {
252    kind: RequestKind<D>,
253}
254
255impl<D: Digest> Request<D> {
256    /// Fetch a notarized proposal for `round`.
257    pub const fn notarized(round: Round) -> Self {
258        Self {
259            kind: RequestKind::Notarized { round },
260        }
261    }
262
263    /// Fetch a finalization for `height`.
264    pub const fn finalized(height: Height) -> Self {
265        Self {
266            kind: RequestKind::Finalized { height },
267        }
268    }
269
270    /// Fetch a certified-chain block by commitment.
271    pub const fn certified_block(commitment: D, height: Height) -> Self {
272        Self {
273            kind: RequestKind::CertifiedBlock { commitment, height },
274        }
275    }
276
277    /// Fetch a finalized-chain block by commitment when its height is known.
278    pub const fn finalized_block_by_height(commitment: D, height: Height) -> Self {
279        Self {
280            kind: RequestKind::FinalizedBlockByHeight { commitment, height },
281        }
282    }
283
284    /// Fetch a finalized-chain block by commitment when only its finalization round is known.
285    pub const fn finalized_block_by_round(commitment: D, round: Round) -> Self {
286        Self {
287            kind: RequestKind::FinalizedBlockByRound { commitment, round },
288        }
289    }
290
291    pub(crate) fn above_height_floor(&self, floor: Height) -> bool {
292        match self.kind {
293            RequestKind::Finalized { height }
294            | RequestKind::CertifiedBlock { height, .. }
295            | RequestKind::FinalizedBlockByHeight { height, .. } => height > floor,
296            RequestKind::Notarized { .. } | RequestKind::FinalizedBlockByRound { .. } => true,
297        }
298    }
299
300    pub(crate) fn above_round_floor(&self, floor: Round) -> bool {
301        match self.kind {
302            RequestKind::Notarized { round } | RequestKind::FinalizedBlockByRound { round, .. } => {
303                round > floor
304            }
305            RequestKind::Finalized { .. }
306            | RequestKind::CertifiedBlock { .. }
307            | RequestKind::FinalizedBlockByHeight { .. } => true,
308        }
309    }
310
311    pub(crate) const fn into_inner(self) -> ResolverFetch<Key<D>, Annotation> {
312        match self.kind {
313            RequestKind::Notarized { round } => ResolverFetch {
314                key: Key::Notarized { round },
315                subscriber: Annotation::Notarization { round },
316            },
317            RequestKind::Finalized { height } => ResolverFetch {
318                key: Key::Finalized { height },
319                subscriber: Annotation::Finalized(Finalized::ByHeight { height }),
320            },
321            RequestKind::CertifiedBlock { commitment, height } => ResolverFetch {
322                key: Key::Block(commitment),
323                subscriber: Annotation::Certified { height },
324            },
325            RequestKind::FinalizedBlockByHeight { commitment, height } => ResolverFetch {
326                key: Key::Block(commitment),
327                subscriber: Annotation::Finalized(Finalized::ByHeight { height }),
328            },
329            RequestKind::FinalizedBlockByRound { commitment, round } => ResolverFetch {
330                key: Key::Block(commitment),
331                subscriber: Annotation::Finalized(Finalized::ByRound { round }),
332            },
333        }
334    }
335}
336
337impl<D: Digest> From<Request<D>> for ResolverFetch<Key<D>, Annotation> {
338    fn from(fetch: Request<D>) -> Self {
339        fetch.into_inner()
340    }
341}
342
343/// Returns a predicate that keeps resolver requests above the processed height floor.
344///
345/// Unrelated requests are retained. Height-bound requests are pruned once the
346/// processed height reaches them.
347pub(crate) fn above_height_floor<D: Digest>(
348    height: Height,
349) -> impl Fn(&Key<D>, &Annotation) -> bool + Send + 'static {
350    move |request, annotation| match (request, annotation) {
351        (Key::Finalized { height: requested }, _) => *requested > height,
352        (
353            Key::Block(_),
354            Annotation::Certified { height: requested }
355            | Annotation::Finalized(Finalized::ByHeight { height: requested }),
356        ) => *requested > height,
357        _ => true,
358    }
359}
360
361/// Returns a predicate that keeps resolver requests above the processed round floor.
362///
363/// Unrelated requests are retained. Round-bound requests are pruned once the
364/// processed round reaches them.
365pub(crate) fn above_round_floor<D: Digest>(
366    round: Round,
367) -> impl Fn(&Key<D>, &Annotation) -> bool + Send + 'static {
368    move |request, annotation| match (request, annotation) {
369        (Key::Notarized { round: requested }, _) => *requested > round,
370        (Key::Block(_), Annotation::Finalized(Finalized::ByRound { round: requested })) => {
371            *requested > round
372        }
373        _ => true,
374    }
375}
376
377impl<D: Digest> Write for Key<D> {
378    fn write(&self, buf: &mut impl BufMut) {
379        self.subject().write(buf);
380        match self {
381            Self::Block(commitment) => commitment.write(buf),
382            Self::Finalized { height } => height.write(buf),
383            Self::Notarized { round } => round.write(buf),
384        }
385    }
386}
387
388impl<D: Digest> Read for Key<D> {
389    type Cfg = ();
390
391    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
392        let request = match u8::read(buf)? {
393            BLOCK_REQUEST => Self::Block(D::read(buf)?),
394            FINALIZED_REQUEST => Self::Finalized {
395                height: Height::read(buf)?,
396            },
397            NOTARIZED_REQUEST => Self::Notarized {
398                round: Round::read(buf)?,
399            },
400            i => return Err(CodecError::InvalidEnum(i)),
401        };
402        Ok(request)
403    }
404}
405
406impl<D: Digest> EncodeSize for Key<D> {
407    fn encode_size(&self) -> usize {
408        1 + match self {
409            Self::Block(commitment) => commitment.encode_size(),
410            Self::Finalized { height } => height.encode_size(),
411            Self::Notarized { round } => round.encode_size(),
412        }
413    }
414}
415
416impl<D: Digest> Span for Key<D> {}
417
418impl<D: Digest> PartialEq for Key<D> {
419    fn eq(&self, other: &Self) -> bool {
420        match (&self, &other) {
421            (Self::Block(a), Self::Block(b)) => a == b,
422            (Self::Finalized { height: a }, Self::Finalized { height: b }) => a == b,
423            (Self::Notarized { round: a }, Self::Notarized { round: b }) => a == b,
424            _ => false,
425        }
426    }
427}
428
429impl<D: Digest> Eq for Key<D> {}
430
431impl<D: Digest> Ord for Key<D> {
432    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
433        match (&self, &other) {
434            (Self::Block(a), Self::Block(b)) => a.cmp(b),
435            (Self::Finalized { height: a }, Self::Finalized { height: b }) => a.cmp(b),
436            (Self::Notarized { round: a }, Self::Notarized { round: b }) => a.cmp(b),
437            (a, b) => a.subject().cmp(&b.subject()),
438        }
439    }
440}
441
442impl<D: Digest> PartialOrd for Key<D> {
443    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
444        Some(self.cmp(other))
445    }
446}
447
448impl<D: Digest> Hash for Key<D> {
449    fn hash<H: Hasher>(&self, state: &mut H) {
450        self.subject().hash(state);
451        match self {
452            Self::Block(commitment) => commitment.hash(state),
453            Self::Finalized { height } => height.hash(state),
454            Self::Notarized { round } => round.hash(state),
455        }
456    }
457}
458
459impl<D: Digest> Display for Key<D> {
460    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461        match self {
462            Self::Block(commitment) => write!(f, "Block({commitment:?})"),
463            Self::Finalized { height } => write!(f, "Finalized({height:?})"),
464            Self::Notarized { round } => write!(f, "Notarized({round:?})"),
465        }
466    }
467}
468
469impl<D: Digest> Debug for Key<D> {
470    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471        match self {
472            Self::Block(commitment) => write!(f, "Block({commitment:?})"),
473            Self::Finalized { height } => write!(f, "Finalized({height:?})"),
474            Self::Notarized { round } => write!(f, "Notarized({round:?})"),
475        }
476    }
477}
478
479#[cfg(feature = "arbitrary")]
480impl<D: Digest> arbitrary::Arbitrary<'_> for Key<D>
481where
482    D: for<'a> arbitrary::Arbitrary<'a>,
483{
484    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
485        let choice = u.int_in_range(0..=2)?;
486        match choice {
487            0 => Ok(Self::Block(u.arbitrary()?)),
488            1 => Ok(Self::Finalized {
489                height: u.arbitrary()?,
490            }),
491            2 => Ok(Self::Notarized {
492                round: u.arbitrary()?,
493            }),
494            _ => unreachable!(),
495        }
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502    use crate::types::{Epoch, View};
503    use commonware_codec::{Encode, ReadExt};
504    use commonware_cryptography::{
505        sha256::{Digest as Sha256Digest, Sha256},
506        Hasher as _,
507    };
508    use std::collections::BTreeSet;
509
510    type D = Sha256Digest;
511
512    #[test]
513    fn handler_drain_skips_closed_responses() {
514        let mut overflow = Pending::<D>::default();
515
516        let (closed_response, closed_receiver) = oneshot::channel();
517        Message::handle(
518            &mut overflow,
519            Message::Produce {
520                key: Key::Finalized {
521                    height: Height::new(1),
522                },
523                response: closed_response,
524            },
525        );
526        drop(closed_receiver);
527
528        let (open_response, _open_receiver) = oneshot::channel();
529        Message::handle(
530            &mut overflow,
531            Message::Produce {
532                key: Key::Finalized {
533                    height: Height::new(2),
534                },
535                response: open_response,
536            },
537        );
538
539        let mut messages = Vec::new();
540        Overflow::drain(&mut overflow, |message| {
541            messages.push(message);
542            None
543        });
544
545        assert_eq!(messages.len(), 1);
546        assert!(matches!(
547            messages.pop(),
548            Some(Message::Produce {
549                key: Key::Finalized { height },
550                ..
551            }) if height == Height::new(2)
552        ));
553    }
554
555    #[test]
556    fn test_cross_variant_hash_differs() {
557        use std::{
558            collections::hash_map::DefaultHasher,
559            hash::{Hash, Hasher},
560        };
561
562        fn hash_of<T: Hash>(t: &T) -> u64 {
563            let mut h = DefaultHasher::new();
564            t.hash(&mut h);
565            h.finish()
566        }
567
568        let finalized = Key::<D>::Finalized {
569            height: Height::new(1),
570        };
571        let notarized = Key::<D>::Notarized {
572            round: Round::new(Epoch::new(0), View::new(1)),
573        };
574        assert_ne!(hash_of(&finalized), hash_of(&notarized));
575    }
576
577    #[test]
578    fn test_subject_block_encoding() {
579        let commitment = Sha256::hash(b"test");
580        let request = Key::<D>::Block(commitment);
581
582        // Test encoding
583        let encoded = request.encode();
584        assert_eq!(encoded.len(), 33); // 1 byte for enum variant + 32 bytes for commitment
585        assert_eq!(encoded[0], 0); // Block variant
586
587        // Test decoding
588        let mut buf = encoded.as_ref();
589        let decoded = Key::<D>::read(&mut buf).unwrap();
590        assert_eq!(request, decoded);
591        assert_eq!(decoded, Key::Block(commitment));
592    }
593
594    #[test]
595    fn test_subject_finalized_encoding() {
596        let height = Height::new(12345u64);
597        let request = Key::<D>::Finalized { height };
598
599        // Test encoding
600        let encoded = request.encode();
601        assert_eq!(encoded[0], 1); // Finalized variant
602
603        // Test decoding
604        let mut buf = encoded.as_ref();
605        let decoded = Key::<D>::read(&mut buf).unwrap();
606        assert_eq!(request, decoded);
607        assert_eq!(decoded, Key::Finalized { height });
608    }
609
610    #[test]
611    fn test_subject_notarized_encoding() {
612        let round = Round::new(Epoch::new(67890), View::new(12345));
613        let request = Key::<D>::Notarized { round };
614
615        // Test encoding
616        let encoded = request.encode();
617        assert_eq!(encoded[0], 2); // Notarized variant
618
619        // Test decoding
620        let mut buf = encoded.as_ref();
621        let decoded = Key::<D>::read(&mut buf).unwrap();
622        assert_eq!(request, decoded);
623        assert_eq!(decoded, Key::Notarized { round });
624    }
625
626    #[test]
627    fn test_subject_decode_rejects_invalid_enum_tag() {
628        let bad = [3u8];
629        let mut buf = bad.as_ref();
630        assert!(matches!(
631            Key::<D>::read(&mut buf),
632            Err(CodecError::InvalidEnum(3))
633        ));
634    }
635
636    #[test]
637    fn test_subject_hash() {
638        use std::collections::HashSet;
639
640        let r1 = Key::<D>::Finalized {
641            height: Height::new(100),
642        };
643        let r2 = Key::<D>::Finalized {
644            height: Height::new(100),
645        };
646        let r3 = Key::<D>::Finalized {
647            height: Height::new(200),
648        };
649
650        let mut set = HashSet::new();
651        set.insert(r1);
652        assert!(!set.insert(r2)); // Should not insert duplicate
653        assert!(set.insert(r3)); // Should insert different value
654    }
655
656    #[test]
657    fn test_height_floor_predicate() {
658        let floor = Height::new(100);
659        let higher_finalized = Key::<D>::Finalized {
660            height: Height::new(200),
661        };
662        let notarized = Key::<D>::Notarized {
663            round: Round::new(Epoch::new(333), View::new(150)),
664        };
665        let block = Key::<D>::Block(Sha256::hash(b"block"));
666        let stale_finalized = Annotation::Finalized(Finalized::ByHeight {
667            height: Height::new(100),
668        });
669        let fresh_certified = Annotation::Certified {
670            height: Height::new(101),
671        };
672        let stale_certified = Annotation::Certified {
673            height: Height::new(100),
674        };
675
676        let predicate = above_height_floor(floor);
677        assert!(predicate(
678            &higher_finalized,
679            &Annotation::Finalized(Finalized::ByHeight {
680                height: Height::new(200),
681            })
682        ));
683        assert!(predicate(
684            &notarized,
685            &Annotation::Notarization {
686                round: Round::new(Epoch::new(333), View::new(150)),
687            }
688        ));
689        assert!(predicate(&block, &fresh_certified));
690
691        let same_height = Key::<D>::Finalized {
692            height: Height::new(100),
693        };
694        assert!(!predicate(
695            &same_height,
696            &Annotation::Finalized(Finalized::ByHeight {
697                height: Height::new(100),
698            })
699        ));
700        assert!(!predicate(&block, &stale_finalized));
701        assert!(!predicate(&block, &stale_certified));
702    }
703
704    #[test]
705    fn test_round_floor_predicate() {
706        let floor = Round::new(Epoch::new(1), View::new(10));
707        let block = Key::<D>::Block(Sha256::hash(b"block"));
708        let higher_notarized = Key::<D>::Notarized {
709            round: Round::new(Epoch::new(1), View::new(11)),
710        };
711        let same_notarized = Key::<D>::Notarized {
712            round: Round::new(Epoch::new(1), View::new(10)),
713        };
714        let finalized = Key::<D>::Finalized {
715            height: Height::new(100),
716        };
717
718        let predicate = above_round_floor(floor);
719        assert!(predicate(
720            &higher_notarized,
721            &Annotation::Notarization {
722                round: Round::new(Epoch::new(1), View::new(11)),
723            }
724        ));
725        assert!(predicate(
726            &finalized,
727            &Annotation::Finalized(Finalized::ByHeight {
728                height: Height::new(100),
729            })
730        ));
731        assert!(predicate(
732            &block,
733            &Annotation::Finalized(Finalized::ByRound {
734                round: Round::new(Epoch::new(1), View::new(11)),
735            })
736        ));
737        assert!(!predicate(
738            &same_notarized,
739            &Annotation::Notarization {
740                round: Round::new(Epoch::new(1), View::new(10)),
741            }
742        ));
743        assert!(!predicate(
744            &block,
745            &Annotation::Finalized(Finalized::ByRound {
746                round: Round::new(Epoch::new(1), View::new(10)),
747            })
748        ));
749    }
750
751    #[test]
752    fn test_encode_size() {
753        let commitment = Sha256::hash(&[0u8; 32]);
754        let r1 = Key::<D>::Block(commitment);
755        let r2 = Key::<D>::Finalized {
756            height: Height::new(u64::MAX),
757        };
758        let r3 = Key::<D>::Notarized {
759            round: Round::new(Epoch::new(333), View::new(0)),
760        };
761
762        // Verify encode_size matches actual encoded length
763        assert_eq!(r1.encode_size(), r1.encode().len());
764        assert_eq!(r2.encode_size(), r2.encode().len());
765        assert_eq!(r3.encode_size(), r3.encode().len());
766    }
767
768    #[test]
769    fn test_request_ord_same_variant() {
770        // Test ordering within the same variant
771        let commitment1 = Sha256::hash(b"test1");
772        let commitment2 = Sha256::hash(b"test2");
773        let block1 = Key::<D>::Block(commitment1);
774        let block2 = Key::<D>::Block(commitment2);
775
776        // Block ordering depends on commitment ordering
777        if commitment1 < commitment2 {
778            assert!(block1 < block2);
779            assert!(block2 > block1);
780        } else {
781            assert!(block1 > block2);
782            assert!(block2 < block1);
783        }
784
785        // Finalized ordering by height
786        let fin1 = Key::<D>::Finalized {
787            height: Height::new(100),
788        };
789        let fin2 = Key::<D>::Finalized {
790            height: Height::new(200),
791        };
792        let fin3 = Key::<D>::Finalized {
793            height: Height::new(200),
794        };
795
796        assert!(fin1 < fin2);
797        assert!(fin2 > fin1);
798        assert_eq!(fin2.cmp(&fin3), std::cmp::Ordering::Equal);
799
800        // Notarized ordering by view
801        let not1 = Key::<D>::Notarized {
802            round: Round::new(Epoch::new(333), View::new(50)),
803        };
804        let not2 = Key::<D>::Notarized {
805            round: Round::new(Epoch::new(333), View::new(150)),
806        };
807        let not3 = Key::<D>::Notarized {
808            round: Round::new(Epoch::new(333), View::new(150)),
809        };
810
811        assert!(not1 < not2);
812        assert!(not2 > not1);
813        assert_eq!(not2.cmp(&not3), std::cmp::Ordering::Equal);
814    }
815
816    #[test]
817    fn test_request_ord_cross_variant() {
818        let commitment = Sha256::hash(b"test");
819        let block = Key::<D>::Block(commitment);
820        let finalized = Key::<D>::Finalized {
821            height: Height::new(100),
822        };
823        let notarized = Key::<D>::Notarized {
824            round: Round::new(Epoch::new(333), View::new(200)),
825        };
826
827        // Block < Finalized < Notarized
828        assert!(block < finalized);
829        assert!(block < notarized);
830        assert!(finalized < notarized);
831
832        assert!(finalized > block);
833        assert!(notarized > block);
834        assert!(notarized > finalized);
835
836        // Test all combinations
837        assert_eq!(block.cmp(&finalized), std::cmp::Ordering::Less);
838        assert_eq!(block.cmp(&notarized), std::cmp::Ordering::Less);
839        assert_eq!(finalized.cmp(&notarized), std::cmp::Ordering::Less);
840        assert_eq!(finalized.cmp(&block), std::cmp::Ordering::Greater);
841        assert_eq!(notarized.cmp(&block), std::cmp::Ordering::Greater);
842        assert_eq!(notarized.cmp(&finalized), std::cmp::Ordering::Greater);
843    }
844
845    #[test]
846    fn test_request_partial_ord() {
847        let commitment1 = Sha256::hash(b"test1");
848        let commitment2 = Sha256::hash(b"test2");
849        let block1 = Key::<D>::Block(commitment1);
850        let block2 = Key::<D>::Block(commitment2);
851        let finalized = Key::<D>::Finalized {
852            height: Height::new(100),
853        };
854        let notarized = Key::<D>::Notarized {
855            round: Round::new(Epoch::new(333), View::new(200)),
856        };
857
858        // PartialOrd should always return Some
859        assert!(block1.partial_cmp(&block2).is_some());
860        assert!(block1.partial_cmp(&finalized).is_some());
861        assert!(finalized.partial_cmp(&notarized).is_some());
862
863        // Verify consistency with Ord
864        assert_eq!(
865            block1.partial_cmp(&finalized),
866            Some(std::cmp::Ordering::Less)
867        );
868        assert_eq!(
869            finalized.partial_cmp(&notarized),
870            Some(std::cmp::Ordering::Less)
871        );
872        assert_eq!(
873            notarized.partial_cmp(&block1),
874            Some(std::cmp::Ordering::Greater)
875        );
876    }
877
878    #[test]
879    fn test_request_ord_sorting() {
880        let commitment1 = Sha256::hash(b"a");
881        let commitment2 = Sha256::hash(b"b");
882        let commitment3 = Sha256::hash(b"c");
883
884        let requests = vec![
885            Key::<D>::Notarized {
886                round: Round::new(Epoch::new(333), View::new(300)),
887            },
888            Key::<D>::Block(commitment2),
889            Key::<D>::Finalized {
890                height: Height::new(200),
891            },
892            Key::<D>::Block(commitment1),
893            Key::<D>::Notarized {
894                round: Round::new(Epoch::new(333), View::new(250)),
895            },
896            Key::<D>::Finalized {
897                height: Height::new(100),
898            },
899            Key::<D>::Block(commitment3),
900        ];
901
902        // Sort using BTreeSet (uses Ord)
903        let sorted: Vec<_> = requests
904            .into_iter()
905            .collect::<BTreeSet<_>>()
906            .into_iter()
907            .collect();
908
909        // Verify order: all Blocks first (sorted by commitment), then Finalized (by height), then Notarized (by view)
910        assert_eq!(sorted.len(), 7);
911
912        // Check that all blocks come first
913        assert!(matches!(sorted[0], Key::<D>::Block(_)));
914        assert!(matches!(sorted[1], Key::<D>::Block(_)));
915        assert!(matches!(sorted[2], Key::<D>::Block(_)));
916
917        // Check that finalized come next
918        assert_eq!(
919            sorted[3],
920            Key::<D>::Finalized {
921                height: Height::new(100)
922            }
923        );
924        assert_eq!(
925            sorted[4],
926            Key::<D>::Finalized {
927                height: Height::new(200)
928            }
929        );
930
931        // Check that notarized come last
932        assert_eq!(
933            sorted[5],
934            Key::<D>::Notarized {
935                round: Round::new(Epoch::new(333), View::new(250))
936            }
937        );
938        assert_eq!(
939            sorted[6],
940            Key::<D>::Notarized {
941                round: Round::new(Epoch::new(333), View::new(300))
942            }
943        );
944    }
945
946    #[test]
947    fn test_request_ord_edge_cases() {
948        // Test with extreme values
949        let min_finalized = Key::<D>::Finalized {
950            height: Height::new(0),
951        };
952        let max_finalized = Key::<D>::Finalized {
953            height: Height::new(u64::MAX),
954        };
955        let min_notarized = Key::<D>::Notarized {
956            round: Round::new(Epoch::new(333), View::new(0)),
957        };
958        let max_notarized = Key::<D>::Notarized {
959            round: Round::new(Epoch::new(333), View::new(u64::MAX)),
960        };
961
962        assert!(min_finalized < max_finalized);
963        assert!(min_notarized < max_notarized);
964        assert!(max_finalized < min_notarized);
965
966        // Test self-comparison
967        let commitment = Sha256::hash(b"self");
968        let block = Key::<D>::Block(commitment);
969        assert_eq!(block.cmp(&block), std::cmp::Ordering::Equal);
970        assert_eq!(min_finalized.cmp(&min_finalized), std::cmp::Ordering::Equal);
971        assert_eq!(max_notarized.cmp(&max_notarized), std::cmp::Ordering::Equal);
972    }
973
974    #[cfg(feature = "arbitrary")]
975    mod conformance {
976        use super::*;
977        use commonware_codec::conformance::CodecConformance;
978
979        commonware_conformance::conformance_tests! {
980            CodecConformance<Key<D>>
981        }
982    }
983}