Skip to main content

commonware_consensus/simplex/
engine.rs

1use super::{
2    actors::{batcher, resolver, voter},
3    config::{Config, SkipPolicy},
4    elector::{self, Elector as _},
5    types::{Activity, Context},
6};
7use crate::{
8    CertifiableAutomaton, Relay, Reporter,
9    simplex::{Lookahead, Plan, scheme::Scheme},
10};
11use commonware_cryptography::Digest;
12use commonware_macros::select;
13use commonware_p2p::{Blocker, Receiver, Sender};
14use commonware_parallel::Strategy;
15use commonware_runtime::{Clock, ContextCell, Handle, Metrics, Spawner, Storage, spawn_cell};
16use rand_core::CryptoRng;
17use tracing::debug;
18
19/// Instance of `simplex` consensus engine.
20pub struct Engine<
21    E: Clock + CryptoRng + Spawner + Storage + Metrics,
22    S: Scheme<D>,
23    L: elector::Config<S>,
24    B: Blocker<PublicKey = S::PublicKey>,
25    D: Digest,
26    A: CertifiableAutomaton<Context = Context<D, S::PublicKey>, Digest = D>,
27    R: Relay<Digest = D, PublicKey = S::PublicKey, Plan = Plan<S::PublicKey>>,
28    F: Reporter<Activity = Activity<S, D>>,
29    T: Strategy,
30> {
31    context: ContextCell<E>,
32
33    voter: voter::Actor<E, S, L::Elector, B, D, A, R, F>,
34    voter_mailbox: voter::Mailbox<S, D>,
35
36    batcher: batcher::Actor<E, S, B, D, F, R, T>,
37    batcher_mailbox: batcher::Mailbox<S, D>,
38
39    resolver: resolver::Actor<E, S, B, D, T>,
40    resolver_mailbox: resolver::Mailbox<S, D>,
41}
42
43impl<
44    E: Clock + CryptoRng + Spawner + Storage + Metrics,
45    S: Scheme<D>,
46    L: elector::Config<S>,
47    B: Blocker<PublicKey = S::PublicKey>,
48    D: Digest,
49    A: CertifiableAutomaton<Context = Context<D, S::PublicKey>, Digest = D>,
50    R: Relay<Digest = D, PublicKey = S::PublicKey, Plan = Plan<S::PublicKey>>,
51    F: Reporter<Activity = Activity<S, D>>,
52    T: Strategy,
53> Engine<E, S, L, B, D, A, R, F, T>
54{
55    /// Create a new `simplex` consensus engine.
56    pub fn new(mut context: E, cfg: Config<S, L, B, D, A, R, F, T>) -> Self {
57        // Ensure configuration is valid
58        cfg.assert(&mut context);
59        let skip_budget = match cfg.skip {
60            SkipPolicy::Disabled => 0,
61            SkipPolicy::Enabled { budget, .. } => budget.resolve(cfg.scheme.participants().len()),
62        };
63        let elector = cfg.elector.build(cfg.scheme.participants());
64        let terms = elector.terms();
65        let term_length = terms.length();
66        if let Some(stall_timeout) = terms.stall_timeout() {
67            assert!(
68                stall_timeout > cfg.certification_timeout,
69                "stall timeout must be greater than certification timeout"
70            );
71        }
72
73        // Create batcher
74        let (batcher, batcher_mailbox) = batcher::Actor::new(
75            context.child("batcher"),
76            batcher::Config {
77                scheme: cfg.scheme.clone(),
78                blocker: cfg.blocker.clone(),
79                reporter: cfg.reporter.clone(),
80                track_historical_votes: cfg.track_historical_votes,
81                relay: cfg.relay.clone(),
82                strategy: cfg.strategy.clone(),
83                epoch: cfg.epoch,
84                mailbox_size: cfg.mailbox_size,
85                view_retention: cfg.view_retention,
86                skip: cfg.skip,
87                lookahead: Lookahead::new(&terms),
88                forward: cfg.forward,
89                floor: cfg.floor.view(),
90            },
91        );
92
93        // Create voter
94        let (voter, voter_mailbox) = voter::Actor::new(
95            context.child("voter"),
96            voter::Config {
97                scheme: cfg.scheme.clone(),
98                elector,
99                blocker: cfg.blocker.clone(),
100                automaton: cfg.automaton,
101                relay: cfg.relay,
102                reporter: cfg.reporter,
103                partition: cfg.partition,
104                mailbox_size: cfg.mailbox_size,
105                epoch: cfg.epoch,
106                floor: cfg.floor,
107                leader_timeout: cfg.leader_timeout,
108                certification_timeout: cfg.certification_timeout,
109                timeout_retry: cfg.timeout_retry,
110                skip_budget,
111                view_retention: cfg.view_retention,
112                replay_buffer: cfg.replay_buffer,
113                write_buffer: cfg.write_buffer,
114                page_cache: cfg.page_cache,
115            },
116        );
117
118        // Create resolver
119        let (resolver, resolver_mailbox) = resolver::Actor::new(
120            context.child("resolver"),
121            resolver::Config {
122                blocker: cfg.blocker,
123                scheme: cfg.scheme,
124                strategy: cfg.strategy,
125                mailbox_size: cfg.mailbox_size,
126                epoch: cfg.epoch,
127                fetch_timeout: cfg.fetch_timeout,
128                fetch_retry_delay: cfg.fetch_retry_delay,
129                term_length,
130            },
131        );
132
133        // Return the engine
134        Self {
135            context: ContextCell::new(context),
136
137            voter,
138            voter_mailbox,
139
140            batcher,
141            batcher_mailbox,
142
143            resolver,
144            resolver_mailbox,
145        }
146    }
147
148    /// Start the `simplex` consensus engine.
149    ///
150    /// This will also rebuild the state of the engine from provided `Journal`.
151    ///
152    /// # Network Channels
153    ///
154    /// The engine requires three separate network channels, each carrying votes or
155    /// certificates to help drive the consensus engine.
156    ///
157    /// ## `vote_network`
158    ///
159    /// Carries **individual votes**:
160    /// - [`Notarize`](super::types::Notarize): Vote to notarize a proposal
161    /// - [`Nullify`](super::types::Nullify): Vote to skip a view
162    /// - [`Finalize`](super::types::Finalize): Vote to finalize a notarized proposal
163    ///
164    /// These messages are sent to the batcher, which performs batch signature
165    /// verification, assembles certificates from valid votes, and forwards
166    /// proposals, certificates, and timeout hints to the voter.
167    ///
168    /// ## `certificate_network`
169    ///
170    /// Carries **certificates**:
171    /// - [`Notarization`](super::types::Notarization): Proof that a proposal was notarized
172    /// - [`Nullification`](super::types::Nullification): Proof that a view was skipped
173    /// - [`Finalization`](super::types::Finalization): Proof that a proposal was finalized
174    ///
175    /// Certificates are broadcast on this channel as soon as they are constructed
176    /// from collected votes. We separate this from the `vote_network` to optimistically
177    /// allow for certificate processing to short-circuit vote processing (if we receive
178    /// a certificate before processing pending votes, we can skip them).
179    ///
180    /// ## `resolver_network`
181    ///
182    /// Used for request-response certificate fetching. When a node needs to
183    /// catch up on a view it missed (e.g., to verify a proposal's parent), it
184    /// uses this channel to request certificates from peers. The resolver handles
185    /// retries and peer selection for these requests.
186    ///
187    /// All three channels must be scoped to this engine's epoch. A message from another
188    /// epoch blocks the sender on the vote and certificate channels, and the resolver
189    /// rejects it as invalid, which also blocks the peer.
190    pub fn start(
191        mut self,
192        vote_network: (
193            impl Sender<PublicKey = S::PublicKey>,
194            impl Receiver<PublicKey = S::PublicKey>,
195        ),
196        certificate_network: (
197            impl Sender<PublicKey = S::PublicKey>,
198            impl Receiver<PublicKey = S::PublicKey>,
199        ),
200        resolver_network: (
201            impl Sender<PublicKey = S::PublicKey>,
202            impl Receiver<PublicKey = S::PublicKey>,
203        ),
204    ) -> Handle<()> {
205        spawn_cell!(
206            self.context,
207            self.run(vote_network, certificate_network, resolver_network)
208        )
209    }
210
211    async fn run(
212        self,
213        vote_network: (
214            impl Sender<PublicKey = S::PublicKey>,
215            impl Receiver<PublicKey = S::PublicKey>,
216        ),
217        certificate_network: (
218            impl Sender<PublicKey = S::PublicKey>,
219            impl Receiver<PublicKey = S::PublicKey>,
220        ),
221        resolver_network: (
222            impl Sender<PublicKey = S::PublicKey>,
223            impl Receiver<PublicKey = S::PublicKey>,
224        ),
225    ) {
226        // Start the batcher (receives votes via vote_network, certificates via certificate_network)
227        // Batcher sends proposals/certificates to voter via voter_mailbox
228        let (vote_sender, vote_receiver) = vote_network;
229        let (certificate_sender, certificate_receiver) = certificate_network;
230        let mut batcher_task = self.batcher.start(
231            self.voter_mailbox.clone(),
232            vote_receiver,
233            certificate_receiver,
234        );
235
236        // Start the resolver (sends certificates to voter via voter_mailbox)
237        let (resolver_sender, resolver_receiver) = resolver_network;
238        let mut resolver_task =
239            self.resolver
240                .start(self.voter_mailbox, resolver_sender, resolver_receiver);
241
242        // Start the voter
243        let mut voter_task = self.voter.start(
244            self.batcher_mailbox,
245            self.resolver_mailbox,
246            vote_sender,
247            certificate_sender,
248        );
249
250        // If any task completes, the engine should stop
251        let mut shutdown = self.context.stopped();
252        select! {
253            _ = &mut shutdown => {
254                debug!("context shutdown, stopping engine");
255            },
256            voter = &mut voter_task => {
257                debug!(?voter, "voter stopped, shutting down engine");
258            },
259            batcher = &mut batcher_task => {
260                debug!(?batcher, "batcher stopped, shutting down engine");
261            },
262            resolver = &mut resolver_task => {
263                debug!(?resolver, "resolver stopped, shutting down engine");
264            },
265        }
266    }
267}