1use alloc::{boxed::Box, vec, vec::Vec};
8
9use crate::{algebra::ResourceVector, wire::RecordAdmission};
10
11use super::super::{
12 AttachCommit, AttachTransition, BindingState, ClaimFrontiers, ClosureAccounting,
13 CommittedDetachTransition, DetachCell, FrontierBinding, FrontierParticipant, IdentityState,
14 InitialEnrollmentFrontierCommit, LeaveCommitError, LeaveCommitParameters, LiveMember,
15 MarkerAckCommit, NonzeroParticipantAckCommit, ObserverProgressProjection, OrderLedger,
16 ParticipantAckCommit, PendingFinalization, PendingLeaveCommitParameters,
17 PrepareLeaveAuthorityError, RetainedCausalRecord, RetainedCausalRecordKind, SequenceLedger,
18 StoredEdge, VerifiedLeaveRequest, claim_frontier::LiveFrontierTransitionError, commit_leave,
19 commit_pending_leave,
20};
21use super::{
22 InitialEnrollmentOperationCommit, MarkerDeliveryProjection, MarkerDrainCommit,
23 RecordAdmissionPersistenceParts, RetainedRecordCharge, UnchangedRecordAdmission,
24};
25
26mod ledger;
27mod state;
28use ledger::{
29 detach_order, detach_sequence, detached_attach_order, detached_attach_sequence,
30 enrollment_order, enrollment_sequence, superseding_attach_order, superseding_attach_sequence,
31};
32use state::{
33 accounting_after_fenced_attach, accounting_after_leave, accounting_after_marker_ack,
34 accounting_after_rows, retained_attached, retained_terminal,
35};
36
37#[derive(Debug, PartialEq, Eq)]
62pub struct LiveFrontierOwner {
63 frontiers: ClaimFrontiers,
64 closure_accounting: ClosureAccounting,
65 retained_charges: Vec<RetainedRecordCharge>,
66 retained_record_limit: u64,
67}
68
69impl LiveFrontierOwner {
70 #[must_use]
72 pub fn from_initial_enrollment<F>(
73 initial: InitialEnrollmentFrontierCommit<F>,
74 retained_record_limit: u64,
75 ) -> (InitialEnrollmentOperationCommit<F>, Self) {
76 let (operation, frontiers, closure_accounting, attached_charge) =
77 initial.into_conversation_parts();
78 let attached = operation.enrollment().attached;
79 let retained_charges = vec![RetainedRecordCharge::new(
80 attached.delivery_seq(),
81 attached.admission_order(),
82 attached_charge,
83 )];
84 (
85 operation,
86 Self {
87 frontiers,
88 closure_accounting,
89 retained_charges,
90 retained_record_limit,
91 },
92 )
93 }
94
95 #[cfg(test)]
96 pub(in crate::lifecycle) const fn from_test_parts(
97 frontiers: ClaimFrontiers,
98 closure_accounting: ClosureAccounting,
99 retained_charges: Vec<RetainedRecordCharge>,
100 retained_record_limit: u64,
101 ) -> Self {
102 Self {
103 frontiers,
104 closure_accounting,
105 retained_charges,
106 retained_record_limit,
107 }
108 }
109
110 #[must_use]
112 pub const fn frontiers(&self) -> &ClaimFrontiers {
113 &self.frontiers
114 }
115
116 #[must_use]
118 pub const fn closure_accounting(&self) -> ClosureAccounting {
119 self.closure_accounting
120 }
121
122 #[must_use]
124 pub fn retained_charges(&self) -> &[RetainedRecordCharge] {
125 &self.retained_charges
126 }
127
128 #[must_use]
130 pub const fn retained_record_limit(&self) -> u64 {
131 self.retained_record_limit
132 }
133
134 #[must_use]
136 pub fn into_parts(
137 self,
138 ) -> (
139 ClaimFrontiers,
140 ClosureAccounting,
141 Vec<RetainedRecordCharge>,
142 u64,
143 ) {
144 (
145 self.frontiers,
146 self.closure_accounting,
147 self.retained_charges,
148 self.retained_record_limit,
149 )
150 }
151
152 #[must_use]
155 pub fn from_unchanged_record_admission<EF, V, LF>(
156 unchanged: UnchangedRecordAdmission<'_, EF, V, LF>,
157 retained_record_limit: u64,
158 ) -> (Self, RecordAdmission, ResourceVector) {
159 let (prestate, encoded_record_charge) = unchanged.into_parts();
160 let (request, frontiers, closure_accounting, retained_charges) =
161 prestate.into_live_owner_parts();
162 (
163 Self {
164 frontiers,
165 closure_accounting,
166 retained_charges,
167 retained_record_limit,
168 },
169 request,
170 encoded_record_charge,
171 )
172 }
173
174 #[must_use]
177 pub fn from_record_admission_persistence(
178 persistence: RecordAdmissionPersistenceParts,
179 retained_record_limit: u64,
180 ) -> Self {
181 Self {
182 frontiers: persistence.frontiers,
183 closure_accounting: persistence.accounting,
184 retained_charges: persistence.retained_charges,
185 retained_record_limit,
186 }
187 }
188
189 #[must_use]
191 pub fn from_marker_drain(
192 commit: MarkerDrainCommit,
193 retained_record_limit: u64,
194 ) -> (Self, StoredEdge, MarkerDeliveryProjection) {
195 let (frontiers, closure_accounting, retained_charges, successor, projection) =
196 commit.into_parts();
197 (
198 Self {
199 frontiers,
200 closure_accounting,
201 retained_charges,
202 retained_record_limit,
203 },
204 successor,
205 projection,
206 )
207 }
208}
209
210#[derive(Debug, PartialEq, Eq)]
212pub struct LiveLeaveCommit<EF, V, LF> {
213 identity: IdentityState<EF, V, LF>,
214 owner: LiveFrontierOwner,
215}
216
217impl<EF, V, LF> LiveLeaveCommit<EF, V, LF> {
218 #[must_use]
220 pub const fn observer_progress_projection(&self) -> Option<ObserverProgressProjection> {
221 let IdentityState::Retired(retired) = &self.identity else {
222 return None;
223 };
224 let committed = retired.committed_result();
225 Some(ObserverProgressProjection::new(
226 committed.conversation_id(),
227 committed.left_delivery_seq(),
228 ))
229 }
230
231 #[must_use]
233 pub fn into_parts(self) -> (IdentityState<EF, V, LF>, LiveFrontierOwner) {
234 (self.identity, self.owner)
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
240pub enum LiveLeaveError {
241 Prepare(PrepareLeaveAuthorityError),
243 Commit(LeaveCommitError),
245 RetainedCharge,
247 RetainedRecordLimit,
249 ClosureAccounting,
251 Identity,
253}
254
255pub fn commit_settled_leave_frontier<EF, V, LF, D>(
263 owner: LiveFrontierOwner,
264 member: LiveMember<EF>,
265 binding: BindingState,
266 detach_cell: DetachCell<D>,
267 verified: VerifiedLeaveRequest<V, LF>,
268 left_delivery_seq: u64,
269 left_charge: RetainedRecordCharge,
270) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
271 let LiveFrontierOwner {
272 frontiers,
273 closure_accounting,
274 mut retained_charges,
275 retained_record_limit,
276 } = owner;
277 let retired_marker_charge =
278 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
279 let authority = frontiers
280 .prepare_settled_leave_authority(&member, binding)
281 .map_err(LiveLeaveError::Prepare)?;
282 let commit = commit_leave(
283 member,
284 binding,
285 detach_cell,
286 verified,
287 authority,
288 LeaveCommitParameters { left_delivery_seq },
289 )
290 .map_err(LiveLeaveError::Commit)?;
291 let (identity, frontiers) = commit.into_parts();
292 let IdentityState::Retired(retired) = &identity else {
293 return Err(LiveLeaveError::Identity);
294 };
295 if left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
296 || left_charge.admission_order() != retired.left_admission_order()
297 || left_charge.encoded_charge().entries != 1
298 {
299 return Err(LiveLeaveError::RetainedCharge);
300 }
301 retained_charges.push(left_charge);
302 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
303 let retained_len = u64::try_from(frontiers.retained_records().len())
304 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
305 if retained_len > retained_record_limit
306 || retained_charges.len() != frontiers.retained_records().len()
307 {
308 return Err(LiveLeaveError::RetainedRecordLimit);
309 }
310 let closure_accounting =
311 accounting_after_leave(closure_accounting, &[left_charge], retired_marker_charge)
312 .ok_or(LiveLeaveError::ClosureAccounting)?;
313 Ok(LiveLeaveCommit {
314 identity,
315 owner: LiveFrontierOwner {
316 frontiers,
317 closure_accounting,
318 retained_charges,
319 retained_record_limit,
320 },
321 })
322}
323
324pub fn commit_pending_leave_frontier<EF, V, LF, D>(
333 owner: LiveFrontierOwner,
334 member: LiveMember<EF>,
335 pending: PendingFinalization,
336 detach_cell: DetachCell<D>,
337 verified: VerifiedLeaveRequest<V, LF>,
338 parameters: PendingLeaveCommitParameters,
339 charges: [RetainedRecordCharge; 2],
340) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
341 let [terminal_charge, left_charge] = charges;
342 let terminal_delivery_seq = parameters.terminal_delivery_seq;
343 let LiveFrontierOwner {
344 frontiers,
345 closure_accounting,
346 mut retained_charges,
347 retained_record_limit,
348 } = owner;
349 let retired_marker_charge =
350 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
351 let authority = frontiers
352 .prepare_pending_leave_authority(&member, pending)
353 .map_err(LiveLeaveError::Prepare)?;
354 let commit = commit_pending_leave(
355 member,
356 pending,
357 detach_cell,
358 verified,
359 authority,
360 parameters,
361 )
362 .map_err(LiveLeaveError::Commit)?;
363 let (identity, frontiers) = commit.into_parts();
364 let IdentityState::Retired(retired) = &identity else {
365 return Err(LiveLeaveError::Identity);
366 };
367 if retired.committed_result().prior_terminal_delivery_seq() != Some(terminal_delivery_seq)
368 || terminal_charge.delivery_seq() != terminal_delivery_seq
369 || terminal_charge.admission_order() != pending.admission_order()
370 || terminal_charge.encoded_charge().entries != 1
371 || left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
372 || left_charge.admission_order() != retired.left_admission_order()
373 || left_charge.encoded_charge().entries != 1
374 {
375 return Err(LiveLeaveError::RetainedCharge);
376 }
377 retained_charges.extend([terminal_charge, left_charge]);
378 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
379 let retained_len = u64::try_from(frontiers.retained_records().len())
380 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
381 if retained_len > retained_record_limit
382 || retained_charges.len() != frontiers.retained_records().len()
383 {
384 return Err(LiveLeaveError::RetainedRecordLimit);
385 }
386 let closure_accounting = accounting_after_leave(
387 closure_accounting,
388 &[terminal_charge, left_charge],
389 retired_marker_charge,
390 )
391 .ok_or(LiveLeaveError::ClosureAccounting)?;
392 Ok(LiveLeaveCommit {
393 identity,
394 owner: LiveFrontierOwner {
395 frontiers,
396 closure_accounting,
397 retained_charges,
398 retained_record_limit,
399 },
400 })
401}
402
403fn retired_marker_charge(
404 frontiers: &ClaimFrontiers,
405 retained_charges: &[RetainedRecordCharge],
406 participant_id: crate::wire::ParticipantId,
407) -> Result<Option<RetainedRecordCharge>, LiveLeaveError> {
408 let marker_sequence = frontiers
409 .retained_marker_records()
410 .iter()
411 .find_map(|record| {
412 matches!(
413 record.kind,
414 RetainedCausalRecordKind::CompactionMarker {
415 participant_index,
416 ..
417 } if participant_index == participant_id
418 )
419 .then_some(record.delivery_seq)
420 });
421 let Some(marker_sequence) = marker_sequence else {
422 return Ok(None);
423 };
424 retained_charges
425 .iter()
426 .copied()
427 .find(|charge| charge.delivery_seq() == marker_sequence)
428 .map(Some)
429 .ok_or(LiveLeaveError::RetainedCharge)
430}
431
432#[derive(Debug, PartialEq, Eq)]
434pub struct AttachFrontierCharges {
435 terminal: Option<RetainedRecordCharge>,
436 attached: RetainedRecordCharge,
437 seal: LiveTransitionInputSeal,
438}
439
440#[derive(Debug, PartialEq, Eq)]
441enum LiveTransitionInputSeal {
442 Validated,
443}
444
445impl AttachFrontierCharges {
446 #[must_use]
448 pub const fn new(
449 terminal: Option<RetainedRecordCharge>,
450 attached: RetainedRecordCharge,
451 ) -> Self {
452 Self {
453 terminal,
454 attached,
455 seal: LiveTransitionInputSeal::Validated,
456 }
457 }
458
459 const fn into_parts(self) -> (Option<RetainedRecordCharge>, RetainedRecordCharge) {
460 let Self {
461 terminal,
462 attached,
463 seal,
464 } = self;
465 match seal {
466 LiveTransitionInputSeal::Validated => (terminal, attached),
467 }
468 }
469}
470
471#[derive(Debug, PartialEq, Eq)]
473pub struct LiveFrontierCommit<T> {
474 operation: T,
475 owner: LiveFrontierOwner,
476}
477
478impl<T> LiveFrontierCommit<T> {
479 #[must_use]
481 pub const fn operation(&self) -> &T {
482 &self.operation
483 }
484
485 #[must_use]
487 pub const fn owner(&self) -> &LiveFrontierOwner {
488 &self.owner
489 }
490
491 #[must_use]
493 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
494 (self.operation, self.owner)
495 }
496}
497
498#[derive(Debug, PartialEq, Eq)]
500pub struct LiveFrontierFailure<T> {
501 error: LiveFrontierError,
502 operation: T,
503 owner: LiveFrontierOwner,
504}
505
506impl<T> LiveFrontierFailure<T> {
507 #[must_use]
509 pub const fn error(&self) -> LiveFrontierError {
510 self.error
511 }
512
513 #[must_use]
515 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
516 (self.operation, self.owner)
517 }
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
522pub enum LiveFrontierError {
523 Authority,
525 Precedence,
527 RetainedCharge,
529 RetainedRecordLimit,
531 Frontier,
533 ClosureAccounting,
535}
536
537pub type LiveFrontierResult<T> = Result<LiveFrontierCommit<T>, Box<LiveFrontierFailure<T>>>;
539
540pub fn apply_enrollment_frontier<F>(
546 owner: LiveFrontierOwner,
547 operation: super::super::EnrollmentCommit<F>,
548 attached_charge: RetainedRecordCharge,
549) -> LiveFrontierResult<super::super::EnrollmentCommit<F>> {
550 let attached = operation.attached;
551 if attached.conversation_id() != owner.frontiers.conversation_id() {
552 return failure(owner, operation, LiveFrontierError::Authority);
553 }
554 let participant_id = attached.participant_id();
555 let mut active = owner.frontiers.active_identities().participants().to_vec();
556 if active
557 .iter()
558 .any(|participant| participant.participant_index() == participant_id)
559 {
560 return failure(owner, operation, LiveFrontierError::Authority);
561 }
562 active.push(FrontierParticipant::new(
563 participant_id,
564 operation.member.cursor(),
565 FrontierBinding::Bound(attached.binding_epoch()),
566 ));
567 active.sort_unstable_by_key(|participant| participant.participant_index());
568 let rows = [retained_attached(attached)];
569 let Some(sequence) =
570 enrollment_sequence(owner.frontiers.sequence().ledger(), attached.delivery_seq())
571 else {
572 return failure(owner, operation, LiveFrontierError::Frontier);
573 };
574 let Some(order) = enrollment_order(
575 owner.frontiers.order().ledger(),
576 attached.admission_order().transaction_order(),
577 ) else {
578 return failure(owner, operation, LiveFrontierError::Frontier);
579 };
580 transition(
581 owner,
582 operation,
583 active,
584 &rows,
585 vec![attached_charge],
586 sequence,
587 order,
588 )
589}
590
591pub fn apply_attach_frontier<F, V>(
598 owner: LiveFrontierOwner,
599 operation: AttachCommit<F, V>,
600 charges: AttachFrontierCharges,
601) -> LiveFrontierResult<AttachCommit<F, V>> {
602 let (terminal_charge, attached_charge) = charges.into_parts();
603 let attached = operation.attached;
604 if attached.conversation_id() != owner.frontiers.conversation_id() {
605 return failure(owner, operation, LiveFrontierError::Authority);
606 }
607 let mut active = owner.frontiers.active_identities().participants().to_vec();
608 let Some(participant) = active
609 .iter_mut()
610 .find(|participant| participant.participant_index() == attached.participant_id())
611 else {
612 return failure(owner, operation, LiveFrontierError::Authority);
613 };
614 *participant = FrontierParticipant::new(
615 participant.participant_index(),
616 operation.member.cursor(),
617 FrontierBinding::Bound(attached.binding_epoch()),
618 );
619 let current_sequence = owner.frontiers.sequence().ledger();
620 let current_order = owner.frontiers.order().ledger();
621 let (rows, keyed_charges, sequence, order) = match operation.transition {
622 AttachTransition::Detached => {
623 if terminal_charge.is_some() {
624 return failure(owner, operation, LiveFrontierError::RetainedCharge);
625 }
626 let Some(sequence) =
627 detached_attach_sequence(current_sequence, attached.delivery_seq())
628 else {
629 return failure(owner, operation, LiveFrontierError::Frontier);
630 };
631 let Some(order) = detached_attach_order(
632 current_order,
633 attached.admission_order().transaction_order(),
634 ) else {
635 return failure(owner, operation, LiveFrontierError::Frontier);
636 };
637 (
638 vec![retained_attached(attached)],
639 vec![attached_charge],
640 sequence,
641 order,
642 )
643 }
644 AttachTransition::Superseded { terminal } => {
645 let Some(terminal_charge) = terminal_charge else {
646 return failure(owner, operation, LiveFrontierError::RetainedCharge);
647 };
648 let rows = vec![
649 retained_terminal(terminal.into()),
650 retained_attached(attached),
651 ];
652 let Some(sequence) = superseding_attach_sequence(current_sequence, &rows) else {
653 return failure(owner, operation, LiveFrontierError::Frontier);
654 };
655 let Some(order) = superseding_attach_order(
656 current_order,
657 attached.admission_order().transaction_order(),
658 ) else {
659 return failure(owner, operation, LiveFrontierError::Frontier);
660 };
661 (
662 rows,
663 vec![terminal_charge, attached_charge],
664 sequence,
665 order,
666 )
667 }
668 AttachTransition::FencedRecovery {
669 prior_binding_epoch,
670 composed_terminal,
671 next_closure_state,
672 } => {
673 return apply_fenced_attach_frontier(
674 owner,
675 operation,
676 terminal_charge,
677 attached_charge,
678 prior_binding_epoch,
679 composed_terminal,
680 next_closure_state,
681 );
682 }
683 };
684 transition(
685 owner,
686 operation,
687 active,
688 &rows,
689 keyed_charges,
690 sequence,
691 order,
692 )
693}
694
695pub fn apply_detach_frontier<EF, V>(
701 owner: LiveFrontierOwner,
702 operation: CommittedDetachTransition<EF, V>,
703 terminal_charge: RetainedRecordCharge,
704) -> LiveFrontierResult<CommittedDetachTransition<EF, V>> {
705 let terminal = operation.terminal();
706 if terminal.conversation_id() != owner.frontiers.conversation_id() {
707 return failure(owner, operation, LiveFrontierError::Authority);
708 }
709 let mut active = owner.frontiers.active_identities().participants().to_vec();
710 let Some(participant) = active
711 .iter_mut()
712 .find(|participant| participant.participant_index() == terminal.participant_id())
713 else {
714 return failure(owner, operation, LiveFrontierError::Authority);
715 };
716 *participant = FrontierParticipant::new(
717 participant.participant_index(),
718 operation.member().cursor(),
719 FrontierBinding::Detached(terminal.binding_epoch()),
720 );
721 let row = retained_terminal(terminal.into());
722 let Some(sequence) = detach_sequence(owner.frontiers.sequence().ledger(), row.delivery_seq)
723 else {
724 return failure(owner, operation, LiveFrontierError::Frontier);
725 };
726 let Some(order) = detach_order(
727 owner.frontiers.order().ledger(),
728 row.admission_order.transaction_order(),
729 ) else {
730 return failure(owner, operation, LiveFrontierError::Frontier);
731 };
732 transition(
733 owner,
734 operation,
735 active,
736 &[row],
737 vec![terminal_charge],
738 sequence,
739 order,
740 )
741}
742
743pub fn apply_participant_ack_frontier(
749 mut owner: LiveFrontierOwner,
750 operation: ParticipantAckCommit,
751) -> LiveFrontierResult<ParticipantAckCommit> {
752 let request = operation.outcome().request();
753 let Some(current) = owner
754 .frontiers
755 .active_identities()
756 .participants()
757 .iter()
758 .find(|participant| participant.participant_index() == request.participant_id)
759 .copied()
760 else {
761 return failure(owner, operation, LiveFrontierError::Authority);
762 };
763 let participant = FrontierParticipant::new(
764 request.participant_id,
765 request.through_seq,
766 current.binding(),
767 );
768 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
769 Ok(frontiers) => frontiers,
770 Err(frontier_failure) => {
771 let (frontiers, error) = *frontier_failure;
772 owner.frontiers = frontiers;
773 return failure(owner, operation, map_frontier_error(error));
774 }
775 };
776 Ok(LiveFrontierCommit { operation, owner })
777}
778
779pub fn apply_nonzero_participant_ack_frontier(
789 mut owner: LiveFrontierOwner,
790 operation: NonzeroParticipantAckCommit,
791) -> LiveFrontierResult<NonzeroParticipantAckCommit> {
792 let request = operation.outcome().request();
793 let Some(current) = owner
794 .frontiers
795 .active_identities()
796 .participants()
797 .iter()
798 .find(|participant| participant.participant_index() == request.participant_id)
799 .copied()
800 else {
801 return failure(owner, operation, LiveFrontierError::Authority);
802 };
803 let participant = FrontierParticipant::new(
804 request.participant_id,
805 request.through_seq,
806 current.binding(),
807 );
808 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
809 Ok(frontiers) => frontiers,
810 Err(frontier_failure) => {
811 let (frontiers, error) = *frontier_failure;
812 owner.frontiers = frontiers;
813 return failure(owner, operation, map_frontier_error(error));
814 }
815 };
816 Ok(LiveFrontierCommit { operation, owner })
817}
818
819pub fn apply_marker_ack_frontier(
825 mut owner: LiveFrontierOwner,
826 operation: MarkerAckCommit,
827) -> LiveFrontierResult<MarkerAckCommit> {
828 let request = operation.outcome().request();
829 if !owner
830 .frontiers
831 .retained_marker_records()
832 .iter()
833 .any(|record| {
834 record.delivery_seq == request.marker_delivery_seq
835 && matches!(
836 record.kind,
837 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
838 if participant_index == request.participant_id
839 )
840 })
841 {
842 return failure(owner, operation, LiveFrontierError::Authority);
843 }
844 let Some(current) = owner
845 .frontiers
846 .active_identities()
847 .participants()
848 .iter()
849 .find(|participant| participant.participant_index() == request.participant_id)
850 .copied()
851 else {
852 return failure(owner, operation, LiveFrontierError::Authority);
853 };
854 let Some(accounting) = accounting_after_marker_ack(owner.closure_accounting) else {
855 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
856 };
857 let participant = FrontierParticipant::new(
858 request.participant_id,
859 request.marker_delivery_seq,
860 current.binding(),
861 );
862 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
863 Ok(frontiers) => frontiers,
864 Err(frontier_failure) => {
865 let (frontiers, error) = *frontier_failure;
866 owner.frontiers = frontiers;
867 return failure(owner, operation, map_frontier_error(error));
868 }
869 };
870 owner.closure_accounting = accounting;
871 Ok(LiveFrontierCommit { operation, owner })
872}
873
874fn apply_fenced_attach_frontier<F, V>(
875 owner: LiveFrontierOwner,
876 operation: AttachCommit<F, V>,
877 terminal_charge: Option<RetainedRecordCharge>,
878 attached_charge: RetainedRecordCharge,
879 prior_binding_epoch: crate::wire::BindingEpoch,
880 composed_terminal: Option<super::super::CommittedBindingTerminal>,
881 next_closure_state: super::super::ClosureState,
882) -> LiveFrontierResult<AttachCommit<F, V>> {
883 let attached = operation.attached;
884 let (rows, charges) = match (composed_terminal, terminal_charge) {
885 (None, None) => (vec![retained_attached(attached)], vec![attached_charge]),
886 (Some(terminal), Some(terminal_charge)) => (
887 vec![retained_terminal(terminal), retained_attached(attached)],
888 vec![terminal_charge, attached_charge],
889 ),
890 (None, Some(_)) | (Some(_), None) => {
891 return failure(owner, operation, LiveFrontierError::RetainedCharge);
892 }
893 };
894 let participant = FrontierParticipant::new(
895 attached.participant_id(),
896 operation.member.cursor(),
897 FrontierBinding::Bound(attached.binding_epoch()),
898 );
899 fenced_attach_transition(
900 owner,
901 operation,
902 participant,
903 prior_binding_epoch,
904 next_closure_state,
905 &rows,
906 charges,
907 )
908}
909
910fn fenced_attach_transition<T>(
911 mut owner: LiveFrontierOwner,
912 operation: T,
913 participant: FrontierParticipant,
914 prior_binding_epoch: crate::wire::BindingEpoch,
915 next_closure_state: super::super::ClosureState,
916 rows: &[RetainedCausalRecord],
917 charges: Vec<RetainedRecordCharge>,
918) -> LiveFrontierResult<T> {
919 if rows.len() != charges.len()
920 || rows.iter().zip(&charges).any(|(row, charge)| {
921 row.delivery_seq != charge.delivery_seq()
922 || row.admission_order != charge.admission_order()
923 || charge.encoded_charge().entries != 1
924 })
925 {
926 return failure(owner, operation, LiveFrontierError::RetainedCharge);
927 }
928 let resulting_len = owner
929 .frontiers
930 .retained_records()
931 .len()
932 .checked_add(rows.len());
933 if resulting_len
934 .and_then(|len| u64::try_from(len).ok())
935 .is_none_or(|len| len > owner.retained_record_limit)
936 {
937 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
938 }
939 let Some(accounting) =
940 accounting_after_fenced_attach(owner.closure_accounting, &charges, next_closure_state)
941 else {
942 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
943 };
944 owner.frontiers =
945 match owner
946 .frontiers
947 .apply_live_fenced_attach(participant, prior_binding_epoch, rows)
948 {
949 Ok(frontiers) => frontiers,
950 Err(frontier_failure) => {
951 let (frontiers, error) = *frontier_failure;
952 owner.frontiers = frontiers;
953 return failure(owner, operation, map_frontier_error(error));
954 }
955 };
956 owner.retained_charges.extend(charges);
957 owner
958 .retained_charges
959 .sort_unstable_by_key(|charge| charge.delivery_seq());
960 owner.closure_accounting = accounting;
961 Ok(LiveFrontierCommit { operation, owner })
962}
963
964fn transition<T>(
965 mut owner: LiveFrontierOwner,
966 operation: T,
967 active: Vec<FrontierParticipant>,
968 rows: &[RetainedCausalRecord],
969 charges: Vec<RetainedRecordCharge>,
970 sequence: SequenceLedger,
971 order: OrderLedger,
972) -> LiveFrontierResult<T> {
973 if rows.len() != charges.len()
974 || rows.iter().zip(&charges).any(|(row, charge)| {
975 row.delivery_seq != charge.delivery_seq()
976 || row.admission_order != charge.admission_order()
977 || charge.encoded_charge().entries != 1
978 })
979 {
980 return failure(owner, operation, LiveFrontierError::RetainedCharge);
981 }
982 let resulting_len = owner
983 .frontiers
984 .retained_records()
985 .len()
986 .checked_add(rows.len());
987 if resulting_len
988 .and_then(|len| u64::try_from(len).ok())
989 .is_none_or(|len| len > owner.retained_record_limit)
990 {
991 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
992 }
993 let Some(accounting) = accounting_after_rows(owner.closure_accounting, &charges) else {
994 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
995 };
996 owner.frontiers = match owner
997 .frontiers
998 .apply_live_transition(active, rows, sequence, order)
999 {
1000 Ok(frontiers) => frontiers,
1001 Err(frontier_failure) => {
1002 let (frontiers, error) = *frontier_failure;
1003 owner.frontiers = frontiers;
1004 return failure(owner, operation, map_frontier_error(error));
1005 }
1006 };
1007 owner.retained_charges.extend(charges);
1008 owner.closure_accounting = accounting;
1009 Ok(LiveFrontierCommit { operation, owner })
1010}
1011
1012const fn map_frontier_error(error: LiveFrontierTransitionError) -> LiveFrontierError {
1013 match error {
1014 LiveFrontierTransitionError::Authority => LiveFrontierError::Authority,
1015 LiveFrontierTransitionError::Precedence => LiveFrontierError::Precedence,
1016 LiveFrontierTransitionError::RecordPosition
1017 | LiveFrontierTransitionError::Exhausted
1018 | LiveFrontierTransitionError::ResultingFrontier => LiveFrontierError::Frontier,
1019 }
1020}
1021
1022fn failure<T, U>(
1023 owner: LiveFrontierOwner,
1024 operation: T,
1025 error: LiveFrontierError,
1026) -> Result<U, Box<LiveFrontierFailure<T>>> {
1027 Err(Box::new(LiveFrontierFailure {
1028 error,
1029 operation,
1030 owner,
1031 }))
1032}