1use alloc::{boxed::Box, vec, vec::Vec};
8
9use crate::{algebra::ResourceVector, wire::RecordAdmission};
10
11use super::super::{
12 AttachCommit, AttachTransition, BindingState, ClaimFrontiers, ClosureAccounting, ClosureDebt,
13 CommittedDetachTransition, DebtCompletion, DetachCell, DetachedCredentialRecovery, Event,
14 FencedAttachCommit, FrontierBinding, FrontierParticipant, IdentityState,
15 InitialEnrollmentFrontierCommit, LeaveCommitError, LeaveCommitParameters, LiveMember,
16 MarkerAckCommit, NonzeroParticipantAckCommit, ObserverProgressProjection, OrderLedger,
17 ParticipantAckCommit, PendingFinalization, PendingLeaveCommitParameters,
18 PrepareLeaveAuthorityError, RetainedCausalRecord, RetainedCausalRecordKind, SequenceLedger,
19 StoredEdge, VerifiedLeaveRequest,
20 claim_frontier::{FencedMarkerSourceRecord, LiveFrontierTransitionError},
21 commit_leave, commit_pending_leave,
22};
23use super::{
24 InitialEnrollmentOperationCommit, MarkerDeliveryProjection, MarkerDrainCommit,
25 RecordAdmissionPersistenceParts, RetainedRecordCharge, UnchangedRecordAdmission,
26};
27
28mod binding_fate_transition;
29mod ledger;
30mod state;
31pub(super) use binding_fate_transition::BindingFateOwnerPlan;
32use ledger::{
33 detach_order, detach_sequence, detached_attach_order, detached_attach_sequence,
34 enrollment_order, enrollment_sequence, superseding_attach_order, superseding_attach_sequence,
35};
36use state::{
37 accounting_after_fenced_attach, accounting_after_leave, accounting_after_marker_ack,
38 accounting_after_rows, retained_attached, retained_terminal,
39};
40
41#[derive(Debug, PartialEq, Eq)]
66pub struct LiveFrontierOwner {
67 frontiers: ClaimFrontiers,
68 closure_accounting: ClosureAccounting,
69 retained_charges: Vec<RetainedRecordCharge>,
70 retained_record_limit: u64,
71}
72
73impl LiveFrontierOwner {
74 #[must_use]
76 pub fn from_initial_enrollment<F>(
77 initial: InitialEnrollmentFrontierCommit<F>,
78 retained_record_limit: u64,
79 ) -> (InitialEnrollmentOperationCommit<F>, Self) {
80 let (operation, frontiers, closure_accounting, attached_charge) =
81 initial.into_conversation_parts();
82 let attached = operation.enrollment().attached;
83 let retained_charges = vec![RetainedRecordCharge::new(
84 attached.delivery_seq(),
85 attached.admission_order(),
86 attached_charge,
87 )];
88 (
89 operation,
90 Self {
91 frontiers,
92 closure_accounting,
93 retained_charges,
94 retained_record_limit,
95 },
96 )
97 }
98
99 #[cfg(any(test, feature = "test-support"))]
100 pub(in crate::lifecycle) const fn from_test_parts(
101 frontiers: ClaimFrontiers,
102 closure_accounting: ClosureAccounting,
103 retained_charges: Vec<RetainedRecordCharge>,
104 retained_record_limit: u64,
105 ) -> Self {
106 Self {
107 frontiers,
108 closure_accounting,
109 retained_charges,
110 retained_record_limit,
111 }
112 }
113
114 #[cfg(any(test, feature = "test-support"))]
122 pub fn with_pending_finalizer_test_capacity(
123 mut self,
124 finalizer_rows: u64,
125 finalizer_charge: ResourceVector,
126 ) -> Result<Self, &'static str> {
127 let retained_count = u64::try_from(self.retained_charges.len())
128 .map_err(|_| "retained record count exceeds u64")?;
129 self.retained_record_limit = retained_count
130 .checked_add(finalizer_rows)
131 .ok_or("pending finalizer retained-record capacity overflow")?;
132 let current = self.closure_accounting;
133 let configured = current.configured_cap();
134 let configured = ResourceVector::new(
135 configured
136 .entries
137 .checked_add(finalizer_charge.entries)
138 .ok_or("pending finalizer entry capacity overflow")?,
139 configured
140 .bytes
141 .checked_add(finalizer_charge.bytes)
142 .ok_or("pending finalizer byte capacity overflow")?,
143 );
144 self.closure_accounting = ClosureAccounting::try_new(
145 current.state(),
146 current.marker_capacity_credits(),
147 current.marker_anchors(),
148 current.edge_sequence_claims(),
149 current.edge_order_position_claims(),
150 current.edge_k_remaining(),
151 current.baseline(),
152 configured,
153 current.episode_churn_used(),
154 current.episode_churn_limit(),
155 )
156 .map_err(|_| "pending finalizer closure capacity extension refused")?;
157 Ok(self)
158 }
159
160 #[must_use]
162 pub const fn frontiers(&self) -> &ClaimFrontiers {
163 &self.frontiers
164 }
165
166 #[must_use]
168 pub const fn closure_accounting(&self) -> ClosureAccounting {
169 self.closure_accounting
170 }
171
172 #[must_use]
174 pub fn retained_charges(&self) -> &[RetainedRecordCharge] {
175 &self.retained_charges
176 }
177
178 #[must_use]
180 pub const fn retained_record_limit(&self) -> u64 {
181 self.retained_record_limit
182 }
183
184 #[must_use]
186 pub fn into_parts(
187 self,
188 ) -> (
189 ClaimFrontiers,
190 ClosureAccounting,
191 Vec<RetainedRecordCharge>,
192 u64,
193 ) {
194 (
195 self.frontiers,
196 self.closure_accounting,
197 self.retained_charges,
198 self.retained_record_limit,
199 )
200 }
201
202 #[must_use]
205 pub fn from_unchanged_record_admission<EF, V, LF>(
206 unchanged: UnchangedRecordAdmission<'_, EF, V, LF>,
207 retained_record_limit: u64,
208 ) -> (Self, RecordAdmission, ResourceVector) {
209 let (prestate, encoded_record_charge) = unchanged.into_parts();
210 let (request, frontiers, closure_accounting, retained_charges) =
211 prestate.into_live_owner_parts();
212 (
213 Self {
214 frontiers,
215 closure_accounting,
216 retained_charges,
217 retained_record_limit,
218 },
219 request,
220 encoded_record_charge,
221 )
222 }
223
224 #[must_use]
227 pub fn from_record_admission_persistence(
228 persistence: RecordAdmissionPersistenceParts,
229 retained_record_limit: u64,
230 ) -> Self {
231 Self {
232 frontiers: persistence.frontiers,
233 closure_accounting: persistence.accounting,
234 retained_charges: persistence.retained_charges,
235 retained_record_limit,
236 }
237 }
238
239 #[must_use]
241 pub fn from_marker_drain(
242 commit: MarkerDrainCommit,
243 retained_record_limit: u64,
244 ) -> (Self, StoredEdge, MarkerDeliveryProjection) {
245 let (frontiers, closure_accounting, retained_charges, successor, projection) =
246 commit.into_parts();
247 (
248 Self {
249 frontiers,
250 closure_accounting,
251 retained_charges,
252 retained_record_limit,
253 },
254 successor,
255 projection,
256 )
257 }
258
259 pub(super) fn commit_binding_terminal_candidate(
260 self,
261 active_binding: super::super::ActiveBinding,
262 admission_order: super::super::AdmissionOrder,
263 delivery_seq: crate::wire::DeliverySeq,
264 charge: RetainedRecordCharge,
265 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
266 let mut active = self.frontiers.active_identities().participants().to_vec();
267 let Some(participant) = active
268 .iter_mut()
269 .find(|participant| participant.participant_index() == active_binding.participant_id)
270 else {
271 return Err(Box::new((self, LiveFrontierError::Authority)));
272 };
273 if participant.binding() != FrontierBinding::Bound(active_binding.binding_epoch) {
274 return Err(Box::new((self, LiveFrontierError::Authority)));
275 }
276 *participant = FrontierParticipant::new(
277 participant.participant_index(),
278 participant.cursor(),
279 FrontierBinding::Detached(active_binding.binding_epoch),
280 );
281 let row = RetainedCausalRecord {
282 delivery_seq,
283 admission_order,
284 kind: RetainedCausalRecordKind::BindingTerminal(super::super::BindingTerminalOwner {
285 participant_index: active_binding.participant_id,
286 binding_epoch: active_binding.binding_epoch,
287 }),
288 };
289 let Some(sequence) = detach_sequence(self.frontiers.sequence().ledger(), delivery_seq)
290 else {
291 return Err(Box::new((self, LiveFrontierError::Frontier)));
292 };
293 let Some(order) = detach_order(
294 self.frontiers.order().ledger(),
295 admission_order.transaction_order(),
296 ) else {
297 return Err(Box::new((self, LiveFrontierError::Frontier)));
298 };
299 match transition(self, (), active, &[row], vec![charge], sequence, order) {
300 Ok(committed) => {
301 let ((), owner) = committed.into_parts();
302 Ok(owner)
303 }
304 Err(failure) => {
305 let error = failure.error();
306 let ((), owner) = failure.into_parts();
307 Err(Box::new((owner, error)))
308 }
309 }
310 }
311
312 pub(super) fn pend_binding_terminal_candidate(
313 self,
314 active_binding: super::super::ActiveBinding,
315 admission_order: super::super::AdmissionOrder,
316 delivery_seq: crate::wire::DeliverySeq,
317 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
318 let Some(order) = detach_order(
319 self.frontiers.order().ledger(),
320 admission_order.transaction_order(),
321 ) else {
322 return Err(Box::new((self, LiveFrontierError::Frontier)));
323 };
324 let Self {
325 frontiers,
326 closure_accounting,
327 retained_charges,
328 retained_record_limit,
329 } = self;
330 match frontiers.apply_pending_binding_terminal(
331 active_binding.participant_id,
332 active_binding.binding_epoch,
333 delivery_seq,
334 admission_order,
335 order,
336 ) {
337 Ok(frontiers) => Ok(Self {
338 frontiers,
339 closure_accounting,
340 retained_charges,
341 retained_record_limit,
342 }),
343 Err(failure) => {
344 let (frontiers, error) = *failure;
345 Err(Box::new((
346 Self {
347 frontiers,
348 closure_accounting,
349 retained_charges,
350 retained_record_limit,
351 },
352 map_frontier_error(error),
353 )))
354 }
355 }
356 }
357
358 pub fn retain_fenced_marker_source(
365 self,
366 recovery: DetachedCredentialRecovery,
367 ) -> Result<RetainedFencedMarkerSource, Box<FencedMarkerSourceRetentionRefused>> {
368 let Some(source) = self.frontiers.fenced_marker_source(recovery) else {
369 return Err(Box::new(FencedMarkerSourceRetentionRefused {
370 owner: self,
371 recovery,
372 }));
373 };
374 Ok(RetainedFencedMarkerSource {
375 owner: self,
376 recovery,
377 expectation: FencedMarkerSourceExpectation { source },
378 })
379 }
380
381 #[must_use]
389 pub fn mint_fenced_attach(
390 mut self,
391 marker_source_sequence: u64,
392 recovery: DetachedCredentialRecovery,
393 debt: ClosureDebt,
394 event: Event,
395 successor: DebtCompletion,
396 ) -> MintFencedAttachResult {
397 let Some(record) = self.frontiers.take_fenced_marker_record(recovery) else {
398 return MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
399 owner: self,
400 marker_source_sequence,
401 recovery,
402 debt,
403 event,
404 successor,
405 reason: FencedAttachMintRefusalReason::MarkerAuthority,
406 }));
407 };
408 match recovery.fenced_attach(record, debt, event, successor) {
409 Ok(proof) => MintFencedAttachResult::Minted(Box::new(MintedFencedAttach {
410 owner_without_marker_authority: self,
411 proof,
412 })),
413 Err(refusal) => {
414 self.frontiers
415 .reinstall_fenced_marker_record((*refusal).into_record());
416 MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
417 owner: self,
418 marker_source_sequence,
419 recovery,
420 debt,
421 event,
422 successor,
423 reason: FencedAttachMintRefusalReason::ProofInputs,
424 }))
425 }
426 }
427 }
428}
429
430#[derive(Clone, Copy, Debug, PartialEq, Eq)]
432pub struct FencedMarkerSourceExpectation {
433 source: FencedMarkerSourceRecord,
434}
435
436impl FencedMarkerSourceExpectation {
437 #[must_use]
439 pub const fn conversation_id(self) -> u64 {
440 self.source.conversation_id
441 }
442
443 #[must_use]
445 pub const fn marker_delivery_seq(self) -> u64 {
446 self.source.delivery_seq
447 }
448
449 #[must_use]
451 pub const fn admission_order(self) -> super::super::AdmissionOrder {
452 self.source.admission_order
453 }
454
455 #[must_use]
457 pub const fn participant_id(self) -> u64 {
458 self.source.participant_id
459 }
460
461 #[must_use]
463 pub const fn provenance(self) -> super::super::MarkerProvenance {
464 self.source.provenance
465 }
466
467 #[must_use]
469 pub const fn target_binding(self) -> FrontierBinding {
470 self.source.target_binding
471 }
472}
473
474#[derive(Debug, PartialEq, Eq)]
476pub struct RetainedFencedMarkerSource {
477 owner: LiveFrontierOwner,
478 recovery: DetachedCredentialRecovery,
479 expectation: FencedMarkerSourceExpectation,
480}
481
482impl RetainedFencedMarkerSource {
483 #[must_use]
485 pub const fn expectation(&self) -> FencedMarkerSourceExpectation {
486 self.expectation
487 }
488
489 #[must_use]
491 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
492 (self.owner, self.recovery)
493 }
494}
495
496#[derive(Debug, PartialEq, Eq)]
498pub struct FencedMarkerSourceRetentionRefused {
499 owner: LiveFrontierOwner,
500 recovery: DetachedCredentialRecovery,
501}
502
503impl FencedMarkerSourceRetentionRefused {
504 #[must_use]
506 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
507 (self.owner, self.recovery)
508 }
509}
510
511#[derive(Clone, Copy, Debug, PartialEq, Eq)]
513pub enum FencedAttachMintRefusalReason {
514 MarkerAuthority,
517 ProofInputs,
519}
520
521#[derive(Debug, PartialEq, Eq)]
523pub struct MintedFencedAttach {
524 owner_without_marker_authority: LiveFrontierOwner,
525 proof: FencedAttachCommit,
526}
527
528impl MintedFencedAttach {
529 #[must_use]
532 pub fn into_parts(self) -> (LiveFrontierOwner, FencedAttachCommit) {
533 (self.owner_without_marker_authority, self.proof)
534 }
535}
536
537#[derive(Debug, PartialEq, Eq)]
539pub struct MintFencedAttachRefused {
540 owner: LiveFrontierOwner,
541 marker_source_sequence: u64,
542 recovery: DetachedCredentialRecovery,
543 debt: ClosureDebt,
544 event: Event,
545 successor: DebtCompletion,
546 reason: FencedAttachMintRefusalReason,
547}
548
549impl MintFencedAttachRefused {
550 #[must_use]
552 pub const fn reason(&self) -> FencedAttachMintRefusalReason {
553 self.reason
554 }
555
556 #[must_use]
558 pub fn into_parts(
559 self,
560 ) -> (
561 LiveFrontierOwner,
562 u64,
563 DetachedCredentialRecovery,
564 ClosureDebt,
565 Event,
566 DebtCompletion,
567 ) {
568 (
569 self.owner,
570 self.marker_source_sequence,
571 self.recovery,
572 self.debt,
573 self.event,
574 self.successor,
575 )
576 }
577}
578
579#[derive(Debug, PartialEq, Eq)]
581pub enum MintFencedAttachResult {
582 Minted(Box<MintedFencedAttach>),
584 MintRefused(Box<MintFencedAttachRefused>),
586}
587
588#[derive(Debug, PartialEq, Eq)]
590pub struct LiveLeaveCommit<EF, V, LF> {
591 identity: IdentityState<EF, V, LF>,
592 owner: LiveFrontierOwner,
593}
594
595impl<EF, V, LF> LiveLeaveCommit<EF, V, LF> {
596 #[must_use]
598 pub const fn observer_progress_projection(&self) -> Option<ObserverProgressProjection> {
599 let IdentityState::Retired(retired) = &self.identity else {
600 return None;
601 };
602 let committed = retired.committed_result();
603 Some(ObserverProgressProjection::new(
604 committed.conversation_id(),
605 committed.left_delivery_seq(),
606 ))
607 }
608
609 #[must_use]
611 pub fn into_parts(self) -> (IdentityState<EF, V, LF>, LiveFrontierOwner) {
612 (self.identity, self.owner)
613 }
614}
615
616#[derive(Clone, Debug, PartialEq, Eq)]
618pub enum LiveLeaveError {
619 Prepare(PrepareLeaveAuthorityError),
621 Commit(LeaveCommitError),
623 RetainedCharge,
625 RetainedRecordLimit,
627 ClosureAccounting,
629 Identity,
631}
632
633pub fn commit_settled_leave_frontier<EF, V, LF, D>(
641 owner: LiveFrontierOwner,
642 member: LiveMember<EF>,
643 binding: BindingState,
644 detach_cell: DetachCell<D>,
645 verified: VerifiedLeaveRequest<V, LF>,
646 left_delivery_seq: u64,
647 left_charge: RetainedRecordCharge,
648) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
649 let LiveFrontierOwner {
650 frontiers,
651 closure_accounting,
652 mut retained_charges,
653 retained_record_limit,
654 } = owner;
655 let retired_marker_charge =
656 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
657 let authority = frontiers
658 .prepare_settled_leave_authority(&member, binding)
659 .map_err(LiveLeaveError::Prepare)?;
660 let commit = commit_leave(
661 member,
662 binding,
663 detach_cell,
664 verified,
665 authority,
666 LeaveCommitParameters { left_delivery_seq },
667 )
668 .map_err(LiveLeaveError::Commit)?;
669 let (identity, frontiers) = commit.into_parts();
670 let IdentityState::Retired(retired) = &identity else {
671 return Err(LiveLeaveError::Identity);
672 };
673 if left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
674 || left_charge.admission_order() != retired.left_admission_order()
675 || left_charge.encoded_charge().entries != 1
676 {
677 return Err(LiveLeaveError::RetainedCharge);
678 }
679 retained_charges.push(left_charge);
680 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
681 let retained_len = u64::try_from(frontiers.retained_records().len())
682 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
683 if retained_len > retained_record_limit
684 || retained_charges.len() != frontiers.retained_records().len()
685 {
686 return Err(LiveLeaveError::RetainedRecordLimit);
687 }
688 let closure_accounting =
689 accounting_after_leave(closure_accounting, &[left_charge], retired_marker_charge)
690 .ok_or(LiveLeaveError::ClosureAccounting)?;
691 Ok(LiveLeaveCommit {
692 identity,
693 owner: LiveFrontierOwner {
694 frontiers,
695 closure_accounting,
696 retained_charges,
697 retained_record_limit,
698 },
699 })
700}
701
702pub fn commit_pending_leave_frontier<EF, V, LF, D>(
711 owner: LiveFrontierOwner,
712 member: LiveMember<EF>,
713 pending: PendingFinalization,
714 detach_cell: DetachCell<D>,
715 verified: VerifiedLeaveRequest<V, LF>,
716 parameters: PendingLeaveCommitParameters,
717 charges: [RetainedRecordCharge; 2],
718) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
719 let [terminal_charge, left_charge] = charges;
720 let terminal_delivery_seq = parameters.terminal_delivery_seq;
721 let LiveFrontierOwner {
722 frontiers,
723 closure_accounting,
724 mut retained_charges,
725 retained_record_limit,
726 } = owner;
727 let retired_marker_charge =
728 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
729 let authority = frontiers
730 .prepare_pending_leave_authority(&member, pending)
731 .map_err(LiveLeaveError::Prepare)?;
732 let commit = commit_pending_leave(
733 member,
734 pending,
735 detach_cell,
736 verified,
737 authority,
738 parameters,
739 )
740 .map_err(LiveLeaveError::Commit)?;
741 let (identity, frontiers) = commit.into_parts();
742 let IdentityState::Retired(retired) = &identity else {
743 return Err(LiveLeaveError::Identity);
744 };
745 if retired.committed_result().prior_terminal_delivery_seq() != Some(terminal_delivery_seq)
746 || terminal_charge.delivery_seq() != terminal_delivery_seq
747 || terminal_charge.admission_order() != pending.admission_order()
748 || terminal_charge.encoded_charge().entries != 1
749 || left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
750 || left_charge.admission_order() != retired.left_admission_order()
751 || left_charge.encoded_charge().entries != 1
752 {
753 return Err(LiveLeaveError::RetainedCharge);
754 }
755 retained_charges.extend([terminal_charge, left_charge]);
756 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
757 let retained_len = u64::try_from(frontiers.retained_records().len())
758 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
759 if retained_len > retained_record_limit {
760 return Err(LiveLeaveError::RetainedRecordLimit);
761 }
762 if retained_charges.len() != frontiers.retained_records().len() {
763 return Err(LiveLeaveError::RetainedCharge);
764 }
765 let closure_accounting = accounting_after_leave(
766 closure_accounting,
767 &[terminal_charge, left_charge],
768 retired_marker_charge,
769 )
770 .ok_or(LiveLeaveError::ClosureAccounting)?;
771 Ok(LiveLeaveCommit {
772 identity,
773 owner: LiveFrontierOwner {
774 frontiers,
775 closure_accounting,
776 retained_charges,
777 retained_record_limit,
778 },
779 })
780}
781
782fn retired_marker_charge(
783 frontiers: &ClaimFrontiers,
784 retained_charges: &[RetainedRecordCharge],
785 participant_id: crate::wire::ParticipantId,
786) -> Result<Option<RetainedRecordCharge>, LiveLeaveError> {
787 let marker_sequence = frontiers
788 .retained_marker_records()
789 .iter()
790 .find_map(|record| {
791 matches!(
792 record.kind,
793 RetainedCausalRecordKind::CompactionMarker {
794 participant_index,
795 ..
796 } if participant_index == participant_id
797 )
798 .then_some(record.delivery_seq)
799 });
800 let Some(marker_sequence) = marker_sequence else {
801 return Ok(None);
802 };
803 retained_charges
804 .iter()
805 .copied()
806 .find(|charge| charge.delivery_seq() == marker_sequence)
807 .map(Some)
808 .ok_or(LiveLeaveError::RetainedCharge)
809}
810
811#[derive(Debug, PartialEq, Eq)]
813pub struct AttachFrontierCharges {
814 terminal: Option<RetainedRecordCharge>,
815 attached: RetainedRecordCharge,
816 seal: LiveTransitionInputSeal,
817}
818
819#[derive(Debug, PartialEq, Eq)]
820enum LiveTransitionInputSeal {
821 Validated,
822}
823
824impl AttachFrontierCharges {
825 #[must_use]
827 pub const fn new(
828 terminal: Option<RetainedRecordCharge>,
829 attached: RetainedRecordCharge,
830 ) -> Self {
831 Self {
832 terminal,
833 attached,
834 seal: LiveTransitionInputSeal::Validated,
835 }
836 }
837
838 const fn into_parts(self) -> (Option<RetainedRecordCharge>, RetainedRecordCharge) {
839 let Self {
840 terminal,
841 attached,
842 seal,
843 } = self;
844 match seal {
845 LiveTransitionInputSeal::Validated => (terminal, attached),
846 }
847 }
848}
849
850#[derive(Debug, PartialEq, Eq)]
852pub struct LiveFrontierCommit<T> {
853 operation: T,
854 owner: LiveFrontierOwner,
855}
856
857impl<T> LiveFrontierCommit<T> {
858 #[must_use]
860 pub const fn operation(&self) -> &T {
861 &self.operation
862 }
863
864 #[must_use]
866 pub const fn owner(&self) -> &LiveFrontierOwner {
867 &self.owner
868 }
869
870 #[must_use]
872 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
873 (self.operation, self.owner)
874 }
875}
876
877#[derive(Debug, PartialEq, Eq)]
879pub struct LiveFrontierFailure<T> {
880 error: LiveFrontierError,
881 operation: T,
882 owner: LiveFrontierOwner,
883}
884
885impl<T> LiveFrontierFailure<T> {
886 #[must_use]
888 pub const fn error(&self) -> LiveFrontierError {
889 self.error
890 }
891
892 #[must_use]
894 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
895 (self.operation, self.owner)
896 }
897}
898
899#[derive(Clone, Copy, Debug, PartialEq, Eq)]
901pub enum LiveFrontierError {
902 Authority,
904 Precedence,
906 RetainedCharge,
908 RetainedRecordLimit,
910 Frontier,
912 ClosureAccounting,
914}
915
916pub type LiveFrontierResult<T> = Result<LiveFrontierCommit<T>, Box<LiveFrontierFailure<T>>>;
918
919pub fn apply_enrollment_frontier<F>(
925 owner: LiveFrontierOwner,
926 operation: super::super::EnrollmentCommit<F>,
927 attached_charge: RetainedRecordCharge,
928) -> LiveFrontierResult<super::super::EnrollmentCommit<F>> {
929 let attached = operation.attached;
930 if attached.conversation_id() != owner.frontiers.conversation_id() {
931 return failure(owner, operation, LiveFrontierError::Authority);
932 }
933 let participant_id = attached.participant_id();
934 let mut active = owner.frontiers.active_identities().participants().to_vec();
935 if active
936 .iter()
937 .any(|participant| participant.participant_index() == participant_id)
938 {
939 return failure(owner, operation, LiveFrontierError::Authority);
940 }
941 active.push(FrontierParticipant::new(
942 participant_id,
943 operation.member.cursor(),
944 FrontierBinding::Bound(attached.binding_epoch()),
945 ));
946 active.sort_unstable_by_key(|participant| participant.participant_index());
947 let rows = [retained_attached(attached)];
948 let Some(sequence) =
949 enrollment_sequence(owner.frontiers.sequence().ledger(), attached.delivery_seq())
950 else {
951 return failure(owner, operation, LiveFrontierError::Frontier);
952 };
953 let Some(order) = enrollment_order(
954 owner.frontiers.order().ledger(),
955 attached.admission_order().transaction_order(),
956 ) else {
957 return failure(owner, operation, LiveFrontierError::Frontier);
958 };
959 transition(
960 owner,
961 operation,
962 active,
963 &rows,
964 vec![attached_charge],
965 sequence,
966 order,
967 )
968}
969
970pub fn apply_attach_frontier<F, V>(
977 owner: LiveFrontierOwner,
978 operation: AttachCommit<F, V>,
979 charges: AttachFrontierCharges,
980) -> LiveFrontierResult<AttachCommit<F, V>> {
981 let (terminal_charge, attached_charge) = charges.into_parts();
982 let attached = operation.attached;
983 if attached.conversation_id() != owner.frontiers.conversation_id() {
984 return failure(owner, operation, LiveFrontierError::Authority);
985 }
986 let mut active = owner.frontiers.active_identities().participants().to_vec();
987 let Some(participant) = active
988 .iter_mut()
989 .find(|participant| participant.participant_index() == attached.participant_id())
990 else {
991 return failure(owner, operation, LiveFrontierError::Authority);
992 };
993 *participant = FrontierParticipant::new(
994 participant.participant_index(),
995 operation.member.cursor(),
996 FrontierBinding::Bound(attached.binding_epoch()),
997 );
998 let current_sequence = owner.frontiers.sequence().ledger();
999 let current_order = owner.frontiers.order().ledger();
1000 let (rows, keyed_charges, sequence, order) = match operation.transition {
1001 AttachTransition::Detached => {
1002 if terminal_charge.is_some() {
1003 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1004 }
1005 let Some(sequence) =
1006 detached_attach_sequence(current_sequence, attached.delivery_seq())
1007 else {
1008 return failure(owner, operation, LiveFrontierError::Frontier);
1009 };
1010 let Some(order) = detached_attach_order(
1011 current_order,
1012 attached.admission_order().transaction_order(),
1013 ) else {
1014 return failure(owner, operation, LiveFrontierError::Frontier);
1015 };
1016 (
1017 vec![retained_attached(attached)],
1018 vec![attached_charge],
1019 sequence,
1020 order,
1021 )
1022 }
1023 AttachTransition::Superseded { terminal } => {
1024 let Some(terminal_charge) = terminal_charge else {
1025 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1026 };
1027 let rows = vec![
1028 retained_terminal(terminal.into()),
1029 retained_attached(attached),
1030 ];
1031 let Some(sequence) = superseding_attach_sequence(current_sequence, &rows) else {
1032 return failure(owner, operation, LiveFrontierError::Frontier);
1033 };
1034 let Some(order) = superseding_attach_order(
1035 current_order,
1036 attached.admission_order().transaction_order(),
1037 ) else {
1038 return failure(owner, operation, LiveFrontierError::Frontier);
1039 };
1040 (
1041 rows,
1042 vec![terminal_charge, attached_charge],
1043 sequence,
1044 order,
1045 )
1046 }
1047 AttachTransition::FencedRecovery {
1048 prior_binding_epoch,
1049 composed_terminal,
1050 next_closure_state,
1051 } => {
1052 return apply_fenced_attach_frontier(
1053 owner,
1054 operation,
1055 terminal_charge,
1056 attached_charge,
1057 prior_binding_epoch,
1058 composed_terminal,
1059 next_closure_state,
1060 );
1061 }
1062 };
1063 transition(
1064 owner,
1065 operation,
1066 active,
1067 &rows,
1068 keyed_charges,
1069 sequence,
1070 order,
1071 )
1072}
1073
1074pub fn apply_detach_frontier<EF, V>(
1080 owner: LiveFrontierOwner,
1081 operation: CommittedDetachTransition<EF, V>,
1082 terminal_charge: RetainedRecordCharge,
1083) -> LiveFrontierResult<CommittedDetachTransition<EF, V>> {
1084 let terminal = operation.terminal();
1085 if terminal.conversation_id() != owner.frontiers.conversation_id() {
1086 return failure(owner, operation, LiveFrontierError::Authority);
1087 }
1088 let mut active = owner.frontiers.active_identities().participants().to_vec();
1089 let Some(participant) = active
1090 .iter_mut()
1091 .find(|participant| participant.participant_index() == terminal.participant_id())
1092 else {
1093 return failure(owner, operation, LiveFrontierError::Authority);
1094 };
1095 *participant = FrontierParticipant::new(
1096 participant.participant_index(),
1097 operation.member().cursor(),
1098 FrontierBinding::Detached(terminal.binding_epoch()),
1099 );
1100 let row = retained_terminal(terminal.into());
1101 let Some(sequence) = detach_sequence(owner.frontiers.sequence().ledger(), row.delivery_seq)
1102 else {
1103 return failure(owner, operation, LiveFrontierError::Frontier);
1104 };
1105 let Some(order) = detach_order(
1106 owner.frontiers.order().ledger(),
1107 row.admission_order.transaction_order(),
1108 ) else {
1109 return failure(owner, operation, LiveFrontierError::Frontier);
1110 };
1111 transition(
1112 owner,
1113 operation,
1114 active,
1115 &[row],
1116 vec![terminal_charge],
1117 sequence,
1118 order,
1119 )
1120}
1121
1122pub fn apply_participant_ack_frontier(
1128 mut owner: LiveFrontierOwner,
1129 operation: ParticipantAckCommit,
1130) -> LiveFrontierResult<ParticipantAckCommit> {
1131 let request = operation.outcome().request();
1132 let Some(current) = owner
1133 .frontiers
1134 .active_identities()
1135 .participants()
1136 .iter()
1137 .find(|participant| participant.participant_index() == request.participant_id)
1138 .copied()
1139 else {
1140 return failure(owner, operation, LiveFrontierError::Authority);
1141 };
1142 let participant = FrontierParticipant::new(
1143 request.participant_id,
1144 request.through_seq,
1145 current.binding(),
1146 );
1147 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1148 Ok(frontiers) => frontiers,
1149 Err(frontier_failure) => {
1150 let (frontiers, error) = *frontier_failure;
1151 owner.frontiers = frontiers;
1152 return failure(owner, operation, map_frontier_error(error));
1153 }
1154 };
1155 Ok(LiveFrontierCommit { operation, owner })
1156}
1157
1158pub fn apply_nonzero_participant_ack_frontier(
1168 mut owner: LiveFrontierOwner,
1169 operation: NonzeroParticipantAckCommit,
1170) -> LiveFrontierResult<NonzeroParticipantAckCommit> {
1171 let request = operation.outcome().request();
1172 let Some(current) = owner
1173 .frontiers
1174 .active_identities()
1175 .participants()
1176 .iter()
1177 .find(|participant| participant.participant_index() == request.participant_id)
1178 .copied()
1179 else {
1180 return failure(owner, operation, LiveFrontierError::Authority);
1181 };
1182 let participant = FrontierParticipant::new(
1183 request.participant_id,
1184 request.through_seq,
1185 current.binding(),
1186 );
1187 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1188 Ok(frontiers) => frontiers,
1189 Err(frontier_failure) => {
1190 let (frontiers, error) = *frontier_failure;
1191 owner.frontiers = frontiers;
1192 return failure(owner, operation, map_frontier_error(error));
1193 }
1194 };
1195 Ok(LiveFrontierCommit { operation, owner })
1196}
1197
1198pub fn apply_marker_ack_frontier(
1204 mut owner: LiveFrontierOwner,
1205 operation: MarkerAckCommit,
1206) -> LiveFrontierResult<MarkerAckCommit> {
1207 let request = operation.outcome().request();
1208 if !owner
1209 .frontiers
1210 .retained_marker_records()
1211 .iter()
1212 .any(|record| {
1213 record.delivery_seq == request.marker_delivery_seq
1214 && matches!(
1215 record.kind,
1216 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
1217 if participant_index == request.participant_id
1218 )
1219 })
1220 {
1221 return failure(owner, operation, LiveFrontierError::Authority);
1222 }
1223 let Some(current) = owner
1224 .frontiers
1225 .active_identities()
1226 .participants()
1227 .iter()
1228 .find(|participant| participant.participant_index() == request.participant_id)
1229 .copied()
1230 else {
1231 return failure(owner, operation, LiveFrontierError::Authority);
1232 };
1233 let Some(accounting) = accounting_after_marker_ack(owner.closure_accounting) else {
1234 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1235 };
1236 let participant = FrontierParticipant::new(
1237 request.participant_id,
1238 request.marker_delivery_seq,
1239 current.binding(),
1240 );
1241 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1242 Ok(frontiers) => frontiers,
1243 Err(frontier_failure) => {
1244 let (frontiers, error) = *frontier_failure;
1245 owner.frontiers = frontiers;
1246 return failure(owner, operation, map_frontier_error(error));
1247 }
1248 };
1249 owner.closure_accounting = accounting;
1250 Ok(LiveFrontierCommit { operation, owner })
1251}
1252
1253fn apply_fenced_attach_frontier<F, V>(
1254 owner: LiveFrontierOwner,
1255 operation: AttachCommit<F, V>,
1256 terminal_charge: Option<RetainedRecordCharge>,
1257 attached_charge: RetainedRecordCharge,
1258 prior_binding_epoch: crate::wire::BindingEpoch,
1259 composed_terminal: Option<super::super::CommittedBindingTerminal>,
1260 next_closure_state: super::super::ClosureState,
1261) -> LiveFrontierResult<AttachCommit<F, V>> {
1262 let attached = operation.attached;
1263 let (rows, charges) = match (composed_terminal, terminal_charge) {
1264 (None, None) => (vec![retained_attached(attached)], vec![attached_charge]),
1265 (Some(terminal), Some(terminal_charge)) => (
1266 vec![retained_terminal(terminal), retained_attached(attached)],
1267 vec![terminal_charge, attached_charge],
1268 ),
1269 (None, Some(_)) | (Some(_), None) => {
1270 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1271 }
1272 };
1273 let participant = FrontierParticipant::new(
1274 attached.participant_id(),
1275 operation.member.cursor(),
1276 FrontierBinding::Bound(attached.binding_epoch()),
1277 );
1278 fenced_attach_transition(
1279 owner,
1280 operation,
1281 participant,
1282 prior_binding_epoch,
1283 next_closure_state,
1284 &rows,
1285 charges,
1286 )
1287}
1288
1289fn fenced_attach_transition<T>(
1290 mut owner: LiveFrontierOwner,
1291 operation: T,
1292 participant: FrontierParticipant,
1293 prior_binding_epoch: crate::wire::BindingEpoch,
1294 next_closure_state: super::super::ClosureState,
1295 rows: &[RetainedCausalRecord],
1296 charges: Vec<RetainedRecordCharge>,
1297) -> LiveFrontierResult<T> {
1298 if rows.len() != charges.len()
1299 || rows.iter().zip(&charges).any(|(row, charge)| {
1300 row.delivery_seq != charge.delivery_seq()
1301 || row.admission_order != charge.admission_order()
1302 || charge.encoded_charge().entries != 1
1303 })
1304 {
1305 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1306 }
1307 let resulting_len = owner
1308 .frontiers
1309 .retained_records()
1310 .len()
1311 .checked_add(rows.len());
1312 if resulting_len
1313 .and_then(|len| u64::try_from(len).ok())
1314 .is_none_or(|len| len > owner.retained_record_limit)
1315 {
1316 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1317 }
1318 let Some(accounting) =
1319 accounting_after_fenced_attach(owner.closure_accounting, &charges, next_closure_state)
1320 else {
1321 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1322 };
1323 owner.frontiers =
1324 match owner
1325 .frontiers
1326 .apply_live_fenced_attach(participant, prior_binding_epoch, rows)
1327 {
1328 Ok(frontiers) => frontiers,
1329 Err(frontier_failure) => {
1330 let (frontiers, error) = *frontier_failure;
1331 owner.frontiers = frontiers;
1332 return failure(owner, operation, map_frontier_error(error));
1333 }
1334 };
1335 owner.retained_charges.extend(charges);
1336 owner
1337 .retained_charges
1338 .sort_unstable_by_key(|charge| charge.delivery_seq());
1339 owner.closure_accounting = accounting;
1340 Ok(LiveFrontierCommit { operation, owner })
1341}
1342
1343fn transition<T>(
1344 mut owner: LiveFrontierOwner,
1345 operation: T,
1346 active: Vec<FrontierParticipant>,
1347 rows: &[RetainedCausalRecord],
1348 charges: Vec<RetainedRecordCharge>,
1349 sequence: SequenceLedger,
1350 order: OrderLedger,
1351) -> LiveFrontierResult<T> {
1352 if rows.len() != charges.len()
1353 || rows.iter().zip(&charges).any(|(row, charge)| {
1354 row.delivery_seq != charge.delivery_seq()
1355 || row.admission_order != charge.admission_order()
1356 || charge.encoded_charge().entries != 1
1357 })
1358 {
1359 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1360 }
1361 let resulting_len = owner
1362 .frontiers
1363 .retained_records()
1364 .len()
1365 .checked_add(rows.len());
1366 if resulting_len
1367 .and_then(|len| u64::try_from(len).ok())
1368 .is_none_or(|len| len > owner.retained_record_limit)
1369 {
1370 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1371 }
1372 let Some(accounting) = accounting_after_rows(owner.closure_accounting, &charges) else {
1373 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1374 };
1375 owner.frontiers = match owner
1376 .frontiers
1377 .apply_live_transition(active, rows, sequence, order)
1378 {
1379 Ok(frontiers) => frontiers,
1380 Err(frontier_failure) => {
1381 let (frontiers, error) = *frontier_failure;
1382 owner.frontiers = frontiers;
1383 return failure(owner, operation, map_frontier_error(error));
1384 }
1385 };
1386 owner.retained_charges.extend(charges);
1387 owner.closure_accounting = accounting;
1388 Ok(LiveFrontierCommit { operation, owner })
1389}
1390
1391const fn map_frontier_error(error: LiveFrontierTransitionError) -> LiveFrontierError {
1392 match error {
1393 LiveFrontierTransitionError::Authority => LiveFrontierError::Authority,
1394 LiveFrontierTransitionError::Precedence => LiveFrontierError::Precedence,
1395 LiveFrontierTransitionError::RecordPosition
1396 | LiveFrontierTransitionError::Exhausted
1397 | LiveFrontierTransitionError::ResultingFrontier => LiveFrontierError::Frontier,
1398 }
1399}
1400
1401fn failure<T, U>(
1402 owner: LiveFrontierOwner,
1403 operation: T,
1404 error: LiveFrontierError,
1405) -> Result<U, Box<LiveFrontierFailure<T>>> {
1406 Err(Box::new(LiveFrontierFailure {
1407 error,
1408 operation,
1409 owner,
1410 }))
1411}