Skip to main content

commonware_consensus/marshal/core/
mailbox.rs

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