1use alloc::{boxed::Box, vec, vec::Vec};
8use core::cmp::Ordering;
9
10use crate::{algebra::ResourceVector, wire::RecordAdmission};
11
12use super::super::{
13 AttachCommit, AttachTransition, BindingState, ClaimFrontiers, ClosureAccounting, ClosureDebt,
14 CommittedDetachTransition, DebtCompletion, DetachCell, DetachedCredentialRecovery, Event,
15 FencedAttachCommit, FrontierBinding, FrontierParticipant, IdentityState,
16 InitialEnrollmentFrontierCommit, LeaveCommitError, LeaveCommitParameters, LiveMember,
17 MarkerAckCommit, NonzeroParticipantAckCommit, ObserverProgressProjection, OrderLedger,
18 ParticipantAckCommit, PendingFinalization, PendingLeaveCommitParameters,
19 PrepareLeaveAuthorityError, RetainedCausalRecord, RetainedCausalRecordKind, SequenceLedger,
20 StoredEdge, VerifiedLeaveRequest,
21 claim_frontier::{
22 BindingTerminalOwner, FencedMarkerSourceRecord, LiveFrontierTransitionError,
23 PrecedenceCondition,
24 },
25 commit_leave, commit_pending_leave,
26};
27use super::{
28 InitialEnrollmentOperationCommit, MarkerDeliveryProjection, MarkerDrainCommit,
29 RecordAdmissionPersistenceParts, RetainedRecordCharge, UnchangedRecordAdmission,
30};
31
32mod binding_fate_transition;
33mod ledger;
34mod state;
35pub(super) use binding_fate_transition::BindingFateOwnerPlan;
36use ledger::{
37 detach_order, detach_sequence, detached_attach_order, detached_attach_sequence,
38 enrollment_order, enrollment_sequence, superseding_attach_order, superseding_attach_sequence,
39};
40use state::{
41 accounting_after_fenced_attach, accounting_after_leave, accounting_after_marker_ack,
42 accounting_after_marker_crossings, accounting_after_marker_remint, accounting_after_rows,
43 retained_attached, retained_terminal,
44};
45
46#[derive(Debug, PartialEq, Eq)]
71pub struct LiveFrontierOwner {
72 frontiers: ClaimFrontiers,
73 closure_accounting: ClosureAccounting,
74 retained_charges: Vec<RetainedRecordCharge>,
75 retained_record_limit: u64,
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub enum MarkerAnchorReconcile {
93 InStep,
95 RetiredOrphans(u64),
98 RemintedDeficit(u64),
101 Refused {
104 census: u64,
106 stored: u64,
108 },
109}
110
111impl MarkerAnchorReconcile {
112 #[must_use]
114 pub const fn reconciled(self) -> u64 {
115 match self {
116 Self::InStep | Self::Refused { .. } => 0,
117 Self::RetiredOrphans(count) | Self::RemintedDeficit(count) => count,
118 }
119 }
120}
121
122impl LiveFrontierOwner {
123 #[must_use]
125 pub fn from_initial_enrollment<F>(
126 initial: InitialEnrollmentFrontierCommit<F>,
127 retained_record_limit: u64,
128 ) -> (InitialEnrollmentOperationCommit<F>, Self) {
129 let (operation, frontiers, closure_accounting, attached_charge) =
130 initial.into_conversation_parts();
131 let attached = operation.enrollment().attached;
132 let retained_charges = vec![RetainedRecordCharge::new(
133 attached.delivery_seq(),
134 attached.admission_order(),
135 attached_charge,
136 )];
137 (
138 operation,
139 Self {
140 frontiers,
141 closure_accounting,
142 retained_charges,
143 retained_record_limit,
144 },
145 )
146 }
147
148 #[cfg(any(test, feature = "test-support"))]
159 #[must_use]
160 pub const fn from_test_parts(
161 frontiers: ClaimFrontiers,
162 closure_accounting: ClosureAccounting,
163 retained_charges: Vec<RetainedRecordCharge>,
164 retained_record_limit: u64,
165 ) -> Self {
166 Self {
167 frontiers,
168 closure_accounting,
169 retained_charges,
170 retained_record_limit,
171 }
172 }
173
174 #[cfg(any(test, feature = "test-support"))]
182 pub fn with_pending_finalizer_test_capacity(
183 mut self,
184 finalizer_rows: u64,
185 finalizer_charge: ResourceVector,
186 ) -> Result<Self, &'static str> {
187 let retained_count = u64::try_from(self.retained_charges.len())
188 .map_err(|_| "retained record count exceeds u64")?;
189 self.retained_record_limit = retained_count
190 .checked_add(finalizer_rows)
191 .ok_or("pending finalizer retained-record capacity overflow")?;
192 let current = self.closure_accounting;
193 let configured = current.configured_cap();
194 let configured = ResourceVector::new(
195 configured
196 .entries
197 .checked_add(finalizer_charge.entries)
198 .ok_or("pending finalizer entry capacity overflow")?,
199 configured
200 .bytes
201 .checked_add(finalizer_charge.bytes)
202 .ok_or("pending finalizer byte capacity overflow")?,
203 );
204 self.closure_accounting = ClosureAccounting::try_new(
205 current.state(),
206 current.marker_capacity_credits(),
207 current.marker_anchors(),
208 current.edge_sequence_claims(),
209 current.edge_order_position_claims(),
210 current.edge_k_remaining(),
211 current.baseline(),
212 configured,
213 current.episode_churn_used(),
214 current.episode_churn_limit(),
215 )
216 .map_err(|_| "pending finalizer closure capacity extension refused")?;
217 Ok(self)
218 }
219
220 #[must_use]
222 pub const fn frontiers(&self) -> &ClaimFrontiers {
223 &self.frontiers
224 }
225
226 #[must_use]
228 pub const fn closure_accounting(&self) -> ClosureAccounting {
229 self.closure_accounting
230 }
231
232 #[must_use]
234 pub fn retained_charges(&self) -> &[RetainedRecordCharge] {
235 &self.retained_charges
236 }
237
238 #[must_use]
240 pub const fn retained_record_limit(&self) -> u64 {
241 self.retained_record_limit
242 }
243
244 #[must_use]
286 pub fn reconcile_marker_anchor_ledgers(&mut self) -> MarkerAnchorReconcile {
287 let census = self
288 .frontiers
289 .unaccepted_marker_anchor_count()
290 .saturating_add(self.frontiers.pending_marker_candidate_count());
291 let stored = self.closure_accounting.marker_anchors();
292 let repaired = match census.cmp(&stored) {
293 Ordering::Equal => return MarkerAnchorReconcile::InStep,
294 Ordering::Less => stored.checked_sub(census).and_then(|orphaned| {
295 accounting_after_marker_crossings(self.closure_accounting, orphaned)
296 .map(|accounting| (accounting, MarkerAnchorReconcile::RetiredOrphans(orphaned)))
297 }),
298 Ordering::Greater => census.checked_sub(stored).and_then(|minted| {
299 accounting_after_marker_remint(self.closure_accounting, minted)
300 .map(|accounting| (accounting, MarkerAnchorReconcile::RemintedDeficit(minted)))
301 }),
302 };
303 match repaired {
304 Some((accounting, outcome)) => {
305 self.closure_accounting = accounting;
306 outcome
307 }
308 None => MarkerAnchorReconcile::Refused { census, stored },
309 }
310 }
311
312 #[must_use]
314 pub fn into_parts(
315 self,
316 ) -> (
317 ClaimFrontiers,
318 ClosureAccounting,
319 Vec<RetainedRecordCharge>,
320 u64,
321 ) {
322 (
323 self.frontiers,
324 self.closure_accounting,
325 self.retained_charges,
326 self.retained_record_limit,
327 )
328 }
329
330 #[must_use]
333 pub fn from_unchanged_record_admission<EF, V, LF>(
334 unchanged: UnchangedRecordAdmission<'_, EF, V, LF>,
335 retained_record_limit: u64,
336 ) -> (Self, RecordAdmission, ResourceVector) {
337 let (prestate, encoded_record_charge) = unchanged.into_parts();
338 let (request, frontiers, closure_accounting, retained_charges) =
339 prestate.into_live_owner_parts();
340 (
341 Self {
342 frontiers,
343 closure_accounting,
344 retained_charges,
345 retained_record_limit,
346 },
347 request,
348 encoded_record_charge,
349 )
350 }
351
352 #[must_use]
355 pub fn from_record_admission_persistence(
356 persistence: RecordAdmissionPersistenceParts,
357 retained_record_limit: u64,
358 ) -> Self {
359 Self {
360 frontiers: persistence.frontiers,
361 closure_accounting: persistence.accounting,
362 retained_charges: persistence.retained_charges,
363 retained_record_limit,
364 }
365 }
366
367 #[must_use]
369 pub fn from_marker_drain(
370 commit: MarkerDrainCommit,
371 retained_record_limit: u64,
372 ) -> (Self, StoredEdge, MarkerDeliveryProjection) {
373 let (frontiers, closure_accounting, retained_charges, successor, projection) =
374 commit.into_parts();
375 (
376 Self {
377 frontiers,
378 closure_accounting,
379 retained_charges,
380 retained_record_limit,
381 },
382 successor,
383 projection,
384 )
385 }
386
387 pub(super) fn commit_binding_terminal_candidate(
388 self,
389 active_binding: super::super::ActiveBinding,
390 admission_order: super::super::AdmissionOrder,
391 delivery_seq: crate::wire::DeliverySeq,
392 charge: RetainedRecordCharge,
393 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
394 let mut active = self.frontiers.active_identities().participants().to_vec();
395 let Some(participant) = active
396 .iter_mut()
397 .find(|participant| participant.participant_index() == active_binding.participant_id)
398 else {
399 return Err(Box::new((self, LiveFrontierError::Authority)));
400 };
401 if participant.binding() != FrontierBinding::Bound(active_binding.binding_epoch) {
402 return Err(Box::new((self, LiveFrontierError::Authority)));
403 }
404 *participant = FrontierParticipant::new(
405 participant.participant_index(),
406 participant.cursor(),
407 FrontierBinding::Detached(active_binding.binding_epoch),
408 );
409 let row = RetainedCausalRecord {
410 delivery_seq,
411 admission_order,
412 kind: RetainedCausalRecordKind::BindingTerminal(super::super::BindingTerminalOwner {
413 participant_index: active_binding.participant_id,
414 binding_epoch: active_binding.binding_epoch,
415 }),
416 };
417 let Some(sequence) = detach_sequence(self.frontiers.sequence().ledger(), delivery_seq)
418 else {
419 return Err(Box::new((self, LiveFrontierError::Frontier)));
420 };
421 let Some(order) = detach_order(
422 self.frontiers.order().ledger(),
423 admission_order.transaction_order(),
424 ) else {
425 return Err(Box::new((self, LiveFrontierError::Frontier)));
426 };
427 match transition(self, (), active, &[row], vec![charge], sequence, order) {
428 Ok(committed) => {
429 let ((), owner) = committed.into_parts();
430 Ok(owner)
431 }
432 Err(failure) => {
433 let error = failure.error();
434 let ((), owner) = failure.into_parts();
435 Err(Box::new((owner, error)))
436 }
437 }
438 }
439
440 pub(super) fn pend_binding_terminal_candidate(
441 self,
442 active_binding: super::super::ActiveBinding,
443 admission_order: super::super::AdmissionOrder,
444 delivery_seq: crate::wire::DeliverySeq,
445 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
446 let Some(order) = detach_order(
447 self.frontiers.order().ledger(),
448 admission_order.transaction_order(),
449 ) else {
450 return Err(Box::new((self, LiveFrontierError::Frontier)));
451 };
452 let Self {
453 frontiers,
454 closure_accounting,
455 retained_charges,
456 retained_record_limit,
457 } = self;
458 match frontiers.apply_pending_binding_terminal(
459 active_binding.participant_id,
460 active_binding.binding_epoch,
461 delivery_seq,
462 admission_order,
463 order,
464 ) {
465 Ok(frontiers) => Ok(Self {
466 frontiers,
467 closure_accounting,
468 retained_charges,
469 retained_record_limit,
470 }),
471 Err(failure) => {
472 let (frontiers, error) = *failure;
473 Err(Box::new((
474 Self {
475 frontiers,
476 closure_accounting,
477 retained_charges,
478 retained_record_limit,
479 },
480 map_frontier_error(error),
481 )))
482 }
483 }
484 }
485
486 pub fn drain_pending_terminal(
514 self,
515 pending: PendingFinalization,
516 terminal_charge: RetainedRecordCharge,
517 ) -> Result<DrainedPendingTerminal, Box<PendingTerminalDrainRefused>> {
518 let Some(expected_sequence) = self
519 .frontiers
520 .sequence()
521 .ledger()
522 .high_watermark()
523 .checked_add(1)
524 else {
525 return drain_refusal(self, LiveFrontierError::Frontier);
526 };
527 if terminal_charge.delivery_seq() != expected_sequence
528 || terminal_charge.admission_order() != pending.admission_order()
529 || terminal_charge.encoded_charge().entries != 1
530 {
531 return drain_refusal(self, LiveFrontierError::RetainedCharge);
532 }
533 if self
534 .retained_charges
535 .len()
536 .checked_add(1)
537 .and_then(|len| u64::try_from(len).ok())
538 .is_none()
539 {
540 return drain_refusal(self, LiveFrontierError::RetainedRecordLimit);
541 }
542 let Some(accounting) = accounting_after_rows(self.closure_accounting, &[terminal_charge])
543 else {
544 return drain_refusal(self, LiveFrontierError::ClosureAccounting);
545 };
546 let Self {
547 frontiers,
548 closure_accounting,
549 mut retained_charges,
550 retained_record_limit,
551 } = self;
552 let expected_owner = BindingTerminalOwner {
553 participant_index: pending.participant_id(),
554 binding_epoch: pending.binding_epoch(),
555 };
556 let (frontiers, record) = match frontiers
557 .drain_first_binding_terminal(expected_owner, pending.admission_order())
558 {
559 Ok(drained) => drained,
560 Err(failure) => {
561 let (frontiers, error) = *failure;
562 return drain_refusal(
563 Self {
564 frontiers,
565 closure_accounting,
566 retained_charges,
567 retained_record_limit,
568 },
569 map_frontier_error(error),
570 );
571 }
572 };
573 retained_charges.push(terminal_charge);
574 let projection =
575 ObserverProgressProjection::new(pending.conversation_id(), record.delivery_seq);
576 Ok(DrainedPendingTerminal {
577 owner: Self {
578 frontiers,
579 closure_accounting: accounting,
580 retained_charges,
581 retained_record_limit,
582 },
583 projection,
584 })
585 }
586
587 pub fn retain_fenced_marker_source(
594 self,
595 recovery: DetachedCredentialRecovery,
596 ) -> Result<RetainedFencedMarkerSource, Box<FencedMarkerSourceRetentionRefused>> {
597 let Some(source) = self.frontiers.fenced_marker_source(recovery) else {
598 return Err(Box::new(FencedMarkerSourceRetentionRefused {
599 owner: self,
600 recovery,
601 }));
602 };
603 Ok(RetainedFencedMarkerSource {
604 owner: self,
605 recovery,
606 expectation: FencedMarkerSourceExpectation { source },
607 })
608 }
609
610 #[must_use]
618 pub fn mint_fenced_attach(
619 mut self,
620 marker_source_sequence: u64,
621 recovery: DetachedCredentialRecovery,
622 debt: ClosureDebt,
623 event: Event,
624 successor: DebtCompletion,
625 ) -> MintFencedAttachResult {
626 let Some(record) = self.frontiers.take_fenced_marker_record(recovery) else {
627 return MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
628 owner: self,
629 marker_source_sequence,
630 recovery,
631 debt,
632 event,
633 successor,
634 reason: FencedAttachMintRefusalReason::MarkerAuthority,
635 }));
636 };
637 match recovery.fenced_attach(record, debt, event, successor) {
638 Ok(proof) => MintFencedAttachResult::Minted(Box::new(MintedFencedAttach {
639 owner_without_marker_authority: self,
640 proof,
641 })),
642 Err(refusal) => {
643 self.frontiers
644 .reinstall_fenced_marker_record((*refusal).into_record());
645 MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
646 owner: self,
647 marker_source_sequence,
648 recovery,
649 debt,
650 event,
651 successor,
652 reason: FencedAttachMintRefusalReason::ProofInputs,
653 }))
654 }
655 }
656 }
657}
658
659#[derive(Debug, PartialEq, Eq)]
664pub struct DrainedPendingTerminal {
665 owner: LiveFrontierOwner,
666 projection: ObserverProgressProjection,
667}
668
669impl DrainedPendingTerminal {
670 #[must_use]
672 pub fn into_parts(self) -> (LiveFrontierOwner, ObserverProgressProjection) {
673 (self.owner, self.projection)
674 }
675}
676
677#[derive(Debug, PartialEq, Eq)]
679pub struct PendingTerminalDrainRefused {
680 owner: LiveFrontierOwner,
681 error: LiveFrontierError,
682}
683
684impl PendingTerminalDrainRefused {
685 #[must_use]
687 pub const fn error(&self) -> LiveFrontierError {
688 self.error
689 }
690
691 #[must_use]
693 pub fn into_owner(self) -> LiveFrontierOwner {
694 self.owner
695 }
696}
697
698fn drain_refusal(
699 owner: LiveFrontierOwner,
700 error: LiveFrontierError,
701) -> Result<DrainedPendingTerminal, Box<PendingTerminalDrainRefused>> {
702 Err(Box::new(PendingTerminalDrainRefused { owner, error }))
703}
704
705#[derive(Clone, Copy, Debug, PartialEq, Eq)]
707pub struct FencedMarkerSourceExpectation {
708 source: FencedMarkerSourceRecord,
709}
710
711impl FencedMarkerSourceExpectation {
712 #[must_use]
714 pub const fn conversation_id(self) -> u64 {
715 self.source.conversation_id
716 }
717
718 #[must_use]
720 pub const fn marker_delivery_seq(self) -> u64 {
721 self.source.delivery_seq
722 }
723
724 #[must_use]
726 pub const fn admission_order(self) -> super::super::AdmissionOrder {
727 self.source.admission_order
728 }
729
730 #[must_use]
732 pub const fn participant_id(self) -> u64 {
733 self.source.participant_id
734 }
735
736 #[must_use]
738 pub const fn provenance(self) -> super::super::MarkerProvenance {
739 self.source.provenance
740 }
741
742 #[must_use]
744 pub const fn target_binding(self) -> FrontierBinding {
745 self.source.target_binding
746 }
747}
748
749#[derive(Debug, PartialEq, Eq)]
751pub struct RetainedFencedMarkerSource {
752 owner: LiveFrontierOwner,
753 recovery: DetachedCredentialRecovery,
754 expectation: FencedMarkerSourceExpectation,
755}
756
757impl RetainedFencedMarkerSource {
758 #[must_use]
760 pub const fn expectation(&self) -> FencedMarkerSourceExpectation {
761 self.expectation
762 }
763
764 #[must_use]
766 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
767 (self.owner, self.recovery)
768 }
769}
770
771#[derive(Debug, PartialEq, Eq)]
773pub struct FencedMarkerSourceRetentionRefused {
774 owner: LiveFrontierOwner,
775 recovery: DetachedCredentialRecovery,
776}
777
778impl FencedMarkerSourceRetentionRefused {
779 #[must_use]
781 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
782 (self.owner, self.recovery)
783 }
784}
785
786#[derive(Clone, Copy, Debug, PartialEq, Eq)]
788pub enum FencedAttachMintRefusalReason {
789 MarkerAuthority,
792 ProofInputs,
794}
795
796#[derive(Debug, PartialEq, Eq)]
798pub struct MintedFencedAttach {
799 owner_without_marker_authority: LiveFrontierOwner,
800 proof: FencedAttachCommit,
801}
802
803impl MintedFencedAttach {
804 #[must_use]
807 pub fn into_parts(self) -> (LiveFrontierOwner, FencedAttachCommit) {
808 (self.owner_without_marker_authority, self.proof)
809 }
810}
811
812#[derive(Debug, PartialEq, Eq)]
814pub struct MintFencedAttachRefused {
815 owner: LiveFrontierOwner,
816 marker_source_sequence: u64,
817 recovery: DetachedCredentialRecovery,
818 debt: ClosureDebt,
819 event: Event,
820 successor: DebtCompletion,
821 reason: FencedAttachMintRefusalReason,
822}
823
824impl MintFencedAttachRefused {
825 #[must_use]
827 pub const fn reason(&self) -> FencedAttachMintRefusalReason {
828 self.reason
829 }
830
831 #[must_use]
833 pub fn into_parts(
834 self,
835 ) -> (
836 LiveFrontierOwner,
837 u64,
838 DetachedCredentialRecovery,
839 ClosureDebt,
840 Event,
841 DebtCompletion,
842 ) {
843 (
844 self.owner,
845 self.marker_source_sequence,
846 self.recovery,
847 self.debt,
848 self.event,
849 self.successor,
850 )
851 }
852}
853
854#[derive(Debug, PartialEq, Eq)]
856pub enum MintFencedAttachResult {
857 Minted(Box<MintedFencedAttach>),
859 MintRefused(Box<MintFencedAttachRefused>),
861}
862
863#[derive(Debug, PartialEq, Eq)]
865pub struct LiveLeaveCommit<EF, V, LF> {
866 identity: IdentityState<EF, V, LF>,
867 owner: LiveFrontierOwner,
868}
869
870impl<EF, V, LF> LiveLeaveCommit<EF, V, LF> {
871 #[must_use]
873 pub const fn observer_progress_projection(&self) -> Option<ObserverProgressProjection> {
874 let IdentityState::Retired(retired) = &self.identity else {
875 return None;
876 };
877 let committed = retired.committed_result();
878 Some(ObserverProgressProjection::new(
879 committed.conversation_id(),
880 committed.left_delivery_seq(),
881 ))
882 }
883
884 #[must_use]
886 pub fn into_parts(self) -> (IdentityState<EF, V, LF>, LiveFrontierOwner) {
887 (self.identity, self.owner)
888 }
889}
890
891#[derive(Clone, Debug, PartialEq, Eq)]
893pub enum LiveLeaveError {
894 Prepare(PrepareLeaveAuthorityError),
896 Commit(LeaveCommitError),
898 RetainedCharge,
900 RetainedRecordLimit,
902 ClosureAccounting,
904 Identity,
906}
907
908pub fn commit_settled_leave_frontier<EF, V, LF, D>(
916 owner: LiveFrontierOwner,
917 member: LiveMember<EF>,
918 binding: BindingState,
919 detach_cell: DetachCell<D>,
920 verified: VerifiedLeaveRequest<V, LF>,
921 left_delivery_seq: u64,
922 left_charge: RetainedRecordCharge,
923) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
924 let LiveFrontierOwner {
925 frontiers,
926 closure_accounting,
927 mut retained_charges,
928 retained_record_limit,
929 } = owner;
930 let retired_marker_charge =
931 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
932 let authority = frontiers
933 .prepare_settled_leave_authority(&member, binding)
934 .map_err(LiveLeaveError::Prepare)?;
935 let commit = commit_leave(
936 member,
937 binding,
938 detach_cell,
939 verified,
940 authority,
941 LeaveCommitParameters { left_delivery_seq },
942 )
943 .map_err(LiveLeaveError::Commit)?;
944 let (identity, frontiers) = commit.into_parts();
945 let IdentityState::Retired(retired) = &identity else {
946 return Err(LiveLeaveError::Identity);
947 };
948 if left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
949 || left_charge.admission_order() != retired.left_admission_order()
950 || left_charge.encoded_charge().entries != 1
951 {
952 return Err(LiveLeaveError::RetainedCharge);
953 }
954 retained_charges.push(left_charge);
955 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
956 let retained_len = u64::try_from(frontiers.retained_records().len())
957 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
958 if retained_len > retained_record_limit
959 || retained_charges.len() != frontiers.retained_records().len()
960 {
961 return Err(LiveLeaveError::RetainedRecordLimit);
962 }
963 let closure_accounting =
964 accounting_after_leave(closure_accounting, &[left_charge], retired_marker_charge)
965 .ok_or(LiveLeaveError::ClosureAccounting)?;
966 Ok(LiveLeaveCommit {
967 identity,
968 owner: LiveFrontierOwner {
969 frontiers,
970 closure_accounting,
971 retained_charges,
972 retained_record_limit,
973 },
974 })
975}
976
977pub fn commit_pending_leave_frontier<EF, V, LF, D>(
986 owner: LiveFrontierOwner,
987 member: LiveMember<EF>,
988 pending: PendingFinalization,
989 detach_cell: DetachCell<D>,
990 verified: VerifiedLeaveRequest<V, LF>,
991 parameters: PendingLeaveCommitParameters,
992 charges: [RetainedRecordCharge; 2],
993) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
994 let [terminal_charge, left_charge] = charges;
995 let terminal_delivery_seq = parameters.terminal_delivery_seq;
996 let LiveFrontierOwner {
997 frontiers,
998 closure_accounting,
999 mut retained_charges,
1000 retained_record_limit,
1001 } = owner;
1002 let retired_marker_charge =
1003 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
1004 let authority = frontiers
1005 .prepare_pending_leave_authority(&member, pending)
1006 .map_err(LiveLeaveError::Prepare)?;
1007 let commit = commit_pending_leave(
1008 member,
1009 pending,
1010 detach_cell,
1011 verified,
1012 authority,
1013 parameters,
1014 )
1015 .map_err(LiveLeaveError::Commit)?;
1016 let (identity, frontiers) = commit.into_parts();
1017 let IdentityState::Retired(retired) = &identity else {
1018 return Err(LiveLeaveError::Identity);
1019 };
1020 if retired.committed_result().prior_terminal_delivery_seq() != Some(terminal_delivery_seq)
1021 || terminal_charge.delivery_seq() != terminal_delivery_seq
1022 || terminal_charge.admission_order() != pending.admission_order()
1023 || terminal_charge.encoded_charge().entries != 1
1024 || left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
1025 || left_charge.admission_order() != retired.left_admission_order()
1026 || left_charge.encoded_charge().entries != 1
1027 {
1028 return Err(LiveLeaveError::RetainedCharge);
1029 }
1030 retained_charges.extend([terminal_charge, left_charge]);
1031 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
1032 let retained_len = u64::try_from(frontiers.retained_records().len())
1033 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
1034 if retained_len > retained_record_limit {
1035 return Err(LiveLeaveError::RetainedRecordLimit);
1036 }
1037 if retained_charges.len() != frontiers.retained_records().len() {
1038 return Err(LiveLeaveError::RetainedCharge);
1039 }
1040 let closure_accounting = accounting_after_leave(
1041 closure_accounting,
1042 &[terminal_charge, left_charge],
1043 retired_marker_charge,
1044 )
1045 .ok_or(LiveLeaveError::ClosureAccounting)?;
1046 Ok(LiveLeaveCommit {
1047 identity,
1048 owner: LiveFrontierOwner {
1049 frontiers,
1050 closure_accounting,
1051 retained_charges,
1052 retained_record_limit,
1053 },
1054 })
1055}
1056
1057fn retired_marker_charge(
1058 frontiers: &ClaimFrontiers,
1059 retained_charges: &[RetainedRecordCharge],
1060 participant_id: crate::wire::ParticipantId,
1061) -> Result<Option<RetainedRecordCharge>, LiveLeaveError> {
1062 let marker_sequence = frontiers
1063 .retained_marker_records()
1064 .iter()
1065 .find_map(|record| {
1066 matches!(
1067 record.kind,
1068 RetainedCausalRecordKind::CompactionMarker {
1069 participant_index,
1070 ..
1071 } if participant_index == participant_id
1072 )
1073 .then_some(record.delivery_seq)
1074 });
1075 let Some(marker_sequence) = marker_sequence else {
1076 return Ok(None);
1077 };
1078 retained_charges
1079 .iter()
1080 .copied()
1081 .find(|charge| charge.delivery_seq() == marker_sequence)
1082 .map(Some)
1083 .ok_or(LiveLeaveError::RetainedCharge)
1084}
1085
1086#[derive(Debug, PartialEq, Eq)]
1088pub struct AttachFrontierCharges {
1089 terminal: Option<RetainedRecordCharge>,
1090 attached: RetainedRecordCharge,
1091 seal: LiveTransitionInputSeal,
1092}
1093
1094#[derive(Debug, PartialEq, Eq)]
1095enum LiveTransitionInputSeal {
1096 Validated,
1097}
1098
1099impl AttachFrontierCharges {
1100 #[must_use]
1102 pub const fn new(
1103 terminal: Option<RetainedRecordCharge>,
1104 attached: RetainedRecordCharge,
1105 ) -> Self {
1106 Self {
1107 terminal,
1108 attached,
1109 seal: LiveTransitionInputSeal::Validated,
1110 }
1111 }
1112
1113 const fn into_parts(self) -> (Option<RetainedRecordCharge>, RetainedRecordCharge) {
1114 let Self {
1115 terminal,
1116 attached,
1117 seal,
1118 } = self;
1119 match seal {
1120 LiveTransitionInputSeal::Validated => (terminal, attached),
1121 }
1122 }
1123}
1124
1125#[derive(Debug, PartialEq, Eq)]
1127pub struct LiveFrontierCommit<T> {
1128 operation: T,
1129 owner: LiveFrontierOwner,
1130}
1131
1132impl<T> LiveFrontierCommit<T> {
1133 #[must_use]
1135 pub const fn operation(&self) -> &T {
1136 &self.operation
1137 }
1138
1139 #[must_use]
1141 pub const fn owner(&self) -> &LiveFrontierOwner {
1142 &self.owner
1143 }
1144
1145 #[must_use]
1147 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
1148 (self.operation, self.owner)
1149 }
1150}
1151
1152#[derive(Debug, PartialEq, Eq)]
1154pub struct LiveFrontierFailure<T> {
1155 error: LiveFrontierError,
1156 operation: T,
1157 owner: LiveFrontierOwner,
1158}
1159
1160impl<T> LiveFrontierFailure<T> {
1161 #[must_use]
1163 pub const fn error(&self) -> LiveFrontierError {
1164 self.error
1165 }
1166
1167 #[must_use]
1169 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
1170 (self.operation, self.owner)
1171 }
1172}
1173
1174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1176pub enum LiveFrontierError {
1177 Authority,
1179 Precedence(PrecedenceCondition),
1187 RetainedCharge,
1189 RetainedRecordLimit,
1191 Frontier,
1193 ClosureAccounting,
1195}
1196
1197pub type LiveFrontierResult<T> = Result<LiveFrontierCommit<T>, Box<LiveFrontierFailure<T>>>;
1199
1200pub fn apply_enrollment_frontier<F>(
1206 owner: LiveFrontierOwner,
1207 operation: super::super::EnrollmentCommit<F>,
1208 attached_charge: RetainedRecordCharge,
1209) -> LiveFrontierResult<super::super::EnrollmentCommit<F>> {
1210 let attached = operation.attached;
1211 if attached.conversation_id() != owner.frontiers.conversation_id() {
1212 return failure(owner, operation, LiveFrontierError::Authority);
1213 }
1214 let participant_id = attached.participant_id();
1215 let mut active = owner.frontiers.active_identities().participants().to_vec();
1216 if active
1217 .iter()
1218 .any(|participant| participant.participant_index() == participant_id)
1219 {
1220 return failure(owner, operation, LiveFrontierError::Authority);
1221 }
1222 active.push(FrontierParticipant::new(
1223 participant_id,
1224 operation.member.cursor(),
1225 FrontierBinding::Bound(attached.binding_epoch()),
1226 ));
1227 active.sort_unstable_by_key(|participant| participant.participant_index());
1228 let rows = [retained_attached(attached)];
1229 let Some(sequence) =
1230 enrollment_sequence(owner.frontiers.sequence().ledger(), attached.delivery_seq())
1231 else {
1232 return failure(owner, operation, LiveFrontierError::Frontier);
1233 };
1234 let Some(order) = enrollment_order(
1235 owner.frontiers.order().ledger(),
1236 attached.admission_order().transaction_order(),
1237 ) else {
1238 return failure(owner, operation, LiveFrontierError::Frontier);
1239 };
1240 transition(
1241 owner,
1242 operation,
1243 active,
1244 &rows,
1245 vec![attached_charge],
1246 sequence,
1247 order,
1248 )
1249}
1250
1251pub fn apply_attach_frontier<F, V>(
1258 owner: LiveFrontierOwner,
1259 operation: AttachCommit<F, V>,
1260 charges: AttachFrontierCharges,
1261) -> LiveFrontierResult<AttachCommit<F, V>> {
1262 let (terminal_charge, attached_charge) = charges.into_parts();
1263 let attached = operation.attached;
1264 if attached.conversation_id() != owner.frontiers.conversation_id() {
1265 return failure(owner, operation, LiveFrontierError::Authority);
1266 }
1267 let mut active = owner.frontiers.active_identities().participants().to_vec();
1268 let Some(participant) = active
1269 .iter_mut()
1270 .find(|participant| participant.participant_index() == attached.participant_id())
1271 else {
1272 return failure(owner, operation, LiveFrontierError::Authority);
1273 };
1274 *participant = FrontierParticipant::new(
1275 participant.participant_index(),
1276 operation.member.cursor(),
1277 FrontierBinding::Bound(attached.binding_epoch()),
1278 );
1279 let current_sequence = owner.frontiers.sequence().ledger();
1280 let current_order = owner.frontiers.order().ledger();
1281 let (rows, keyed_charges, sequence, order) = match operation.transition {
1282 AttachTransition::Detached => {
1283 if terminal_charge.is_some() {
1284 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1285 }
1286 let Some(sequence) =
1287 detached_attach_sequence(current_sequence, attached.delivery_seq())
1288 else {
1289 return failure(owner, operation, LiveFrontierError::Frontier);
1290 };
1291 let Some(order) = detached_attach_order(
1292 current_order,
1293 attached.admission_order().transaction_order(),
1294 ) else {
1295 return failure(owner, operation, LiveFrontierError::Frontier);
1296 };
1297 (
1298 vec![retained_attached(attached)],
1299 vec![attached_charge],
1300 sequence,
1301 order,
1302 )
1303 }
1304 AttachTransition::Superseded { terminal } => {
1305 let Some(terminal_charge) = terminal_charge else {
1306 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1307 };
1308 let rows = vec![
1309 retained_terminal(terminal.into()),
1310 retained_attached(attached),
1311 ];
1312 let Some(sequence) = superseding_attach_sequence(current_sequence, &rows) else {
1313 return failure(owner, operation, LiveFrontierError::Frontier);
1314 };
1315 let Some(order) = superseding_attach_order(
1316 current_order,
1317 attached.admission_order().transaction_order(),
1318 ) else {
1319 return failure(owner, operation, LiveFrontierError::Frontier);
1320 };
1321 (
1322 rows,
1323 vec![terminal_charge, attached_charge],
1324 sequence,
1325 order,
1326 )
1327 }
1328 AttachTransition::FencedRecovery {
1329 prior_binding_epoch,
1330 composed_terminal,
1331 next_closure_state,
1332 } => {
1333 return apply_fenced_attach_frontier(
1334 owner,
1335 operation,
1336 terminal_charge,
1337 attached_charge,
1338 prior_binding_epoch,
1339 composed_terminal,
1340 next_closure_state,
1341 );
1342 }
1343 };
1344 transition(
1345 owner,
1346 operation,
1347 active,
1348 &rows,
1349 keyed_charges,
1350 sequence,
1351 order,
1352 )
1353}
1354
1355pub fn apply_detach_frontier<EF, V>(
1361 owner: LiveFrontierOwner,
1362 operation: CommittedDetachTransition<EF, V>,
1363 terminal_charge: RetainedRecordCharge,
1364) -> LiveFrontierResult<CommittedDetachTransition<EF, V>> {
1365 let terminal = operation.terminal();
1366 if terminal.conversation_id() != owner.frontiers.conversation_id() {
1367 return failure(owner, operation, LiveFrontierError::Authority);
1368 }
1369 let mut active = owner.frontiers.active_identities().participants().to_vec();
1370 let Some(participant) = active
1371 .iter_mut()
1372 .find(|participant| participant.participant_index() == terminal.participant_id())
1373 else {
1374 return failure(owner, operation, LiveFrontierError::Authority);
1375 };
1376 *participant = FrontierParticipant::new(
1377 participant.participant_index(),
1378 operation.member().cursor(),
1379 FrontierBinding::Detached(terminal.binding_epoch()),
1380 );
1381 let row = retained_terminal(terminal.into());
1382 let Some(sequence) = detach_sequence(owner.frontiers.sequence().ledger(), row.delivery_seq)
1383 else {
1384 return failure(owner, operation, LiveFrontierError::Frontier);
1385 };
1386 let Some(order) = detach_order(
1387 owner.frontiers.order().ledger(),
1388 row.admission_order.transaction_order(),
1389 ) else {
1390 return failure(owner, operation, LiveFrontierError::Frontier);
1391 };
1392 transition(
1393 owner,
1394 operation,
1395 active,
1396 &[row],
1397 vec![terminal_charge],
1398 sequence,
1399 order,
1400 )
1401}
1402
1403fn crossed_marker_anchors(
1413 owner: &LiveFrontierOwner,
1414 participant_id: crate::wire::ParticipantId,
1415 previous_cursor: crate::wire::DeliverySeq,
1416 through_seq: crate::wire::DeliverySeq,
1417) -> u64 {
1418 owner
1419 .frontiers
1420 .retained_marker_records()
1421 .iter()
1422 .filter(|record| {
1423 matches!(
1424 record.kind,
1425 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
1426 if participant_index == participant_id
1427 ) && previous_cursor < record.delivery_seq
1428 && record.delivery_seq <= through_seq
1429 })
1430 .fold(0_u64, |count, _| count.saturating_add(1))
1431}
1432
1433pub fn apply_participant_ack_frontier(
1447 mut owner: LiveFrontierOwner,
1448 operation: ParticipantAckCommit,
1449) -> LiveFrontierResult<ParticipantAckCommit> {
1450 let request = operation.outcome().request();
1451 let Some(current) = owner
1452 .frontiers
1453 .active_identities()
1454 .participants()
1455 .iter()
1456 .find(|participant| participant.participant_index() == request.participant_id)
1457 .copied()
1458 else {
1459 return failure(owner, operation, LiveFrontierError::Authority);
1460 };
1461 let crossed = crossed_marker_anchors(
1462 &owner,
1463 request.participant_id,
1464 current.cursor(),
1465 request.through_seq,
1466 );
1467 let accounting = if crossed == 0 {
1468 None
1469 } else {
1470 match accounting_after_marker_crossings(owner.closure_accounting, crossed) {
1471 Some(accounting) => Some(accounting),
1472 None => {
1473 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1474 }
1475 }
1476 };
1477 let participant = FrontierParticipant::new(
1478 request.participant_id,
1479 request.through_seq,
1480 current.binding(),
1481 );
1482 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1483 Ok(frontiers) => frontiers,
1484 Err(frontier_failure) => {
1485 let (frontiers, error) = *frontier_failure;
1486 owner.frontiers = frontiers;
1487 return failure(owner, operation, map_frontier_error(error));
1488 }
1489 };
1490 if let Some(accounting) = accounting {
1491 owner.closure_accounting = accounting;
1492 }
1493 Ok(LiveFrontierCommit { operation, owner })
1494}
1495
1496pub fn apply_nonzero_participant_ack_frontier(
1506 mut owner: LiveFrontierOwner,
1507 operation: NonzeroParticipantAckCommit,
1508) -> LiveFrontierResult<NonzeroParticipantAckCommit> {
1509 let request = operation.outcome().request();
1510 let Some(current) = owner
1511 .frontiers
1512 .active_identities()
1513 .participants()
1514 .iter()
1515 .find(|participant| participant.participant_index() == request.participant_id)
1516 .copied()
1517 else {
1518 return failure(owner, operation, LiveFrontierError::Authority);
1519 };
1520 let crossed = crossed_marker_anchors(
1523 &owner,
1524 request.participant_id,
1525 current.cursor(),
1526 request.through_seq,
1527 );
1528 let accounting = if crossed == 0 {
1529 None
1530 } else {
1531 match accounting_after_marker_crossings(owner.closure_accounting, crossed) {
1532 Some(accounting) => Some(accounting),
1533 None => {
1534 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1535 }
1536 }
1537 };
1538 let participant = FrontierParticipant::new(
1539 request.participant_id,
1540 request.through_seq,
1541 current.binding(),
1542 );
1543 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1544 Ok(frontiers) => frontiers,
1545 Err(frontier_failure) => {
1546 let (frontiers, error) = *frontier_failure;
1547 owner.frontiers = frontiers;
1548 return failure(owner, operation, map_frontier_error(error));
1549 }
1550 };
1551 if let Some(accounting) = accounting {
1552 owner.closure_accounting = accounting;
1553 }
1554 Ok(LiveFrontierCommit { operation, owner })
1555}
1556
1557pub fn apply_marker_ack_frontier(
1563 mut owner: LiveFrontierOwner,
1564 operation: MarkerAckCommit,
1565) -> LiveFrontierResult<MarkerAckCommit> {
1566 let request = operation.outcome().request();
1567 if !owner
1568 .frontiers
1569 .retained_marker_records()
1570 .iter()
1571 .any(|record| {
1572 record.delivery_seq == request.marker_delivery_seq
1573 && matches!(
1574 record.kind,
1575 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
1576 if participant_index == request.participant_id
1577 )
1578 })
1579 {
1580 return failure(owner, operation, LiveFrontierError::Authority);
1581 }
1582 let Some(current) = owner
1583 .frontiers
1584 .active_identities()
1585 .participants()
1586 .iter()
1587 .find(|participant| participant.participant_index() == request.participant_id)
1588 .copied()
1589 else {
1590 return failure(owner, operation, LiveFrontierError::Authority);
1591 };
1592 let Some(accounting) = accounting_after_marker_ack(owner.closure_accounting) else {
1593 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1594 };
1595 let participant = FrontierParticipant::new(
1596 request.participant_id,
1597 request.marker_delivery_seq,
1598 current.binding(),
1599 );
1600 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1601 Ok(frontiers) => frontiers,
1602 Err(frontier_failure) => {
1603 let (frontiers, error) = *frontier_failure;
1604 owner.frontiers = frontiers;
1605 return failure(owner, operation, map_frontier_error(error));
1606 }
1607 };
1608 owner.closure_accounting = accounting;
1609 Ok(LiveFrontierCommit { operation, owner })
1610}
1611
1612fn apply_fenced_attach_frontier<F, V>(
1613 owner: LiveFrontierOwner,
1614 operation: AttachCommit<F, V>,
1615 terminal_charge: Option<RetainedRecordCharge>,
1616 attached_charge: RetainedRecordCharge,
1617 prior_binding_epoch: crate::wire::BindingEpoch,
1618 composed_terminal: Option<super::super::CommittedBindingTerminal>,
1619 next_closure_state: super::super::ClosureState,
1620) -> LiveFrontierResult<AttachCommit<F, V>> {
1621 let attached = operation.attached;
1622 let (rows, charges) = match (composed_terminal, terminal_charge) {
1623 (None, None) => (vec![retained_attached(attached)], vec![attached_charge]),
1624 (Some(terminal), Some(terminal_charge)) => (
1625 vec![retained_terminal(terminal), retained_attached(attached)],
1626 vec![terminal_charge, attached_charge],
1627 ),
1628 (None, Some(_)) | (Some(_), None) => {
1629 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1630 }
1631 };
1632 let participant = FrontierParticipant::new(
1633 attached.participant_id(),
1634 operation.member.cursor(),
1635 FrontierBinding::Bound(attached.binding_epoch()),
1636 );
1637 fenced_attach_transition(
1638 owner,
1639 operation,
1640 participant,
1641 prior_binding_epoch,
1642 next_closure_state,
1643 &rows,
1644 charges,
1645 )
1646}
1647
1648fn fenced_attach_transition<T>(
1649 mut owner: LiveFrontierOwner,
1650 operation: T,
1651 participant: FrontierParticipant,
1652 prior_binding_epoch: crate::wire::BindingEpoch,
1653 next_closure_state: super::super::ClosureState,
1654 rows: &[RetainedCausalRecord],
1655 charges: Vec<RetainedRecordCharge>,
1656) -> LiveFrontierResult<T> {
1657 if rows.len() != charges.len()
1658 || rows.iter().zip(&charges).any(|(row, charge)| {
1659 row.delivery_seq != charge.delivery_seq()
1660 || row.admission_order != charge.admission_order()
1661 || charge.encoded_charge().entries != 1
1662 })
1663 {
1664 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1665 }
1666 let resulting_len = owner
1667 .frontiers
1668 .retained_records()
1669 .len()
1670 .checked_add(rows.len());
1671 if resulting_len
1672 .and_then(|len| u64::try_from(len).ok())
1673 .is_none_or(|len| len > owner.retained_record_limit)
1674 {
1675 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1676 }
1677 let Some(accounting) =
1678 accounting_after_fenced_attach(owner.closure_accounting, &charges, next_closure_state)
1679 else {
1680 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1681 };
1682 owner.frontiers =
1683 match owner
1684 .frontiers
1685 .apply_live_fenced_attach(participant, prior_binding_epoch, rows)
1686 {
1687 Ok(frontiers) => frontiers,
1688 Err(frontier_failure) => {
1689 let (frontiers, error) = *frontier_failure;
1690 owner.frontiers = frontiers;
1691 return failure(owner, operation, map_frontier_error(error));
1692 }
1693 };
1694 owner.retained_charges.extend(charges);
1695 owner
1696 .retained_charges
1697 .sort_unstable_by_key(|charge| charge.delivery_seq());
1698 owner.closure_accounting = accounting;
1699 Ok(LiveFrontierCommit { operation, owner })
1700}
1701
1702fn transition<T>(
1703 mut owner: LiveFrontierOwner,
1704 operation: T,
1705 active: Vec<FrontierParticipant>,
1706 rows: &[RetainedCausalRecord],
1707 charges: Vec<RetainedRecordCharge>,
1708 sequence: SequenceLedger,
1709 order: OrderLedger,
1710) -> LiveFrontierResult<T> {
1711 if rows.len() != charges.len()
1712 || rows.iter().zip(&charges).any(|(row, charge)| {
1713 row.delivery_seq != charge.delivery_seq()
1714 || row.admission_order != charge.admission_order()
1715 || charge.encoded_charge().entries != 1
1716 })
1717 {
1718 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1719 }
1720 let resulting_len = owner
1721 .frontiers
1722 .retained_records()
1723 .len()
1724 .checked_add(rows.len());
1725 if resulting_len
1726 .and_then(|len| u64::try_from(len).ok())
1727 .is_none_or(|len| len > owner.retained_record_limit)
1728 {
1729 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1730 }
1731 let Some(accounting) = accounting_after_rows(owner.closure_accounting, &charges) else {
1732 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1733 };
1734 owner.frontiers = match owner
1735 .frontiers
1736 .apply_live_transition(active, rows, sequence, order)
1737 {
1738 Ok(frontiers) => frontiers,
1739 Err(frontier_failure) => {
1740 let (frontiers, error) = *frontier_failure;
1741 owner.frontiers = frontiers;
1742 return failure(owner, operation, map_frontier_error(error));
1743 }
1744 };
1745 owner.retained_charges.extend(charges);
1746 owner.closure_accounting = accounting;
1747 Ok(LiveFrontierCommit { operation, owner })
1748}
1749
1750const fn map_frontier_error(error: LiveFrontierTransitionError) -> LiveFrontierError {
1751 match error {
1752 LiveFrontierTransitionError::Authority => LiveFrontierError::Authority,
1753 LiveFrontierTransitionError::Precedence(condition) => {
1754 LiveFrontierError::Precedence(condition)
1755 }
1756 LiveFrontierTransitionError::RecordPosition
1757 | LiveFrontierTransitionError::Exhausted
1758 | LiveFrontierTransitionError::ResultingFrontier => LiveFrontierError::Frontier,
1759 }
1760}
1761
1762fn failure<T, U>(
1763 owner: LiveFrontierOwner,
1764 operation: T,
1765 error: LiveFrontierError,
1766) -> Result<U, Box<LiveFrontierFailure<T>>> {
1767 Err(Box::new(LiveFrontierFailure {
1768 error,
1769 operation,
1770 owner,
1771 }))
1772}