Skip to main content

commonware_consensus/marshal/core/
mailbox.rs

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