use crate::outcome::CandidatePhase;
use crate::wire::{
AttachSecret, BindingEpoch, ConversationId, DeliverySeq, EnrollBound, EnrollmentRequest,
ParticipantId, ParticipantIndex, TransactionOrder,
};
use super::{ActiveBinding, AdmissionOrder, BindingState, EnrollmentFingerprint, LiveMember};
pub trait ParticipantSlotAllocatorProof {
fn conversation_id(&self) -> ConversationId;
fn participant_index(&self) -> ParticipantIndex;
fn identity_limit(&self) -> u64;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AllocatedParticipantSlot<P> {
allocator_proof: P,
conversation_id: ConversationId,
participant_id: ParticipantId,
identity_limit: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParticipantSlotAllocationError {
IdentityLimit,
}
impl<P: ParticipantSlotAllocatorProof> AllocatedParticipantSlot<P> {
pub fn from_allocator(allocator_proof: P) -> Result<Self, ParticipantSlotAllocationError> {
let participant_id = allocator_proof.participant_index();
let identity_limit = allocator_proof.identity_limit();
if participant_id >= identity_limit {
return Err(ParticipantSlotAllocationError::IdentityLimit);
}
Ok(Self {
conversation_id: allocator_proof.conversation_id(),
participant_id,
identity_limit,
allocator_proof,
})
}
#[must_use]
pub const fn conversation_id(&self) -> ConversationId {
self.conversation_id
}
#[must_use]
pub const fn participant_id(&self) -> ParticipantId {
self.participant_id
}
#[must_use]
pub const fn identity_limit(&self) -> u64 {
self.identity_limit
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AttachedRecordPosition {
transaction_order: TransactionOrder,
delivery_seq: DeliverySeq,
}
impl AttachedRecordPosition {
#[must_use]
pub const fn new(transaction_order: TransactionOrder, delivery_seq: DeliverySeq) -> Self {
Self {
transaction_order,
delivery_seq,
}
}
#[must_use]
pub const fn transaction_order(self) -> TransactionOrder {
self.transaction_order
}
#[must_use]
pub const fn delivery_seq(self) -> DeliverySeq {
self.delivery_seq
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AttachedLifecycleRecord {
participant_id: ParticipantId,
conversation_id: ConversationId,
binding_epoch: BindingEpoch,
admission_order: AdmissionOrder,
delivery_seq: DeliverySeq,
}
impl AttachedLifecycleRecord {
pub(crate) const fn from_binding(
binding: ActiveBinding,
position: AttachedRecordPosition,
) -> Self {
Self {
participant_id: binding.participant_id,
conversation_id: binding.conversation_id,
binding_epoch: binding.binding_epoch,
admission_order: AdmissionOrder::new(
position.transaction_order,
CandidatePhase::AttachLifecycle,
binding.participant_id,
),
delivery_seq: position.delivery_seq,
}
}
#[must_use]
pub const fn participant_id(self) -> ParticipantId {
self.participant_id
}
#[must_use]
pub const fn conversation_id(self) -> ConversationId {
self.conversation_id
}
#[must_use]
pub const fn binding_epoch(self) -> BindingEpoch {
self.binding_epoch
}
#[must_use]
pub const fn admission_order(self) -> AdmissionOrder {
self.admission_order
}
#[must_use]
pub const fn delivery_seq(self) -> DeliverySeq {
self.delivery_seq
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BindingOriginKind {
Unfenced,
Recovered {
marker_delivery_seq: DeliverySeq,
prior_binding_epoch: BindingEpoch,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BindingOrigin {
attached: AttachedLifecycleRecord,
kind: BindingOriginKind,
}
impl BindingOrigin {
pub(crate) const fn unfenced(attached: AttachedLifecycleRecord) -> Self {
Self {
attached,
kind: BindingOriginKind::Unfenced,
}
}
pub(crate) const fn recovered(
attached: AttachedLifecycleRecord,
marker_delivery_seq: DeliverySeq,
prior_binding_epoch: BindingEpoch,
) -> Self {
Self {
attached,
kind: BindingOriginKind::Recovered {
marker_delivery_seq,
prior_binding_epoch,
},
}
}
pub(crate) const fn attached(self) -> AttachedLifecycleRecord {
self.attached
}
pub(crate) const fn conversation_id(self) -> ConversationId {
self.attached.conversation_id()
}
pub(crate) const fn participant_id(self) -> ParticipantId {
self.attached.participant_id()
}
pub(crate) const fn binding_epoch(self) -> BindingEpoch {
self.attached.binding_epoch()
}
pub(crate) const fn is_unfenced(self) -> bool {
matches!(self.kind, BindingOriginKind::Unfenced)
}
pub(crate) const fn recovered_marker(self) -> Option<(DeliverySeq, BindingEpoch)> {
match self.kind {
BindingOriginKind::Unfenced => None,
BindingOriginKind::Recovered {
marker_delivery_seq,
prior_binding_epoch,
} => Some((marker_delivery_seq, prior_binding_epoch)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnrollmentCommitParameters<F, P> {
pub allocated_slot: AllocatedParticipantSlot<P>,
pub attach_secret: AttachSecret,
pub origin_binding_epoch: BindingEpoch,
pub attached_position: AttachedRecordPosition,
pub receipt_expires_at: u128,
pub provenance_expires_at: u128,
pub enrollment_fingerprint: EnrollmentFingerprint<F>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnrollmentCommit<F> {
pub member: LiveMember<F>,
pub binding_state: BindingState,
pub attached: AttachedLifecycleRecord,
pub outcome: EnrollBound,
binding_origin: BindingOrigin,
}
impl<F> EnrollmentCommit<F> {
#[must_use]
#[allow(
dead_code,
reason = "the crate-owned event replay boundary persists this producer-emitted origin"
)]
pub(crate) const fn binding_origin(&self) -> BindingOrigin {
self.binding_origin
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EnrollmentCommitError {
Conversation,
BindingGeneration,
ReceiptInvariant,
}
pub fn commit_enrollment<F, P>(
request: &EnrollmentRequest,
parameters: EnrollmentCommitParameters<F, P>,
) -> Result<EnrollmentCommit<F>, EnrollmentCommitError> {
if request.conversation_id != parameters.allocated_slot.conversation_id {
return Err(EnrollmentCommitError::Conversation);
}
if parameters.origin_binding_epoch.capability_generation != crate::wire::Generation::ONE {
return Err(EnrollmentCommitError::BindingGeneration);
}
let participant_id = parameters.allocated_slot.participant_id;
let binding = ActiveBinding {
participant_id,
conversation_id: request.conversation_id,
binding_epoch: parameters.origin_binding_epoch,
};
let attached = AttachedLifecycleRecord::from_binding(binding, parameters.attached_position);
let Some(outcome) = EnrollBound::new(
request.conversation_id,
request.enrollment_token,
participant_id,
parameters.attach_secret,
parameters.origin_binding_epoch,
parameters.receipt_expires_at,
parameters.provenance_expires_at,
) else {
return Err(EnrollmentCommitError::ReceiptInvariant);
};
let member = LiveMember::from_enrollment(
participant_id,
request.conversation_id,
parameters.attach_secret,
parameters.enrollment_fingerprint,
);
let _consumed_allocator_proof = parameters.allocated_slot.allocator_proof;
Ok(EnrollmentCommit {
member,
binding_state: BindingState::Bound(binding),
attached,
outcome,
binding_origin: BindingOrigin::unfenced(attached),
})
}