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