use super::{
elector,
types::{Activity, Context, Finalization},
};
use crate::{
CertifiableAutomaton, Epochable, Relay, Reporter, Viewable,
types::{Epoch, View, ViewDelta},
};
use commonware_cryptography::{Digest, certificate::Scheme};
use commonware_p2p::Blocker;
use commonware_parallel::Strategy;
use commonware_runtime::buffer::paged::CacheRef;
use rand_core::CryptoRng;
use std::{
num::{NonZeroU64, NonZeroUsize},
time::Duration,
};
#[derive(Debug, Clone, Copy, Default)]
pub enum SkipBudget {
#[default]
Participants,
Fixed(NonZeroU64),
}
impl SkipBudget {
pub(crate) const fn resolve(self, participants: usize) -> u64 {
match self {
Self::Participants => participants as u64,
Self::Fixed(budget) => budget.get(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SkipPolicy {
Disabled,
Enabled {
timeout: Duration,
budget: SkipBudget,
},
}
#[derive(Debug, Clone, Copy)]
pub enum ForwardPolicy {
Disabled,
SilentVoters,
SilentLeader,
}
impl ForwardPolicy {
pub const fn is_enabled(&self) -> bool {
!matches!(self, Self::Disabled)
}
}
#[derive(Clone, Debug)]
pub enum Floor<S: Scheme, D: Digest> {
Genesis(D),
Finalized(Finalization<S, D>),
}
impl<S: Scheme, D: Digest> Floor<S, D> {
pub(crate) fn view(&self) -> View {
match self {
Self::Genesis(_) => View::zero(),
Self::Finalized(finalization) => finalization.view(),
}
}
fn assert<Rng>(&self, epoch: Epoch, rng: &mut Rng, scheme: &S, strategy: &impl Strategy)
where
Rng: CryptoRng,
S: super::scheme::Scheme<D>,
{
if let Self::Finalized(finalization) = self {
assert_eq!(
finalization.epoch(),
epoch,
"floor finalization must be in the configured epoch"
);
assert!(
!finalization.view().is_zero(),
"use Floor::Genesis for the genesis view"
);
assert!(
finalization.verify(rng, scheme, strategy),
"floor finalization must verify"
);
}
}
}
pub struct Config<S, L, B, D, A, R, F, T>
where
S: Scheme,
L: elector::Config<S>,
B: Blocker<PublicKey = S::PublicKey>,
D: Digest,
A: CertifiableAutomaton<Context = Context<D, S::PublicKey>>,
R: Relay,
F: Reporter<Activity = Activity<S, D>>,
T: Strategy,
{
pub scheme: S,
pub elector: L,
pub blocker: B,
pub automaton: A,
pub relay: R,
pub reporter: F,
pub track_historical_votes: bool,
pub strategy: T,
pub partition: String,
pub mailbox_size: NonZeroUsize,
pub epoch: Epoch,
pub floor: Floor<S, D>,
pub replay_buffer: NonZeroUsize,
pub write_buffer: NonZeroUsize,
pub page_cache: CacheRef,
pub leader_timeout: Duration,
pub certification_timeout: Duration,
pub timeout_retry: Duration,
pub view_retention: ViewDelta,
pub skip: SkipPolicy,
pub fetch_timeout: Duration,
pub forward: ForwardPolicy,
}
impl<
S: Scheme,
L: elector::Config<S>,
B: Blocker<PublicKey = S::PublicKey>,
D: Digest,
A: CertifiableAutomaton<Context = Context<D, S::PublicKey>>,
R: Relay,
F: Reporter<Activity = Activity<S, D>>,
T: Strategy,
> Config<S, L, B, D, A, R, F, T>
{
pub fn assert<Rng>(&self, rng: &mut Rng)
where
Rng: CryptoRng,
S: super::scheme::Scheme<D>,
{
assert!(
!self.scheme.participants().is_empty(),
"there must be at least one participant"
);
assert!(
self.leader_timeout > Duration::default(),
"leader timeout must be greater than zero"
);
assert!(
self.certification_timeout > self.leader_timeout,
"certification timeout must be greater than leader timeout"
);
if let SkipPolicy::Enabled { timeout, .. } = self.skip {
assert!(
timeout > self.certification_timeout,
"skip timeout must be greater than certification timeout"
);
assert!(
timeout > self.timeout_retry,
"skip timeout must be greater than timeout retry"
);
}
assert!(
self.timeout_retry > Duration::default(),
"timeout retry broadcast must be greater than zero"
);
assert!(
!self.view_retention.is_zero(),
"view retention timeout must be greater than zero"
);
assert!(
self.fetch_timeout > Duration::default(),
"fetch timeout must be greater than zero"
);
self.floor
.assert(self.epoch, rng, &self.scheme, &self.strategy);
}
}
#[cfg(test)]
mod tests {
use super::SkipBudget;
use std::num::NonZeroU64;
#[test]
fn skip_budget_resolves() {
assert_eq!(SkipBudget::default().resolve(4), 4);
assert_eq!(SkipBudget::Participants.resolve(7), 7);
assert_eq!(SkipBudget::Fixed(NonZeroU64::new(9).unwrap()).resolve(4), 9);
}
}