Skip to main content

recon_core/
protocol.rs

1//! The protocol contract.
2
3use crate::{Cx, Effect, NodeId, Time, TimerId};
4use rand::RngCore;
5
6/// A synchronous protocol state machine.
7///
8/// Implementors handle three kinds of event — a command from the layer above, a message from a
9/// peer, and a timer fire — and emit effects through the context. Handling an event runs to
10/// completion: it cannot await, cannot be suspended, and cannot be cancelled part-way, so a
11/// state transition is atomic with respect to whatever drives it.
12///
13/// The four associated types are the protocol's ports, in Kompics terms: what it accepts from
14/// above, what it promises upward, what it puts on the wire, and what it schedules.
15pub trait Protocol {
16    /// Requests from the layer above.
17    type Cmd;
18    /// Indications to the layer above — this protocol delivering on its guarantee.
19    type Ind;
20    /// What crosses the wire to a peer running the same protocol.
21    type Msg;
22    /// The durable value this protocol rewrites: a position, a count, an epoch. Small enough that
23    /// rewriting it costs nothing.
24    ///
25    /// A protocol keeping nothing durably declares this and [`Protocol::Entry`] as
26    /// [`core::convert::Infallible`], and then a write cannot be constructed — the same
27    /// check-rather-than-trust that [`Protocol::Scope`] uses.
28    type Meta;
29
30    /// The durable entries this protocol appends: what accumulates.
31    type Entry;
32
33    /// Scopes whose boundaries this protocol's guarantees depend on, and which it can observe.
34    ///
35    /// A guarantee is rarely absolute. It holds while some condition does — a transport session,
36    /// a retention window — and the end of that condition is an event the protocol must be told
37    /// about, not an implementation detail beneath it. See `docs/scope-annotated-modules.md`.
38    ///
39    /// **Both** boundaries travel here, not only the ending. A scope beginning is what makes a
40    /// bridge possible at all: an ending says a suffix may be gone, and only the beginning of the
41    /// successor says where to send it again. A port carrying just the ending would name a
42    /// problem with no event on which to act.
43    ///
44    /// A protocol with no such condition declares [`core::convert::Infallible`]. That is not a
45    /// convention: an uninhabited type has no values, so a scope event cannot be constructed for
46    /// it and [`Protocol::on_scope_event`] can never be called. The absence is checked rather than
47    /// trusted, and such a protocol writes no handler at all.
48    ///
49    /// A scope may only be named by a protocol that can observe its end. Naming one it cannot
50    /// detect creates an obligation no implementation can discharge and no test can exercise.
51    type Scope;
52
53    /// The vocabulary in which this protocol narrates its decisions.
54    ///
55    /// A record of effects says what a protocol *did*. It cannot say what the protocol *decided*,
56    /// and in particular it is silent about a decision whose outcome was to do nothing — a message
57    /// refused, a candidate already passed, an announcement not made. Those are the cases that have
58    /// cost this project the most, because a silence leaves nothing behind to read.
59    ///
60    /// A protocol narrating nothing declares [`core::convert::Infallible`], as with
61    /// [`Protocol::Scope`] and for the same reason: an uninhabited type has no values, so
62    /// [`Cx::note`] cannot be called for it. The absence is checked rather than trusted.
63    ///
64    /// **A vocabulary belongs to the run, not to a layer**, exactly as a [`TimerId`] does. A
65    /// composed stack narrates in one, and a note passes through composition untouched — no
66    /// mapper, no conversion, nothing for a parent to re-wrap. A parent never restates a child's
67    /// decision, because the decision was the child's; it merely lets it through, which is why
68    /// `Cx::with_child` hands the child the parent's own note sink the way it hands down the
69    /// source of timer identities.
70    ///
71    /// Narration is output-only. Nothing a protocol can observe reveals whether anything received a
72    /// note, so no behaviour may depend on one and a run is reproducible whether or not it was
73    /// read.
74    type Note;
75
76    /// Handle a request from the layer above.
77    fn on_cmd(&mut self, cmd: Self::Cmd, cx: &mut ProtoCx<'_, Self>);
78
79    /// Handle a message received from `from`.
80    fn on_msg(&mut self, from: NodeId, msg: Self::Msg, cx: &mut ProtoCx<'_, Self>);
81
82    /// Handle a timer that fired somewhere in this protocol or in what it composes.
83    ///
84    /// The identity says nothing about which layer registered it, so a protocol that composes
85    /// children hands the expiry to each of them, and a protocol that registered timers acts only
86    /// on one it registered. A protocol that registered none does nothing but pass it on. That is
87    /// the price of a timer's type not encoding the composition path, and it is a handful of calls
88    /// deep rather than a type that grows with the stack.
89    fn on_timer(&mut self, id: TimerId, cx: &mut ProtoCx<'_, Self>);
90
91    /// Begin, on a first start — when nothing has been written down.
92    ///
93    /// Exactly one of this and [`Protocol::on_recovery`] runs at startup, which is the branch the
94    /// book draws with `⟨ Init ⟩` and `⟨ Recovery ⟩`. It matters because some first-start work must
95    /// *not* happen on a restart: writing an initial value down is the standard case, and doing it
96    /// again on recovery would overwrite what was being recovered.
97    ///
98    /// The constructor cannot serve this purpose. It runs in both cases — it is the common prefix
99    /// of the two branches, not one of them — and it cannot emit effects, so it has nowhere to put
100    /// a write. Volatile setup belongs there; anything a first start must *do* belongs here.
101    fn on_init(&mut self, _cx: &mut ProtoCx<'_, Self>) {}
102
103    /// Resume after a crash, reading what survived.
104    ///
105    /// Distinct from construction, and deliberately: the algorithms that need it *act* on
106    /// recovering — re-announcing what they had already delivered, re-sending what was still
107    /// pending — and those are effects, which a constructor cannot emit.
108    ///
109    /// What survived is read from [`Cx::storage`] rather than handed over, since a protocol may
110    /// need only part of it. Exactly one of this and [`Protocol::on_init`] runs at startup.
111    ///
112    /// Nothing else is dispatched between being told to recover and this returning, which is what
113    /// makes it safe to hold state not yet loaded.
114    fn on_recovery(&mut self, _cx: &mut ProtoCx<'_, Self>) {}
115
116    /// Handle a boundary of a scope this protocol's guarantees depend on — its end, or the
117    /// beginning of the one that succeeds it.
118    ///
119    /// Scope events travel *downward*, like messages: they originate outside the stack and are
120    /// routed by each layer to whichever child cares, in the concrete type the bottom layer
121    /// declares. What travels back up is an indication — a layer that cannot restore its
122    /// guarantee says so in its own terms.
123    ///
124    /// The default does nothing, which is unreachable for a protocol whose `Scope` is uninhabited.
125    fn on_scope_event(&mut self, _scope: Self::Scope, _cx: &mut ProtoCx<'_, Self>) {}
126}
127
128/// The context type for a given protocol.
129pub type ProtoCx<'a, P> = Cx<
130    'a,
131    <P as Protocol>::Msg,
132    <P as Protocol>::Ind,
133    <P as Protocol>::Note,
134    <P as Protocol>::Meta,
135    <P as Protocol>::Entry,
136>;
137
138/// The effect type for a given protocol.
139pub type ProtoEffect<P> = Effect<<P as Protocol>::Msg, <P as Protocol>::Ind>;
140
141/// An event a protocol can be given. Used by drivers and by the test helper.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum Event<C, M, S> {
144    Cmd(C),
145    Msg {
146        from: NodeId,
147        msg: M,
148    },
149    /// A timer registered by this protocol, or by something it composes, has fired.
150    Timer(TimerId),
151    /// A scope this protocol's guarantees depended on has ended.
152    ScopeEvent(S),
153    /// This process is starting for the first time, with nothing written down.
154    Init,
155    /// This process restarted, and something it wrote down survived.
156    Recovery,
157}
158
159/// The event type for a given protocol.
160pub type ProtoEvent<P> = Event<<P as Protocol>::Cmd, <P as Protocol>::Msg, <P as Protocol>::Scope>;
161
162/// Deliver one event to `p` and return the effects it emitted.
163///
164/// Restores the ergonomics of a pure function for tests — `assert_eq!(step(..), [..])` — without
165/// making production paths allocate a vector per event. Intended for tests; drivers own a
166/// reusable buffer and call the handlers directly.
167///
168/// **For a protocol driven alone.** This starts the timer identities at zero on every call, so a
169/// composition driven through it hands two layers the same handle and each accepts the other's
170/// expiry as its own — a wrong test that need not fail. Use [`step_with`] for a stack, and
171/// [`step_in`] for a protocol whose writes must survive between calls. The distinction is not
172/// expressible in the type: a composed protocol looks like any other from here.
173pub fn step<P: Protocol + ?Sized>(
174    p: &mut P,
175    event: ProtoEvent<P>,
176    now: Time,
177    rng: &mut dyn RngCore,
178) -> Vec<ProtoEffect<P>> {
179    let mut store = crate::store::MemStore::default();
180    step_in(p, event, now, rng, &mut store)
181}
182
183/// Deliver one event to `p` against a store the caller owns, and return the effects it emitted.
184///
185/// [`step`] gives the protocol a fresh store each call, which is right for one that keeps nothing
186/// durably and wrong for one that does — a write in one call would be invisible in the next. A
187/// test that cares about what survives passes its own store here and can inspect it afterwards.
188pub fn step_in<P: Protocol + ?Sized>(
189    p: &mut P,
190    event: ProtoEvent<P>,
191    now: Time,
192    rng: &mut dyn RngCore,
193    store: &mut dyn crate::store::Store<P::Meta, P::Entry>,
194) -> Vec<ProtoEffect<P>> {
195    let mut next_timer = 0;
196    step_with(p, event, now, rng, store, &mut next_timer)
197}
198
199/// Deliver one event to `p` against a timer identity source the caller owns.
200///
201/// [`step`] and [`step_in`] start identities at zero on every call, which is right for a protocol
202/// driven alone and wrong for a composed one: two layers would each be handed identity zero, and
203/// each would accept the other's expiry as its own. A driver owns one source for a whole run — see
204/// `Sim` — and a test driving a stack by hand must do the same.
205///
206/// Nothing listens for what the protocol narrates. Use [`step_noting`] to read that too.
207pub fn step_with<P: Protocol + ?Sized>(
208    p: &mut P,
209    event: ProtoEvent<P>,
210    now: Time,
211    rng: &mut dyn RngCore,
212    store: &mut dyn crate::store::Store<P::Meta, P::Entry>,
213    next_timer: &mut u64,
214) -> Vec<ProtoEffect<P>> {
215    step_noting(p, event, now, rng, store, next_timer, &mut crate::NoNotes)
216}
217
218/// [`step_with`], collecting what the protocol narrates as well as what it emitted.
219///
220/// The two are returned separately here because a caller stepping a protocol by hand has no trace
221/// to interleave them into. Under the simulator they land in one account, in order, which is what
222/// lets a claim be checked against what happened.
223pub fn step_noting<P: Protocol + ?Sized>(
224    p: &mut P,
225    event: ProtoEvent<P>,
226    now: Time,
227    rng: &mut dyn RngCore,
228    store: &mut dyn crate::store::Store<P::Meta, P::Entry>,
229    next_timer: &mut u64,
230    notes: &mut dyn crate::NoteSink<P::Note>,
231) -> Vec<ProtoEffect<P>> {
232    let mut effects = Vec::new();
233    {
234        let mut cx = Cx::new(&mut effects, now, rng, store, next_timer, notes);
235        match event {
236            Event::Cmd(c) => p.on_cmd(c, &mut cx),
237            Event::Msg { from, msg } => p.on_msg(from, msg, &mut cx),
238            Event::Timer(id) => p.on_timer(id, &mut cx),
239            Event::ScopeEvent(s) => p.on_scope_event(s, &mut cx),
240            Event::Init => p.on_init(&mut cx),
241            Event::Recovery => p.on_recovery(&mut cx),
242        }
243    }
244    effects
245}