#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod configuration_record;
pub mod driver;
pub mod flow;
mod installation;
pub mod local;
mod normal;
pub mod recovery;
mod view_change;
pub mod wire;
pub use configuration_record::{
CONFIGURATION_RECORD_BYTES, ConfigurationRecord, ConfigurationRecordError, ConfiguredVoter,
};
pub use installation::{
InstallOutcome, InstallTicket, InstallingView, RejectedInstallation, StartView,
};
pub use normal::{Admission, NormalReplica, ReplicaSnapshot, Status};
pub use ozzy_journal::operation::Digest;
pub use ozzy_journal::progress::{JournalGeneration, OpNumber, SyncTicket, WriteTicket};
pub use view_change::{
DoViewChange, FrozenLog, LogSource, PromiseTicket, RecoveredState, SelectedView,
StartViewChange, ViewChange, ViewChangeError,
};
use ozzy_journal::operation::{CanonicalOperation, logical_operation_digest_with_body_digest};
use ozzy_journal::progress::ProgressError;
use ozzy_proto::{GroupId, NodeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Prefix {
pub op: OpNumber,
pub digest: Digest,
}
impl Prefix {
pub const GENESIS: Self = Self {
op: OpNumber(0),
digest: Digest::ZERO,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Scope {
pub group_id: GroupId,
pub configuration_epoch: u64,
pub configuration_digest: Digest,
pub view: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum QuorumPolicy {
Durable = 1,
Replicated = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Configuration {
scope: Scope,
voters: [NodeId; 3],
policy: QuorumPolicy,
}
impl Configuration {
pub fn new(
group_id: GroupId,
configuration_epoch: u64,
digest: Digest,
voters: [NodeId; 3],
) -> Result<Self, ReplicationError> {
if group_id.as_bytes() == &[0; 16]
|| voters.iter().any(|voter| voter.as_bytes() == &[0; 16])
|| digest == Digest::ZERO
|| voters[0] == voters[1]
|| voters[0] == voters[2]
|| voters[1] == voters[2]
{
return Err(ReplicationError::InvalidConfiguration);
}
Ok(Self {
scope: Scope {
group_id,
configuration_epoch,
configuration_digest: digest,
view: 0,
},
voters,
policy: QuorumPolicy::Durable,
})
}
pub const fn policy(self) -> QuorumPolicy {
self.policy
}
pub const fn append_policy(self) -> ozzy_proto::append::Policy {
match self.policy {
QuorumPolicy::Durable => ozzy_proto::append::Policy::QuorumDurable,
QuorumPolicy::Replicated => ozzy_proto::append::Policy::QuorumReplicatedPersisting,
}
}
pub const fn scope(self) -> Scope {
self.scope
}
pub const fn voters(&self) -> &[NodeId; 3] {
&self.voters
}
pub fn primary(&self, view: u64) -> NodeId {
self.voters[(view % 3) as usize]
}
fn voter_index(&self, id: NodeId) -> Result<usize, ReplicationError> {
self.voters
.iter()
.position(|&voter| voter == id)
.ok_or(ReplicationError::UnknownVoter)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PipelineLimits {
pub max_operations: usize,
pub max_body_bytes: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PreparedOperation {
group_id: GroupId,
configuration_epoch: u64,
original_view: u64,
prefix: Prefix,
previous_digest: Digest,
body_bytes: usize,
}
impl PreparedOperation {
pub fn from_verified(operation: &CanonicalOperation<'_>, body_digest: Digest) -> Self {
Self {
group_id: operation.group_id,
configuration_epoch: operation.configuration_epoch,
original_view: operation.original_view,
prefix: Prefix {
op: OpNumber(operation.op_number),
digest: logical_operation_digest_with_body_digest(operation, body_digest),
},
previous_digest: operation.previous_digest,
body_bytes: operation.body.len(),
}
}
pub const fn prefix(self) -> Prefix {
self.prefix
}
pub const fn body_bytes(self) -> usize {
self.body_bytes
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PrepareOk {
pub scope: Scope,
pub durable: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RetainedPrepareOk {
pub scope: Scope,
pub retained: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Commit {
pub scope: Scope,
pub committed: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ReplicationError {
#[error("replication confirmation policy mismatch")]
PolicyMismatch,
#[error("invalid fixed replication configuration")]
InvalidConfiguration,
#[error("invalid replication pipeline limits")]
InvalidLimits,
#[error("unknown replica voter")]
UnknownVoter,
#[error("action requires the current primary or a backup")]
WrongRole,
#[error("replication message scope mismatch")]
ScopeMismatch,
#[error("replica is not in normal operation")]
NotNormal,
#[error("replication pipeline capacity exhausted")]
Capacity,
#[error("empty prepare group")]
EmptyPrepare,
#[error("prepare or evidence leaves a local history gap")]
HistoryGap,
#[error("operation metadata is outside the retained pipeline")]
HistoryUnavailable,
#[error("conflicting canonical replication lineage")]
ConflictingHistory,
#[error("application completion exceeds committed prefix")]
ApplyBeyondCommit,
#[error("selected view tail has not reached quorum and application")]
ActivationPending,
#[error(transparent)]
Journal(#[from] ProgressError),
}