1use crate::dkg::{
4 ReshareBlock,
5 fence::Gate,
6 network::{Directory, Manager},
7 orchestrator::{Mailbox, mailbox::Message},
8 state_sync::{self, Plan as StateSyncPlan},
9 types::{EpochInfo, Payload},
10};
11use commonware_actor::mailbox;
12use commonware_consensus::{
13 CertifiableAutomaton, Heightable, Relay,
14 marshal::core::{Mailbox as MarshalMailbox, Variant as MarshalVariant},
15 simplex::{
16 self, Floor, ForwardPolicy, Plan, SkipPolicy, elector::Config as Elector, scheme,
17 types::Context,
18 },
19 types::{Epoch, Epocher, FixedEpocher, Height, ViewDelta},
20};
21use commonware_cryptography::{
22 Digest, PublicKey, Signer,
23 bls12381::primitives::variant::Variant as BlsVariant,
24 certificate::{Provider, Verifier},
25};
26use commonware_macros::{select, select_loop};
27use commonware_p2p::{
28 Blocker, Channel, Message as P2pMessage, Receiver, Sender,
29 utils::mux::{Builder, MuxHandle, Muxer},
30};
31use commonware_parallel::Strategy;
32use commonware_runtime::{
33 BufferPooler, Clock, ContextCell, Handle, Metrics, Network, Spawner, Storage,
34 buffer::paged::CacheRef,
35 spawn_cell,
36 telemetry::metrics::{Gauge, GaugeExt, MetricsExt as _},
37};
38use commonware_utils::{Acknowledgement, acknowledgement::Exact, channel::mpsc, vec::NonEmptyVec};
39use rand_core::CryptoRng;
40use std::{
41 marker::PhantomData,
42 num::{NonZeroU16, NonZeroU64, NonZeroUsize},
43 sync::Arc,
44 time::Duration,
45};
46use tracing::{debug, info, warn};
47
48struct Channels<C, S, R>
49where
50 C: Verifier,
51 S: Sender<PublicKey = C::PublicKey>,
52 R: Receiver<PublicKey = C::PublicKey>,
53{
54 vote: MuxHandle<S, R>,
55 vote_backup: mpsc::Receiver<(Channel, P2pMessage<C::PublicKey>)>,
56 certificate: MuxHandle<S, R>,
57 certificate_backup: mpsc::Receiver<(Channel, P2pMessage<C::PublicKey>)>,
58 resolver: MuxHandle<S, R>,
59}
60
61struct ActiveEpoch {
62 epoch: Epoch,
63 handle: Handle<()>,
64}
65
66impl Drop for ActiveEpoch {
67 fn drop(&mut self) {
68 self.handle.abort();
69 }
70}
71
72enum EnterEpochError<E> {
73 GateClosed,
74 PeerSet(E),
75 MuxClosed,
76 Stopped,
77}
78
79struct ResolvedStart<S, D, V, P, Dir>
80where
81 S: scheme::Scheme<D, PublicKey = P>,
82 D: Digest,
83 V: BlsVariant,
84 P: PublicKey,
85 Dir: Directory<P>,
86{
87 epoch: Epoch,
88 floor: Floor<S, D>,
89 info: EpochInfo<V, P, Dir>,
90}
91
92#[derive(Clone)]
94pub struct SimplexConfig<L> {
95 pub elector: L,
97
98 pub mailbox_size: NonZeroUsize,
100
101 pub replay_buffer: NonZeroUsize,
103
104 pub write_buffer: NonZeroUsize,
106
107 pub page_cache_page_size: NonZeroU16,
109
110 pub page_cache_pages: NonZeroUsize,
112
113 pub leader_timeout: Duration,
115
116 pub certification_timeout: Duration,
118
119 pub timeout_retry: Duration,
121
122 pub fetch_timeout: Duration,
124
125 pub view_retention: ViewDelta,
127
128 pub skip: SkipPolicy,
130
131 pub track_historical_votes: bool,
138
139 pub forward: ForwardPolicy,
141}
142
143pub struct Config<B, M, P, MV, DV, A, L, T>
145where
146 P: Provider<Scope = Epoch>,
147 P::Scheme: scheme::Scheme<MV::Commitment>,
148 MV: MarshalVariant,
149 MV::ApplicationBlock: ReshareBlock,
150 <MV::ApplicationBlock as ReshareBlock>::Signer:
151 Signer<PublicKey = <P::Scheme as Verifier>::PublicKey>,
152 DV: BlsVariant,
153{
154 pub oracle: B,
156
157 pub manager: M,
159
160 pub provider: P,
162
163 pub marshal: MarshalMailbox<P::Scheme, MV>,
165
166 pub application: A,
168
169 pub strategy: T,
171
172 pub simplex: SimplexConfig<L>,
174
175 pub gate: Gate,
178
179 pub state_sync: StateSyncPlan<
181 P::Scheme,
182 MV::Commitment,
183 DV,
184 <MV::ApplicationBlock as ReshareBlock>::Directory,
185 >,
186
187 pub blocks_per_epoch: NonZeroU64,
189
190 pub muxer_size: usize,
192
193 pub mailbox_size: NonZeroUsize,
195
196 pub partition_prefix: String,
198}
199
200pub struct Actor<E, B, M, P, MV, DV, C, A, L, T, ACK = Exact>
202where
203 E: BufferPooler + Spawner + Metrics + CryptoRng + Clock + Storage + Network,
204 B: Blocker<PublicKey = <P::Scheme as Verifier>::PublicKey>,
205 M: Manager<
206 PublicKey = <P::Scheme as Verifier>::PublicKey,
207 Directory = <MV::ApplicationBlock as ReshareBlock>::Directory,
208 >,
209 P: Provider<Scope = Epoch>,
210 P::Scheme: scheme::Scheme<MV::Commitment>,
211 MV: MarshalVariant,
212 MV::ApplicationBlock: ReshareBlock<Variant = DV, Signer = C>,
213 DV: BlsVariant,
214 C: Signer<PublicKey = <P::Scheme as Verifier>::PublicKey>,
215 A: CertifiableAutomaton<
216 Context = Context<MV::Commitment, <P::Scheme as Verifier>::PublicKey>,
217 Digest = MV::Commitment,
218 > + Relay<
219 Digest = MV::Commitment,
220 PublicKey = <P::Scheme as Verifier>::PublicKey,
221 Plan = Plan<<P::Scheme as Verifier>::PublicKey>,
222 >,
223 L: Elector<P::Scheme>,
224 T: Strategy,
225 ACK: Acknowledgement,
226{
227 context: ContextCell<E>,
228 mailbox: mailbox::Receiver<Message<MV::ApplicationBlock, ACK>>,
229 oracle: B,
230 manager: M,
231 provider: P,
232 marshal: MarshalMailbox<P::Scheme, MV>,
233 application: A,
234 strategy: T,
235 simplex: SimplexConfig<L>,
236 gate: Gate,
237 state_sync: StateSyncPlan<
238 P::Scheme,
239 MV::Commitment,
240 DV,
241 <MV::ApplicationBlock as ReshareBlock>::Directory,
242 >,
243 blocks_per_epoch: NonZeroU64,
244 muxer_size: usize,
245 partition_prefix: String,
246 page_cache_ref: CacheRef,
247 latest_epoch: Gauge,
248 _payload: PhantomData<(DV, C)>,
249}
250
251impl<E, B, M, P, MV, DV, C, A, L, T, ACK> Actor<E, B, M, P, MV, DV, C, A, L, T, ACK>
252where
253 E: BufferPooler + Spawner + Metrics + CryptoRng + Clock + Storage + Network,
254 B: Blocker<PublicKey = <P::Scheme as Verifier>::PublicKey>,
255 M: Manager<
256 PublicKey = <P::Scheme as Verifier>::PublicKey,
257 Directory = <MV::ApplicationBlock as ReshareBlock>::Directory,
258 >,
259 P: Provider<Scope = Epoch>,
260 P::Scheme: scheme::Scheme<MV::Commitment>,
261 MV: MarshalVariant,
262 MV::ApplicationBlock: ReshareBlock<Variant = DV, Signer = C>,
263 DV: BlsVariant,
264 C: Signer<PublicKey = <P::Scheme as Verifier>::PublicKey>,
265 A: CertifiableAutomaton<
266 Context = Context<MV::Commitment, <P::Scheme as Verifier>::PublicKey>,
267 Digest = MV::Commitment,
268 > + Relay<
269 Digest = MV::Commitment,
270 PublicKey = <P::Scheme as Verifier>::PublicKey,
271 Plan = Plan<<P::Scheme as Verifier>::PublicKey>,
272 >,
273 L: Elector<P::Scheme>,
274 T: Strategy,
275 ACK: Acknowledgement,
276{
277 pub fn new(
283 context: E,
284 config: Config<B, M, P, MV, DV, A, L, T>,
285 ) -> (Self, Mailbox<MV::ApplicationBlock, ACK>) {
286 let (sender, mailbox) = mailbox::new(context.child("mailbox"), config.mailbox_size);
287 let page_cache_ref = CacheRef::from_pooler(
288 &context,
289 config.simplex.page_cache_page_size,
290 config.simplex.page_cache_pages,
291 );
292 let latest_epoch = context.gauge("latest_epoch", "current epoch");
293
294 (
295 Self {
296 context: ContextCell::new(context),
297 mailbox,
298 oracle: config.oracle,
299 manager: config.manager,
300 provider: config.provider,
301 marshal: config.marshal,
302 application: config.application,
303 strategy: config.strategy,
304 simplex: config.simplex,
305 gate: config.gate,
306 state_sync: config.state_sync,
307 blocks_per_epoch: config.blocks_per_epoch,
308 muxer_size: config.muxer_size,
309 partition_prefix: config.partition_prefix,
310 page_cache_ref,
311 latest_epoch,
312 _payload: PhantomData,
313 },
314 Mailbox::new(sender),
315 )
316 }
317
318 pub fn start<S, R>(
323 mut self,
324 votes: (S, R),
325 certificates: (S, R),
326 resolver: (S, R),
327 ) -> Handle<()>
328 where
329 S: Sender<PublicKey = <P::Scheme as Verifier>::PublicKey>,
330 R: Receiver<PublicKey = <P::Scheme as Verifier>::PublicKey>,
331 {
332 spawn_cell!(self.context, self.run(votes, certificates, resolver,))
333 }
334
335 async fn run<S, R>(
342 mut self,
343 (vote_sender, vote_receiver): (S, R),
344 (certificate_sender, certificate_receiver): (S, R),
345 (resolver_sender, resolver_receiver): (S, R),
346 ) where
347 S: Sender<PublicKey = <P::Scheme as Verifier>::PublicKey>,
348 R: Receiver<PublicKey = <P::Scheme as Verifier>::PublicKey>,
349 {
350 let mut channels = self.create_channels(
351 (vote_sender, vote_receiver),
352 (certificate_sender, certificate_receiver),
353 (resolver_sender, resolver_receiver),
354 );
355 let epocher = FixedEpocher::new(self.blocks_per_epoch);
356 let Some(start) = self.resolve_start(&epocher).await else {
357 debug!("context shutdown while resolving startup epoch");
358 return;
359 };
360 let mut active = match self
361 .enter_epoch(start.epoch, start.floor, &start.info, &mut channels)
362 .await
363 {
364 Ok(active) => active,
365 Err(EnterEpochError::GateClosed) => {
366 debug!(
367 epoch = start.epoch.get(),
368 "epoch gate closed before startup"
369 );
370 return;
371 }
372 Err(EnterEpochError::PeerSet(error)) => {
373 warn!(epoch = %start.epoch, %error, "failed to activate startup peer set");
374 return;
375 }
376 Err(EnterEpochError::MuxClosed) => {
377 debug!(
378 epoch = start.epoch.get(),
379 "consensus mux closed before startup epoch"
380 );
381 return;
382 }
383 Err(EnterEpochError::Stopped) => {
384 debug!("context shutdown before startup epoch");
385 return;
386 }
387 };
388
389 select_loop! {
390 self.context,
391 on_stopped => {
392 debug!("context shutdown, stopping orchestrator");
393 },
394 Some((their_epoch, (from, _))) = channels.vote_backup.recv() else {
395 debug!("vote mux backup channel closed, shutting down orchestrator");
396 break;
397 } => {
398 self.handle_backup(&epocher, active.epoch, their_epoch, from);
399 },
400 Some((their_epoch, (from, _))) = channels.certificate_backup.recv() else {
401 debug!("certificate mux backup channel closed, shutting down orchestrator");
402 break;
403 } => {
404 self.handle_backup(&epocher, active.epoch, their_epoch, from);
405 },
406 result = &mut active.handle => match result {
407 Ok(()) => {
408 debug!(epoch = active.epoch.get(), "simplex engine stopped, shutting down orchestrator");
409 break;
410 }
411 Err(error) => {
412 panic!("simplex engine for epoch {} stopped unexpectedly: {error}", active.epoch);
413 }
414 },
415 Some(message) = self.mailbox.recv() else {
416 debug!("mailbox closed, shutting down orchestrator");
417 break;
418 } => match message {
419 Message::Finalized {
420 block,
421 acknowledgement,
422 } => {
423 let keep_running = self
424 .handle_finalized(
425 &epocher,
426 &mut active,
427 block,
428 acknowledgement,
429 &mut channels,
430 )
431 .await;
432 if !keep_running {
433 break;
434 }
435 }
436 },
437 }
438 }
439
440 async fn resolve_start(
450 &mut self,
451 epocher: &FixedEpocher,
452 ) -> Option<
453 ResolvedStart<
454 P::Scheme,
455 MV::Commitment,
456 DV,
457 <P::Scheme as Verifier>::PublicKey,
458 <MV::ApplicationBlock as ReshareBlock>::Directory,
459 >,
460 > {
461 let recovered_epoch = state_sync::recovered_epoch(&self.marshal, epocher).await;
462 if let Some(state_sync) = self
463 .state_sync
464 .resolve(
465 self.context.as_present().child("state_sync"),
466 recovered_epoch,
467 )
468 .await
469 {
470 return Some(ResolvedStart {
471 epoch: state_sync.info.epoch,
472 floor: Floor::Finalized(state_sync.floor),
473 info: state_sync.info,
474 });
475 }
476
477 self.resolve_boundary(recovered_epoch.unwrap_or_else(Epoch::zero), epocher)
478 .await
479 }
480
481 async fn resolve_boundary(
500 &mut self,
501 epoch: Epoch,
502 epocher: &FixedEpocher,
503 ) -> Option<
504 ResolvedStart<
505 P::Scheme,
506 MV::Commitment,
507 DV,
508 <P::Scheme as Verifier>::PublicKey,
509 <MV::ApplicationBlock as ReshareBlock>::Directory,
510 >,
511 > {
512 let height = epoch
513 .previous()
514 .and_then(|epoch| epocher.last(epoch))
515 .unwrap_or_else(Height::zero);
516 let Some(boundary) = self.marshal.get_block(height).await else {
517 debug!(%height, "boundary block unavailable, shutting down orchestrator");
518 return None;
519 };
520 let commitment = MV::commitment(&boundary);
521 let block = MV::into_inner(boundary);
522 let Some(Payload::EpochInfo(info)) = block.payload() else {
523 panic!("boundary block {height} missing epoch info");
524 };
525 if info.epoch != epoch {
526 panic!(
527 "boundary block {height} carries epoch info for {}, expected {epoch}",
528 info.epoch
529 );
530 }
531
532 Some(ResolvedStart {
533 epoch,
534 floor: Floor::Genesis(commitment),
535 info,
536 })
537 }
538
539 fn create_channels<S, R>(
545 &self,
546 (vote_sender, vote_receiver): (S, R),
547 (certificate_sender, certificate_receiver): (S, R),
548 (resolver_sender, resolver_receiver): (S, R),
549 ) -> Channels<P::Scheme, S, R>
550 where
551 S: Sender<PublicKey = <P::Scheme as Verifier>::PublicKey>,
552 R: Receiver<PublicKey = <P::Scheme as Verifier>::PublicKey>,
553 {
554 let (mux, vote, vote_backup) = Muxer::builder(
555 self.context.child("vote_mux"),
556 vote_sender,
557 vote_receiver,
558 self.muxer_size,
559 )
560 .with_backup()
561 .build();
562 mux.start();
563
564 let (mux, certificate, certificate_backup) = Muxer::builder(
565 self.context.child("certificate_mux"),
566 certificate_sender,
567 certificate_receiver,
568 self.muxer_size,
569 )
570 .with_backup()
571 .build();
572 mux.start();
573
574 let (mux, resolver) = Muxer::new(
575 self.context.child("resolver_mux"),
576 resolver_sender,
577 resolver_receiver,
578 self.muxer_size,
579 );
580 mux.start();
581
582 Channels {
583 vote,
584 vote_backup,
585 certificate,
586 certificate_backup,
587 resolver,
588 }
589 }
590
591 fn handle_backup(
599 &self,
600 epocher: &FixedEpocher,
601 our_epoch: Epoch,
602 their_epoch: u64,
603 from: <P::Scheme as Verifier>::PublicKey,
604 ) {
605 let their_epoch = Epoch::new(their_epoch);
606 if their_epoch <= our_epoch {
607 debug!(%their_epoch, %our_epoch, ?from, "received message from past epoch");
608 return;
609 }
610
611 let boundary_height = epocher
612 .last(our_epoch)
613 .expect("our epoch should be covered by epoch strategy");
614 debug!(
615 ?from,
616 %their_epoch,
617 %our_epoch,
618 %boundary_height,
619 "received backup message from future epoch, ensuring boundary finalization"
620 );
621 self.marshal
622 .hint_finalized(boundary_height, NonEmptyVec::new(from));
623 }
624
625 async fn handle_finalized<S, R>(
632 &mut self,
633 epocher: &FixedEpocher,
634 active: &mut ActiveEpoch,
635 block: Arc<MV::ApplicationBlock>,
636 acknowledgement: ACK,
637 channels: &mut Channels<P::Scheme, S, R>,
638 ) -> bool
639 where
640 S: Sender<PublicKey = <P::Scheme as Verifier>::PublicKey>,
641 R: Receiver<PublicKey = <P::Scheme as Verifier>::PublicKey>,
642 {
643 let height = block.height();
644 let current = active.epoch;
645 if epocher.last(current) != Some(height) {
646 acknowledgement.acknowledge();
647 return true;
648 }
649
650 let next_epoch = current.next();
651 let Some(Payload::EpochInfo(info)) = block.payload() else {
652 panic!("boundary block of epoch {current} missing EpochInfo");
653 };
654 if info.epoch != next_epoch {
655 panic!(
656 "boundary block of epoch {current} carries epoch info for wrong epoch (got: {}, expected: {next_epoch})",
657 info.epoch
658 );
659 }
660
661 let Some(boundary) = self.marshal.get_block(height).await else {
662 debug!(%height, "boundary block unavailable, shutting down orchestrator");
663 return false;
664 };
665 let floor = Floor::Genesis(MV::commitment(&boundary));
666
667 let next = self.enter_epoch(next_epoch, floor, &info, channels).await;
668 let next = match next {
669 Ok(next) => next,
670 Err(EnterEpochError::GateClosed) => {
671 debug!(%next_epoch, "epoch gate closed before boundary transition");
672 return false;
673 }
674 Err(EnterEpochError::PeerSet(error)) => {
675 warn!(%next_epoch, %error, "failed to activate boundary peer set");
676 return false;
677 }
678 Err(EnterEpochError::MuxClosed) => {
679 debug!(%next_epoch, "consensus mux closed before boundary transition");
680 return false;
681 }
682 Err(EnterEpochError::Stopped) => {
683 debug!(%next_epoch, "context shutdown while waiting to enter epoch");
684 return false;
685 }
686 };
687
688 *active = next;
689 acknowledgement.acknowledge();
690 true
691 }
692
693 async fn enter_epoch<S, R>(
700 &mut self,
701 epoch: Epoch,
702 floor: Floor<P::Scheme, MV::Commitment>,
703 info: &EpochInfo<
704 DV,
705 <P::Scheme as Verifier>::PublicKey,
706 <MV::ApplicationBlock as ReshareBlock>::Directory,
707 >,
708 channels: &mut Channels<P::Scheme, S, R>,
709 ) -> Result<ActiveEpoch, EnterEpochError<M::Error>>
710 where
711 S: Sender<PublicKey = <P::Scheme as Verifier>::PublicKey>,
712 R: Receiver<PublicKey = <P::Scheme as Verifier>::PublicKey>,
713 {
714 let mut shutdown = self.context.stopped();
717 select! {
718 _ = &mut shutdown => {
719 return Err(EnterEpochError::Stopped);
720 },
721 result = self.gate.wait(epoch) => {
722 if result.is_err() {
723 return Err(EnterEpochError::GateClosed);
724 }
725 },
726 };
727 drop(shutdown);
728
729 self.manager
730 .track(epoch, info.participants().tracked_peers(), &info.directory)
731 .map_err(EnterEpochError::PeerSet)?;
732 let scheme = self
733 .provider
734 .scheme(epoch)
735 .unwrap_or_else(|| panic!("missing consensus scheme for epoch {epoch}"));
736 let context = self
737 .context
738 .child("consensus_engine")
739 .with_attribute("epoch", epoch);
740 let engine = simplex::Engine::new(
741 context,
742 simplex::Config {
743 scheme: scheme.as_ref().clone(),
744 elector: self.simplex.elector.clone(),
745 blocker: self.oracle.clone(),
746 automaton: self.application.clone(),
747 relay: self.application.clone(),
748 reporter: self.marshal.clone(),
749 strategy: self.strategy.clone(),
750 partition: format!("{}_consensus_{epoch}", self.partition_prefix),
751 mailbox_size: self.simplex.mailbox_size,
752 epoch,
753 floor,
754 replay_buffer: self.simplex.replay_buffer,
755 write_buffer: self.simplex.write_buffer,
756 page_cache: self.page_cache_ref.clone(),
757 leader_timeout: self.simplex.leader_timeout,
758 certification_timeout: self.simplex.certification_timeout,
759 timeout_retry: self.simplex.timeout_retry,
760 fetch_timeout: self.simplex.fetch_timeout,
761 view_retention: self.simplex.view_retention,
762 skip: self.simplex.skip,
763 forward: self.simplex.forward,
764 track_historical_votes: self.simplex.track_historical_votes,
765 },
766 );
767
768 let Ok(vote) = channels.vote.register(epoch.get()).await else {
772 return Err(EnterEpochError::MuxClosed);
773 };
774 let Ok(certificate) = channels.certificate.register(epoch.get()).await else {
775 return Err(EnterEpochError::MuxClosed);
776 };
777 let Ok(resolver) = channels.resolver.register(epoch.get()).await else {
778 return Err(EnterEpochError::MuxClosed);
779 };
780 let handle = engine.start(vote, certificate, resolver);
781 let _ = self.latest_epoch.try_set(epoch.get());
782
783 info!(%epoch, "entered epoch");
784 Ok(ActiveEpoch { epoch, handle })
785 }
786}