recon_protocols/stacks.rs
1//! Ready-made compositions: a layer, spelled with the link it runs over.
2//!
3//! Every protocol here takes its link as a type parameter, which is what removed the four forked
4//! `session_*` broadcast modules. The cost is that naming a stack means naming both halves, and the
5//! payload the link carries is the layer's business rather than the caller's:
6//!
7//! ```text
8//! ReliableBroadcast<u32, SessionLink<reliable_broadcast::Data<u32>>>
9//! ```
10//!
11//! `Data` is an implementation detail of Algorithm 3.3 — the originator and sequence number it
12//! deduplicates on — and a caller who wants "reliable broadcast over a session link, carrying
13//! `u32`" should not have to know it exists. Each layer names it as `Carried<P>`, and the aliases
14//! below use that to spell the whole stack in one type parameter.
15//!
16//! # Why these live here and not beside either half
17//!
18//! Putting `OverSessions` in `reliable_broadcast` would make that module name a particular link,
19//! which is exactly the dependency the port was built to remove — a layer states its requirement as
20//! `Link` and names no implementation. Putting it in `session_link` would invert the same problem.
21//! A composition belongs to neither of the things it composes, so it lives in its own module.
22//!
23//! Only the session stacks are named, because they are the ones that existed as forked modules and
24//! so have call sites wanting them. A stack over an application's own link is spelled at its own
25//! call site with its own alias; there is nothing for this crate to name.
26
27use crate::best_effort_broadcast::BestEffortBroadcast;
28use crate::eventual_leader_detector::EventualLeaderDetector;
29use crate::flooding_consensus::{self as fc, FloodingConsensus};
30use crate::lazy_probabilistic_broadcast::{self as lpb, LazyProbabilisticBroadcast};
31use crate::majority_ack_uniform_reliable_broadcast::{
32 self as maurb, MajorityAckUniformReliableBroadcast,
33};
34use crate::perfect_failure_detector::PerfectFailureDetector;
35use crate::probabilistic_broadcast::{self as pb, ProbabilisticBroadcast};
36use crate::reliable_broadcast::{self as rb, ReliableBroadcast};
37use crate::session_link::SessionLink;
38use crate::uniform_reliable_broadcast::{self as urb, UniformReliableBroadcast};
39
40/// Best-effort broadcast over a session link — what `session_best_effort_broadcast` was.
41///
42/// Validity holds while the sessions carrying a broadcast hold. This layer keeps nothing it could
43/// resend from, so it reports a boundary upward rather than repairing one.
44pub type BestEffortBroadcastOverSessions<P> = BestEffortBroadcast<P, SessionLink<P>>;
45
46/// Eager reliable broadcast over a session link — what `session_reliable_broadcast` was.
47///
48/// `RB4` is scoped to the sessions that carried the relay: this layer relays once and keeps
49/// identifiers rather than payloads, so a relay lost to an ending is lost for good.
50pub type ReliableBroadcastOverSessions<P> = ReliableBroadcast<P, SessionLink<rb::Carried<P>>>;
51
52/// Uniform reliable broadcast over a session link — what `session_uniform_reliable_broadcast` was.
53///
54/// Bridges an ending rather than propagating it: `pending` outlives the scope, so a
55/// re-establishment prompts a directed resend.
56pub type UniformReliableBroadcastOverSessions<P> =
57 UniformReliableBroadcast<P, SessionLink<urb::Carried<P>>>;
58
59/// Majority-ack uniform reliable broadcast over a session link — what
60/// `session_majority_ack_uniform_reliable_broadcast` was.
61///
62/// Bridges an ending the same way, and without a failure detector, so no timing assumption comes
63/// with it.
64pub type MajorityAckUniformReliableBroadcastOverSessions<P> =
65 MajorityAckUniformReliableBroadcast<P, SessionLink<maurb::Carried<P>>>;
66
67/// Flooding consensus over a session link.
68///
69/// Named for completeness rather than because a fork of it existed. Its failure detector's
70/// synchrony assumption is unaffected by the link beneath.
71pub type FloodingConsensusOverSessions<P> = FloodingConsensus<P, SessionLink<fc::Carried<P>>>;
72
73/// Eager gossip over a session link — the real-world set's form of Algorithm 3.9.
74///
75/// Within a session nothing is lost, so the only loss is a session ending, which this layer
76/// propagates: it keeps identifiers rather than payloads and has nothing to resend. `PB1` stays
77/// probabilistic because `picktargets` is still random.
78pub type ProbabilisticBroadcastOverSessions<P> =
79 ProbabilisticBroadcast<P, SessionLink<pb::Carried<P>>>;
80
81/// Lazy gossip over session links — both halves, the gossip and the recovery, over one session per
82/// peer pair.
83///
84/// Bridges a session ending the way the algorithm bridges any loss: the next message from that
85/// sender exposes the gap, and a request pulls what the ending dropped from whoever stored it.
86pub type LazyProbabilisticBroadcastOverSessions<P> = LazyProbabilisticBroadcast<
87 P,
88 SessionLink<lpb::Recovery<P>>,
89 SessionLink<pb::Carried<lpb::Data<P>>>,
90>;
91
92/// Ω over the **perfect** failure detector — what this module was before `◇P` existed.
93///
94/// Strictly stronger than Algorithm 2.8 asks for, and therefore correct: a detector that is right
95/// from the start satisfies "eventually right". What it costs is that a suspicion is permanent, so
96/// leadership only ever walks *downward* through the membership and a process that crashed and
97/// recovered can never lead again. In the crash-stop model nothing recovers and that costs nothing;
98/// in the fail-recovery model it is the difference between a stack that can regain a leader and one
99/// that cannot.
100///
101/// Use it where the delivery bound really is known and permanence is wanted. Otherwise use
102/// [`EventualLeaderDetector`], which defaults to the detector its algorithm names.
103pub type EventualLeaderDetectorOverPerfectDetection =
104 EventualLeaderDetector<PerfectFailureDetector>;
105
106impl EventualLeaderDetectorOverPerfectDetection {
107 /// Ω over a perfect failure detector beating every `heartbeat` and accusing after
108 /// `detect_after` of silence.
109 pub fn over_perfect_detection(
110 me: recon_core::NodeId,
111 peers: impl IntoIterator<Item = recon_core::NodeId>,
112 heartbeat: core::time::Duration,
113 detect_after: core::time::Duration,
114 ) -> Self {
115 EventualLeaderDetector::with_detector(me, peers, |me, all| {
116 PerfectFailureDetector::new(me, all, heartbeat, detect_after)
117 })
118 }
119}