use crate::dkg::{
ParticipantsProvider, Registrar, ReshareBlock, SecretStore,
fence::Fence,
network::{Directory, Manager},
reshare::{Mailbox, Message, metrics::Metrics as ReshareMetrics, store::Store},
state_sync::{self, Plan as StateSyncPlan},
types::EpochInfo,
};
use commonware_actor::mailbox::{self as actor_mailbox, Receiver as MailboxReceiver};
use commonware_consensus::{
Heightable as _,
marshal::core::{CommitmentFallback, Mailbox as MarshalMailbox, Variant as MarshalVariant},
simplex::scheme::Scheme as SimplexScheme,
types::{EpochPhase, FixedEpocher},
};
use commonware_cryptography::{
BatchVerifier, PublicKey, Signer,
bls12381::{
dkg::feldman_desmedt::Reveal,
primitives::{sharing::Mode as SharingMode, variant::Variant as BlsVariant},
},
certificate::Scheme,
};
use commonware_p2p::{Blocker, Receiver, Sender, utils::mux::Muxer};
use commonware_parallel::Strategy;
use commonware_runtime::{
BufferPooler, Clock, ContextCell, Handle, Metrics, Spawner, Storage, spawn_cell,
};
use commonware_utils::{Acknowledgement, acknowledgement::Exact, ordered::Set};
use rand_core::CryptoRng;
use std::{
marker::PhantomData,
num::{NonZeroU32, NonZeroU64, NonZeroUsize},
};
type DkgCompletion<V, P, D> = Box<dyn FnOnce(Option<EpochInfo<V, P, D>>) + Send>;
mod dealing;
mod dkg;
mod follower;
mod inclusion;
mod setup;
#[cfg(test)]
mod utils;
use setup::{Setup, StateSyncStart};
pub(crate) struct DkgConfig<V, P, D>
where
V: BlsVariant,
P: PublicKey,
D: Directory<P>,
{
pub(crate) participants: Set<P>,
pub(crate) directory: D,
pub(crate) completion: DkgCompletion<V, P, D>,
}
enum Mode<V, P, D>
where
V: BlsVariant,
P: PublicKey,
D: Directory<P>,
{
Reshare,
Dkg {
participants: Set<P>,
directory: D,
completion: Option<DkgCompletion<V, P, D>>,
},
}
pub struct Config<C, M, X, P, SS, T, BV, S, MV, R>
where
C: Signer,
X: Blocker<PublicKey = C::PublicKey>,
S: Scheme + SimplexScheme<MV::Commitment, PublicKey = C::PublicKey>,
MV: MarshalVariant,
MV::ApplicationBlock: ReshareBlock,
<MV::ApplicationBlock as ReshareBlock>::Signer: Signer<PublicKey = C::PublicKey>,
R: Registrar<PublicKey = C::PublicKey>,
{
pub signer: C,
pub manager: M,
pub blocker: X,
pub participants_provider: P,
pub secret_store: SS,
pub strategy: T,
pub registrar: R,
pub marshal: MarshalMailbox<S, MV>,
pub state_sync: StateSyncPlan<
S,
MV::Commitment,
R::Variant,
<MV::ApplicationBlock as ReshareBlock>::Directory,
>,
pub fence: Fence,
pub namespace: &'static [u8],
pub sharing_mode: SharingMode,
pub reveal: Reveal,
pub mailbox_size: NonZeroUsize,
pub partition_prefix: String,
pub max_participants: NonZeroU32,
pub blocks_per_epoch: NonZeroU64,
pub batch_verifier: PhantomData<BV>,
}
pub struct Actor<E, B, V, C, M, X, P, SS, T, BV, S, MV, R, A = Exact>
where
E: Spawner + CryptoRng + Metrics + BufferPooler + Clock + Storage,
B: ReshareBlock<Variant = V, Signer = C>,
V: BlsVariant,
C: Signer,
M: Manager<PublicKey = C::PublicKey, Directory = B::Directory>,
X: Blocker<PublicKey = C::PublicKey>,
P: ParticipantsProvider<PublicKey = C::PublicKey, Directory = B::Directory>,
SS: SecretStore,
T: Strategy,
BV: BatchVerifier<PublicKey = C::PublicKey> + Send + 'static,
S: Scheme + SimplexScheme<MV::Commitment, PublicKey = C::PublicKey>,
MV: MarshalVariant<ApplicationBlock = B>,
R: Registrar<Variant = V, PublicKey = C::PublicKey>,
A: Acknowledgement,
{
context: ContextCell<E>,
mailbox: MailboxReceiver<Message<B, V, C, A>>,
signer: C,
manager: M,
blocker: X,
participants_provider: P,
secret_store: Option<SS>,
strategy: T,
registrar: R,
marshal: MarshalMailbox<S, MV>,
state_sync: StateSyncPlan<S, MV::Commitment, V, B::Directory>,
fence: Fence,
namespace: &'static [u8],
sharing_mode: SharingMode,
reveal: Reveal,
partition_prefix: String,
max_participants: NonZeroU32,
blocks_per_epoch: NonZeroU64,
epocher: FixedEpocher,
metrics: ReshareMetrics<C::PublicKey>,
mode: Mode<V, C::PublicKey, B::Directory>,
batch_verifier: PhantomData<BV>,
}
impl<E, B, V, C, M, X, P, SS, T, BV, S, MV, R, A> Actor<E, B, V, C, M, X, P, SS, T, BV, S, MV, R, A>
where
E: Spawner + CryptoRng + Metrics + BufferPooler + Clock + Storage,
B: ReshareBlock<Variant = V, Signer = C>,
V: BlsVariant,
C: Signer,
M: Manager<PublicKey = C::PublicKey, Directory = B::Directory>,
X: Blocker<PublicKey = C::PublicKey>,
P: ParticipantsProvider<PublicKey = C::PublicKey, Directory = B::Directory>,
SS: SecretStore,
T: Strategy,
BV: BatchVerifier<PublicKey = C::PublicKey> + Send + 'static,
S: Scheme + SimplexScheme<MV::Commitment, PublicKey = C::PublicKey>,
MV: MarshalVariant<ApplicationBlock = B>,
R: Registrar<Variant = V, PublicKey = C::PublicKey>,
A: Acknowledgement,
{
pub fn new(
context: E,
config: Config<C, M, X, P, SS, T, BV, S, MV, R>,
) -> (Self, Mailbox<B, V, C, A>) {
let epocher = FixedEpocher::new(config.blocks_per_epoch);
let (sender, mailbox) = actor_mailbox::new(context.child("mailbox"), config.mailbox_size);
let metrics = ReshareMetrics::new(&context);
(
Self {
context: ContextCell::new(context),
mailbox,
signer: config.signer,
manager: config.manager,
blocker: config.blocker,
participants_provider: config.participants_provider,
secret_store: Some(config.secret_store),
strategy: config.strategy,
registrar: config.registrar,
marshal: config.marshal,
state_sync: config.state_sync,
fence: config.fence,
namespace: config.namespace,
sharing_mode: config.sharing_mode,
reveal: config.reveal,
partition_prefix: config.partition_prefix,
max_participants: config.max_participants,
blocks_per_epoch: config.blocks_per_epoch,
epocher,
metrics,
mode: Mode::Reshare,
batch_verifier: config.batch_verifier,
},
Mailbox::new(sender),
)
}
pub(crate) fn new_dkg(
context: E,
config: Config<C, M, X, P, SS, T, BV, S, MV, R>,
dkg: DkgConfig<V, C::PublicKey, B::Directory>,
) -> (Self, Mailbox<B, V, C, A>) {
let (mut actor, mailbox) = Self::new(context, config);
actor.mode = Mode::Dkg {
participants: dkg.participants,
directory: dkg.directory,
completion: Some(dkg.completion),
};
(actor, mailbox)
}
pub fn start<SE, RE>(mut self, chan: (SE, RE)) -> Handle<()>
where
SE: Sender<PublicKey = C::PublicKey>,
RE: Receiver<PublicKey = C::PublicKey>,
{
spawn_cell!(self.context, self.run(chan))
}
async fn run<SE, RE>(mut self, (sender, receiver): (SE, RE))
where
SE: Sender<PublicKey = C::PublicKey>,
RE: Receiver<PublicKey = C::PublicKey>,
{
let secret_store = self
.secret_store
.take()
.expect("secret store must be available when actor starts");
let mut store = Store::init(
self.context.child("store"),
&self.partition_prefix,
self.max_participants,
secret_store,
)
.await;
let (mux, mut dealing_mux) = Muxer::new(self.context.child("mux"), sender, receiver, 128);
mux.start();
let recovered_epoch = state_sync::recovered_epoch(&self.marshal, &self.epocher).await;
let state_sync = self
.state_sync
.resolve(
self.context.as_present().child("state_sync"),
recovered_epoch,
)
.await;
let mut state_sync = if let Some(state_sync) = state_sync {
let share = self.recovered_share(&mut store, &state_sync.info).await;
self.register_epoch(&state_sync.info, share).await;
let floor = self
.marshal
.subscribe_by_commitment(
state_sync.floor.proposal.payload,
CommitmentFallback::Wait,
)
.await
.expect("marshal must yield state sync floor block");
Some(StateSyncStart {
info: state_sync.info,
floor: floor.height(),
})
} else {
None
};
if matches!(self.mode, Mode::Dkg { .. }) {
self.run_dkg(&mut store, &mut dealing_mux).await;
return;
}
let mut current_epoch = state_sync.as_ref().map(|start| start.info.epoch);
loop {
let Some(prepared) = self
.setup(&mut store, current_epoch.take(), state_sync.take())
.await
else {
return;
};
let Setup::Participate(prepared) = prepared else {
if self.follow(&mut store).await.is_break() {
return;
}
current_epoch = store.current().map(|info| info.epoch);
continue;
};
let mut prepared = *prepared;
let chan = dealing_mux
.register(prepared.epoch.get())
.await
.expect("failed to register reshare epoch channel");
if prepared.phase == EpochPhase::Early {
let dealer = prepared.dealer.as_mut();
let player = prepared.player.as_mut();
if self
.dealing(prepared.epoch, &mut store, dealer, player, chan)
.await
.is_break()
{
return;
}
}
if self
.inclusion(
prepared.epoch,
&prepared.info,
&mut store,
prepared.dealer.as_mut(),
)
.await
.is_break()
{
return;
}
current_epoch = Some(prepared.epoch.next());
}
}
}