Skip to main content

commonware_consensus/marshal/core/
mailbox.rs

1use super::{Variant, durability::Durable as _};
2use crate::{
3    Reporter,
4    marshal::{
5        Identifier,
6        ancestry::{AncestorStream, Ancestry, BlockProvider},
7    },
8    simplex::types::{Activity, Finalization, Notarization},
9    types::{Height, Round},
10};
11use commonware_actor::{
12    Feedback,
13    mailbox::{Overflow, Policy, Sender},
14};
15use commonware_cryptography::{Digestible, certificate::Scheme};
16use commonware_p2p::Recipients;
17use commonware_runtime::{
18    Clock, Handle,
19    telemetry::{metrics::histogram::Timed, traces::TracedExt as _},
20};
21use commonware_utils::{channel::oneshot, vec::NonEmptyVec};
22use std::{
23    collections::{BTreeMap, VecDeque, btree_map::Entry},
24    num::NonZeroUsize,
25    sync::Arc,
26};
27use tracing::{Span, info_span};
28
29/// Messages sent to the marshal [Actor](super::Actor).
30///
31/// These messages are sent from the consensus engine and other parts of the
32/// system to drive the state of the marshal.
33pub(crate) enum Message<S: Scheme, V: Variant> {
34    /// A request to retrieve the `(height, digest)` of a block by its identifier.
35    /// The block must be finalized; returns `None` if the block is not finalized.
36    GetInfo {
37        /// The span carried with this request.
38        span: Span,
39        /// The identifier of the block to get the information of.
40        identifier: Identifier<<V::Block as Digestible>::Digest>,
41        /// A channel to send the retrieved `(height, digest)`.
42        response: oneshot::Sender<Option<(Height, <V::Block as Digestible>::Digest)>>,
43    },
44    /// A request to retrieve a block by its identifier.
45    ///
46    /// Requesting by [Identifier::Height] or [Identifier::Latest] will only return finalized
47    /// blocks, whereas requesting by [Identifier::Digest] may return non-finalized
48    /// or even unverified blocks.
49    GetBlock {
50        /// The span carried with this request.
51        span: Span,
52        /// The identifier of the block to retrieve.
53        identifier: Identifier<<V::Block as Digestible>::Digest>,
54        /// A channel to send the retrieved block.
55        response: oneshot::Sender<Option<V::Block>>,
56    },
57    /// A request to retrieve a finalization by height.
58    GetFinalization {
59        /// The span carried with this request.
60        span: Span,
61        /// The height of the finalization to retrieve.
62        height: Height,
63        /// A channel to send the retrieved finalization.
64        response: oneshot::Sender<Option<Finalization<S, V::Commitment>>>,
65    },
66    /// A request to retrieve the latest processed height.
67    GetProcessedHeight {
68        /// The span carried with this request.
69        span: Span,
70        /// A channel to send the latest processed height.
71        response: oneshot::Sender<Option<Height>>,
72    },
73    /// A hint that a finalized block may be available at a given height.
74    ///
75    /// This triggers a network fetch if the finalization is not available locally.
76    /// This is fire-and-forget: the finalization will be stored in marshal and
77    /// delivered via the normal finalization flow when available.
78    ///
79    /// The height must be covered by both the epocher and the provider. If the
80    /// epocher cannot map the height to an epoch, or the provider cannot supply
81    /// a scheme for that epoch, the hint is silently dropped.
82    ///
83    /// Targets are required because this is typically called when a peer claims to
84    /// be ahead. If a target returns invalid data, the resolver will block them.
85    /// Sending this message multiple times with different targets adds to the
86    /// target set.
87    HintFinalized {
88        /// The span carried with this request.
89        span: Span,
90        /// The height of the finalization to fetch.
91        height: Height,
92        /// Target peers to fetch from. Added to any existing targets for this height.
93        targets: NonEmptyVec<S::PublicKey>,
94    },
95    /// A request to subscribe to a block by its digest.
96    SubscribeByDigest {
97        /// The span carried with this request.
98        span: Span,
99        /// The digest of the block to retrieve.
100        digest: <V::Block as Digestible>::Digest,
101        /// How marshal should behave if the block is missing locally.
102        fallback: DigestFallback,
103        /// A channel to send the retrieved block.
104        response: oneshot::Sender<Arc<V::Block>>,
105    },
106    /// A request to subscribe to a block by its commitment.
107    SubscribeByCommitment {
108        /// The span carried with this request.
109        span: Span,
110        /// The commitment of the block to retrieve.
111        commitment: V::Commitment,
112        /// How marshal should behave if the block is missing locally.
113        fallback: CommitmentFallback,
114        /// A channel to send the retrieved block.
115        response: oneshot::Sender<Arc<V::Block>>,
116    },
117    /// A hint to fetch a notarized block by round without adding another local subscriber.
118    ///
119    /// `commitment` is used as a locality check: if the block is already
120    /// available locally, the fetch is skipped.
121    HintNotarized {
122        /// The span carried with this request.
123        span: Span,
124        /// The notarized round to request.
125        round: Round,
126        /// The commitment used to short-circuit if the block is already local.
127        commitment: V::Commitment,
128    },
129    /// A request to retrieve the verified block previously persisted for `round`.
130    GetVerified {
131        /// The span carried with this request.
132        span: Span,
133        /// The round to query.
134        round: Round,
135        /// A channel to send the retrieved block, if any.
136        response: oneshot::Sender<Option<V::Block>>,
137    },
138    /// A request to forward a block to a set of recipients.
139    Forward {
140        /// The span carried with this request.
141        span: Span,
142        /// The round in which the block was proposed.
143        round: Round,
144        /// The commitment of the block to forward.
145        commitment: V::Commitment,
146        /// The recipients to forward the block to.
147        recipients: Recipients<S::PublicKey>,
148    },
149    /// A request to broadcast a locally proposed block and persist it.
150    Proposed {
151        /// The span carried with this request.
152        span: Span,
153        /// The round in which the block was proposed.
154        round: Round,
155        /// The proposed block.
156        block: Arc<V::Block>,
157        /// The recipients to broadcast the block to.
158        recipients: Recipients<S::PublicKey>,
159        /// A channel sent once the block sync has started.
160        ack: oneshot::Sender<Handle<()>>,
161    },
162    /// A notification that a block reached the verify stage. Persisting it
163    /// does not imply application validity.
164    Verified {
165        /// The span carried with this request.
166        span: Span,
167        /// The round of the verify request.
168        round: Round,
169        /// The block.
170        block: Arc<V::Block>,
171        /// A channel sent once the block sync has started.
172        ack: oneshot::Sender<Handle<()>>,
173    },
174    /// A notification that a block reached the certify stage. Persisting it
175    /// does not imply application validity.
176    Certified {
177        /// The span carried with this request.
178        span: Span,
179        /// The round of the certify request.
180        round: Round,
181        /// The block.
182        block: Arc<V::Block>,
183        /// A channel sent once the block and notarization syncs have started; the
184        /// handle covers both.
185        ack: oneshot::Sender<Handle<()>>,
186    },
187    /// Attempts to set the sync starting point from a finalized commitment.
188    ///
189    /// If the verified finalization advances marshal's current floor, marshal
190    /// anchors on its block, prunes below it, then syncs and delivers blocks
191    /// starting at the floor height. Stale or superseded floors may be ignored.
192    ///
193    /// To prune data without changing the sync starting point, use
194    /// [Message::Prune] instead.
195    SetFloor {
196        /// The span carried with this request.
197        span: Span,
198        /// The candidate floor finalization, verified by the actor before use.
199        finalization: Finalization<S, V::Commitment>,
200    },
201    /// Requests pruning finalized blocks and certificates below the given height.
202    ///
203    /// Unlike [Message::SetFloor], this does not affect the sync starting
204    /// point. Requests above marshal's current floor are ignored.
205    Prune {
206        /// The span carried with this request.
207        span: Span,
208        /// The minimum height to keep (blocks below this are pruned).
209        height: Height,
210    },
211    /// A notarization from the consensus engine.
212    Notarization {
213        /// The span carried with this request.
214        span: Span,
215        /// The notarization.
216        notarization: Notarization<S, V::Commitment>,
217    },
218    /// A finalization from the consensus engine.
219    Finalization {
220        /// The span carried with this request.
221        span: Span,
222        /// The finalization.
223        finalization: Finalization<S, V::Commitment>,
224    },
225}
226
227/// How a digest-keyed block subscription should behave when the block is missing locally.
228#[derive(Clone, Copy, Debug, Eq, PartialEq)]
229pub enum DigestFallback {
230    /// Wait for local availability only.
231    Wait,
232    /// Request the notarized proposal for `round` from peers.
233    ///
234    /// Use this only when the caller has a trusted round for the digest. Digest-keyed
235    /// subscriptions intentionally cannot request exact commitment fetches.
236    FetchByRound { round: Round },
237}
238
239impl From<DigestFallback> for CommitmentFallback {
240    fn from(fallback: DigestFallback) -> Self {
241        match fallback {
242            DigestFallback::Wait => Self::Wait,
243            DigestFallback::FetchByRound { round } => Self::FetchByRound { round },
244        }
245    }
246}
247
248/// How a commitment-keyed block subscription should behave when the block is missing locally.
249#[derive(Clone, Copy, Debug, Eq, PartialEq)]
250pub enum CommitmentFallback {
251    /// Wait for local availability only.
252    ///
253    /// Use this for pending candidate proposal data before notarization.
254    Wait,
255    /// Request the notarized proposal for `round` from peers.
256    ///
257    /// Use this when the caller knows a trusted notarized or certified round and
258    /// commitment but not the proposal height, such as proposal construction,
259    /// verification of a known child, or certification of a notarized candidate. Do not infer
260    /// height from the finalized tip or another block: proposals may build on
261    /// a certified parent that is not finalized locally yet, and an unverified
262    /// child may lie about its height.
263    ///
264    /// The returned block is heightable once decoded, but that is too late for
265    /// the in-flight resolver key or retention bound.
266    FetchByRound { round: Round },
267    /// Request the exact commitment from peers and prune the request at
268    /// `height`.
269    ///
270    /// Use this only when no certified parent round is available. The caller must have a locally
271    /// validated resolver retention bound. Examples include repairing a finalized gap or walking
272    /// an accepted ancestry stream. Do not use it for a candidate's immediate parent when the
273    /// consensus context supplies the parent round.
274    ///
275    /// The height is not sent to peers. It is a local hint for request retention.
276    /// It is not part of response validity. A fetched block is delivered if its
277    /// commitment matches. A matching block above this bound is delivered but
278    /// not cached.
279    FetchByCommitment { height: Height },
280}
281
282impl<S: Scheme, V: Variant> Message<S, V> {
283    /// Returns the span carried with this message.
284    pub(crate) const fn span(&self) -> &Span {
285        match self {
286            Self::GetInfo { span, .. }
287            | Self::GetBlock { span, .. }
288            | Self::GetFinalization { span, .. }
289            | Self::GetVerified { span, .. }
290            | Self::SubscribeByDigest { span, .. }
291            | Self::SubscribeByCommitment { span, .. }
292            | Self::Forward { span, .. }
293            | Self::Proposed { span, .. }
294            | Self::Verified { span, .. }
295            | Self::Certified { span, .. }
296            | Self::Notarization { span, .. }
297            | Self::Finalization { span, .. }
298            | Self::GetProcessedHeight { span, .. }
299            | Self::HintFinalized { span, .. }
300            | Self::HintNotarized { span, .. }
301            | Self::SetFloor { span, .. }
302            | Self::Prune { span, .. } => span,
303        }
304    }
305
306    /// Returns the operation name of this message.
307    pub(crate) const fn name(&self) -> &'static str {
308        match self {
309            Self::GetInfo { .. } => "get_info",
310            Self::GetBlock { .. } => "get_block",
311            Self::GetFinalization { .. } => "get_finalization",
312            Self::GetProcessedHeight { .. } => "get_processed_height",
313            Self::HintFinalized { .. } => "hint_finalized",
314            Self::SubscribeByDigest { .. } => "subscribe_by_digest",
315            Self::SubscribeByCommitment { .. } => "subscribe_by_commitment",
316            Self::HintNotarized { .. } => "hint_notarized",
317            Self::GetVerified { .. } => "get_verified",
318            Self::Forward { .. } => "forward",
319            Self::Proposed { .. } => "proposed",
320            Self::Verified { .. } => "verified",
321            Self::Certified { .. } => "certified",
322            Self::SetFloor { .. } => "set_floor",
323            Self::Prune { .. } => "prune",
324            Self::Notarization { .. } => "notarization",
325            Self::Finalization { .. } => "finalization",
326        }
327    }
328
329    fn stale(&self, current: Option<Height>) -> bool {
330        match self {
331            // Height-targeted reads below the floor can never be served
332            Self::GetInfo {
333                identifier: Identifier::Height(height),
334                ..
335            }
336            | Self::GetBlock {
337                identifier: Identifier::Height(height),
338                ..
339            }
340            | Self::GetFinalization { height, .. } => Some(*height) < current,
341            // Hints only inform the actor about heights strictly above the floor
342            Self::HintFinalized { height, .. } => Some(*height) <= current,
343            // Durability acks cannot be dropped: callers depend on them
344            Self::Proposed { .. } | Self::Verified { .. } | Self::Certified { .. } => false,
345            // Digest and latest lookups are not bound to a specific height
346            Self::GetBlock {
347                identifier: Identifier::Digest(_) | Identifier::Latest,
348                ..
349            }
350            | Self::GetInfo {
351                identifier: Identifier::Digest(_) | Identifier::Latest,
352                ..
353            }
354            | Self::GetProcessedHeight { .. } => false,
355            Self::HintNotarized { .. } => false,
356            Self::SubscribeByDigest { .. }
357            | Self::SubscribeByCommitment { .. }
358            | Self::GetVerified { .. }
359            | Self::Forward { .. }
360            | Self::SetFloor { .. }
361            | Self::Prune { .. }
362            | Self::Notarization { .. }
363            | Self::Finalization { .. } => false,
364        }
365    }
366
367    pub(crate) fn response_closed(&self) -> bool {
368        match self {
369            Self::GetInfo { response, .. } => response.is_closed(),
370            Self::GetBlock { response, .. } | Self::GetVerified { response, .. } => {
371                response.is_closed()
372            }
373            Self::GetFinalization { response, .. } => response.is_closed(),
374            Self::GetProcessedHeight { response, .. } => response.is_closed(),
375            Self::SubscribeByDigest { response, .. }
376            | Self::SubscribeByCommitment { response, .. } => response.is_closed(),
377            Self::HintNotarized { .. } => false,
378            Self::HintFinalized { .. }
379            | Self::Forward { .. }
380            | Self::Proposed { .. }
381            | Self::Verified { .. }
382            | Self::Certified { .. }
383            | Self::SetFloor { .. }
384            | Self::Prune { .. }
385            | Self::Notarization { .. }
386            | Self::Finalization { .. } => false,
387        }
388    }
389}
390
391pub(crate) struct Pending<S: Scheme, V: Variant> {
392    floor: Option<(Span, Finalization<S, V::Commitment>)>,
393    prune: Option<(Span, Height)>,
394    hints: BTreeMap<Height, (Span, NonEmptyVec<S::PublicKey>)>,
395    messages: VecDeque<PendingMessage<S, V>>,
396}
397
398enum PendingMessage<S: Scheme, V: Variant> {
399    Message(Message<S, V>),
400    HintFinalized(Height),
401}
402
403impl<S: Scheme, V: Variant> Default for Pending<S, V> {
404    fn default() -> Self {
405        Self {
406            floor: None,
407            prune: None,
408            hints: BTreeMap::new(),
409            messages: VecDeque::new(),
410        }
411    }
412}
413
414impl<S: Scheme, V: Variant> Pending<S, V> {
415    // Only prune advances are usable for height staleness checks. A pending
416    // floor finalization does not carry the block height until the block is decoded.
417    fn height(&self) -> Option<Height> {
418        self.prune.as_ref().map(|(_, height)| *height)
419    }
420
421    fn retain(&mut self) {
422        let current = self.height();
423        self.hints.retain(|height, _| Some(*height) > current);
424
425        let hints = &self.hints;
426        self.messages.retain(|message| match message {
427            PendingMessage::Message(message) => {
428                !message.response_closed() && !message.stale(current)
429            }
430            PendingMessage::HintFinalized(height) => hints.contains_key(height),
431        });
432    }
433
434    fn set_floor(&mut self, span: Span, finalization: Finalization<S, V::Commitment>) {
435        let round = finalization.round();
436        if self
437            .floor
438            .as_ref()
439            .is_some_and(|(_, floor)| floor.round() >= round)
440        {
441            return;
442        }
443
444        self.floor = Some((span, finalization));
445    }
446
447    fn prune(&mut self, span: Span, height: Height) {
448        let current = self.height();
449        if current >= Some(height) {
450            return;
451        }
452
453        self.prune = Some((span, height));
454        self.retain();
455    }
456
457    fn extend_hint_targets(
458        pending: &mut NonEmptyVec<S::PublicKey>,
459        targets: NonEmptyVec<S::PublicKey>,
460    ) {
461        for target in targets {
462            if !pending.contains(&target) {
463                pending.push(target);
464            }
465        }
466    }
467
468    fn hint_finalized(&mut self, span: Span, height: Height, targets: NonEmptyVec<S::PublicKey>) {
469        // The finalized height is already covered by the floor or prune point.
470        let current = self.height();
471        if current.is_some_and(|current| height <= current) {
472            return;
473        }
474
475        match self.hints.entry(height) {
476            Entry::Vacant(entry) => {
477                entry.insert((span, targets));
478                self.messages
479                    .push_back(PendingMessage::HintFinalized(height));
480            }
481            Entry::Occupied(mut entry) => {
482                Self::extend_hint_targets(&mut entry.get_mut().1, targets);
483            }
484        }
485    }
486
487    fn restore_hint(&mut self, span: Span, height: Height, targets: NonEmptyVec<S::PublicKey>) {
488        match self.hints.entry(height) {
489            Entry::Vacant(entry) => {
490                entry.insert((span, targets));
491            }
492            Entry::Occupied(mut entry) => {
493                Self::extend_hint_targets(&mut entry.get_mut().1, targets);
494            }
495        }
496        self.messages
497            .push_front(PendingMessage::HintFinalized(height));
498    }
499
500    fn drain_one<F>(&mut self, message: Message<S, V>, push: &mut F) -> bool
501    where
502        F: FnMut(Message<S, V>) -> Option<Message<S, V>>,
503    {
504        // Receiver accepted; the message is consumed
505        let Some(message) = push(message) else {
506            return true;
507        };
508
509        // Receiver rejected; restore so the next drain retries from the same point
510        match message {
511            Message::SetFloor { span, finalization } => self.set_floor(span, finalization),
512            Message::Prune { span, height } => self.prune(span, height),
513            Message::HintFinalized {
514                span,
515                height,
516                targets,
517            } => self.restore_hint(span, height, targets),
518            message => self.messages.push_front(PendingMessage::Message(message)),
519        }
520        false
521    }
522}
523
524impl<S: Scheme, V: Variant> Overflow<Message<S, V>> for Pending<S, V> {
525    fn is_empty(&self) -> bool {
526        self.floor.is_none()
527            && self.prune.is_none()
528            && self.hints.is_empty()
529            && self.messages.is_empty()
530    }
531
532    fn drain<F>(&mut self, mut push: F)
533    where
534        F: FnMut(Message<S, V>) -> Option<Message<S, V>>,
535    {
536        // Drain floor and prune first so the actor advances its floor before
537        // it sees the height-bounded reads that follow
538        if let Some((span, finalization)) = self.floor.take()
539            && !self.drain_one(Message::SetFloor { span, finalization }, &mut push)
540        {
541            return;
542        }
543        if let Some((span, height)) = self.prune.take()
544            && !self.drain_one(Message::Prune { span, height }, &mut push)
545        {
546            return;
547        }
548
549        // Drain the remaining queued messages in FIFO order
550        while let Some(pending) = self.messages.pop_front() {
551            match pending {
552                PendingMessage::Message(message) => {
553                    if message.response_closed() {
554                        continue;
555                    }
556                    if !self.drain_one(message, &mut push) {
557                        break;
558                    }
559                }
560                PendingMessage::HintFinalized(hint_height) => {
561                    let Some((span, targets)) = self.hints.remove(&hint_height) else {
562                        continue;
563                    };
564                    let message = Message::HintFinalized {
565                        span,
566                        height: hint_height,
567                        targets,
568                    };
569                    if !self.drain_one(message, &mut push) {
570                        break;
571                    }
572                }
573            }
574        }
575    }
576}
577
578/// Coalesces `HintFinalized`, `SetFloor`, and `Prune`. Other overflowed messages
579/// retain FIFO order.
580impl<S: Scheme, V: Variant> Policy for Message<S, V> {
581    type Overflow = Pending<S, V>;
582
583    fn handle(overflow: &mut Self::Overflow, message: Self) {
584        // A closed responder cannot be served
585        if message.response_closed() {
586            return;
587        }
588        match message {
589            // Coalesce hints: a single entry per height with a unioned target set
590            Self::HintFinalized {
591                span,
592                height,
593                targets,
594            } => {
595                overflow.hint_finalized(span, height, targets);
596            }
597            // Floors collapse to the highest round seen; prune collapses to
598            // the highest height seen.
599            Self::SetFloor { span, finalization } => {
600                overflow.set_floor(span, finalization);
601            }
602            Self::Prune { span, height } => {
603                overflow.prune(span, height);
604            }
605            message => {
606                if message.stale(overflow.height()) {
607                    return;
608                }
609                overflow
610                    .messages
611                    .push_back(PendingMessage::Message(message));
612            }
613        }
614    }
615}
616
617/// A mailbox for sending messages to the marshal [Actor](super::Actor).
618#[derive(Clone)]
619pub struct Mailbox<S: Scheme, V: Variant> {
620    sender: Sender<Message<S, V>>,
621    max_pending_acks: usize,
622}
623
624impl<S: Scheme, V: Variant> Mailbox<S, V> {
625    /// Creates a new mailbox.
626    pub(crate) const fn new(sender: Sender<Message<S, V>>, max_pending_acks: NonZeroUsize) -> Self {
627        Self {
628            sender,
629            max_pending_acks: max_pending_acks.get(),
630        }
631    }
632
633    /// Returns the maximum number of application blocks marshal can dispatch before
634    /// acknowledgements advance its processed floor.
635    pub const fn max_pending_acks(&self) -> usize {
636        self.max_pending_acks
637    }
638
639    /// Create an ancestor stream that fetches missing parents by commitment.
640    ///
641    /// This stream is always a fetching stream. Callers must already have a block
642    /// that is safe to verify, certify, build on, or repair from. The stream derives
643    /// each missing parent's height and digest from its child and terminates if a
644    /// fetched block does not match that relationship.
645    ///
646    /// Do not use this to wait for pending candidate proposal data.
647    pub(crate) fn ancestor_stream<I, C>(
648        &self,
649        clock: Arc<C>,
650        initial: I,
651        fetch_duration: Timed,
652    ) -> impl Ancestry<V::ApplicationBlock> + use<S, V, I, C>
653    where
654        Self: BlockProvider<Block = V::ApplicationBlock>,
655        I: IntoIterator<Item = Arc<V::ApplicationBlock>>,
656        C: Clock,
657    {
658        AncestorStream::new(clock, self.clone(), initial, fetch_duration)
659    }
660
661    /// Retrieve `(height, digest)` for a finalized block by height, digest, or latest.
662    pub async fn get_info(
663        &self,
664        identifier: impl Into<Identifier<<V::Block as Digestible>::Digest>>,
665    ) -> Option<(Height, <V::Block as Digestible>::Digest)> {
666        let identifier = identifier.into();
667        let (response, receiver) = oneshot::channel();
668        let _ = self.sender.enqueue(Message::GetInfo {
669            span: info_span!("marshal.mailbox.get_info"),
670            identifier,
671            response,
672        });
673        receiver.await.ok().flatten()
674    }
675
676    /// A best-effort attempt to retrieve a given block from local
677    /// storage. It is not an indication to go fetch the block from the network.
678    pub async fn get_block(
679        &self,
680        identifier: impl Into<Identifier<<V::Block as Digestible>::Digest>>,
681    ) -> Option<V::Block> {
682        let identifier = identifier.into();
683        let (response, receiver) = oneshot::channel();
684        let _ = self.sender.enqueue(Message::GetBlock {
685            span: info_span!("marshal.mailbox.get_block"),
686            identifier,
687            response,
688        });
689        receiver.await.ok().flatten()
690    }
691
692    /// A best-effort attempt to retrieve a given [Finalization] from local
693    /// storage. It is not an indication to go fetch the [Finalization] from the network.
694    pub async fn get_finalization(&self, height: Height) -> Option<Finalization<S, V::Commitment>> {
695        let (response, receiver) = oneshot::channel();
696        let _ = self.sender.enqueue(Message::GetFinalization {
697            span: info_span!("marshal.mailbox.get_finalization", height = height.traced()),
698            height,
699            response,
700        });
701        receiver.await.ok().flatten()
702    }
703
704    /// Retrieve the latest processed height.
705    pub async fn get_processed_height(&self) -> Option<Height> {
706        let (response, receiver) = oneshot::channel();
707        let _ = self.sender.enqueue(Message::GetProcessedHeight {
708            span: info_span!("marshal.mailbox.get_processed_height"),
709            response,
710        });
711        receiver.await.ok().flatten()
712    }
713
714    /// Hints that a finalized block may be available at the given height.
715    ///
716    /// This method will request the finalization from the network via the resolver
717    /// if it is not available locally.
718    ///
719    /// Targets are required because this is typically called when a peer claims to be
720    /// ahead. By targeting only those peers, we limit who we ask. If a target returns
721    /// invalid data, they will be blocked by the resolver. If targets don't respond
722    /// or return "no data", they effectively rate-limit themselves.
723    ///
724    /// Calling this multiple times for the same height with different targets will
725    /// add to the target set if there is an ongoing fetch, allowing more peers to be tried.
726    ///
727    /// This is fire-and-forget: the finalization will be stored in marshal and delivered
728    /// via the normal finalization flow when available.
729    ///
730    /// The height must be covered by both the epocher and the provider. If the
731    /// epocher cannot map the height to an epoch, or the provider cannot supply
732    /// a scheme for that epoch, the hint is silently dropped.
733    pub fn hint_finalized(&self, height: Height, targets: NonEmptyVec<S::PublicKey>) {
734        let _ = self.sender.enqueue(Message::HintFinalized {
735            span: info_span!("marshal.mailbox.hint_finalized", height = height.traced()),
736            height,
737            targets,
738        });
739    }
740
741    /// Subscribe to a block by its digest.
742    ///
743    /// If the block is found available locally, the block will be returned immediately.
744    ///
745    /// If the block is not available locally, the subscription will be registered and the caller
746    /// will be notified when the block is available. If the block is not finalized, it's possible
747    /// that it may never become available.
748    ///
749    /// Resolver fetches and subscriptions have independent lifetimes. The processed floor may deny
750    /// or retire a fetch. The subscription remains open while the block may still arrive through
751    /// local ingress. It closes without delivery only when marshal can no longer obtain the block.
752    ///
753    /// The `fallback` parameter controls whether marshal also asks peers for the missing block.
754    /// Digest-keyed subscriptions only support waiting locally or fetching by round.
755    ///
756    /// Delivery makes no durability promise. A delivered block may not have been persisted by
757    /// marshal, so it may not be retrievable after an unclean shutdown. Consumers that need
758    /// durable height-ordered delivery should rely on application dispatch instead.
759    ///
760    /// The oneshot receiver should be dropped to cancel the subscription.
761    pub fn subscribe_by_digest(
762        &self,
763        digest: <V::Block as Digestible>::Digest,
764        fallback: DigestFallback,
765    ) -> oneshot::Receiver<Arc<V::Block>> {
766        let (tx, rx) = oneshot::channel();
767        let _ = self.sender.enqueue(Message::SubscribeByDigest {
768            span: info_span!("marshal.mailbox.subscribe_by_digest", digest = %digest),
769            digest,
770            fallback,
771            response: tx,
772        });
773        rx
774    }
775
776    /// Subscribe to a block by its commitment.
777    ///
778    /// If the block is found available locally, the block will be returned immediately.
779    ///
780    /// If the block is not available locally, the subscription will be registered and the caller
781    /// will be notified when the block is available. If the block is not finalized, it's possible
782    /// that it may never become available.
783    ///
784    /// Resolver fetches and subscriptions have independent lifetimes. The processed floor may deny
785    /// or retire a fetch. The subscription remains open while the block may still arrive through
786    /// local ingress. It closes without delivery only when marshal can no longer obtain the block.
787    ///
788    /// The `fallback` parameter controls whether marshal also asks peers for the missing block.
789    ///
790    /// Delivery makes no durability promise. A delivered block may not have been persisted by
791    /// marshal, so it may not be retrievable after an unclean shutdown. Consumers that need
792    /// durable height-ordered delivery should rely on application dispatch instead.
793    ///
794    /// The oneshot receiver should be dropped to cancel the subscription.
795    pub fn subscribe_by_commitment(
796        &self,
797        commitment: V::Commitment,
798        fallback: CommitmentFallback,
799    ) -> oneshot::Receiver<Arc<V::Block>> {
800        let (tx, rx) = oneshot::channel();
801        let _ = self.sender.enqueue(Message::SubscribeByCommitment {
802            span: info_span!("marshal.mailbox.subscribe_by_commitment", commitment = %commitment),
803            fallback,
804            commitment,
805            response: tx,
806        });
807        rx
808    }
809
810    /// Hint that peers may have the block notarized at `round`.
811    ///
812    /// This issues a round-bound resolver request without registering a new
813    /// block subscriber. The `commitment` is only used to skip the request when
814    /// the block is already available locally.
815    ///
816    /// This is useful when a local-only waiter already exists and later
817    /// certification makes a network fetch by notarized round valid.
818    pub fn hint_notarized(&self, round: Round, commitment: V::Commitment) {
819        let _ = self.sender.enqueue(Message::HintNotarized {
820            span: info_span!(
821                "marshal.mailbox.hint_notarized",
822                round = %round,
823                commitment = %commitment
824            ),
825            round,
826            commitment,
827        });
828    }
829
830    /// Returns a stream over the ancestry of a given block, leading up to genesis.
831    ///
832    /// This stream may fetch missing parents because callers should only request
833    /// ancestry for data they already have locally and are willing to build on,
834    /// verify, certify, or repair from. It is not a candidate fetch path.
835    ///
836    /// If the starting block is not found, `None` is returned.
837    pub async fn ancestry<C>(
838        &self,
839        clock: Arc<C>,
840        (fallback, start_digest): (DigestFallback, <V::Block as Digestible>::Digest),
841        fetch_duration: Timed,
842    ) -> Option<impl Ancestry<V::ApplicationBlock> + use<S, V, C>>
843    where
844        Self: BlockProvider<Block = V::ApplicationBlock>,
845        C: Clock,
846    {
847        let receiver = self.subscribe_by_digest(start_digest, fallback);
848        receiver.await.ok().map(|block| {
849            let block = V::into_inner_shared(block);
850            self.ancestor_stream(clock, [block], fetch_duration)
851        })
852    }
853
854    /// Returns the verified block previously persisted for `round`, if any.
855    ///
856    /// Multiple candidates can exist for one round (an equivocating leader can
857    /// land one before a crash and another after), and this returns the first
858    /// stored. Callers must not assume it is the most recently verified
859    /// candidate: check context/digest before reuse, or look up by digest.
860    pub async fn get_verified(&self, round: Round) -> Option<V::Block> {
861        let (response, receiver) = oneshot::channel();
862        let _ = self.sender.enqueue(Message::GetVerified {
863            span: info_span!("marshal.mailbox.get_verified", round = %round),
864            round,
865            response,
866        });
867        receiver.await.ok().flatten()
868    }
869
870    /// Requests the broadcast of a locally proposed block, persisting it after
871    /// the send.
872    ///
873    /// The actor hands the block to the network before ingesting and persisting
874    /// it, so the storage write never delays propagation. `ack` receives the
875    /// durable-sync handle once the write's sync has started. The propose path
876    /// stages the block (and `ack`) at propose time and calls this when consensus
877    /// requests the broadcast via [`crate::Relay::broadcast`], awaiting durability
878    /// only at certification so the sync overlaps consensus voting.
879    ///
880    /// A dropped `ack` (the mailbox is closed) abandons the handshake.
881    pub fn proposed(
882        &self,
883        round: Round,
884        block: impl Into<Arc<V::Block>>,
885        recipients: Recipients<S::PublicKey>,
886        ack: oneshot::Sender<Handle<()>>,
887    ) -> Feedback {
888        self.sender.enqueue(Message::Proposed {
889            span: info_span!("marshal.mailbox.proposed", round = %round),
890            round,
891            block: block.into(),
892            recipients,
893            ack,
894        })
895    }
896
897    /// Notifies the actor that a block should be durably persisted at `round`,
898    /// delivering its durable-sync handle through `ack` without awaiting it.
899    ///
900    /// Takes a sender rather than returning a receiver so certification can
901    /// deliver the handle into a handshake staged at propose time. A dropped
902    /// `ack` (the mailbox is closed) abandons the handshake.
903    pub fn verified_deferred(
904        &self,
905        round: Round,
906        block: impl Into<Arc<V::Block>>,
907        ack: oneshot::Sender<Handle<()>>,
908    ) {
909        let _ = self.sender.enqueue(Message::Verified {
910            span: info_span!("marshal.mailbox.verified", round = %round),
911            round,
912            block: block.into(),
913            ack,
914        });
915    }
916
917    /// Notifies the actor that a block reached the verify stage.
918    ///
919    /// Returns after the block is durably persisted. Mirrors [Self::certified]: the
920    /// durable sync is awaited on the caller's task (off the actor), so the actor
921    /// never blocks on fsync.
922    #[must_use = "callers must consider block durability before proceeding"]
923    pub async fn verified(&self, round: Round, block: impl Into<Arc<V::Block>>) -> bool {
924        let (ack, receiver) = oneshot::channel();
925        self.verified_deferred(round, block, ack);
926        let Ok(handle) = receiver.await else {
927            return false;
928        };
929        handle.durable(round, "verified").await
930    }
931
932    /// Notifies the actor that a block reached the certify stage.
933    ///
934    /// Returns after the block is durably persisted.
935    #[must_use = "callers must consider block durability before proceeding"]
936    pub async fn certified(&self, round: Round, block: impl Into<Arc<V::Block>>) -> bool {
937        let (ack, receiver) = oneshot::channel();
938        let _ = self.sender.enqueue(Message::Certified {
939            span: info_span!("marshal.mailbox.certified", round = %round),
940            round,
941            block: block.into(),
942            ack,
943        });
944        let Ok(handle) = receiver.await else {
945            return false;
946        };
947        handle.durable(round, "certified").await
948    }
949
950    /// Attempts to set the sync starting point from a finalized commitment.
951    ///
952    /// If the verified finalization advances marshal's current floor, marshal
953    /// anchors on its block, prunes below it, then syncs and delivers blocks
954    /// starting at the floor height. Stale or superseded floors may be ignored.
955    ///
956    /// To prune data without changing the sync starting point, use
957    /// [Self::prune] instead.
958    /// Use [`crate::marshal::Config::start`] to provide the startup anchor.
959    pub fn set_floor(&self, finalization: Finalization<S, V::Commitment>) {
960        let _ = self.sender.enqueue(Message::SetFloor {
961            span: info_span!("marshal.mailbox.set_floor", round = %finalization.round()),
962            finalization,
963        });
964    }
965
966    /// Requests pruning finalized blocks and certificates below the given height.
967    ///
968    /// Unlike [Self::set_floor], this does not affect the sync starting point.
969    /// Requests above marshal's current floor are ignored.
970    pub fn prune(&self, height: Height) {
971        let _ = self.sender.enqueue(Message::Prune {
972            span: info_span!("marshal.mailbox.prune", height = height.traced()),
973            height,
974        });
975    }
976
977    /// Forward a locally stored block to a set of recipients.
978    pub fn forward(
979        &self,
980        round: Round,
981        commitment: V::Commitment,
982        recipients: Recipients<S::PublicKey>,
983    ) -> Feedback {
984        self.sender.enqueue(Message::Forward {
985            span: info_span!("marshal.mailbox.forward", round = %round, commitment = %commitment),
986            round,
987            commitment,
988            recipients,
989        })
990    }
991}
992
993impl<S: Scheme, V: Variant> Reporter for Mailbox<S, V> {
994    type Activity = Activity<S, V::Commitment>;
995
996    fn report(&mut self, activity: Self::Activity) -> Feedback {
997        let message = match activity {
998            Activity::Notarization(notarization) => Message::Notarization {
999                span: info_span!("marshal.mailbox.notarization", round = %notarization.round()),
1000                notarization,
1001            },
1002            Activity::Finalization(finalization) => Message::Finalization {
1003                span: info_span!("marshal.mailbox.finalization", round = %finalization.round()),
1004                finalization,
1005            },
1006            _ => return Feedback::Ok,
1007        };
1008        self.sender.enqueue(message)
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use crate::{
1016        Heightable,
1017        marshal::{mocks::harness, standard::Standard},
1018        simplex::{scheme::bls12381_threshold::vrf as bls12381_threshold_vrf, types::Proposal},
1019        types::{Epoch, View},
1020    };
1021    use commonware_cryptography::{
1022        Digest as _, Signer as _, certificate::mocks::Fixture, ed25519::PrivateKey,
1023    };
1024    use commonware_runtime::{Runner as _, deterministic};
1025    use commonware_utils::{NZUsize, TestRng, channel::oneshot::error::TryRecvError};
1026
1027    type TestMessage = Message<harness::S, Standard<harness::B>>;
1028    type TestPending = Pending<harness::S, Standard<harness::B>>;
1029
1030    fn public_key(seed: u64) -> harness::K {
1031        PrivateKey::from_seed(seed).public_key()
1032    }
1033
1034    fn round(height: u64) -> Round {
1035        Round::new(Epoch::zero(), View::new(height))
1036    }
1037
1038    fn block(height: u64) -> harness::B {
1039        harness::make_raw_block(harness::D::EMPTY, Height::new(height), height)
1040    }
1041
1042    fn commitment(height: u64) -> harness::D {
1043        <Standard<harness::B> as Variant>::commitment(&block(height))
1044    }
1045
1046    fn finalization(height: u64) -> Finalization<harness::S, harness::D> {
1047        let mut rng = TestRng::new(height);
1048        let Fixture { schemes, .. } = bls12381_threshold_vrf::fixture::<harness::V, _>(
1049            &mut rng,
1050            harness::NAMESPACE,
1051            harness::NUM_VALIDATORS,
1052        );
1053        let proposal = Proposal::new(round(height), View::zero(), commitment(height));
1054        <harness::StandardHarness as harness::TestHarness>::make_finalization(
1055            proposal,
1056            &schemes,
1057            harness::QUORUM,
1058        )
1059    }
1060
1061    fn get_info(height: u64) -> (TestMessage, oneshot::Receiver<Option<(Height, harness::D)>>) {
1062        let (response, receiver) = oneshot::channel();
1063        (
1064            TestMessage::GetInfo {
1065                span: Span::none(),
1066                identifier: Identifier::Height(Height::new(height)),
1067                response,
1068            },
1069            receiver,
1070        )
1071    }
1072
1073    fn proposed(height: u64) -> (TestMessage, oneshot::Receiver<Handle<()>>) {
1074        let (ack, receiver) = oneshot::channel();
1075        (
1076            TestMessage::Proposed {
1077                span: Span::none(),
1078                round: round(height),
1079                block: block(height).into(),
1080                recipients: Recipients::All,
1081                ack,
1082            },
1083            receiver,
1084        )
1085    }
1086
1087    fn verified(height: u64) -> (TestMessage, oneshot::Receiver<Handle<()>>) {
1088        let (ack, receiver) = oneshot::channel();
1089        (
1090            TestMessage::Verified {
1091                span: Span::none(),
1092                round: round(height),
1093                block: block(height).into(),
1094                ack,
1095            },
1096            receiver,
1097        )
1098    }
1099
1100    fn certified(height: u64) -> (TestMessage, oneshot::Receiver<Handle<()>>) {
1101        let (ack, receiver) = oneshot::channel();
1102        (
1103            TestMessage::Certified {
1104                span: Span::none(),
1105                round: round(height),
1106                block: block(height).into(),
1107                ack,
1108            },
1109            receiver,
1110        )
1111    }
1112
1113    fn get_block(height: u64) -> (TestMessage, oneshot::Receiver<Option<harness::B>>) {
1114        let (response, receiver) = oneshot::channel();
1115        (
1116            TestMessage::GetBlock {
1117                span: Span::none(),
1118                identifier: Identifier::Height(Height::new(height)),
1119                response,
1120            },
1121            receiver,
1122        )
1123    }
1124
1125    fn get_finalization(
1126        height: u64,
1127    ) -> (
1128        TestMessage,
1129        oneshot::Receiver<Option<Finalization<harness::S, harness::D>>>,
1130    ) {
1131        let (response, receiver) = oneshot::channel();
1132        (
1133            TestMessage::GetFinalization {
1134                span: Span::none(),
1135                height: Height::new(height),
1136                response,
1137            },
1138            receiver,
1139        )
1140    }
1141
1142    fn subscribe_by_digest(height: u64) -> (TestMessage, oneshot::Receiver<Arc<harness::B>>) {
1143        let (response, receiver) = oneshot::channel();
1144        (
1145            TestMessage::SubscribeByDigest {
1146                span: Span::none(),
1147                digest: block(height).digest(),
1148                fallback: DigestFallback::FetchByRound {
1149                    round: round(height),
1150                },
1151                response,
1152            },
1153            receiver,
1154        )
1155    }
1156
1157    fn subscribe_by_commitment_message(
1158        height: u64,
1159        fallback: CommitmentFallback,
1160    ) -> (TestMessage, oneshot::Receiver<Arc<harness::B>>) {
1161        let (response, receiver) = oneshot::channel();
1162        (
1163            TestMessage::SubscribeByCommitment {
1164                span: Span::none(),
1165                commitment: commitment(height),
1166                fallback,
1167                response,
1168            },
1169            receiver,
1170        )
1171    }
1172
1173    fn hint_finalized(height: u64, target: harness::K) -> TestMessage {
1174        TestMessage::HintFinalized {
1175            span: Span::none(),
1176            height: Height::new(height),
1177            targets: NonEmptyVec::new(target),
1178        }
1179    }
1180
1181    fn hint_notarized(height: u64) -> TestMessage {
1182        TestMessage::HintNotarized {
1183            span: Span::none(),
1184            round: round(height),
1185            commitment: commitment(height),
1186        }
1187    }
1188
1189    fn set_floor(height: u64) -> TestMessage {
1190        TestMessage::SetFloor {
1191            span: Span::none(),
1192            finalization: finalization(height),
1193        }
1194    }
1195
1196    fn prune(height: u64) -> TestMessage {
1197        TestMessage::Prune {
1198            span: Span::none(),
1199            height: Height::new(height),
1200        }
1201    }
1202
1203    fn pending() -> TestPending {
1204        TestPending::default()
1205    }
1206
1207    fn drain(overflow: &mut TestPending) -> VecDeque<TestMessage> {
1208        let mut drained = VecDeque::new();
1209        overflow.drain(|message| {
1210            drained.push_back(message);
1211            None
1212        });
1213        drained
1214    }
1215
1216    fn has_get_info(overflow: &TestPending, height: u64) -> bool {
1217        overflow.messages.iter().any(|message| {
1218            matches!(
1219                message,
1220                PendingMessage::Message(TestMessage::GetInfo {
1221                    identifier: Identifier::Height(found),
1222                    response,
1223                    ..
1224                }) if *found == Height::new(height) && !response.is_closed()
1225            )
1226        })
1227    }
1228
1229    fn has_get_block(overflow: &TestPending, height: u64) -> bool {
1230        overflow.messages.iter().any(|message| {
1231            matches!(
1232                message,
1233                PendingMessage::Message(TestMessage::GetBlock {
1234                    identifier: Identifier::Height(found),
1235                    response,
1236                    ..
1237                }) if *found == Height::new(height) && !response.is_closed()
1238            )
1239        })
1240    }
1241
1242    fn has_get_finalization(overflow: &TestPending, height: u64) -> bool {
1243        overflow.messages.iter().any(|message| {
1244            matches!(
1245                message,
1246                PendingMessage::Message(TestMessage::GetFinalization {
1247                    height: found,
1248                    response,
1249                    ..
1250                }) if *found == Height::new(height) && !response.is_closed()
1251            )
1252        })
1253    }
1254
1255    fn hint_targets(overflow: &TestPending, height: u64) -> Option<&NonEmptyVec<harness::K>> {
1256        overflow
1257            .hints
1258            .get(&Height::new(height))
1259            .map(|(_, targets)| targets)
1260    }
1261
1262    fn has_block_message(overflow: &TestPending, height: u64) -> bool {
1263        overflow.messages.iter().any(|message| {
1264            matches!(
1265                message,
1266                PendingMessage::Message(
1267                    TestMessage::Proposed { block, .. }
1268                        | TestMessage::Verified { block, .. }
1269                        | TestMessage::Certified { block, .. }
1270                )
1271                    if block.height() == Height::new(height)
1272            )
1273        })
1274    }
1275
1276    fn has_prune(overflow: &TestPending, height: u64) -> bool {
1277        overflow.prune.as_ref().map(|(_, height)| *height) == Some(Height::new(height))
1278    }
1279
1280    fn has_subscription(overflow: &TestPending, height: u64) -> bool {
1281        let expected_digest = block(height).digest();
1282        let expected_commitment = commitment(height);
1283        overflow.messages.iter().any(|message| {
1284            matches!(
1285                message,
1286                PendingMessage::Message(TestMessage::SubscribeByDigest { digest, response, .. })
1287                    if *digest == expected_digest && !response.is_closed()
1288            ) || matches!(
1289                message,
1290                PendingMessage::Message(TestMessage::SubscribeByCommitment {
1291                    commitment,
1292                    response,
1293                    ..
1294                }) if *commitment == expected_commitment && !response.is_closed()
1295            )
1296        })
1297    }
1298
1299    #[test]
1300    fn durable_methods_report_failure_when_mailbox_closed() {
1301        let runner = deterministic::Runner::default();
1302        runner.start(|context| async move {
1303            let (sender, receiver) =
1304                commonware_actor::mailbox::new::<TestMessage>(context, NZUsize!(1));
1305            let mailbox = Mailbox::<harness::S, Standard<harness::B>>::new(sender, NZUsize!(1));
1306            drop(receiver);
1307
1308            let (ack, receiver) = oneshot::channel();
1309            let _ = mailbox.proposed(round(1), block(1), Recipients::All, ack);
1310            assert!(receiver.await.is_err());
1311            assert!(!mailbox.verified(round(2), block(2)).await);
1312            assert!(!mailbox.certified(round(3), block(3)).await);
1313        });
1314    }
1315
1316    #[test]
1317    fn policy_coalesces_hint_targets() {
1318        let mut overflow = pending();
1319        let first = public_key(1);
1320        let second = public_key(2);
1321
1322        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(10, first.clone()));
1323        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(10, first.clone()));
1324        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(10, second.clone()));
1325
1326        assert_eq!(overflow.messages.len(), 1);
1327        let targets = hint_targets(&overflow, 10).expect("expected hint");
1328        assert_eq!(targets.len().get(), 2);
1329        assert!(targets.contains(&first));
1330        assert!(targets.contains(&second));
1331    }
1332
1333    #[test]
1334    fn policy_preserves_commitment_subscription_fallbacks() {
1335        let mut overflow = pending();
1336
1337        let (wait, _wait_rx) = subscribe_by_commitment_message(1, CommitmentFallback::Wait);
1338        let (by_round, _by_round_rx) = subscribe_by_commitment_message(
1339            2,
1340            CommitmentFallback::FetchByRound { round: round(2) },
1341        );
1342        let (by_commitment, _by_commitment_rx) = subscribe_by_commitment_message(
1343            3,
1344            CommitmentFallback::FetchByCommitment {
1345                height: Height::new(3),
1346            },
1347        );
1348
1349        <TestMessage as Policy>::handle(&mut overflow, wait);
1350        <TestMessage as Policy>::handle(&mut overflow, by_round);
1351        <TestMessage as Policy>::handle(&mut overflow, by_commitment);
1352
1353        let drained = drain(&mut overflow);
1354        assert_eq!(drained.len(), 3);
1355        assert!(matches!(
1356            &drained[0],
1357            TestMessage::SubscribeByCommitment {
1358                fallback: CommitmentFallback::Wait,
1359                ..
1360            }
1361        ));
1362        assert!(matches!(
1363            &drained[1],
1364            TestMessage::SubscribeByCommitment {
1365                fallback: CommitmentFallback::FetchByRound { round: found },
1366                ..
1367            } if *found == round(2)
1368        ));
1369        assert!(matches!(
1370            &drained[2],
1371            TestMessage::SubscribeByCommitment {
1372                fallback: CommitmentFallback::FetchByCommitment { height },
1373                ..
1374            } if *height == Height::new(3)
1375        ));
1376    }
1377
1378    #[test]
1379    fn policy_handles_closed_subscriptions() {
1380        let mut overflow = pending();
1381
1382        let (pending_closed, pending_closed_rx) = subscribe_by_digest(1);
1383        drop(pending_closed_rx);
1384        overflow
1385            .messages
1386            .push_back(PendingMessage::Message(pending_closed));
1387
1388        let (pending_open, mut pending_open_rx) = subscribe_by_commitment_message(
1389            2,
1390            CommitmentFallback::FetchByRound { round: round(2) },
1391        );
1392        overflow
1393            .messages
1394            .push_back(PendingMessage::Message(pending_open));
1395
1396        let (current_closed, current_closed_rx) = subscribe_by_digest(3);
1397        drop(current_closed_rx);
1398        <TestMessage as Policy>::handle(&mut overflow, current_closed);
1399
1400        assert!(!has_subscription(&overflow, 1));
1401        assert!(has_subscription(&overflow, 2));
1402        assert!(!has_subscription(&overflow, 3));
1403        assert!(matches!(
1404            pending_open_rx.try_recv(),
1405            Err(TryRecvError::Empty)
1406        ));
1407    }
1408
1409    #[test]
1410    fn policy_handles_closed_responses() {
1411        let mut overflow = pending();
1412
1413        let (pending_closed, pending_closed_rx) = get_block(1);
1414        drop(pending_closed_rx);
1415        overflow
1416            .messages
1417            .push_back(PendingMessage::Message(pending_closed));
1418
1419        let (pending_open, mut pending_open_rx) = get_info(2);
1420        overflow
1421            .messages
1422            .push_back(PendingMessage::Message(pending_open));
1423
1424        let (current_closed, current_closed_rx) = get_finalization(3);
1425        drop(current_closed_rx);
1426        <TestMessage as Policy>::handle(&mut overflow, current_closed);
1427
1428        assert!(!has_get_block(&overflow, 1));
1429        assert!(has_get_info(&overflow, 2));
1430        assert!(!has_get_finalization(&overflow, 3));
1431        assert!(matches!(
1432            pending_open_rx.try_recv(),
1433            Err(TryRecvError::Empty)
1434        ));
1435    }
1436
1437    #[test]
1438    fn policy_drain_stops_after_returned_response_closes() {
1439        let mut overflow = pending();
1440        let (first, first_rx) = get_block(1);
1441        let (second, mut second_rx) = get_info(2);
1442        overflow.messages.push_back(PendingMessage::Message(first));
1443        overflow.messages.push_back(PendingMessage::Message(second));
1444
1445        let mut first_rx = Some(first_rx);
1446        let mut attempts = 0;
1447        overflow.drain(|message| {
1448            attempts += 1;
1449            drop(first_rx.take());
1450            Some(message)
1451        });
1452        assert_eq!(attempts, 1);
1453
1454        let drained = drain(&mut overflow);
1455        assert_eq!(drained.len(), 1);
1456        assert!(matches!(
1457            &drained[0],
1458            TestMessage::GetInfo {
1459                identifier: Identifier::Height(height),
1460                response,
1461                ..
1462            } if *height == Height::new(2) && !response.is_closed()
1463        ));
1464        assert!(matches!(second_rx.try_recv(), Err(TryRecvError::Empty)));
1465    }
1466
1467    #[test]
1468    fn policy_drains_fifo() {
1469        let mut overflow = pending();
1470        let first = public_key(1);
1471        let second = public_key(2);
1472        let (response, _subscribe_rx) = oneshot::channel();
1473        let subscribe = TestMessage::SubscribeByDigest {
1474            span: Span::none(),
1475            digest: block(1).digest(),
1476            fallback: DigestFallback::Wait,
1477            response,
1478        };
1479        let (response, _processed_rx) = oneshot::channel();
1480        let processed = TestMessage::GetProcessedHeight {
1481            span: Span::none(),
1482            response,
1483        };
1484
1485        <TestMessage as Policy>::handle(&mut overflow, subscribe);
1486        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(10, first.clone()));
1487        <TestMessage as Policy>::handle(&mut overflow, hint_notarized(1));
1488        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(10, second.clone()));
1489        <TestMessage as Policy>::handle(&mut overflow, processed);
1490
1491        let drained = drain(&mut overflow);
1492        assert_eq!(drained.len(), 4);
1493        assert!(matches!(
1494            &drained[0],
1495            TestMessage::SubscribeByDigest {
1496                digest,
1497                fallback: DigestFallback::Wait,
1498                ..
1499            } if *digest == block(1).digest()
1500        ));
1501        let TestMessage::HintFinalized {
1502            height, targets, ..
1503        } = &drained[1]
1504        else {
1505            panic!("expected hint");
1506        };
1507        assert_eq!(*height, Height::new(10));
1508        assert_eq!(targets.len().get(), 2);
1509        assert!(targets.contains(&first));
1510        assert!(targets.contains(&second));
1511        assert!(matches!(
1512            &drained[2],
1513            TestMessage::HintNotarized { round: hinted, .. } if *hinted == round(1)
1514        ));
1515        assert!(matches!(
1516            &drained[3],
1517            TestMessage::GetProcessedHeight { .. }
1518        ));
1519    }
1520
1521    #[test]
1522    fn policy_keeps_highest_floor_and_prune() {
1523        let mut overflow = pending();
1524
1525        <TestMessage as Policy>::handle(&mut overflow, set_floor(5));
1526        <TestMessage as Policy>::handle(&mut overflow, set_floor(3));
1527        <TestMessage as Policy>::handle(&mut overflow, set_floor(8));
1528        <TestMessage as Policy>::handle(&mut overflow, prune(4));
1529        <TestMessage as Policy>::handle(&mut overflow, prune(2));
1530        <TestMessage as Policy>::handle(&mut overflow, prune(7));
1531
1532        assert_eq!(
1533            overflow.floor.as_ref().map(|(_, floor)| floor.round()),
1534            Some(round(8))
1535        );
1536        assert_eq!(
1537            overflow.prune.as_ref().map(|(_, height)| *height),
1538            Some(Height::new(7))
1539        );
1540        assert!(overflow.messages.is_empty());
1541
1542        let drained = drain(&mut overflow);
1543        assert_eq!(drained.len(), 2);
1544        assert!(matches!(
1545            &drained[0],
1546            TestMessage::SetFloor { finalization, .. } if finalization.round() == round(8)
1547        ));
1548        assert!(matches!(
1549            &drained[1],
1550            TestMessage::Prune { height, .. } if *height == Height::new(7)
1551        ));
1552    }
1553
1554    #[test]
1555    fn policy_replaces_floor_and_prune_and_drops_stale_pending_on_drain() {
1556        let mut overflow = pending();
1557
1558        overflow.floor = Some((Span::none(), finalization(5)));
1559        let (get_info_4, _get_info_4_rx) = get_info(4);
1560        let (get_block_7, _get_block_7_rx) = get_block(7);
1561        let (get_block_8, _get_block_8_rx) = get_block(8);
1562        overflow
1563            .messages
1564            .push_back(PendingMessage::Message(get_info_4));
1565        overflow
1566            .messages
1567            .push_back(PendingMessage::Message(get_block_7));
1568        overflow.hint_finalized(
1569            Span::none(),
1570            Height::new(8),
1571            NonEmptyVec::new(public_key(1)),
1572        );
1573        overflow
1574            .messages
1575            .push_back(PendingMessage::Message(get_block_8));
1576        <TestMessage as Policy>::handle(&mut overflow, set_floor(8));
1577        <TestMessage as Policy>::handle(&mut overflow, prune(8));
1578        assert_eq!(
1579            overflow.floor.as_ref().map(|(_, floor)| floor.round()),
1580            Some(round(8))
1581        );
1582        assert_eq!(overflow.messages.len(), 1);
1583        assert!(!has_get_info(&overflow, 4));
1584        assert!(!has_get_block(&overflow, 7));
1585        assert!(has_get_block(&overflow, 8));
1586        assert!(hint_targets(&overflow, 8).is_none());
1587        let drained = drain(&mut overflow);
1588        assert_eq!(drained.len(), 3);
1589        assert!(matches!(
1590            &drained[0],
1591            TestMessage::SetFloor { finalization, .. } if finalization.round() == round(8)
1592        ));
1593        assert!(matches!(
1594            &drained[1],
1595            TestMessage::Prune { height, .. } if *height == Height::new(8)
1596        ));
1597        assert!(matches!(
1598            &drained[2],
1599            TestMessage::GetBlock {
1600                identifier: Identifier::Height(height),
1601                ..
1602            } if *height == Height::new(8)
1603        ));
1604
1605        let mut overflow = pending();
1606        overflow.prune = Some((Span::none(), Height::new(5)));
1607        let (get_finalization_4, _get_finalization_4_rx) = get_finalization(4);
1608        let (get_block_6, _get_block_6_rx) = get_block(6);
1609        let (get_block_7, _get_block_7_rx) = get_block(7);
1610        overflow
1611            .messages
1612            .push_back(PendingMessage::Message(get_finalization_4));
1613        overflow
1614            .messages
1615            .push_back(PendingMessage::Message(get_block_6));
1616        overflow.hint_finalized(
1617            Span::none(),
1618            Height::new(6),
1619            NonEmptyVec::new(public_key(2)),
1620        );
1621        overflow
1622            .messages
1623            .push_back(PendingMessage::Message(get_block_7));
1624        <TestMessage as Policy>::handle(&mut overflow, prune(7));
1625        assert_eq!(
1626            overflow.prune.as_ref().map(|(_, height)| *height),
1627            Some(Height::new(7))
1628        );
1629        assert_eq!(overflow.messages.len(), 1);
1630        assert!(!has_get_finalization(&overflow, 4));
1631        assert!(!has_get_block(&overflow, 6));
1632        assert!(has_get_block(&overflow, 7));
1633        assert!(hint_targets(&overflow, 6).is_none());
1634        let drained = drain(&mut overflow);
1635        assert_eq!(drained.len(), 2);
1636        assert!(matches!(
1637            &drained[0],
1638            TestMessage::Prune { height, .. } if *height == Height::new(7)
1639        ));
1640        assert!(matches!(
1641            &drained[1],
1642            TestMessage::GetBlock {
1643                identifier: Identifier::Height(height),
1644                ..
1645            } if *height == Height::new(7)
1646        ));
1647    }
1648
1649    #[test]
1650    fn policy_prune_drops_closed_pending() {
1651        let mut overflow = pending();
1652        let (closed_message, closed_rx) = get_block(8);
1653        drop(closed_rx);
1654        let (open_message, mut open_rx) = get_block(8);
1655
1656        overflow
1657            .messages
1658            .push_back(PendingMessage::Message(closed_message));
1659        overflow
1660            .messages
1661            .push_back(PendingMessage::Message(open_message));
1662
1663        <TestMessage as Policy>::handle(&mut overflow, prune(7));
1664        assert_eq!(overflow.messages.len(), 1);
1665        assert!(has_get_block(&overflow, 8));
1666        assert!(matches!(open_rx.try_recv(), Err(TryRecvError::Empty)));
1667
1668        let mut overflow = pending();
1669        let (closed_message, closed_rx) = get_finalization(8);
1670        drop(closed_rx);
1671        let (open_message, mut open_rx) = get_finalization(8);
1672
1673        overflow
1674            .messages
1675            .push_back(PendingMessage::Message(closed_message));
1676        overflow
1677            .messages
1678            .push_back(PendingMessage::Message(open_message));
1679
1680        <TestMessage as Policy>::handle(&mut overflow, prune(7));
1681        assert_eq!(overflow.messages.len(), 1);
1682        assert!(has_get_finalization(&overflow, 8));
1683        assert!(matches!(open_rx.try_recv(), Err(TryRecvError::Empty)));
1684    }
1685
1686    #[test]
1687    fn policy_skips_retain_when_prune_height_does_not_increase() {
1688        let mut overflow = pending();
1689        <TestMessage as Policy>::handle(&mut overflow, prune(10));
1690
1691        let (closed_message, closed_rx) = get_block(11);
1692        drop(closed_rx);
1693        overflow
1694            .messages
1695            .push_back(PendingMessage::Message(closed_message));
1696
1697        <TestMessage as Policy>::handle(&mut overflow, set_floor(9));
1698        assert_eq!(overflow.messages.len(), 1);
1699
1700        <TestMessage as Policy>::handle(&mut overflow, prune(9));
1701        assert_eq!(overflow.messages.len(), 1);
1702
1703        <TestMessage as Policy>::handle(&mut overflow, prune(12));
1704        assert!(overflow.messages.is_empty());
1705    }
1706
1707    #[test]
1708    fn policy_drops_stale_requests_against_pending_floor_and_prune() {
1709        let mut overflow = pending();
1710        let (get_info_4, _get_info_4_rx) = get_info(4);
1711        let (get_info_5, _get_info_5_rx) = get_info(5);
1712        let (get_info_6, _get_info_6_rx) = get_info(6);
1713        let (get_info_7, _get_info_7_rx) = get_info(7);
1714        let (get_block_4, _get_block_4_rx) = get_block(4);
1715        let (get_block_5, _get_block_5_rx) = get_block(5);
1716        let (get_block_6, _get_block_6_rx) = get_block(6);
1717        let (get_block_7, _get_block_7_rx) = get_block(7);
1718        let (get_finalization_4, _get_finalization_4_rx) = get_finalization(4);
1719        let (get_finalization_6, _get_finalization_6_rx) = get_finalization(6);
1720
1721        <TestMessage as Policy>::handle(&mut overflow, set_floor(5));
1722        <TestMessage as Policy>::handle(&mut overflow, get_info_4);
1723        <TestMessage as Policy>::handle(&mut overflow, get_info_5);
1724        <TestMessage as Policy>::handle(&mut overflow, get_block_4);
1725        <TestMessage as Policy>::handle(&mut overflow, get_block_5);
1726        <TestMessage as Policy>::handle(&mut overflow, get_finalization_4);
1727        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(5, public_key(1)));
1728        <TestMessage as Policy>::handle(&mut overflow, hint_finalized(6, public_key(2)));
1729
1730        <TestMessage as Policy>::handle(&mut overflow, prune(7));
1731        assert!(has_prune(&overflow, 7));
1732        <TestMessage as Policy>::handle(&mut overflow, get_info_6);
1733        <TestMessage as Policy>::handle(&mut overflow, get_finalization_6);
1734        assert!(!has_get_finalization(&overflow, 6));
1735        <TestMessage as Policy>::handle(&mut overflow, get_block_6);
1736        <TestMessage as Policy>::handle(&mut overflow, get_info_7);
1737        assert!(has_get_info(&overflow, 7));
1738        <TestMessage as Policy>::handle(&mut overflow, get_block_7);
1739        assert!(has_get_block(&overflow, 7));
1740
1741        let drained = drain(&mut overflow);
1742        assert_eq!(drained.len(), 4);
1743        assert!(matches!(
1744            &drained[0],
1745            TestMessage::SetFloor { finalization, .. } if finalization.round() == round(5)
1746        ));
1747        assert!(matches!(
1748            &drained[1],
1749            TestMessage::Prune { height, .. } if *height == Height::new(7)
1750        ));
1751        assert!(matches!(
1752            &drained[2],
1753            TestMessage::GetInfo {
1754                identifier: Identifier::Height(height),
1755                ..
1756            } if *height == Height::new(7)
1757        ));
1758        assert!(matches!(
1759            &drained[3],
1760            TestMessage::GetBlock {
1761                identifier: Identifier::Height(height),
1762                ..
1763            } if *height == Height::new(7)
1764        ));
1765    }
1766
1767    #[test]
1768    fn policy_keeps_block_messages_and_waiters() {
1769        let mut overflow = pending();
1770
1771        let (proposed_message, mut proposed_ack) = proposed(4);
1772        let (verified_message, mut verified_ack) = verified(6);
1773        let (certified_message, mut certified_ack) = certified(8);
1774        overflow
1775            .messages
1776            .push_back(PendingMessage::Message(proposed_message));
1777        overflow
1778            .messages
1779            .push_back(PendingMessage::Message(verified_message));
1780        overflow
1781            .messages
1782            .push_back(PendingMessage::Message(certified_message));
1783
1784        <TestMessage as Policy>::handle(&mut overflow, set_floor(7));
1785        assert!(has_block_message(&overflow, 4));
1786        assert!(has_block_message(&overflow, 6));
1787        assert!(has_block_message(&overflow, 8));
1788        assert!(matches!(proposed_ack.try_recv(), Err(TryRecvError::Empty)));
1789        assert!(matches!(verified_ack.try_recv(), Err(TryRecvError::Empty)));
1790        assert!(matches!(certified_ack.try_recv(), Err(TryRecvError::Empty)));
1791
1792        <TestMessage as Policy>::handle(&mut overflow, prune(9));
1793        assert!(has_block_message(&overflow, 8));
1794        assert!(matches!(certified_ack.try_recv(), Err(TryRecvError::Empty)));
1795
1796        let (stale, mut stale_ack) = proposed(8);
1797        <TestMessage as Policy>::handle(&mut overflow, stale);
1798        assert!(has_block_message(&overflow, 8));
1799        assert!(matches!(stale_ack.try_recv(), Err(TryRecvError::Empty)));
1800
1801        let (current, mut current_ack) = verified(9);
1802        <TestMessage as Policy>::handle(&mut overflow, current);
1803        assert!(has_block_message(&overflow, 9));
1804        assert!(matches!(current_ack.try_recv(), Err(TryRecvError::Empty)));
1805
1806        let drained = drain(&mut overflow);
1807        assert!(matches!(drained[0], TestMessage::SetFloor { .. }));
1808        assert!(matches!(drained[1], TestMessage::Prune { .. }));
1809    }
1810}