commonware_consensus/marshal/mod.rs
1//! Ordered delivery of finalized blocks.
2//!
3//! # Architecture
4//!
5//! The core of the module is the unified [`core::Actor`]. It marshals finalized blocks into order by:
6//!
7//! - Receiving uncertified blocks from a broadcast mechanism
8//! - Receiving notarizations and finalizations from consensus
9//! - Reconstructing a total order of finalized blocks
10//! - Providing a backfill mechanism for missing blocks
11//!
12//! The actor interacts with several components:
13//! - [`crate::Reporter`]: Receives ordered, finalized blocks at-least-once
14//! - [`crate::simplex`]: Provides consensus messages
15//! - Application: Provides verified blocks
16//! - [`commonware_broadcast::buffered`]: Provides uncertified blocks (standard mode)
17//! - [`coding::shards::Engine`]: Provides erasure-coded shards (coding mode)
18//! - [`resolver`]: Provides a backfill mechanism for missing blocks
19//!
20//! # Design
21//!
22//! ## Delivery
23//!
24//! The actor delivers each finalized block from its starting height onward, in height order and
25//! at least once. Installing a floor can skip ahead. A restart or a floor installation can
26//! redeliver already reported blocks with fresh acknowledgements, so reporters must handle
27//! duplicates.
28//!
29//! ## Finalization
30//!
31//! The actor uses a view-based model to track the state of the chain. Each view corresponds
32//! to a potential block in the chain. The actor will only finalize a block (and its ancestors)
33//! if it has a corresponding finalization from consensus.
34//!
35//! _It is possible that there may exist multiple finalizations for the same block in different views. Marshal
36//! only concerns itself with verifying a valid finalization exists for a block, not that a specific finalization
37//! exists. This means different Marshals may have different finalizations for the same block persisted to disk._
38//!
39//! ## Backfill
40//!
41//! The actor provides a backfill mechanism for missing blocks. If the actor notices a gap in its
42//! knowledge of finalized blocks, it will request the missing blocks from its peers. This ensures
43//! that the actor can catch up to the rest of the network if it falls behind.
44//!
45//! ## Message Sizes
46//!
47//! Marshal sends requests, responses, and errors through its resolver on the backfill network, and
48//! its buffer ([`commonware_broadcast::buffered`] or [`coding::shards::Engine`]) disseminates
49//! blocks or their shards. `V` and `S` are the variant and certificate scheme given to
50//! [`max_payload_size`]:
51//!
52//! ```text
53//! Any payload on a muxer subchannel:
54//!
55//! +---------------------------+---------+
56//! | subchannel (varint, 1-10) | payload |
57//! +---------------------------+---------+
58//!
59//! Request for Key::Block (tag 0, subject 0):
60//!
61//! +--------+---------+-------------+----------------------------------+
62//! | id (8) | tag (1) | subject (1) | commitment (V::Commitment::SIZE) |
63//! +--------+---------+-------------+----------------------------------+
64//!
65//! Request for Key::Finalized (tag 0, subject 1):
66//!
67//! +--------+---------+-------------+-----------------------+
68//! | id (8) | tag (1) | subject (1) | height (varint, 1-10) |
69//! +--------+---------+-------------+-----------------------+
70//!
71//! Request for Key::Notarized (tag 0, subject 2):
72//!
73//! +--------+---------+-------------+----------------------+---------------------+
74//! | id (8) | tag (1) | subject (1) | epoch (varint, 1-10) | view (varint, 1-10) |
75//! +--------+---------+-------------+----------------------+---------------------+
76//!
77//! Response for Key::Block (tag 1):
78//!
79//! +--------+---------+----------------------+----------------+
80//! | id (8) | tag (1) | length (varint, 1-5) | backfill block |
81//! +--------+---------+----------------------+----------------+
82//!
83//! Response for Key::Finalized (tag 1):
84//!
85//! +--------+---------+----------------------+--------------+-------------------+
86//! | id (8) | tag (1) | length (varint, 1-5) | finalization | application block |
87//! +--------+---------+----------------------+--------------+-------------------+
88//!
89//! Response for Key::Notarized (tag 1):
90//!
91//! +--------+---------+----------------------+--------------+----------------+
92//! | id (8) | tag (1) | length (varint, 1-5) | notarization | backfill block |
93//! +--------+---------+----------------------+--------------+----------------+
94//!
95//! Error (tag 2):
96//!
97//! +--------+---------+
98//! | id (8) | tag (1) |
99//! +--------+---------+
100//!
101//! Notarization or finalization:
102//!
103//! +----------------------+---------------------+-----------------------------------+
104//! | epoch (varint, 1-10) | view (varint, 1-10) | parent (varint, 1-10) |
105//! +----------------------+-----------+---------+-----------------------------------+
106//! | commitment (V::Commitment::SIZE) | certificate (up to S::max_certificate_size) |
107//! +----------------------------------+---------------------------------------------+
108//!
109//! Backfill block under standard, and each buffered broadcast message:
110//!
111//! +---------------------------------------------------------+
112//! | application block (up to the application block bound) |
113//! +---------------------------------------------------------+
114//!
115//! Backfill block under coding:
116//!
117//! +---------------------------------------------------------+-------------------+
118//! | application block (up to the application block bound) | coding config (4) |
119//! +---------------------------------------------------------+-------------------+
120//!
121//! Shard:
122//!
123//! +------------------+-----------+-------+
124//! | commitment (100) | index (2) | chunk |
125//! +------------------+-----------+-------+
126//!
127//! Chunk (Reed-Solomon):
128//!
129//! +----------------------------+---------------------+-----------------------+
130//! | shard length (varint, 1-5) | shard (shard width) | index (2) |
131//! +----------------+-----------+---------------------+-----------------------+
132//! | leaf count (4) | sibling count (varint, 1-5) | siblings (n x digest) |
133//! +----------------+---------------------------------+-----------------------+
134//! ```
135//!
136//! [`max_payload_size`] sizes every varint at its widest, every certificate at
137//! `S::max_certificate_size` for committees of at most the given size, and every backfill block at
138//! its bound. It returns the larger of the backfill and buffer bounds.
139//!
140//! The buffer bound is the application block bound under [`standard`]. Under [`coding`], it is
141//! the largest shard bound for the backfill block bound across committees of 4 to the given size.
142//!
143//! The application block bound is what
144//! [`BoundedBlock::max_encode_size`](crate::BoundedBlock::max_encode_size) reports for
145//! [`Config::block_codec_config`]. Every application block marshal holds must fit within it: the
146//! blocks [`Application::prepare`](crate::Application::prepare) and
147//! [`Application::propose`](crate::Application::propose) return, the genesis anchor, and every
148//! block passed to [`core::Mailbox`].
149//!
150//! Every network that carries backfill or the buffer must admit [`max_payload_size`], or its
151//! [`prefixed`] size when marshal runs on a muxer subchannel. [`coding::shards::Engine`] also
152//! relays the shards it receives, so its receiver must yield no payload larger than its sender's
153//! network admits.
154//!
155//! Every node that runs marshal on one network must give marshal and its buffer the same block
156//! codec configuration and limits. Across restarts, neither the application block bound nor the
157//! `max_message_size` of a network that carries backfill or the buffer may decrease while stored
158//! blocks remain.
159//!
160//! [`prefixed`]: commonware_p2p::utils::mux::prefixed
161//!
162//! ## Storage
163//!
164//! The actor uses a combination of internal and external ([`store::Certificates`], [`store::Blocks`]) storage
165//! to store blocks and finalizations. Internal storage (in-memory caches) is used for data that is only
166//! needed for a short period of time, such as unverified blocks or notarizations. External storage
167//! (archive backends) is used to persist finalized blocks and certificates.
168//!
169//! Marshal stores finalized blocks from a configurable starting height (or, floor) onward.
170//! This allows for state sync from a specific height rather than from genesis. The floor
171//! is supplied as a finalization. Marshal obtains the corresponding block asynchronously,
172//! from local storage, the buffer, or peers, before dispatching application blocks starting
173//! at that height. Installing a floor may prune older history if [`store::Blocks`] supports
174//! pruning, but keeps the stored block preceding the floor.
175//!
176//! _History below the starting height may be unavailable to peers. This feature is only
177//! recommended for applications that support state sync and do not require full block history to
178//! participate in consensus._
179//!
180//! ## Limitations and Future Work
181//!
182//! - Only works with [crate::simplex] rather than general consensus.
183//! - Assumes at-most one notarization per view, incompatible with some consensus protocols.
184//! - Uses [`broadcast::buffered`](`commonware_broadcast::buffered`) for broadcasting and receiving
185//! uncertified blocks from the network.
186
187use crate::{
188 Block,
189 types::{Height, Round},
190};
191use commonware_cryptography::{Digest, Digested};
192use commonware_storage::archive;
193use commonware_utils::{Acknowledgement, acknowledgement::Exact};
194
195mod config;
196pub use config::{Config, Start};
197mod limits;
198pub use limits::max_payload_size;
199
200pub mod ancestry;
201pub mod core;
202pub mod resolver;
203pub mod standard;
204pub mod store;
205
206#[cfg(all(test, feature = "arbitrary"))]
207mod conformance;
208
209commonware_macros::stability_scope!(ALPHA {
210 pub(crate) mod application;
211 pub mod coding;
212});
213
214#[cfg(test)]
215pub mod mocks;
216
217/// An identifier for a block request.
218pub enum Identifier<D: Digest> {
219 /// The height of the block to retrieve.
220 Height(Height),
221 /// The digest of the block to retrieve.
222 Digest(D),
223 /// The highest finalized block. It may be the case that marshal does not have some of the
224 /// blocks below this height.
225 Latest,
226}
227
228// Allows using u64 directly for convenience.
229impl<D: Digest> From<Height> for Identifier<D> {
230 fn from(src: Height) -> Self {
231 Self::Height(src)
232 }
233}
234
235// Allows using &Digest directly for convenience.
236impl<D: Digest> From<&D> for Identifier<D> {
237 fn from(src: &D) -> Self {
238 Self::Digest(*src)
239 }
240}
241
242// Allows using archive identifiers directly for convenience.
243impl<D: Digest> From<archive::Identifier<'_, D>> for Identifier<D> {
244 fn from(src: archive::Identifier<'_, D>) -> Self {
245 match src {
246 archive::Identifier::Index(index) => Self::Height(Height::new(index)),
247 archive::Identifier::Key(key) => Self::Digest(*key),
248 }
249 }
250}
251
252/// An update reported to the application, either a new finalized tip or a finalized block.
253///
254/// Finalized tips are reported as soon as known, whether or not we hold all blocks up to that height.
255/// Finalized blocks are reported from the starting height onward, in height order without gaps.
256/// Installing a floor can skip ahead. A restart or a floor installation can redeliver already
257/// reported blocks with fresh acknowledgements.
258#[derive(Clone, Debug)]
259pub enum Update<B: Block, A: Acknowledgement = Exact> {
260 /// A new finalized tip and the finalization round.
261 ///
262 /// This update can be reported before the finalized block and finalization archives complete
263 /// a durable sync. Applications must not use this update as a storage durability signal.
264 /// [`Update::Block`] provides that guarantee.
265 Tip(Round, Height, B::Digest),
266 /// A new finalized block and an [Acknowledgement] for the application to signal once processed.
267 ///
268 /// Marshal waits to mark a block as delivered until the application explicitly acknowledges the
269 /// update. Dropping an [Acknowledgement] while marshal still waits for it stops marshal.
270 ///
271 /// Cloning the update shares the immutable block, so applications can fan it out without requiring
272 /// block clones. Marshal only considers the block delivered once every acknowledgement is handled.
273 ///
274 /// Marshal only emits a block after durably persisting it, so applications that keep state
275 /// derived from a block can read the same block after a restart. See [core::Processed] for
276 /// the block that backs the processed height.
277 Block(Digested<B>, A),
278}