commonware_consensus/lib.rs
1//! Order opaque messages in a Byzantine environment.
2//!
3//! # Status
4//!
5//! Stability varies by primitive. See [README](https://github.com/commonwarexyz/monorepo#stability) for details.
6
7#![doc(
8 html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
9 html_favicon_url = "https://commonware.xyz/favicon.ico"
10)]
11
12use commonware_macros::stability_scope;
13
14stability_scope!(BETA {
15 use commonware_codec::{Encode, Read};
16 use commonware_cryptography::{Digestible, Digested};
17 use std::sync::Arc;
18
19 pub mod simplex;
20
21 pub mod types;
22 use types::{Epoch, Height, Round, View};
23
24 /// Epochable is a trait that provides access to the epoch number.
25 /// Any consensus message or object that is associated with a specific epoch should implement this.
26 pub trait Epochable {
27 /// Returns the epoch associated with this object.
28 fn epoch(&self) -> Epoch;
29 }
30
31 /// Heightable is a trait that provides access to the height.
32 /// Any consensus message or object that is associated with a specific height should implement this.
33 pub trait Heightable {
34 /// Returns the height associated with this object.
35 fn height(&self) -> Height;
36 }
37
38 impl<T: Heightable + ?Sized> Heightable for Arc<T> {
39 fn height(&self) -> Height {
40 self.as_ref().height()
41 }
42 }
43
44 /// Viewable is a trait that provides access to the view (round) number.
45 /// Any consensus message or object that is associated with a specific view should implement this.
46 pub trait Viewable {
47 /// Returns the view associated with this object.
48 fn view(&self) -> View;
49 }
50
51 /// Roundable is a trait that provides access to the [`Round`] number.
52 /// Any consensus message or object that implements [`Epochable`] and [`Viewable`] automatically
53 /// implements this trait.
54 pub trait Roundable: Epochable + Viewable {
55 /// Returns the round associated with this object, derived from its epoch and view.
56 fn round(&self) -> Round {
57 Round::new(self.epoch(), self.view())
58 }
59 }
60
61 impl<T: Epochable + Viewable> Roundable for T {}
62
63 /// Block is the interface for a block in the blockchain.
64 ///
65 /// A block type that is also decoded, such as an application block received over the wire or
66 /// read from storage, must use a canonical encoding. Every byte sequence `bytes` accepted by
67 /// the decoder must satisfy `encode(decode(bytes)) == bytes`, and decoders must reject
68 /// alternate encodings of the same block.
69 pub trait Block: Heightable + Digestible + Encode {
70 /// Get the parent block's digest.
71 fn parent(&self) -> Self::Digest;
72 }
73
74 impl<B: Block> Block for Arc<B> {
75 fn parent(&self) -> Self::Digest {
76 self.as_ref().parent()
77 }
78 }
79
80 impl<B: Block> Heightable for Digested<B> {
81 fn height(&self) -> Height {
82 self.inner().height()
83 }
84 }
85
86 impl<B: Block> Block for Digested<B> {
87 fn parent(&self) -> Self::Digest {
88 self.inner().parent()
89 }
90 }
91
92 /// CertifiableBlock extends [Block] with consensus context information.
93 ///
94 /// This trait is required for blocks used with deferred verification in [CertifiableAutomaton].
95 /// It allows the verification context to be derived directly from the block when a validator
96 /// needs to participate in certification but never verified the block locally (necessary for liveness).
97 ///
98 /// The [`Digestible`] implementation for a [`CertifiableBlock`] must commit to the block's
99 /// embedded consensus context. In other words, changing [`CertifiableBlock::context`] for a
100 /// block must also change [`Digestible::digest`].
101 pub trait CertifiableBlock: Block {
102 /// The consensus context type stored in this block.
103 type Context: Clone + Encode;
104
105 /// Get the consensus context that was used when this block was proposed.
106 fn context(&self) -> Self::Context;
107 }
108
109 impl<B: CertifiableBlock> CertifiableBlock for Digested<B> {
110 type Context = B::Context;
111
112 fn context(&self) -> Self::Context {
113 self.inner().context()
114 }
115 }
116
117 /// A block that bounds its encoded size under a codec configuration.
118 pub trait BoundedBlock: Block + Read {
119 /// Returns an upper bound on the encoded size of any block [`Read::read_cfg`] admits under
120 /// `cfg`, using each encoded type's maximum width.
121 fn max_encode_size(cfg: &Self::Cfg) -> usize;
122 }
123});
124stability_scope!(BETA, cfg(not(target_arch = "wasm32")) {
125 use commonware_actor::Feedback;
126 use commonware_cryptography::{Digest, PublicKey};
127 use commonware_utils::channel::{fallible::OneshotExt, oneshot, ring};
128 use std::future::Future;
129
130 pub mod marshal;
131
132 mod reporter;
133 pub use reporter::*;
134
135 /// Histogram buckets for measuring consensus latency.
136 const LATENCY: [f64; 36] = [
137 0.05, 0.1, 0.125, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26,
138 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.45,
139 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
140 ];
141
142 /// Automaton is the interface responsible for driving the consensus forward by proposing new payloads
143 /// and verifying payloads proposed by other participants.
144 pub trait Automaton: Clone + Send + 'static {
145 /// Context is metadata provided by the consensus engine associated with a given payload.
146 ///
147 /// This often includes things like the proposer, view number, the height, or the epoch.
148 type Context;
149
150 /// Hash of an arbitrary payload.
151 type Digest: Digest;
152
153 /// Generate a new payload for the given context.
154 ///
155 /// If it is possible to generate a payload, the Digest should be returned over the provided
156 /// channel. If it is not possible to generate a payload, the channel can be dropped. If construction
157 /// takes too long, the consensus engine may drop the provided proposal.
158 ///
159 /// Returning a payload from `propose` commits the local proposer to verifying
160 /// the same `(context, payload)`.
161 ///
162 /// For [`CertifiableAutomaton`] implementations, returning a payload from
163 /// `propose` also commits the local proposer to certifying that same
164 /// `(round, payload)` if it later becomes notarized.
165 ///
166 /// Consensus may request a payload for a future context before earlier
167 /// contexts complete. Honor any dependencies supplied in the context
168 /// rather than rebuilding them from current local state. If consensus
169 /// later abandons a dependency, it also abandons the proposal.
170 ///
171 /// Closing the response declines this request, which consensus may
172 /// treat as final for the context. Keep the response pending when
173 /// temporary unavailability should not abandon the context.
174 fn propose(
175 &mut self,
176 context: Self::Context,
177 ) -> impl Future<Output = oneshot::Receiver<Self::Digest>> + Send;
178
179 /// Verify the payload is valid.
180 ///
181 /// Honest participants must agree on validity for the same `(context, payload)`.
182 ///
183 /// This request is single-shot for the given `(context, payload)`. Once the returned
184 /// channel resolves or closes, consensus treats verification as concluded and will not
185 /// retry the same request. After a restart, however, consensus may request verification
186 /// for the same `(context, payload)` again if the result was not durably recorded before
187 /// shutdown.
188 ///
189 /// Implementations should therefore keep the request pending while the verdict may still
190 /// change. Return `false` only when the payload is permanently invalid for this context.
191 /// For example, temporary conditions such as time skew, missing dependencies, or data
192 /// that may arrive later should not conclude verification with `false`.
193 ///
194 /// Closing the channel is also terminal for this request and should be reserved for cases
195 /// where verification cannot ever produce a verdict anymore (for example, shutdown), not
196 /// for temporary inability to decide.
197 ///
198 /// The future-context requirement on [`Self::propose`] applies here
199 /// too: the context's dependencies may not be resolvable locally yet.
200 fn verify(
201 &mut self,
202 context: Self::Context,
203 payload: Self::Digest,
204 ) -> impl Future<Output = oneshot::Receiver<bool>> + Send;
205 }
206
207 /// An application's response to a handoff proposal request.
208 ///
209 /// `Handoff<()>` is the same decision without a payload, and [`map`](Self::map) attaches one.
210 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
211 pub enum Handoff<D> {
212 /// Relay the candidate and cast the proposer's notarize vote without waiting for the
213 /// parent to certify.
214 ///
215 /// This trusts the outgoing leader not to equivocate and to complete its term, since
216 /// the proposal is usable only if every uncertified view it builds on certifies.
217 ///
218 /// Validators buffer the early vote only once it is inside their admission window, which
219 /// always includes the next term start from the parent's term (see
220 /// [Optimistic Validation](crate::simplex#optimistic-validation)), and consensus does not
221 /// resend it. If the quorum needs a validator whose window does not reach the vote yet, the
222 /// incoming term times out.
223 Vote(D),
224 /// Relay the candidate and withhold the proposer's notarize vote until its parent
225 /// certifies or finalizes.
226 ///
227 /// The relay commits to nothing: only the vote names a proposal. If the parent is
228 /// replaced before it certifies, the proposer can relay another candidate for the same
229 /// view.
230 ///
231 /// A relay may defer sending the candidate until consensus requests the broadcast that
232 /// precedes the proposer's vote.
233 Stage(D),
234 /// Request an ordinary proposal after the parent certifies.
235 Wait,
236 }
237
238 impl<D> Handoff<D> {
239 /// Returns whether this response declines to build.
240 pub const fn is_wait(&self) -> bool {
241 matches!(self, Self::Wait)
242 }
243
244 /// Applies `f` to the payload, keeping the decision.
245 pub fn map<T>(self, f: impl FnOnce(D) -> T) -> Handoff<T> {
246 match self {
247 Self::Vote(payload) => Handoff::Vote(f(payload)),
248 Self::Stage(payload) => Handoff::Stage(f(payload)),
249 Self::Wait => Handoff::Wait,
250 }
251 }
252 }
253
254 /// CertifiableAutomaton extends [Automaton] with the ability to certify payloads before finalization.
255 ///
256 /// This trait is required by consensus implementations (like Simplex) that support a certification
257 /// phase between notarization and finalization. Applications that do not need custom certification
258 /// logic can use the default implementation which always certifies.
259 pub trait CertifiableAutomaton: Automaton {
260 /// Prepare a payload for a term-start proposal whose parent is not yet certified.
261 ///
262 /// Returning [`Handoff::Vote`] or [`Handoff::Stage`] commits the application to
263 /// the same verification and certification obligations as returning a payload from
264 /// [`Automaton::propose`].
265 ///
266 /// With [`Handoff::Wait`], consensus issues an ordinary [`Automaton::propose`] for the same
267 /// context once the parent certifies, unless it has already voted to nullify this view or
268 /// has left it.
269 ///
270 /// Consensus requests the relay of a [`Handoff::Stage`] candidate as soon as it arrives.
271 ///
272 /// Closing the response forfeits the local proposal opportunity for this view once the
273 /// parent certifies or finalizes, and the view then times out as a missing proposal.
274 ///
275 /// A replacement parent can supersede the request's parent before it certifies. Consensus
276 /// then drops the request and can request a proposal on the replacement, unless it has
277 /// voted to nullify this view or has left it.
278 ///
279 /// Return the receiver promptly and do any work behind it. Parent certification does not
280 /// cancel this request, and while the response is pending, consensus requests no other
281 /// proposal for this view.
282 ///
283 /// Consensus drops the receiver when it abandons the context, so stop pending work when
284 /// the receiver closes.
285 ///
286 /// Consensus also drops the receiver once the parent can no longer be built on before it
287 /// certifies, as after a nullification in the parent's term, or once it votes to nullify
288 /// the view it is waiting in, at or below the parent. An application that verifies other
289 /// blocks only after this build completes could otherwise keep that parent or view from
290 /// certifying. The request then waits as if the application had returned
291 /// [`Handoff::Wait`].
292 ///
293 /// # Request Contract
294 ///
295 /// Per view and per process, with at most `f` faulty validators, consensus calls this
296 /// method at most twice and [`Automaton::propose`] at most once. It skips this method when
297 /// the parent is already certified. A second call follows only when the outgoing leader
298 /// equivocated at the parent view and its other block there was notarized, replacing the
299 /// parent this node voted for. A restart resets these counts.
300 fn prepare(
301 &mut self,
302 _context: Self::Context,
303 ) -> impl Future<Output = oneshot::Receiver<Handoff<Self::Digest>>> + Send
304 {
305 #[allow(clippy::async_yields_async)]
306 async move {
307 let (sender, receiver) = oneshot::channel();
308 sender.send_lossy(Handoff::Wait);
309 receiver
310 }
311 }
312
313 /// Determine whether a verified payload is safe to commit.
314 ///
315 /// The round parameter identifies which consensus round is being certified, allowing
316 /// applications to associate certification with the correct verification context. The
317 /// same payload may appear in multiple rounds, so implementations must key any state
318 /// on `(round, payload)` rather than `payload` alone.
319 ///
320 /// Like [`Automaton::verify`], payloads produced by [`Automaton::propose`] are certifiable-by-construction.
321 /// Also like [`Automaton::verify`], certification is single-shot for the given
322 /// `(round, payload)`. Once the returned channel resolves or closes, consensus treats
323 /// certification as concluded and will not retry the same request. After a restart,
324 /// however, consensus may request certification for the same `(round, payload)` again
325 /// if the result was not durably recorded before shutdown. Until that repeated request
326 /// resolves, the proposal is pending again, exactly as before the first request
327 /// completed, so consensus may still verify or build upon proposals that build upon
328 /// it. A `false` result takes effect once consensus records it.
329 ///
330 /// Implementations should therefore keep the request pending while the verdict may still
331 /// change. Return `false` only when the payload is permanently uncertifiable for that
332 /// round. Temporary conditions such as waiting for more data should not conclude
333 /// certification with `false`.
334 ///
335 /// Closing the channel is also terminal for this request and should be reserved for cases
336 /// where certification can no longer produce a verdict (for example, shutdown), not for temporary
337 /// inability to decide.
338 ///
339 /// # Determinism Requirement
340 ///
341 /// The decision returned by `certify` must be deterministic and consistent across
342 /// all honest participants to ensure liveness.
343 ///
344 /// # Finality
345 ///
346 /// A certified payload may never be finalized. A competing payload can also certify in
347 /// another round, even when every participant is honest. Only treat a payload as final
348 /// once the [`Reporter`] receives a finalization for it or one of its descendants.
349 fn certify(
350 &mut self,
351 _round: Round,
352 _payload: Self::Digest,
353 ) -> impl Future<Output = oneshot::Receiver<bool>> + Send {
354 #[allow(clippy::async_yields_async)]
355 async move {
356 let (sender, receiver) = oneshot::channel();
357 sender.send_lossy(true);
358 receiver
359 }
360 }
361 }
362
363 /// Relay is the interface responsible for broadcasting payloads to the network.
364 ///
365 /// The consensus engine is only aware of a payload's digest, not its contents. It is up
366 /// to the relay to efficiently broadcast the full payload to other participants.
367 pub trait Relay: Clone + Send + 'static {
368 /// Hash of an arbitrary payload.
369 type Digest: Digest;
370
371 /// Identity key of a network participant.
372 type PublicKey: PublicKey;
373
374 /// Directive for how a payload should be broadcast.
375 ///
376 /// Consensus mechanisms that need broadcast control (e.g. distinguishing
377 /// initial broadcast from rebroadcasts) define a custom enum here. Mechanisms that
378 /// treat every broadcast identically can set this to `()`.
379 type Plan: Send;
380
381 /// Broadcast a payload according to the given plan.
382 fn broadcast(&mut self, payload: Self::Digest, plan: Self::Plan) -> Feedback;
383 }
384
385 /// Reporter is the interface responsible for reporting activity to some external actor.
386 pub trait Reporter: Clone + Send + 'static {
387 /// Activity is specified by the underlying consensus implementation and can be interpreted if desired.
388 ///
389 /// Examples of activity would be "vote", "finalize", or "fault". Various consensus implementations may
390 /// want to reward (or penalize) participation in different ways and in different places. For example,
391 /// validators could be required to send multiple types of messages (i.e. vote and finalize) and rewarding
392 /// both equally may better align incentives with desired behavior.
393 type Activity;
394
395 /// Report some activity observed by the consensus implementation.
396 fn report(&mut self, activity: Self::Activity) -> Feedback;
397 }
398
399 /// Alias for the subscription type returned by [`Monitor::subscribe`].
400 ///
401 /// Each value is the latest index, so a subscriber that falls behind skips intermediate indices.
402 pub type IndexSubscription<I> = ring::Receiver<I>;
403
404 /// Monitor is the interface an external actor can use to observe the progress of a consensus implementation.
405 ///
406 /// Monitor is used to implement mechanisms that share the same set of active participants as consensus and/or
407 /// perform some activity that requires some synchronization with the progress of consensus.
408 ///
409 /// Monitor can be implemented using [crate::Reporter] to avoid introducing complexity
410 /// into any particular consensus implementation.
411 pub trait Monitor: Clone + Send + 'static {
412 /// Index is the type used to indicate the in-progress consensus decision.
413 type Index;
414
415 /// Returns the latest index and a subscription that yields the newest index whenever it
416 /// has changed.
417 fn subscribe(
418 &mut self,
419 ) -> impl Future<Output = (Self::Index, IndexSubscription<Self::Index>)> + Send;
420 }
421});
422stability_scope!(ALPHA {
423 pub mod aggregation;
424});
425stability_scope!(ALPHA, cfg(not(target_arch = "wasm32")) {
426 use crate::marshal::ancestry::Ancestry;
427 use commonware_cryptography::certificate::Scheme;
428
429 /// Application is a minimal interface for standard implementations that operate over a stream
430 /// of epoched blocks.
431 pub trait Application<E>: Clone + Send + 'static {
432 /// The signing scheme used by the application.
433 type SigningScheme: Scheme;
434
435 /// Context is metadata provided by the consensus engine associated with a given payload.
436 ///
437 /// This often includes things like the proposer, view number, the height, or the epoch.
438 type Context: Epochable;
439
440 /// The block type produced by the application's builder.
441 ///
442 /// The builder hands marshal a [`Digested`] block, so the application computes the digest
443 /// of a block it built exactly once, on its own strategy, before returning it.
444 type Block: Block;
445
446 /// Per-proposal input handed to [`prepare`](Self::prepare) and
447 /// [`propose`](Self::propose). Applications that need no input set this to `()`.
448 type Input: Send;
449
450 /// Build a block on a parent that has not yet certified, or decline with
451 /// [`Handoff::Wait`].
452 ///
453 /// The marshal calls this in place of [`Self::propose`] for a term-start proposal whose
454 /// parent is uncertified, with the same `ancestry` and after the same checks. It
455 /// re-proposes the epoch boundary block and declines a parent it cannot build on with
456 /// [`Handoff::Wait`], in both cases without calling this method. The default declines.
457 ///
458 /// With [`Handoff::Vote`] or [`Handoff::Stage`], the marshal keeps the returned block for
459 /// relay as it would a proposal. With [`Handoff::Wait`], consensus waits for parent
460 /// certification before requesting [`Self::propose`] for the same context, unless it has
461 /// voted to nullify the context's view or has left it. The decision is final for the
462 /// request.
463 ///
464 /// With deferred or coded verification, a prepared build can start before this node
465 /// has verified the parent's contents. The parent may later fail certification,
466 /// which discards the candidate.
467 ///
468 /// Parent certification does not cancel a prepared build. Consensus cancels it once the
469 /// parent can no longer be built on before it certifies, as after a nullification in the
470 /// parent's term, or once it votes to nullify the view it is waiting in, at or below the
471 /// parent. Unless it has voted to nullify the proposal's view or has left it, it then
472 /// requests an ordinary proposal for the same context if the parent certifies, or can
473 /// request a proposal on a replacement parent once one is selectable.
474 ///
475 /// [`Handoff::Vote`] trusts the outgoing consensus leader not to equivocate and to
476 /// complete its term (see [`Handoff::Vote`]). The context names the parent by view and
477 /// digest, and its leader field names the incoming leader, not the outgoing one. Identify
478 /// the outgoing leader from the elector's schedule or authenticated metadata for the
479 /// parent's consensus round. A verified parent block can name an earlier proposer in its
480 /// embedded context, as with an epoch-boundary reproposal. If that identity or trust is
481 /// uncertain, return [`Handoff::Stage`], which can relay the block before the parent
482 /// certifies but withholds the vote.
483 ///
484 /// This future may be cancelled before it completes. Implementations must be
485 /// cancellation-safe.
486 ///
487 /// If readiness is uncertain, return [`Handoff::Wait`].
488 ///
489 /// A wrapper around another application must forward this method, since the default
490 /// would otherwise replace the inner application's choice with [`Handoff::Wait`].
491 fn prepare(
492 &mut self,
493 _context: (E, Self::Context),
494 _ancestry: impl Ancestry<Self::Block>,
495 _input: Self::Input,
496 ) -> impl Future<Output = Handoff<Digested<Self::Block>>> + Send {
497 async { Handoff::Wait }
498 }
499
500 /// Build a new block on top of the provided parent ancestry. If the build job fails,
501 /// or the proposer's slot should be skipped, the implementor should return [None].
502 ///
503 /// `input` is the per-proposal input for this build.
504 ///
505 /// This future may be cancelled before it completes. Implementations must be
506 /// cancellation-safe.
507 fn propose(
508 &mut self,
509 context: (E, Self::Context),
510 ancestry: impl Ancestry<Self::Block>,
511 input: Self::Input,
512 ) -> impl Future<Output = Option<Digested<Self::Block>>> + Send;
513
514 /// Verify a block produced by the application's proposer, relative to its ancestry.
515 ///
516 /// This future should not resolve until the implementation can produce a stable verdict.
517 /// Return `false` only when the block is permanently invalid for the supplied context and
518 /// ancestry. If validity may still change as additional information becomes available,
519 /// continue waiting instead of returning `false`.
520 ///
521 /// In other words, to abstain from voting, do not resolve this future yet. Keep it
522 /// pending until the implementation can either prove the block valid, prove it invalid,
523 /// or the consensus engine cancels the request. Abstaining is not represented by a
524 /// special return value.
525 ///
526 /// This future may be cancelled before it completes. Implementations must be
527 /// cancellation-safe.
528 fn verify(
529 &mut self,
530 context: (E, Self::Context),
531 ancestry: impl Ancestry<Self::Block>,
532 ) -> impl Future<Output = bool> + Send;
533 }
534});