extern crate alloc;
mod arrival;
mod bootstrap;
mod consigned;
mod gate;
mod preparation;
mod shadow;
mod snapshot;
mod vouched;
mod wire;
pub use arrival::{Admission, ArrivalRefusal, ArrivalRound, Endorsement};
pub use bootstrap::EpochBootstrapError;
pub use consigned::Consigned;
pub use gate::{EpochAddress, EpochAdoptionMismatch, EpochGate, InvalidEpochAddress};
pub use preparation::{EpochPreparation, EpochPreparationFold, EpochPreparationMiss};
#[cfg(feature = "instrumentation")]
pub use shadow::EpochShadowProfile;
pub use shadow::{
EpochConsignment, EpochConsignmentError, EpochProjection, EpochShadow, EpochStratum,
EpochStratumError, EpochTransition, SweptIdentity,
};
pub use snapshot::{
EpochLedgerDecodeBudget, EpochLedgerDecodeError, EpochLedgerRehydrateError, EpochLedgerSnapshot,
};
pub use vouched::Vouched;
pub use wire::{
AdoptionReportDecodeError, AdoptionReportRecord, ConfirmationDecodeError, ConfirmationRecord,
DeclarationDecodeError, EndorsementDecodeBudget, EndorsementDecodeError,
LineageProofDecodeBudget, LineageProofDecodeError, LineageProofEntry, LineageProofRecord,
SealDecodeBudget, SealDecodeError, SealRecord, WireCutError,
};
use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
use alloc::vec::Vec;
use core::num::{NonZeroU64, NonZeroUsize};
use crate::kairos::Kairos;
use super::dot::{Dot, RawDot};
use super::stability::{Stability, UnknownStation};
use super::{Cut, VersionVector};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Declaration {
address: EpochAddress,
rank: Kairos,
cut: Cut,
admission: Option<Admission>,
admission_commitment: Option<[u8; 32]>,
}
impl Declaration {
#[must_use]
pub const fn dot(&self) -> Dot {
self.address.declaration()
}
#[must_use]
pub const fn address(&self) -> EpochAddress {
self.address
}
#[must_use]
pub const fn rank(&self) -> Kairos {
self.rank
}
#[must_use]
pub const fn cut(&self) -> &Cut {
&self.cut
}
#[must_use]
pub const fn admission(&self) -> Option<&Admission> {
self.admission.as_ref()
}
#[must_use]
pub const fn admission_commitment(&self) -> Option<&[u8; 32]> {
self.admission_commitment.as_ref()
}
const fn key(&self) -> (Kairos, Dot) {
(self.rank, self.address.declaration())
}
fn covers(&self, dot: Dot) -> bool {
self.cut.as_vector().get(dot.station()) >= dot.counter()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Adopted {
declaration: Declaration,
}
impl Adopted {
#[must_use]
pub const fn declaration(&self) -> &Declaration {
&self.declaration
}
#[must_use]
pub const fn dot(&self) -> Dot {
self.declaration.dot()
}
#[must_use]
pub const fn address(&self) -> EpochAddress {
self.declaration.address()
}
#[must_use]
pub const fn rank(&self) -> Kairos {
self.declaration.rank()
}
#[must_use]
pub const fn cut(&self) -> &Cut {
self.declaration.cut()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SealedEpoch {
declaration: EpochAddress,
candidates: BTreeSet<EpochAddress>,
protocol: BTreeSet<Dot>,
sealed_join: VersionVector,
admission: Option<Admission>,
}
impl SealedEpoch {
#[must_use]
pub const fn declaration(&self) -> EpochAddress {
self.declaration
}
#[must_use]
pub const fn sealed_join(&self) -> &VersionVector {
&self.sealed_join
}
#[must_use]
pub fn contains(&self, declaration: EpochAddress) -> bool {
self.candidates.contains(&declaration)
}
pub fn candidates(&self) -> impl Iterator<Item = EpochAddress> + '_ {
self.candidates.iter().copied()
}
#[must_use]
pub const fn admission(&self) -> Option<&Admission> {
self.admission.as_ref()
}
pub fn declaration_dots(&self) -> impl Iterator<Item = Dot> + '_ {
self.protocol.iter().copied()
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum EpochRefusal {
#[error("epoch unwitnessed: no watermark cut licenses a declaration")]
Unwitnessed,
#[error("epoch unrisen: witnessed {witnessed} for station {station} is below basis {required}")]
Unrisen {
station: u32,
required: u64,
witnessed: u64,
},
#[error("epoch window open: declaration {open:?} is not yet sealed")]
WindowOpen {
open: EpochAddress,
},
#[error("epoch unconfirmed: the confirmation watermark has not fixed a winner")]
Unconfirmed,
#[error("epoch declaration {dot} is not fresh above ceiling {ceiling}")]
StaleDeclaration {
dot: Dot,
ceiling: u64,
},
#[error("epoch declaration station {station} is outside the roster")]
UnknownDeclarationStation {
station: u32,
},
#[error("epoch confirmation from {station} covers {covered}, below declaration {epoch:?}")]
ConfirmationDoesNotCover {
epoch: EpochAddress,
station: u32,
covered: u64,
},
#[error("epoch address miss: dot {dot} is not covered by epoch {epoch:?}")]
AddressMiss {
epoch: EpochAddress,
dot: RawDot,
},
#[error("epoch beyond horizon: {epoch:?} is below the retained lineage")]
BeyondHorizon {
epoch: EpochAddress,
},
#[error("admission base discord: offered {offered:?}, sealed {sealed:?}")]
AdmissionBaseDiscord {
offered: EpochAddress,
sealed: Option<EpochAddress>,
},
#[error("admission already present: station {station} is on the roster")]
AdmissionAlreadyPresent {
station: u32,
},
#[error(
"unassented admission: winner {winner:?} carries a boundary this machine did not endorse"
)]
UnassentedAdmission {
winner: EpochAddress,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Round {
reports: BTreeMap<u32, Option<VersionVector>>,
}
impl Round {
fn over(roster: &BTreeSet<u32>) -> Self {
Self {
reports: roster.iter().map(|&station| (station, None)).collect(),
}
}
fn fold(&mut self, station: u32, report: &VersionVector) -> Result<(), UnknownStation> {
let slot = self
.reports
.get_mut(&station)
.ok_or(UnknownStation { station })?;
*slot = Some(
slot.take()
.map_or_else(|| report.clone(), |held| held.merge(report)),
);
Ok(())
}
fn join_when_complete(&self, stability: &Stability) -> Option<VersionVector> {
self.reports
.iter()
.try_fold(VersionVector::new(), |mut join, (&station, slot)| {
if let Some(bound) = stability.attested(station) {
join.observe(station, bound);
return Some(join);
}
Some(join.merge(slot.as_ref()?))
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Window {
candidates: BTreeMap<EpochAddress, Declaration>,
protocol: BTreeSet<Dot>,
confirmations: Round,
adoptions: BTreeMap<EpochAddress, Round>,
fixed: Option<EpochAddress>,
adopted: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Epochs {
roster: BTreeSet<u32>,
horizon: NonZeroUsize,
generation: NonZeroU64,
window: Option<Window>,
lineage: VecDeque<SealedEpoch>,
arrival: Option<ArrivalRound>,
}
impl Epochs {
#[must_use]
pub fn new(roster: impl IntoIterator<Item = u32>, horizon: NonZeroUsize) -> Self {
Self {
roster: roster.into_iter().collect(),
horizon,
generation: NonZeroU64::MIN,
window: None,
lineage: VecDeque::new(),
arrival: None,
}
}
pub fn declare(
&mut self,
dot: Dot,
rank: Kairos,
stability: &Stability,
basis: &Cut,
) -> Result<Declaration, EpochRefusal> {
if !self.roster.contains(&dot.station()) {
return Err(EpochRefusal::UnknownDeclarationStation {
station: dot.station(),
});
}
if let Some(window) = &self.window {
return Err(EpochRefusal::WindowOpen {
open: window
.fixed
.or_else(|| window.candidates.keys().next().copied())
.unwrap_or_else(|| EpochAddress::new(self.generation, dot)),
});
}
let cut = stability.watermark_cut().ok_or(EpochRefusal::Unwitnessed)?;
if let Some(refusal) = Self::unrisen(basis, &cut) {
return Err(refusal);
}
let ceiling = cut.as_vector().get(dot.station());
let address = EpochAddress::new(self.generation, dot);
if dot.counter() <= ceiling {
return Err(EpochRefusal::StaleDeclaration { dot, ceiling });
}
let tag = self
.arrival
.as_ref()
.filter(|round| {
round.complete()
&& round
.family()
.all(|station| stability.attested(station).is_none())
})
.map(|round| (round.admission().clone(), *round.commitment()));
let (admission, admission_commitment) = match tag {
Some((admission, commitment)) => (Some(admission), Some(commitment)),
None => (None, None),
};
let declaration = Declaration {
address,
rank,
cut,
admission,
admission_commitment,
};
self.window = Some(Window {
candidates: BTreeMap::from([(address, declaration.clone())]),
protocol: BTreeSet::from([dot]),
confirmations: Round::over(&self.roster),
adoptions: BTreeMap::from([(address, Round::over(&self.roster))]),
fixed: None,
adopted: false,
});
Ok(declaration)
}
pub fn deliver(
&mut self,
declaration: Declaration,
stability: &Stability,
basis: &Cut,
) -> Result<(), EpochRefusal> {
let dot = declaration.dot();
if !self.roster.contains(&dot.station()) {
return Err(EpochRefusal::UnknownDeclarationStation {
station: dot.station(),
});
}
let address = declaration.address();
if self.lineage.iter().any(|sealed| sealed.contains(address)) {
return Ok(());
}
if self.lineage.iter().any(|sealed| {
sealed.declaration.generation() == address.generation()
&& sealed.sealed_join.get(dot.station()) >= dot.counter()
}) {
return Err(EpochRefusal::AddressMiss {
epoch: address,
dot: dot.into(),
});
}
if address.generation() < self.generation.get() {
return Err(EpochRefusal::BeyondHorizon { epoch: address });
}
if address.generation() > self.generation.get() {
return Err(EpochRefusal::AddressMiss {
epoch: address,
dot: dot.into(),
});
}
if let Some(refusal) = Self::unrisen(basis, &declaration.cut) {
return Err(refusal);
}
let ceiling = declaration.cut.as_vector().get(dot.station());
if dot.counter() <= ceiling {
return Err(EpochRefusal::StaleDeclaration { dot, ceiling });
}
if let Some(admission) = declaration.admission() {
let sealed = self.lineage.back().map(SealedEpoch::declaration);
if sealed != Some(admission.base()) {
return Err(EpochRefusal::AdmissionBaseDiscord {
offered: admission.base(),
sealed,
});
}
if let Some(station) = admission
.joiners()
.find(|station| self.roster.contains(station))
{
return Err(EpochRefusal::AdmissionAlreadyPresent { station });
}
}
let declaration_address = declaration.address();
match &mut self.window {
None => {
self.window = Some(Window {
candidates: BTreeMap::from([(declaration_address, declaration)]),
protocol: BTreeSet::from([dot]),
confirmations: Round::over(&self.roster),
adoptions: BTreeMap::from([(declaration_address, Round::over(&self.roster))]),
fixed: None,
adopted: false,
});
Ok(())
}
Some(window) => {
if window.candidates.contains_key(&declaration.address()) {
return Ok(());
}
let _ = window.protocol.insert(dot);
if let Some(&open) = window
.candidates
.keys()
.find(|&&candidate| declaration.covers(candidate.declaration()))
{
return Err(EpochRefusal::WindowOpen { open });
}
Self::latch_winner(window, stability);
if let Some(open) = window.fixed {
return Err(EpochRefusal::WindowOpen { open });
}
let later: Vec<EpochAddress> = window
.candidates
.iter()
.filter_map(|(&address, held)| held.covers(dot).then_some(address))
.collect();
for displaced in later {
let _ = window.candidates.remove(&displaced);
let _ = window.adoptions.remove(&displaced);
}
let address = declaration.address();
let _ = window.candidates.insert(address, declaration);
let _ = window.adoptions.insert(address, Round::over(&self.roster));
Ok(())
}
}
}
pub fn confirm(
&mut self,
epoch: EpochAddress,
report: &Vouched<Cut>,
) -> Result<(), EpochRefusal> {
let station = report.by();
let delivered = report.claim();
if self.lineage.iter().any(|sealed| sealed.contains(epoch)) {
return Ok(());
}
let Some(window) = &mut self.window else {
return Err(EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, 0),
});
};
if !window.candidates.contains_key(&epoch) {
return Err(EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, 0),
});
}
if window.fixed.is_some() {
return Ok(());
}
let declaration = epoch.declaration();
let covered = delivered.as_vector().get(declaration.station());
if covered < declaration.counter() {
return Err(EpochRefusal::ConfirmationDoesNotCover {
epoch,
station,
covered,
});
}
window
.confirmations
.fold(station, delivered.as_vector())
.map_err(|UnknownStation { station }| EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, 0),
})
}
pub fn adopt(
&mut self,
station: u32,
own_counter: u64,
stability: &Stability,
) -> Result<Adopted, EpochRefusal> {
let Some(window) = &mut self.window else {
return Err(EpochRefusal::Unconfirmed);
};
Self::latch_winner(window, stability);
let Some(winner) = window.fixed else {
return Err(EpochRefusal::Unconfirmed);
};
if let Some(candidate) = window.candidates.get(&winner)
&& let Some(required) = candidate.admission()
{
let assented = self.arrival.as_ref().is_some_and(|round| {
round.own() == station
&& round.admission() == required
&& Some(round.commitment()) == candidate.admission_commitment()
});
if !assented {
return Err(EpochRefusal::UnassentedAdmission { winner });
}
}
let mut own = VersionVector::new();
own.observe(station, own_counter);
let Some(round) = window.adoptions.get_mut(&winner) else {
return Err(EpochRefusal::Unconfirmed);
};
round
.fold(station, &own)
.map_err(|UnknownStation { station }| EpochRefusal::AddressMiss {
epoch: winner,
dot: RawDot::new(station, 0),
})?;
window.adopted = true;
Ok(Adopted {
declaration: window.candidates[&winner].clone(),
})
}
pub fn adopt_report(
&mut self,
epoch: EpochAddress,
report: &Vouched<u64>,
) -> Result<(), EpochRefusal> {
let station = report.by();
let own_counter = *report.claim();
if self.lineage.iter().any(|sealed| sealed.contains(epoch)) {
return Ok(());
}
let Some(window) = &mut self.window else {
return Err(EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, own_counter),
});
};
if !window.candidates.contains_key(&epoch) {
return Err(EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, own_counter),
});
}
let mut own = VersionVector::new();
own.observe(station, own_counter);
let Some(round) = window.adoptions.get_mut(&epoch) else {
return Err(EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, own_counter),
});
};
round
.fold(station, &own)
.map_err(|UnknownStation { station }| EpochRefusal::AddressMiss {
epoch,
dot: RawDot::new(station, own_counter),
})
}
pub fn try_seal(&mut self, stability: &Stability) -> Option<&SealedEpoch> {
let window = self.window.as_mut()?;
Self::latch_winner(window, stability);
let winner = window.fixed?;
if !window.adopted {
return None;
}
let join = window
.adoptions
.get(&winner)?
.join_when_complete(stability)?;
let watermark = stability.watermark_cut()?;
if !join
.partial_cmp(watermark.as_vector())
.is_some_and(core::cmp::Ordering::is_le)
{
return None;
}
let next = self.generation.checked_add(1)?;
let admission = window
.candidates
.get(&winner)
.and_then(Declaration::admission)
.cloned();
self.lineage.push_back(SealedEpoch {
declaration: winner,
candidates: window.candidates.keys().copied().collect(),
protocol: window
.protocol
.iter()
.copied()
.filter(|dot| join.get(dot.station()) >= dot.counter())
.collect(),
sealed_join: join,
admission: admission.clone(),
});
self.window = None;
self.generation = next;
if let Some(admission) = &admission {
self.roster.extend(admission.joiners());
}
self.arrival = None;
while self.lineage.len() > self.horizon.get() {
let _ = self.lineage.pop_front();
}
self.lineage.back()
}
pub fn recognize(&self, epoch: EpochAddress, dot: Dot) -> Result<(), EpochRefusal> {
if let Some(window) = &self.window
&& window.candidates.contains_key(&epoch)
{
return Err(EpochRefusal::WindowOpen {
open: window.fixed.unwrap_or(epoch),
});
}
match self.lineage.iter().find(|sealed| sealed.contains(epoch)) {
Some(sealed) if sealed.sealed_join.get(dot.station()) >= dot.counter() => Ok(()),
Some(_) => Err(EpochRefusal::AddressMiss {
epoch,
dot: dot.into(),
}),
None => Err(EpochRefusal::BeyondHorizon { epoch }),
}
}
pub fn candidates(&self) -> impl Iterator<Item = &Declaration> {
self.window
.iter()
.flat_map(|window| window.candidates.values())
}
#[must_use]
pub fn fixed(&self) -> Option<&Declaration> {
let window = self.window.as_ref()?;
window.candidates.get(&window.fixed?)
}
#[must_use]
pub fn adopted(&self) -> bool {
self.window.as_ref().is_some_and(|window| window.adopted)
}
pub fn sealed(&self) -> impl Iterator<Item = &SealedEpoch> {
self.lineage.iter()
}
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation.get()
}
pub fn roster(&self) -> impl Iterator<Item = u32> + '_ {
self.roster.iter().copied()
}
#[must_use]
pub const fn horizon(&self) -> NonZeroUsize {
self.horizon
}
#[must_use]
pub fn newest_sealed(&self) -> Option<&SealedEpoch> {
self.lineage.back()
}
fn unrisen(basis: &Cut, cut: &Cut) -> Option<EpochRefusal> {
basis.as_vector().iter().find_map(|(station, required)| {
let witnessed = cut.as_vector().get(station);
(witnessed < required).then_some(EpochRefusal::Unrisen {
station,
required,
witnessed,
})
})
}
fn latch_winner(window: &mut Window, stability: &Stability) {
if window.fixed.is_some() {
return;
}
let Some(join) = window.confirmations.join_when_complete(stability) else {
return;
};
let Some(watermark) = stability.watermark_cut() else {
return;
};
if !join
.partial_cmp(watermark.as_vector())
.is_some_and(core::cmp::Ordering::is_le)
{
return;
}
window.fixed = window
.candidates
.values()
.max_by_key(|candidate| candidate.key())
.map(Declaration::address);
}
}