use std::error::Error;
use std::fmt::{self, Display, Formatter};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct MutationReplayIdentity {
authority_binding: [u8; 32],
operation_identity: [u8; 32],
semantic_digest: [u8; 32],
}
impl MutationReplayIdentity {
pub fn new(
authority_binding: [u8; 32],
operation_identity: [u8; 32],
semantic_digest: [u8; 32],
) -> Result<Self, MutationLifecycleError> {
if authority_binding == [0; 32] {
return Err(MutationLifecycleError::ZeroAuthorityBinding);
}
if operation_identity == [0; 32] {
return Err(MutationLifecycleError::ZeroOperationIdentity);
}
if semantic_digest == [0; 32] {
return Err(MutationLifecycleError::ZeroSemanticDigest);
}
Ok(Self {
authority_binding,
operation_identity,
semantic_digest,
})
}
#[must_use]
pub const fn authority_binding(self) -> [u8; 32] {
self.authority_binding
}
#[must_use]
pub const fn operation_identity(self) -> [u8; 32] {
self.operation_identity
}
#[must_use]
pub const fn semantic_digest(self) -> [u8; 32] {
self.semantic_digest
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum MutationLifecycleStage {
NotSubmitted,
Prepared,
Committed,
Visible,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PreparedMutationDisposition {
SafelyReplayable,
StillPending,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubmittedIndeterminateResolution {
Committed { visible: bool },
SafelyReplayable,
StillPending,
ProvenNotSubmitted,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MutationLifecycleState {
NotSubmitted,
Prepared(PreparedMutationDisposition),
Committed,
Visible,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MutationLifecycleObservation {
replay_identity: MutationReplayIdentity,
state: MutationLifecycleState,
}
impl MutationLifecycleObservation {
#[must_use]
pub const fn proven_not_submitted(replay_identity: MutationReplayIdentity) -> Self {
Self {
replay_identity,
state: MutationLifecycleState::NotSubmitted,
}
}
#[must_use]
pub const fn prepared(replay_identity: MutationReplayIdentity, disposition: PreparedMutationDisposition) -> Self {
Self {
replay_identity,
state: MutationLifecycleState::Prepared(disposition),
}
}
#[must_use]
pub const fn committed(replay_identity: MutationReplayIdentity) -> Self {
Self {
replay_identity,
state: MutationLifecycleState::Committed,
}
}
#[must_use]
pub const fn visible(replay_identity: MutationReplayIdentity) -> Self {
Self {
replay_identity,
state: MutationLifecycleState::Visible,
}
}
#[must_use]
pub const fn replay_identity(self) -> MutationReplayIdentity {
self.replay_identity
}
#[must_use]
pub const fn stage(self) -> MutationLifecycleStage {
match self.state {
MutationLifecycleState::NotSubmitted => MutationLifecycleStage::NotSubmitted,
MutationLifecycleState::Prepared(_) => MutationLifecycleStage::Prepared,
MutationLifecycleState::Committed => MutationLifecycleStage::Committed,
MutationLifecycleState::Visible => MutationLifecycleStage::Visible,
}
}
#[must_use]
pub const fn prepared_disposition(self) -> Option<PreparedMutationDisposition> {
match self.state {
MutationLifecycleState::Prepared(disposition) => Some(disposition),
MutationLifecycleState::NotSubmitted
| MutationLifecycleState::Committed
| MutationLifecycleState::Visible => None,
}
}
#[must_use]
pub const fn resolve_submitted_indeterminate(self) -> SubmittedIndeterminateResolution {
match self.state {
MutationLifecycleState::NotSubmitted => SubmittedIndeterminateResolution::ProvenNotSubmitted,
MutationLifecycleState::Prepared(PreparedMutationDisposition::SafelyReplayable) => {
SubmittedIndeterminateResolution::SafelyReplayable
}
MutationLifecycleState::Prepared(PreparedMutationDisposition::StillPending) => {
SubmittedIndeterminateResolution::StillPending
}
MutationLifecycleState::Committed => SubmittedIndeterminateResolution::Committed { visible: false },
MutationLifecycleState::Visible => SubmittedIndeterminateResolution::Committed { visible: true },
}
}
pub fn require_monotonic_successor(self, successor: Self) -> Result<(), MutationLifecycleError> {
if self.replay_identity != successor.replay_identity {
return Err(MutationLifecycleError::ReplayIdentityChanged);
}
let stage = self.stage();
let successor_stage = successor.stage();
if successor_stage < stage {
return Err(MutationLifecycleError::StageRegressed {
from: stage,
to: successor_stage,
});
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MutationLifecycleError {
ZeroAuthorityBinding,
ZeroOperationIdentity,
ZeroSemanticDigest,
ReplayIdentityChanged,
StageRegressed {
from: MutationLifecycleStage,
to: MutationLifecycleStage,
},
OutcomeEvidenceRetired,
}
impl Display for MutationLifecycleError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::ZeroAuthorityBinding => formatter.write_str("mutation authority binding cannot be zero"),
Self::ZeroOperationIdentity => formatter.write_str("mutation operation identity cannot be zero"),
Self::ZeroSemanticDigest => formatter.write_str("mutation semantic digest cannot be zero"),
Self::ReplayIdentityChanged => {
formatter.write_str("mutation replay identity changed across lifecycle observations")
}
Self::StageRegressed { from, to } => {
write!(formatter, "mutation lifecycle regressed from {from:?} to {to:?}")
}
Self::OutcomeEvidenceRetired => {
formatter.write_str("mutation outcome evidence retired before deterministic resolution")
}
}
}
}
impl Error for MutationLifecycleError {}
#[cfg(test)]
mod tests {
use super::*;
fn replay_identity(value: u8) -> Result<MutationReplayIdentity, MutationLifecycleError> {
MutationReplayIdentity::new(
[value; 32],
[value.saturating_add(1); 32],
[value.saturating_add(2); 32],
)
}
#[test]
fn submitted_indeterminate_has_exactly_four_resolution_classes() -> Result<(), Box<dyn Error>> {
let identity = replay_identity(1)?;
assert_eq!(
MutationLifecycleObservation::proven_not_submitted(identity).resolve_submitted_indeterminate(),
SubmittedIndeterminateResolution::ProvenNotSubmitted
);
assert_eq!(
MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::SafelyReplayable)
.resolve_submitted_indeterminate(),
SubmittedIndeterminateResolution::SafelyReplayable
);
assert_eq!(
MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::StillPending)
.resolve_submitted_indeterminate(),
SubmittedIndeterminateResolution::StillPending
);
assert_eq!(
MutationLifecycleObservation::committed(identity).resolve_submitted_indeterminate(),
SubmittedIndeterminateResolution::Committed { visible: false }
);
assert_eq!(
MutationLifecycleObservation::visible(identity).resolve_submitted_indeterminate(),
SubmittedIndeterminateResolution::Committed { visible: true }
);
Ok(())
}
#[test]
fn lifecycle_is_monotonic_and_identity_exact() -> Result<(), Box<dyn Error>> {
let identity = replay_identity(7)?;
let not_submitted = MutationLifecycleObservation::proven_not_submitted(identity);
let replayable =
MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::SafelyReplayable);
let pending = MutationLifecycleObservation::prepared(identity, PreparedMutationDisposition::StillPending);
let committed = MutationLifecycleObservation::committed(identity);
let visible = MutationLifecycleObservation::visible(identity);
not_submitted.require_monotonic_successor(replayable)?;
replayable.require_monotonic_successor(pending)?;
pending.require_monotonic_successor(replayable)?;
pending.require_monotonic_successor(committed)?;
committed.require_monotonic_successor(visible)?;
assert!(matches!(
visible.require_monotonic_successor(committed),
Err(MutationLifecycleError::StageRegressed { .. })
));
assert_eq!(
committed.require_monotonic_successor(MutationLifecycleObservation::visible(replay_identity(8)?,)),
Err(MutationLifecycleError::ReplayIdentityChanged)
);
Ok(())
}
#[test]
fn replay_identity_refuses_ambiguous_components() {
assert_eq!(
MutationReplayIdentity::new([0; 32], [1; 32], [2; 32]),
Err(MutationLifecycleError::ZeroAuthorityBinding)
);
assert_eq!(
MutationReplayIdentity::new([1; 32], [0; 32], [2; 32]),
Err(MutationLifecycleError::ZeroOperationIdentity)
);
assert_eq!(
MutationReplayIdentity::new([1; 32], [2; 32], [0; 32]),
Err(MutationLifecycleError::ZeroSemanticDigest)
);
}
}