extern crate alloc;
use alloc::collections::VecDeque;
use core::num::{NonZeroU64, NonZeroUsize};
use super::wire::LineageProofRecord;
use super::{Epochs, SealedEpoch};
use crate::metis::dot::Dot;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum EpochBootstrapError {
#[error("bootstrap proof is empty: a fresh fleet joins through `Epochs::new`")]
EmptyProof,
#[error(
"bootstrap proof retains {generations} generations, past the declared horizon {horizon}"
)]
HorizonExceeded {
generations: usize,
horizon: usize,
},
#[error("bootstrap proof ends at the generation ceiling: no next window generation exists")]
CeilingGeneration,
#[error("sealed generation {generation}: candidate claims foreign generation {found}")]
CandidateOutsideGeneration {
generation: u64,
found: u64,
},
#[error("sealed generation {generation}: station {station} is outside the roster")]
ForeignStation {
generation: u64,
station: u32,
},
#[error("sealed generation {generation}: winner {winner} is not among its candidates")]
WinnerNotCandidate {
generation: u64,
winner: Dot,
},
#[error("sealed generation {generation}: ledger dot {dot} is above the sealed join")]
UncoveredLedgerDot {
generation: u64,
dot: Dot,
},
#[error("sealed generation {generation}: the admission base is not the predecessor")]
AdmissionBaseMismatch {
generation: u64,
},
#[error("sealed generation {generation}: joiner {station} is already a member")]
AdmissionAlreadyMember {
generation: u64,
station: u32,
},
}
fn verify_entry_containment(
seal: &super::wire::SealRecord,
winner: super::gate::EpochAddress,
generation: u64,
historical: &alloc::collections::BTreeSet<u32>,
) -> Result<(), EpochBootstrapError> {
for candidate in seal.candidates() {
if candidate.generation() != generation {
return Err(EpochBootstrapError::CandidateOutsideGeneration {
generation,
found: candidate.generation(),
});
}
if !historical.contains(&candidate.declaration().station()) {
return Err(EpochBootstrapError::ForeignStation {
generation,
station: candidate.declaration().station(),
});
}
}
if !seal.candidates().any(|candidate| candidate == winner) {
return Err(EpochBootstrapError::WinnerNotCandidate {
generation,
winner: winner.declaration(),
});
}
for dot in seal.declaration_dots() {
if !historical.contains(&dot.station()) {
return Err(EpochBootstrapError::ForeignStation {
generation,
station: dot.station(),
});
}
if dot.counter() > seal.sealed_join().get(dot.station()) {
return Err(EpochBootstrapError::UncoveredLedgerDot { generation, dot });
}
}
if let Some((station, _)) = seal
.sealed_join()
.iter()
.find(|(station, _)| !historical.contains(station))
{
return Err(EpochBootstrapError::ForeignStation {
generation,
station,
});
}
Ok(())
}
impl Epochs {
pub fn bootstrap(
roster: impl IntoIterator<Item = u32>,
horizon: NonZeroUsize,
proof: &LineageProofRecord,
) -> Result<Self, EpochBootstrapError> {
let roster: alloc::collections::BTreeSet<u32> = roster.into_iter().collect();
let Some(newest) = proof.entries().last() else {
return Err(EpochBootstrapError::EmptyProof);
};
if proof.len() > horizon.get() {
return Err(EpochBootstrapError::HorizonExceeded {
generations: proof.len(),
horizon: horizon.get(),
});
}
let Some(generation) = newest
.seal()
.declaration()
.generation()
.checked_add(1)
.filter(|&next| next < u64::MAX)
.and_then(NonZeroU64::new)
else {
return Err(EpochBootstrapError::CeilingGeneration);
};
let mut historical = roster;
let mut lineage = VecDeque::with_capacity(proof.len());
let mut previous: Option<super::gate::EpochAddress> = None;
for entry in proof.entries() {
let seal = entry.seal();
let winner = seal.declaration();
let generation = winner.generation();
verify_entry_containment(seal, winner, generation, &historical)?;
if let Some(admission) = seal.admission() {
if admission.base().generation().checked_add(1) != Some(generation)
|| previous.is_some_and(|previous| previous != admission.base())
{
return Err(EpochBootstrapError::AdmissionBaseMismatch { generation });
}
if let Some(station) = admission
.joiners()
.find(|joiner| historical.contains(joiner))
{
return Err(EpochBootstrapError::AdmissionAlreadyMember {
generation,
station,
});
}
historical.extend(admission.joiners());
}
previous = Some(winner);
lineage.push_back(SealedEpoch {
declaration: winner,
candidates: seal.candidates().collect(),
protocol: seal.declaration_dots().collect(),
sealed_join: seal.sealed_join().clone(),
admission: seal.admission().cloned(),
});
}
Ok(Self {
roster: historical,
horizon,
generation,
window: None,
lineage,
arrival: None,
})
}
}