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 checkpoint_restore;
34pub use checkpoint_restore::{
35 CheckpointParticipant, FrontierCheckpointSource, LiveFrontierRestoreError,
36 RestoredLiveFrontierOwner,
37};
38mod ledger;
39mod state;
40pub(super) use binding_fate_transition::BindingFateOwnerPlan;
41use ledger::{
42 detach_order, detach_sequence, detached_attach_order, detached_attach_sequence,
43 enrollment_order, enrollment_sequence, superseding_attach_order, superseding_attach_sequence,
44};
45use state::{
46 accounting_after_fenced_attach, accounting_after_leave, accounting_after_marker_ack,
47 accounting_after_marker_crossings, accounting_after_marker_remint, accounting_after_rows,
48 retained_attached, retained_terminal,
49};
50
51#[derive(Debug, PartialEq, Eq)]
76pub struct LiveFrontierOwner {
77 frontiers: ClaimFrontiers,
78 closure_accounting: ClosureAccounting,
79 retained_charges: Vec<RetainedRecordCharge>,
80 retained_record_limit: u64,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum MarkerAnchorReconcile {
98 InStep,
100 RetiredOrphans(u64),
103 RemintedDeficit(u64),
106 Refused {
109 census: u64,
111 stored: u64,
113 },
114}
115
116impl MarkerAnchorReconcile {
117 #[must_use]
119 pub const fn reconciled(self) -> u64 {
120 match self {
121 Self::InStep | Self::Refused { .. } => 0,
122 Self::RetiredOrphans(count) | Self::RemintedDeficit(count) => count,
123 }
124 }
125}
126
127impl LiveFrontierOwner {
128 pub(super) fn retain_ordinary_cursor_release(
129 &mut self,
130 source: Box<crate::lifecycle::storage::OrdinaryBindingFateRestore>,
131 ) {
132 self.frontiers.retain_ordinary_cursor_release(source);
133 }
134
135 #[must_use]
137 pub fn encode_closure_accounting_source(&self) -> Vec<u8> {
138 super::super::checkpoint_codec::encode_accounting(&self.closure_accounting)
139 }
140
141 pub fn decode_closure_accounting_source(
147 state: super::super::ClosureState,
148 bytes: &[u8],
149 ) -> Result<ClosureAccounting, super::super::FrontierAccountingDecodeError> {
150 super::super::checkpoint_codec::decode_accounting(state, bytes)
151 }
152
153 #[must_use]
155 pub fn encode_retained_charge_source(&self) -> Option<Vec<u8>> {
156 super::super::checkpoint_codec::encode_charges(&self.retained_charges)
157 }
158
159 pub fn decode_retained_charge_source(
165 bytes: &[u8],
166 ) -> Result<Vec<RetainedRecordCharge>, super::super::CapsuleDecodeError> {
167 super::super::checkpoint_codec::decode_charges(bytes)
168 }
169
170 #[must_use]
172 pub fn from_initial_enrollment<F>(
173 initial: InitialEnrollmentFrontierCommit<F>,
174 retained_record_limit: u64,
175 ) -> (InitialEnrollmentOperationCommit<F>, Self) {
176 let (operation, frontiers, closure_accounting, attached_charge) =
177 initial.into_conversation_parts();
178 let attached = operation.enrollment().attached;
179 let retained_charges = vec![RetainedRecordCharge::new(
180 attached.delivery_seq(),
181 attached.admission_order(),
182 attached_charge,
183 )];
184 (
185 operation,
186 Self {
187 frontiers,
188 closure_accounting,
189 retained_charges,
190 retained_record_limit,
191 },
192 )
193 }
194
195 #[cfg(any(test, feature = "test-support"))]
206 #[must_use]
207 pub const fn from_test_parts(
208 frontiers: ClaimFrontiers,
209 closure_accounting: ClosureAccounting,
210 retained_charges: Vec<RetainedRecordCharge>,
211 retained_record_limit: u64,
212 ) -> Self {
213 Self {
214 frontiers,
215 closure_accounting,
216 retained_charges,
217 retained_record_limit,
218 }
219 }
220
221 #[cfg(any(test, feature = "test-support"))]
229 pub fn with_pending_finalizer_test_capacity(
230 mut self,
231 finalizer_rows: u64,
232 finalizer_charge: ResourceVector,
233 ) -> Result<Self, &'static str> {
234 let retained_count = u64::try_from(self.retained_charges.len())
235 .map_err(|_| "retained record count exceeds u64")?;
236 self.retained_record_limit = retained_count
237 .checked_add(finalizer_rows)
238 .ok_or("pending finalizer retained-record capacity overflow")?;
239 let current = self.closure_accounting;
240 let configured = current.configured_cap();
241 let configured = ResourceVector::new(
242 configured
243 .entries
244 .checked_add(finalizer_charge.entries)
245 .ok_or("pending finalizer entry capacity overflow")?,
246 configured
247 .bytes
248 .checked_add(finalizer_charge.bytes)
249 .ok_or("pending finalizer byte capacity overflow")?,
250 );
251 self.closure_accounting = ClosureAccounting::try_new(
252 current.state(),
253 current.marker_capacity_credits(),
254 current.marker_anchors(),
255 current.edge_sequence_claims(),
256 current.edge_order_position_claims(),
257 current.edge_k_remaining(),
258 current.baseline(),
259 configured,
260 current.episode_churn_used(),
261 current.episode_churn_limit(),
262 )
263 .map_err(|_| "pending finalizer closure capacity extension refused")?;
264 Ok(self)
265 }
266
267 #[must_use]
269 pub const fn frontiers(&self) -> &ClaimFrontiers {
270 &self.frontiers
271 }
272
273 #[must_use]
275 pub const fn closure_accounting(&self) -> ClosureAccounting {
276 self.closure_accounting
277 }
278
279 #[must_use]
281 pub fn retained_charges(&self) -> &[RetainedRecordCharge] {
282 &self.retained_charges
283 }
284
285 #[must_use]
287 pub const fn retained_record_limit(&self) -> u64 {
288 self.retained_record_limit
289 }
290
291 #[must_use]
333 pub fn reconcile_marker_anchor_ledgers(&mut self) -> MarkerAnchorReconcile {
334 let census = self
335 .frontiers
336 .unaccepted_marker_anchor_count()
337 .saturating_add(self.frontiers.pending_marker_candidate_count());
338 let stored = self.closure_accounting.marker_anchors();
339 let repaired = match census.cmp(&stored) {
340 Ordering::Equal => return MarkerAnchorReconcile::InStep,
341 Ordering::Less => stored.checked_sub(census).and_then(|orphaned| {
342 accounting_after_marker_crossings(self.closure_accounting, orphaned)
343 .map(|accounting| (accounting, MarkerAnchorReconcile::RetiredOrphans(orphaned)))
344 }),
345 Ordering::Greater => census.checked_sub(stored).and_then(|minted| {
346 accounting_after_marker_remint(self.closure_accounting, minted)
347 .map(|accounting| (accounting, MarkerAnchorReconcile::RemintedDeficit(minted)))
348 }),
349 };
350 match repaired {
351 Some((accounting, outcome)) => {
352 self.closure_accounting = accounting;
353 outcome
354 }
355 None => MarkerAnchorReconcile::Refused { census, stored },
356 }
357 }
358
359 #[must_use]
361 pub fn into_parts(
362 self,
363 ) -> (
364 ClaimFrontiers,
365 ClosureAccounting,
366 Vec<RetainedRecordCharge>,
367 u64,
368 ) {
369 (
370 self.frontiers,
371 self.closure_accounting,
372 self.retained_charges,
373 self.retained_record_limit,
374 )
375 }
376
377 #[must_use]
380 pub fn from_unchanged_record_admission<EF, V, LF>(
381 unchanged: UnchangedRecordAdmission<'_, EF, V, LF>,
382 retained_record_limit: u64,
383 ) -> (Self, RecordAdmission, ResourceVector) {
384 let (prestate, encoded_record_charge) = unchanged.into_parts();
385 let (request, frontiers, closure_accounting, retained_charges) =
386 prestate.into_live_owner_parts();
387 (
388 Self {
389 frontiers,
390 closure_accounting,
391 retained_charges,
392 retained_record_limit,
393 },
394 request,
395 encoded_record_charge,
396 )
397 }
398
399 #[must_use]
402 pub fn from_record_admission_persistence(
403 persistence: RecordAdmissionPersistenceParts,
404 retained_record_limit: u64,
405 ) -> Self {
406 Self {
407 frontiers: persistence.frontiers,
408 closure_accounting: persistence.accounting,
409 retained_charges: persistence.retained_charges,
410 retained_record_limit,
411 }
412 }
413
414 #[must_use]
416 pub fn from_marker_drain(
417 commit: MarkerDrainCommit,
418 retained_record_limit: u64,
419 ) -> (Self, StoredEdge, MarkerDeliveryProjection) {
420 let (frontiers, closure_accounting, retained_charges, successor, projection) =
421 commit.into_parts();
422 (
423 Self {
424 frontiers,
425 closure_accounting,
426 retained_charges,
427 retained_record_limit,
428 },
429 successor,
430 projection,
431 )
432 }
433
434 pub(super) fn commit_binding_terminal_candidate(
435 self,
436 active_binding: super::super::ActiveBinding,
437 admission_order: super::super::AdmissionOrder,
438 delivery_seq: crate::wire::DeliverySeq,
439 charge: RetainedRecordCharge,
440 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
441 let mut active = self.frontiers.active_identities().participants().to_vec();
442 let Some(participant) = active
443 .iter_mut()
444 .find(|participant| participant.participant_index() == active_binding.participant_id)
445 else {
446 return Err(Box::new((self, LiveFrontierError::Authority)));
447 };
448 if participant.binding() != FrontierBinding::Bound(active_binding.binding_epoch) {
449 return Err(Box::new((self, LiveFrontierError::Authority)));
450 }
451 *participant = FrontierParticipant::new(
452 participant.participant_index(),
453 participant.cursor(),
454 FrontierBinding::Detached(active_binding.binding_epoch),
455 );
456 let row = RetainedCausalRecord {
457 delivery_seq,
458 admission_order,
459 kind: RetainedCausalRecordKind::BindingTerminal(super::super::BindingTerminalOwner {
460 participant_index: active_binding.participant_id,
461 binding_epoch: active_binding.binding_epoch,
462 }),
463 };
464 let Some(sequence) = detach_sequence(self.frontiers.sequence().ledger(), delivery_seq)
465 else {
466 return Err(Box::new((self, LiveFrontierError::Frontier)));
467 };
468 let Some(order) = detach_order(
469 self.frontiers.order().ledger(),
470 admission_order.transaction_order(),
471 ) else {
472 return Err(Box::new((self, LiveFrontierError::Frontier)));
473 };
474 match transition(self, (), active, &[row], vec![charge], sequence, order) {
475 Ok(committed) => {
476 let ((), owner) = committed.into_parts();
477 Ok(owner)
478 }
479 Err(failure) => {
480 let error = failure.error();
481 let ((), owner) = failure.into_parts();
482 Err(Box::new((owner, error)))
483 }
484 }
485 }
486
487 pub(super) fn pend_binding_terminal_candidate(
488 self,
489 active_binding: super::super::ActiveBinding,
490 admission_order: super::super::AdmissionOrder,
491 delivery_seq: crate::wire::DeliverySeq,
492 ) -> Result<Self, Box<(Self, LiveFrontierError)>> {
493 let Some(order) = detach_order(
494 self.frontiers.order().ledger(),
495 admission_order.transaction_order(),
496 ) else {
497 return Err(Box::new((self, LiveFrontierError::Frontier)));
498 };
499 let Self {
500 frontiers,
501 closure_accounting,
502 retained_charges,
503 retained_record_limit,
504 } = self;
505 match frontiers.apply_pending_binding_terminal(
506 active_binding.participant_id,
507 active_binding.binding_epoch,
508 delivery_seq,
509 admission_order,
510 order,
511 ) {
512 Ok(frontiers) => Ok(Self {
513 frontiers,
514 closure_accounting,
515 retained_charges,
516 retained_record_limit,
517 }),
518 Err(failure) => {
519 let (frontiers, error) = *failure;
520 Err(Box::new((
521 Self {
522 frontiers,
523 closure_accounting,
524 retained_charges,
525 retained_record_limit,
526 },
527 map_frontier_error(error),
528 )))
529 }
530 }
531 }
532
533 pub fn drain_pending_terminal(
566 self,
567 pending: PendingFinalization,
568 terminal_charge: RetainedRecordCharge,
569 ) -> Result<DrainedPendingTerminal, Box<PendingTerminalDrainRefused>> {
570 let Some(expected_sequence) = self
571 .frontiers
572 .sequence()
573 .ledger()
574 .high_watermark()
575 .checked_add(1)
576 else {
577 return drain_refusal(self, LiveFrontierError::Frontier);
578 };
579 if terminal_charge.delivery_seq() != expected_sequence
580 || terminal_charge.admission_order() != pending.admission_order()
581 || terminal_charge.encoded_charge().entries != 1
582 {
583 return drain_refusal(self, LiveFrontierError::RetainedCharge);
584 }
585 if self
586 .retained_charges
587 .len()
588 .checked_add(1)
589 .and_then(|len| u64::try_from(len).ok())
590 .is_none()
591 {
592 return drain_refusal(self, LiveFrontierError::RetainedRecordLimit);
593 }
594 let Some(accounting) = accounting_after_rows(self.closure_accounting, &[terminal_charge])
595 else {
596 return drain_refusal(self, LiveFrontierError::ClosureAccounting);
597 };
598 let Self {
599 frontiers,
600 closure_accounting,
601 mut retained_charges,
602 retained_record_limit,
603 } = self;
604 let expected_owner = BindingTerminalOwner {
605 participant_index: pending.participant_id(),
606 binding_epoch: pending.binding_epoch(),
607 };
608 let (frontiers, record) = match frontiers.drain_first_binding_terminal(
609 expected_owner,
610 pending.admission_order(),
611 matches!(pending, PendingFinalization::Died(_)),
612 ) {
613 Ok(drained) => drained,
614 Err(failure) => {
615 let (frontiers, error) = *failure;
616 return drain_refusal(
617 Self {
618 frontiers,
619 closure_accounting,
620 retained_charges,
621 retained_record_limit,
622 },
623 map_frontier_error(error),
624 );
625 }
626 };
627 retained_charges.push(terminal_charge);
628 let projection = ObserverProgressProjection::sourced(
629 pending.conversation_id(),
630 record.delivery_seq,
631 super::super::checkpoint_codec::ObserverProjectionSource::Terminal(
632 pending.commit(record.delivery_seq).storage_description(),
633 ),
634 );
635 Ok(DrainedPendingTerminal {
636 owner: Self {
637 frontiers,
638 closure_accounting: accounting,
639 retained_charges,
640 retained_record_limit,
641 },
642 projection,
643 })
644 }
645
646 pub fn retain_fenced_marker_source(
653 self,
654 recovery: DetachedCredentialRecovery,
655 ) -> Result<RetainedFencedMarkerSource, Box<FencedMarkerSourceRetentionRefused>> {
656 let Some(source) = self.frontiers.fenced_marker_source(recovery) else {
657 return Err(Box::new(FencedMarkerSourceRetentionRefused {
658 owner: self,
659 recovery,
660 }));
661 };
662 Ok(RetainedFencedMarkerSource {
663 owner: self,
664 recovery,
665 expectation: FencedMarkerSourceExpectation { source },
666 })
667 }
668
669 #[must_use]
677 pub fn mint_fenced_attach(
678 mut self,
679 marker_source_sequence: u64,
680 recovery: DetachedCredentialRecovery,
681 debt: ClosureDebt,
682 event: Event,
683 successor: DebtCompletion,
684 ) -> MintFencedAttachResult {
685 let Some(record) = self.frontiers.take_fenced_marker_record(recovery) else {
686 return MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
687 owner: self,
688 marker_source_sequence,
689 recovery,
690 debt,
691 event,
692 successor,
693 reason: FencedAttachMintRefusalReason::MarkerAuthority,
694 }));
695 };
696 let marker_source_bytes = super::super::checkpoint_codec::encode_marker_source(&record);
697 match recovery.fenced_attach(record, debt, event, successor) {
698 Ok(proof) => MintFencedAttachResult::Minted(Box::new(MintedFencedAttach {
699 owner_without_marker_authority: self,
700 proof,
701 marker_source_sequence,
702 marker_source_bytes,
703 })),
704 Err(refusal) => {
705 self.frontiers
706 .reinstall_fenced_marker_record((*refusal).into_record());
707 MintFencedAttachResult::MintRefused(Box::new(MintFencedAttachRefused {
708 owner: self,
709 marker_source_sequence,
710 recovery,
711 debt,
712 event,
713 successor,
714 reason: FencedAttachMintRefusalReason::ProofInputs,
715 }))
716 }
717 }
718 }
719}
720
721#[derive(Debug, PartialEq, Eq)]
726pub struct DrainedPendingTerminal {
727 owner: LiveFrontierOwner,
728 projection: ObserverProgressProjection,
729}
730
731impl DrainedPendingTerminal {
732 #[must_use]
734 pub fn into_parts(self) -> (LiveFrontierOwner, ObserverProgressProjection) {
735 (self.owner, self.projection)
736 }
737}
738
739#[derive(Debug, PartialEq, Eq)]
741pub struct PendingTerminalDrainRefused {
742 owner: LiveFrontierOwner,
743 error: LiveFrontierError,
744}
745
746impl PendingTerminalDrainRefused {
747 #[must_use]
749 pub const fn error(&self) -> LiveFrontierError {
750 self.error
751 }
752
753 #[must_use]
755 pub fn into_owner(self) -> LiveFrontierOwner {
756 self.owner
757 }
758}
759
760fn drain_refusal(
761 owner: LiveFrontierOwner,
762 error: LiveFrontierError,
763) -> Result<DrainedPendingTerminal, Box<PendingTerminalDrainRefused>> {
764 Err(Box::new(PendingTerminalDrainRefused { owner, error }))
765}
766
767#[derive(Clone, Copy, Debug, PartialEq, Eq)]
769pub struct FencedMarkerSourceExpectation {
770 source: FencedMarkerSourceRecord,
771}
772
773impl FencedMarkerSourceExpectation {
774 #[must_use]
776 pub const fn conversation_id(self) -> u64 {
777 self.source.conversation_id
778 }
779
780 #[must_use]
782 pub const fn marker_delivery_seq(self) -> u64 {
783 self.source.delivery_seq
784 }
785
786 #[must_use]
788 pub const fn admission_order(self) -> super::super::AdmissionOrder {
789 self.source.admission_order
790 }
791
792 #[must_use]
794 pub const fn participant_id(self) -> u64 {
795 self.source.participant_id
796 }
797
798 #[must_use]
800 pub const fn provenance(self) -> super::super::MarkerProvenance {
801 self.source.provenance
802 }
803
804 #[must_use]
806 pub const fn target_binding(self) -> FrontierBinding {
807 self.source.target_binding
808 }
809}
810
811#[derive(Debug, PartialEq, Eq)]
813pub struct RetainedFencedMarkerSource {
814 owner: LiveFrontierOwner,
815 recovery: DetachedCredentialRecovery,
816 expectation: FencedMarkerSourceExpectation,
817}
818
819impl RetainedFencedMarkerSource {
820 #[must_use]
822 pub const fn expectation(&self) -> FencedMarkerSourceExpectation {
823 self.expectation
824 }
825
826 #[must_use]
828 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
829 (self.owner, self.recovery)
830 }
831}
832
833#[derive(Debug, PartialEq, Eq)]
835pub struct FencedMarkerSourceRetentionRefused {
836 owner: LiveFrontierOwner,
837 recovery: DetachedCredentialRecovery,
838}
839
840impl FencedMarkerSourceRetentionRefused {
841 #[must_use]
843 pub fn into_parts(self) -> (LiveFrontierOwner, DetachedCredentialRecovery) {
844 (self.owner, self.recovery)
845 }
846}
847
848#[derive(Clone, Copy, Debug, PartialEq, Eq)]
850pub enum FencedAttachMintRefusalReason {
851 MarkerAuthority,
854 ProofInputs,
856}
857
858#[derive(Debug, PartialEq, Eq)]
860pub struct MintedFencedAttach {
861 marker_source_sequence: u64,
862 marker_source_bytes: Vec<u8>,
863 owner_without_marker_authority: LiveFrontierOwner,
864 proof: FencedAttachCommit,
865}
866
867impl MintedFencedAttach {
868 #[must_use]
870 pub fn marker_record_source(&self) -> (u64, &[u8]) {
871 (self.marker_source_sequence, &self.marker_source_bytes)
872 }
873
874 #[must_use]
877 pub fn into_parts(self) -> (LiveFrontierOwner, FencedAttachCommit) {
878 (self.owner_without_marker_authority, self.proof)
879 }
880}
881
882#[derive(Debug, PartialEq, Eq)]
884pub struct MintFencedAttachRefused {
885 owner: LiveFrontierOwner,
886 marker_source_sequence: u64,
887 recovery: DetachedCredentialRecovery,
888 debt: ClosureDebt,
889 event: Event,
890 successor: DebtCompletion,
891 reason: FencedAttachMintRefusalReason,
892}
893
894impl MintFencedAttachRefused {
895 #[must_use]
897 pub const fn reason(&self) -> FencedAttachMintRefusalReason {
898 self.reason
899 }
900
901 #[must_use]
903 pub fn into_parts(
904 self,
905 ) -> (
906 LiveFrontierOwner,
907 u64,
908 DetachedCredentialRecovery,
909 ClosureDebt,
910 Event,
911 DebtCompletion,
912 ) {
913 (
914 self.owner,
915 self.marker_source_sequence,
916 self.recovery,
917 self.debt,
918 self.event,
919 self.successor,
920 )
921 }
922}
923
924#[derive(Debug, PartialEq, Eq)]
926pub enum MintFencedAttachResult {
927 Minted(Box<MintedFencedAttach>),
929 MintRefused(Box<MintFencedAttachRefused>),
931}
932
933#[derive(Debug, PartialEq, Eq)]
935pub struct LiveLeaveCommit<EF, V, LF> {
936 identity: IdentityState<EF, V, LF>,
937 owner: LiveFrontierOwner,
938}
939
940impl<EF, V, LF> LiveLeaveCommit<EF, V, LF> {
941 #[must_use]
943 pub const fn observer_progress_projection(&self) -> Option<ObserverProgressProjection> {
944 let IdentityState::Retired(retired) = &self.identity else {
945 return None;
946 };
947 let committed = retired.committed_result();
948 Some(ObserverProgressProjection::sourced(
949 committed.conversation_id(),
950 committed.left_delivery_seq(),
951 super::super::checkpoint_codec::ObserverProjectionSource::Leave(
952 super::super::LeaveCommittedRestore {
953 conversation_id: committed.conversation_id(),
954 leave_attempt_token: committed.leave_attempt_token(),
955 participant_id: committed.participant_id(),
956 retired_generation: committed.retired_generation(),
957 ended_binding_epoch: committed.ended_binding_epoch(),
958 prior_terminal_delivery_seq: committed.prior_terminal_delivery_seq(),
959 left_delivery_seq: committed.left_delivery_seq(),
960 },
961 ),
962 ))
963 }
964
965 #[must_use]
967 pub fn into_parts(self) -> (IdentityState<EF, V, LF>, LiveFrontierOwner) {
968 (self.identity, self.owner)
969 }
970}
971
972#[derive(Clone, Debug, PartialEq, Eq)]
974pub enum LiveLeaveError {
975 Prepare(PrepareLeaveAuthorityError),
977 Commit(LeaveCommitError),
979 RetainedCharge,
981 RetainedRecordLimit,
983 ClosureAccounting,
985 Identity,
987}
988
989pub fn commit_settled_leave_frontier<EF, V, LF, D>(
997 owner: LiveFrontierOwner,
998 member: LiveMember<EF>,
999 binding: BindingState,
1000 detach_cell: DetachCell<D>,
1001 verified: VerifiedLeaveRequest<V, LF>,
1002 left_delivery_seq: u64,
1003 left_charge: RetainedRecordCharge,
1004) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
1005 let LiveFrontierOwner {
1006 frontiers,
1007 closure_accounting,
1008 mut retained_charges,
1009 retained_record_limit,
1010 } = owner;
1011 let retired_marker_charge =
1012 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
1013 let authority = frontiers
1014 .prepare_settled_leave_authority(&member, binding)
1015 .map_err(LiveLeaveError::Prepare)?;
1016 let commit = commit_leave(
1017 member,
1018 binding,
1019 detach_cell,
1020 verified,
1021 authority,
1022 LeaveCommitParameters { left_delivery_seq },
1023 )
1024 .map_err(LiveLeaveError::Commit)?;
1025 let (identity, frontiers) = commit.into_parts();
1026 let IdentityState::Retired(retired) = &identity else {
1027 return Err(LiveLeaveError::Identity);
1028 };
1029 if left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
1030 || left_charge.admission_order() != retired.left_admission_order()
1031 || left_charge.encoded_charge().entries != 1
1032 {
1033 return Err(LiveLeaveError::RetainedCharge);
1034 }
1035 retained_charges.push(left_charge);
1036 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
1037 let retained_len = u64::try_from(frontiers.retained_records().len())
1038 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
1039 if retained_len > retained_record_limit
1040 || retained_charges.len() != frontiers.retained_records().len()
1041 {
1042 return Err(LiveLeaveError::RetainedRecordLimit);
1043 }
1044 let closure_accounting =
1045 accounting_after_leave(closure_accounting, &[left_charge], retired_marker_charge)
1046 .ok_or(LiveLeaveError::ClosureAccounting)?;
1047 Ok(LiveLeaveCommit {
1048 identity,
1049 owner: LiveFrontierOwner {
1050 frontiers,
1051 closure_accounting,
1052 retained_charges,
1053 retained_record_limit,
1054 },
1055 })
1056}
1057
1058pub fn commit_pending_leave_frontier<EF, V, LF, D>(
1067 owner: LiveFrontierOwner,
1068 member: LiveMember<EF>,
1069 pending: PendingFinalization,
1070 detach_cell: DetachCell<D>,
1071 verified: VerifiedLeaveRequest<V, LF>,
1072 parameters: PendingLeaveCommitParameters,
1073 charges: [RetainedRecordCharge; 2],
1074) -> Result<LiveLeaveCommit<EF, V, LF>, LiveLeaveError> {
1075 let [terminal_charge, left_charge] = charges;
1076 let terminal_delivery_seq = parameters.terminal_delivery_seq;
1077 let LiveFrontierOwner {
1078 frontiers,
1079 closure_accounting,
1080 mut retained_charges,
1081 retained_record_limit,
1082 } = owner;
1083 let retired_marker_charge =
1084 retired_marker_charge(&frontiers, &retained_charges, member.participant_id())?;
1085 let authority = frontiers
1086 .prepare_pending_leave_authority(&member, pending)
1087 .map_err(LiveLeaveError::Prepare)?;
1088 let commit = commit_pending_leave(
1089 member,
1090 pending,
1091 detach_cell,
1092 verified,
1093 authority,
1094 parameters,
1095 )
1096 .map_err(LiveLeaveError::Commit)?;
1097 let (identity, frontiers) = commit.into_parts();
1098 let IdentityState::Retired(retired) = &identity else {
1099 return Err(LiveLeaveError::Identity);
1100 };
1101 if retired.committed_result().prior_terminal_delivery_seq() != Some(terminal_delivery_seq)
1102 || terminal_charge.delivery_seq() != terminal_delivery_seq
1103 || terminal_charge.admission_order() != pending.admission_order()
1104 || terminal_charge.encoded_charge().entries != 1
1105 || left_charge.delivery_seq() != retired.committed_result().left_delivery_seq()
1106 || left_charge.admission_order() != retired.left_admission_order()
1107 || left_charge.encoded_charge().entries != 1
1108 {
1109 return Err(LiveLeaveError::RetainedCharge);
1110 }
1111 retained_charges.extend([terminal_charge, left_charge]);
1112 retained_charges.sort_unstable_by_key(|charge| charge.delivery_seq());
1113 let retained_len = u64::try_from(frontiers.retained_records().len())
1114 .map_err(|_| LiveLeaveError::RetainedRecordLimit)?;
1115 if retained_len > retained_record_limit {
1116 return Err(LiveLeaveError::RetainedRecordLimit);
1117 }
1118 if retained_charges.len() != frontiers.retained_records().len() {
1119 return Err(LiveLeaveError::RetainedCharge);
1120 }
1121 let closure_accounting = accounting_after_leave(
1122 closure_accounting,
1123 &[terminal_charge, left_charge],
1124 retired_marker_charge,
1125 )
1126 .ok_or(LiveLeaveError::ClosureAccounting)?;
1127 Ok(LiveLeaveCommit {
1128 identity,
1129 owner: LiveFrontierOwner {
1130 frontiers,
1131 closure_accounting,
1132 retained_charges,
1133 retained_record_limit,
1134 },
1135 })
1136}
1137
1138fn retired_marker_charge(
1139 frontiers: &ClaimFrontiers,
1140 retained_charges: &[RetainedRecordCharge],
1141 participant_id: crate::wire::ParticipantId,
1142) -> Result<Option<RetainedRecordCharge>, LiveLeaveError> {
1143 let marker_sequence = frontiers
1144 .retained_marker_records()
1145 .iter()
1146 .find_map(|record| {
1147 matches!(
1148 record.kind,
1149 RetainedCausalRecordKind::CompactionMarker {
1150 participant_index,
1151 ..
1152 } if participant_index == participant_id
1153 )
1154 .then_some(record.delivery_seq)
1155 });
1156 let Some(marker_sequence) = marker_sequence else {
1157 return Ok(None);
1158 };
1159 retained_charges
1160 .iter()
1161 .copied()
1162 .find(|charge| charge.delivery_seq() == marker_sequence)
1163 .map(Some)
1164 .ok_or(LiveLeaveError::RetainedCharge)
1165}
1166
1167#[derive(Debug, PartialEq, Eq)]
1169pub struct AttachFrontierCharges {
1170 terminal: Option<RetainedRecordCharge>,
1171 attached: RetainedRecordCharge,
1172 seal: LiveTransitionInputSeal,
1173}
1174
1175#[derive(Debug, PartialEq, Eq)]
1176enum LiveTransitionInputSeal {
1177 Validated,
1178}
1179
1180impl AttachFrontierCharges {
1181 #[must_use]
1183 pub const fn new(
1184 terminal: Option<RetainedRecordCharge>,
1185 attached: RetainedRecordCharge,
1186 ) -> Self {
1187 Self {
1188 terminal,
1189 attached,
1190 seal: LiveTransitionInputSeal::Validated,
1191 }
1192 }
1193
1194 const fn into_parts(self) -> (Option<RetainedRecordCharge>, RetainedRecordCharge) {
1195 let Self {
1196 terminal,
1197 attached,
1198 seal,
1199 } = self;
1200 match seal {
1201 LiveTransitionInputSeal::Validated => (terminal, attached),
1202 }
1203 }
1204}
1205
1206#[derive(Debug, PartialEq, Eq)]
1208pub struct LiveFrontierCommit<T> {
1209 operation: T,
1210 owner: LiveFrontierOwner,
1211}
1212
1213impl<T> LiveFrontierCommit<T> {
1214 #[must_use]
1216 pub const fn operation(&self) -> &T {
1217 &self.operation
1218 }
1219
1220 #[must_use]
1222 pub const fn owner(&self) -> &LiveFrontierOwner {
1223 &self.owner
1224 }
1225
1226 #[must_use]
1228 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
1229 (self.operation, self.owner)
1230 }
1231}
1232
1233#[derive(Debug, PartialEq, Eq)]
1235pub struct LiveFrontierFailure<T> {
1236 error: LiveFrontierError,
1237 operation: T,
1238 owner: LiveFrontierOwner,
1239}
1240
1241impl<T> LiveFrontierFailure<T> {
1242 #[must_use]
1244 pub const fn error(&self) -> LiveFrontierError {
1245 self.error
1246 }
1247
1248 #[must_use]
1250 pub fn into_parts(self) -> (T, LiveFrontierOwner) {
1251 (self.operation, self.owner)
1252 }
1253}
1254
1255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1257pub enum LiveFrontierError {
1258 Authority,
1260 Precedence(PrecedenceCondition),
1268 RetainedCharge,
1270 RetainedRecordLimit,
1272 Frontier,
1274 ClosureAccounting,
1276}
1277
1278pub type LiveFrontierResult<T> = Result<LiveFrontierCommit<T>, Box<LiveFrontierFailure<T>>>;
1280
1281pub fn apply_enrollment_frontier<F>(
1287 owner: LiveFrontierOwner,
1288 operation: super::super::EnrollmentCommit<F>,
1289 attached_charge: RetainedRecordCharge,
1290) -> LiveFrontierResult<super::super::EnrollmentCommit<F>> {
1291 let attached = operation.attached;
1292 if attached.conversation_id() != owner.frontiers.conversation_id() {
1293 return failure(owner, operation, LiveFrontierError::Authority);
1294 }
1295 let participant_id = attached.participant_id();
1296 let mut active = owner.frontiers.active_identities().participants().to_vec();
1297 if active
1298 .iter()
1299 .any(|participant| participant.participant_index() == participant_id)
1300 {
1301 return failure(owner, operation, LiveFrontierError::Authority);
1302 }
1303 active.push(FrontierParticipant::new(
1304 participant_id,
1305 operation.member.cursor(),
1306 FrontierBinding::Bound(attached.binding_epoch()),
1307 ));
1308 active.sort_unstable_by_key(|participant| participant.participant_index());
1309 let rows = [retained_attached(attached)];
1310 let Some(sequence) =
1311 enrollment_sequence(owner.frontiers.sequence().ledger(), attached.delivery_seq())
1312 else {
1313 return failure(owner, operation, LiveFrontierError::Frontier);
1314 };
1315 let Some(order) = enrollment_order(
1316 owner.frontiers.order().ledger(),
1317 attached.admission_order().transaction_order(),
1318 ) else {
1319 return failure(owner, operation, LiveFrontierError::Frontier);
1320 };
1321 transition(
1322 owner,
1323 operation,
1324 active,
1325 &rows,
1326 vec![attached_charge],
1327 sequence,
1328 order,
1329 )
1330}
1331
1332pub fn apply_attach_frontier<F, V>(
1339 owner: LiveFrontierOwner,
1340 operation: AttachCommit<F, V>,
1341 charges: AttachFrontierCharges,
1342) -> LiveFrontierResult<AttachCommit<F, V>> {
1343 let (terminal_charge, attached_charge) = charges.into_parts();
1344 let attached = operation.attached;
1345 if attached.conversation_id() != owner.frontiers.conversation_id() {
1346 return failure(owner, operation, LiveFrontierError::Authority);
1347 }
1348 let mut active = owner.frontiers.active_identities().participants().to_vec();
1349 let Some(participant) = active
1350 .iter_mut()
1351 .find(|participant| participant.participant_index() == attached.participant_id())
1352 else {
1353 return failure(owner, operation, LiveFrontierError::Authority);
1354 };
1355 let frontier_cursor = participant.cursor();
1356 *participant = FrontierParticipant::new(
1357 participant.participant_index(),
1358 operation.member.cursor(),
1359 FrontierBinding::Bound(attached.binding_epoch()),
1360 );
1361 let reopened = reopened_marker_anchors(
1362 &owner,
1363 attached.participant_id(),
1364 frontier_cursor,
1365 operation.member.cursor(),
1366 );
1367 with_reopened_marker_anchors(owner, operation, reopened, |owner, operation| {
1368 apply_attach_rows(owner, operation, active, terminal_charge, attached_charge)
1369 })
1370}
1371
1372fn apply_attach_rows<F, V>(
1374 owner: LiveFrontierOwner,
1375 operation: AttachCommit<F, V>,
1376 active: Vec<FrontierParticipant>,
1377 terminal_charge: Option<RetainedRecordCharge>,
1378 attached_charge: RetainedRecordCharge,
1379) -> LiveFrontierResult<AttachCommit<F, V>> {
1380 let attached = operation.attached;
1381 let current_sequence = owner.frontiers.sequence().ledger();
1382 let current_order = owner.frontiers.order().ledger();
1383 let (rows, keyed_charges, sequence, order) = match operation.transition {
1384 AttachTransition::Detached => {
1385 if terminal_charge.is_some() {
1386 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1387 }
1388 let Some(sequence) =
1389 detached_attach_sequence(current_sequence, attached.delivery_seq())
1390 else {
1391 return failure(owner, operation, LiveFrontierError::Frontier);
1392 };
1393 let Some(order) = detached_attach_order(
1394 current_order,
1395 attached.admission_order().transaction_order(),
1396 ) else {
1397 return failure(owner, operation, LiveFrontierError::Frontier);
1398 };
1399 (
1400 vec![retained_attached(attached)],
1401 vec![attached_charge],
1402 sequence,
1403 order,
1404 )
1405 }
1406 AttachTransition::Superseded { terminal } => {
1407 let Some(terminal_charge) = terminal_charge else {
1408 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1409 };
1410 let rows = vec![
1411 retained_terminal(terminal.into()),
1412 retained_attached(attached),
1413 ];
1414 let Some(sequence) = superseding_attach_sequence(current_sequence, &rows) else {
1415 return failure(owner, operation, LiveFrontierError::Frontier);
1416 };
1417 let Some(order) = superseding_attach_order(
1418 current_order,
1419 attached.admission_order().transaction_order(),
1420 ) else {
1421 return failure(owner, operation, LiveFrontierError::Frontier);
1422 };
1423 (
1424 rows,
1425 vec![terminal_charge, attached_charge],
1426 sequence,
1427 order,
1428 )
1429 }
1430 AttachTransition::FencedRecovery {
1431 prior_binding_epoch,
1432 composed_terminal,
1433 next_closure_state,
1434 } => {
1435 return apply_fenced_attach_frontier(
1436 owner,
1437 operation,
1438 terminal_charge,
1439 attached_charge,
1440 prior_binding_epoch,
1441 composed_terminal,
1442 next_closure_state,
1443 );
1444 }
1445 };
1446 transition(
1447 owner,
1448 operation,
1449 active,
1450 &rows,
1451 keyed_charges,
1452 sequence,
1453 order,
1454 )
1455}
1456
1457pub fn apply_detach_frontier<EF, V>(
1463 owner: LiveFrontierOwner,
1464 operation: CommittedDetachTransition<EF, V>,
1465 terminal_charge: RetainedRecordCharge,
1466) -> LiveFrontierResult<CommittedDetachTransition<EF, V>> {
1467 let terminal = operation.terminal();
1468 if terminal.conversation_id() != owner.frontiers.conversation_id() {
1469 return failure(owner, operation, LiveFrontierError::Authority);
1470 }
1471 let mut active = owner.frontiers.active_identities().participants().to_vec();
1472 let Some(participant) = active
1473 .iter_mut()
1474 .find(|participant| participant.participant_index() == terminal.participant_id())
1475 else {
1476 return failure(owner, operation, LiveFrontierError::Authority);
1477 };
1478 *participant = FrontierParticipant::new(
1479 participant.participant_index(),
1480 operation.member().cursor(),
1481 FrontierBinding::Detached(terminal.binding_epoch()),
1482 );
1483 let row = retained_terminal(terminal.into());
1484 let Some(sequence) = detach_sequence(owner.frontiers.sequence().ledger(), row.delivery_seq)
1485 else {
1486 return failure(owner, operation, LiveFrontierError::Frontier);
1487 };
1488 let Some(order) = detach_order(
1489 owner.frontiers.order().ledger(),
1490 row.admission_order.transaction_order(),
1491 ) else {
1492 return failure(owner, operation, LiveFrontierError::Frontier);
1493 };
1494 transition(
1495 owner,
1496 operation,
1497 active,
1498 &[row],
1499 vec![terminal_charge],
1500 sequence,
1501 order,
1502 )
1503}
1504
1505fn crossed_marker_anchors(
1515 owner: &LiveFrontierOwner,
1516 participant_id: crate::wire::ParticipantId,
1517 previous_cursor: crate::wire::DeliverySeq,
1518 through_seq: crate::wire::DeliverySeq,
1519) -> u64 {
1520 owner
1521 .frontiers
1522 .retained_marker_records()
1523 .iter()
1524 .filter(|record| {
1525 matches!(
1526 record.kind,
1527 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
1528 if participant_index == participant_id
1529 ) && previous_cursor < record.delivery_seq
1530 && record.delivery_seq <= through_seq
1531 })
1532 .fold(0_u64, |count, _| count.saturating_add(1))
1533}
1534
1535pub fn apply_participant_ack_frontier(
1549 mut owner: LiveFrontierOwner,
1550 operation: ParticipantAckCommit,
1551) -> LiveFrontierResult<ParticipantAckCommit> {
1552 let request = operation.outcome().request();
1553 let Some(current) = owner
1554 .frontiers
1555 .active_identities()
1556 .participants()
1557 .iter()
1558 .find(|participant| participant.participant_index() == request.participant_id)
1559 .copied()
1560 else {
1561 return failure(owner, operation, LiveFrontierError::Authority);
1562 };
1563 let crossed = crossed_marker_anchors(
1564 &owner,
1565 request.participant_id,
1566 current.cursor(),
1567 request.through_seq,
1568 );
1569 let accounting = if crossed == 0 {
1570 None
1571 } else {
1572 match accounting_after_marker_crossings(owner.closure_accounting, crossed) {
1573 Some(accounting) => Some(accounting),
1574 None => {
1575 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1576 }
1577 }
1578 };
1579 let participant = FrontierParticipant::new(
1580 request.participant_id,
1581 request.through_seq,
1582 current.binding(),
1583 );
1584 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1585 Ok(frontiers) => frontiers,
1586 Err(frontier_failure) => {
1587 let (frontiers, error) = *frontier_failure;
1588 owner.frontiers = frontiers;
1589 return failure(owner, operation, map_frontier_error(error));
1590 }
1591 };
1592 if let Some(accounting) = accounting {
1593 owner.closure_accounting = accounting;
1594 }
1595 Ok(LiveFrontierCommit { operation, owner })
1596}
1597
1598pub fn apply_nonzero_participant_ack_frontier(
1608 mut owner: LiveFrontierOwner,
1609 operation: NonzeroParticipantAckCommit,
1610) -> LiveFrontierResult<NonzeroParticipantAckCommit> {
1611 let request = operation.outcome().request();
1612 let Some(current) = owner
1613 .frontiers
1614 .active_identities()
1615 .participants()
1616 .iter()
1617 .find(|participant| participant.participant_index() == request.participant_id)
1618 .copied()
1619 else {
1620 return failure(owner, operation, LiveFrontierError::Authority);
1621 };
1622 let crossed = crossed_marker_anchors(
1625 &owner,
1626 request.participant_id,
1627 current.cursor(),
1628 request.through_seq,
1629 );
1630 let accounting = if crossed == 0 {
1631 None
1632 } else {
1633 match accounting_after_marker_crossings(owner.closure_accounting, crossed) {
1634 Some(accounting) => Some(accounting),
1635 None => {
1636 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1637 }
1638 }
1639 };
1640 let participant = FrontierParticipant::new(
1641 request.participant_id,
1642 request.through_seq,
1643 current.binding(),
1644 );
1645 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1646 Ok(frontiers) => frontiers,
1647 Err(frontier_failure) => {
1648 let (frontiers, error) = *frontier_failure;
1649 owner.frontiers = frontiers;
1650 return failure(owner, operation, map_frontier_error(error));
1651 }
1652 };
1653 if let Some(accounting) = accounting {
1654 owner.closure_accounting = accounting;
1655 }
1656 Ok(LiveFrontierCommit { operation, owner })
1657}
1658
1659pub fn apply_marker_ack_frontier(
1665 mut owner: LiveFrontierOwner,
1666 operation: MarkerAckCommit,
1667) -> LiveFrontierResult<MarkerAckCommit> {
1668 let request = operation.outcome().request();
1669 if !owner
1670 .frontiers
1671 .retained_marker_records()
1672 .iter()
1673 .any(|record| {
1674 record.delivery_seq == request.marker_delivery_seq
1675 && matches!(
1676 record.kind,
1677 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
1678 if participant_index == request.participant_id
1679 )
1680 })
1681 {
1682 return failure(owner, operation, LiveFrontierError::Authority);
1683 }
1684 let Some(current) = owner
1685 .frontiers
1686 .active_identities()
1687 .participants()
1688 .iter()
1689 .find(|participant| participant.participant_index() == request.participant_id)
1690 .copied()
1691 else {
1692 return failure(owner, operation, LiveFrontierError::Authority);
1693 };
1694 let Some(accounting) = accounting_after_marker_ack(owner.closure_accounting) else {
1695 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1696 };
1697 let participant = FrontierParticipant::new(
1698 request.participant_id,
1699 request.marker_delivery_seq,
1700 current.binding(),
1701 );
1702 owner.frontiers = match owner.frontiers.apply_live_identity(participant) {
1703 Ok(frontiers) => frontiers,
1704 Err(frontier_failure) => {
1705 let (frontiers, error) = *frontier_failure;
1706 owner.frontiers = frontiers;
1707 return failure(owner, operation, map_frontier_error(error));
1708 }
1709 };
1710 owner.closure_accounting = accounting;
1711 Ok(LiveFrontierCommit { operation, owner })
1712}
1713
1714fn apply_fenced_attach_frontier<F, V>(
1715 owner: LiveFrontierOwner,
1716 operation: AttachCommit<F, V>,
1717 terminal_charge: Option<RetainedRecordCharge>,
1718 attached_charge: RetainedRecordCharge,
1719 prior_binding_epoch: crate::wire::BindingEpoch,
1720 composed_terminal: Option<super::super::CommittedBindingTerminal>,
1721 next_closure_state: super::super::ClosureState,
1722) -> LiveFrontierResult<AttachCommit<F, V>> {
1723 let attached = operation.attached;
1724 let (rows, charges) = match (composed_terminal, terminal_charge) {
1725 (None, None) => (vec![retained_attached(attached)], vec![attached_charge]),
1726 (Some(terminal), Some(terminal_charge)) => (
1727 vec![retained_terminal(terminal), retained_attached(attached)],
1728 vec![terminal_charge, attached_charge],
1729 ),
1730 (None, Some(_)) | (Some(_), None) => {
1731 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1732 }
1733 };
1734 let participant = FrontierParticipant::new(
1735 attached.participant_id(),
1736 operation.member.cursor(),
1737 FrontierBinding::Bound(attached.binding_epoch()),
1738 );
1739 fenced_attach_transition(
1740 owner,
1741 operation,
1742 participant,
1743 prior_binding_epoch,
1744 next_closure_state,
1745 &rows,
1746 charges,
1747 )
1748}
1749
1750fn reopened_marker_anchors(
1768 owner: &LiveFrontierOwner,
1769 participant_id: crate::wire::ParticipantId,
1770 frontier_cursor: crate::wire::DeliverySeq,
1771 member_cursor: crate::wire::DeliverySeq,
1772) -> u64 {
1773 if member_cursor >= frontier_cursor {
1774 return 0;
1775 }
1776 crossed_marker_anchors(owner, participant_id, member_cursor, frontier_cursor)
1777}
1778
1779fn with_reopened_marker_anchors<T>(
1782 mut owner: LiveFrontierOwner,
1783 operation: T,
1784 reopened: u64,
1785 apply: impl FnOnce(LiveFrontierOwner, T) -> LiveFrontierResult<T>,
1786) -> LiveFrontierResult<T> {
1787 if reopened == 0 {
1788 return apply(owner, operation);
1789 }
1790 let unchanged = owner.closure_accounting;
1791 let Some(accounting) = accounting_after_marker_remint(unchanged, reopened) else {
1792 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1793 };
1794 owner.closure_accounting = accounting;
1795 apply(owner, operation).map_err(|mut refused| {
1796 refused.owner.closure_accounting = unchanged;
1797 refused
1798 })
1799}
1800
1801fn fenced_attach_transition<T>(
1802 mut owner: LiveFrontierOwner,
1803 operation: T,
1804 participant: FrontierParticipant,
1805 prior_binding_epoch: crate::wire::BindingEpoch,
1806 next_closure_state: super::super::ClosureState,
1807 rows: &[RetainedCausalRecord],
1808 charges: Vec<RetainedRecordCharge>,
1809) -> LiveFrontierResult<T> {
1810 if rows.len() != charges.len()
1811 || rows.iter().zip(&charges).any(|(row, charge)| {
1812 row.delivery_seq != charge.delivery_seq()
1813 || row.admission_order != charge.admission_order()
1814 || charge.encoded_charge().entries != 1
1815 })
1816 {
1817 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1818 }
1819 let resulting_len = owner
1820 .frontiers
1821 .retained_records()
1822 .len()
1823 .checked_add(rows.len());
1824 if resulting_len
1825 .and_then(|len| u64::try_from(len).ok())
1826 .is_none_or(|len| len > owner.retained_record_limit)
1827 {
1828 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1829 }
1830 let Some(accounting) =
1831 accounting_after_fenced_attach(owner.closure_accounting, &charges, next_closure_state)
1832 else {
1833 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1834 };
1835 owner.frontiers =
1836 match owner
1837 .frontiers
1838 .apply_live_fenced_attach(participant, prior_binding_epoch, rows)
1839 {
1840 Ok(frontiers) => frontiers,
1841 Err(frontier_failure) => {
1842 let (frontiers, error) = *frontier_failure;
1843 owner.frontiers = frontiers;
1844 return failure(owner, operation, map_frontier_error(error));
1845 }
1846 };
1847 owner.retained_charges.extend(charges);
1848 owner
1849 .retained_charges
1850 .sort_unstable_by_key(|charge| charge.delivery_seq());
1851 owner.closure_accounting = accounting;
1852 Ok(LiveFrontierCommit { operation, owner })
1853}
1854
1855fn transition<T>(
1856 mut owner: LiveFrontierOwner,
1857 operation: T,
1858 active: Vec<FrontierParticipant>,
1859 rows: &[RetainedCausalRecord],
1860 charges: Vec<RetainedRecordCharge>,
1861 sequence: SequenceLedger,
1862 order: OrderLedger,
1863) -> LiveFrontierResult<T> {
1864 if rows.len() != charges.len()
1865 || rows.iter().zip(&charges).any(|(row, charge)| {
1866 row.delivery_seq != charge.delivery_seq()
1867 || row.admission_order != charge.admission_order()
1868 || charge.encoded_charge().entries != 1
1869 })
1870 {
1871 return failure(owner, operation, LiveFrontierError::RetainedCharge);
1872 }
1873 let resulting_len = owner
1874 .frontiers
1875 .retained_records()
1876 .len()
1877 .checked_add(rows.len());
1878 if resulting_len
1879 .and_then(|len| u64::try_from(len).ok())
1880 .is_none_or(|len| len > owner.retained_record_limit)
1881 {
1882 return failure(owner, operation, LiveFrontierError::RetainedRecordLimit);
1883 }
1884 let Some(accounting) = accounting_after_rows(owner.closure_accounting, &charges) else {
1885 return failure(owner, operation, LiveFrontierError::ClosureAccounting);
1886 };
1887 owner.frontiers = match owner
1888 .frontiers
1889 .apply_live_transition(active, rows, sequence, order)
1890 {
1891 Ok(frontiers) => frontiers,
1892 Err(frontier_failure) => {
1893 let (frontiers, error) = *frontier_failure;
1894 owner.frontiers = frontiers;
1895 return failure(owner, operation, map_frontier_error(error));
1896 }
1897 };
1898 owner.retained_charges.extend(charges);
1899 owner.closure_accounting = accounting;
1900 Ok(LiveFrontierCommit { operation, owner })
1901}
1902
1903const fn map_frontier_error(error: LiveFrontierTransitionError) -> LiveFrontierError {
1904 match error {
1905 LiveFrontierTransitionError::Authority => LiveFrontierError::Authority,
1906 LiveFrontierTransitionError::Precedence(condition) => {
1907 LiveFrontierError::Precedence(condition)
1908 }
1909 LiveFrontierTransitionError::RecordPosition
1910 | LiveFrontierTransitionError::Exhausted
1911 | LiveFrontierTransitionError::ResultingFrontier => LiveFrontierError::Frontier,
1912 }
1913}
1914
1915fn failure<T, U>(
1916 owner: LiveFrontierOwner,
1917 operation: T,
1918 error: LiveFrontierError,
1919) -> Result<U, Box<LiveFrontierFailure<T>>> {
1920 Err(Box::new(LiveFrontierFailure {
1921 error,
1922 operation,
1923 owner,
1924 }))
1925}