1use alloc::{boxed::Box, vec::Vec};
10
11mod binding_fate_transition;
12pub(in crate::lifecycle) use binding_fate_transition::BindingFateFrontierPlan;
13
14use crate::{
15 algebra::ResourceVector,
16 outcome::{CandidatePhase, ClaimCounter, ParticipantStateCorruptReason},
17 wire::{
18 BindingEpoch, ClosureCheckedEnvelope, ConnectionIncarnation, ConversationId, DeliverySeq,
19 Generation, ParticipantId, TransactionOrder,
20 },
21};
22
23use super::{
24 AttachedLifecycleRecord, BindingOrigin, BindingState, ClosureAccounting, ClosureState,
25 CommittedBindingTerminal, Event, InitialEnrollmentClosureProjection,
26 InitialEnrollmentOperationCommit, LeaveCommitError, LiveMember, MarkerDelivery,
27 ObserverCheckedOperation, ObserverFloorDecision, ObserverProjection, OrderClaims, OrderHigh,
28 OrderLedger, ParticipantCursorProgress, PendingFinalization, PreparedLeaveAuthority,
29 RecoveryQuartetStatus, RecoverySequenceReserve, RemainingClosureDecision, SequenceClaims,
30 SequenceLedger, StoredEdge, check_observer_floor, check_remaining_closure,
31 operations::ordinary_record_projection::{
32 OrdinaryFixedPointPlan, OrdinaryProjectionError, OrdinaryProjectionFacts,
33 OrdinaryProjectionKernelDecision, OrdinaryRecordDrainFirst,
34 OrdinaryRecordProjectionDecision, OrdinaryRecordProjectionFailure,
35 OrdinaryRecordProjectionInput, ProjectedOrdinaryRecord, project_ordinary_fixed_point,
36 },
37};
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum ClaimFrontierCounter {
42 DeliverySequence,
44 TransactionOrder,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum ClaimFrontierInvalidReason {
51 NumericPosition,
53 CandidateKey,
55 LogicalOwner,
57 ProductRange,
59 RecoveryBlock,
61 AggregateLedger,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub struct ClaimFrontierError {
68 pub counter: ClaimFrontierCounter,
70 pub first_bad_position: u128,
72 pub reason: ClaimFrontierInvalidReason,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum FrontierBinding {
79 Bound(BindingEpoch),
81 Detached(BindingEpoch),
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct FrontierParticipant {
88 participant_index: ParticipantId,
89 cursor: DeliverySeq,
90 binding: FrontierBinding,
91}
92
93impl FrontierParticipant {
94 #[must_use]
96 pub const fn new(
97 participant_index: ParticipantId,
98 cursor: DeliverySeq,
99 binding: FrontierBinding,
100 ) -> Self {
101 Self {
102 participant_index,
103 cursor,
104 binding,
105 }
106 }
107
108 #[must_use]
110 pub const fn participant_index(self) -> ParticipantId {
111 self.participant_index
112 }
113
114 #[must_use]
116 pub const fn cursor(self) -> DeliverySeq {
117 self.cursor
118 }
119
120 #[must_use]
122 pub const fn binding(self) -> FrontierBinding {
123 self.binding
124 }
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct ActiveIdentityRanks {
130 participants: Vec<FrontierParticipant>,
131}
132
133impl ActiveIdentityRanks {
134 pub fn try_new(
142 participants: Vec<FrontierParticipant>,
143 high_watermark: DeliverySeq,
144 identity_slot_limit: u64,
145 ) -> Result<Self, ClaimFrontierError> {
146 if usize_to_u128(participants.len()) > u128::from(identity_slot_limit) {
147 return Err(sequence_error(
148 u128::from(identity_slot_limit),
149 ClaimFrontierInvalidReason::LogicalOwner,
150 ));
151 }
152 let mut previous = None;
153 for (rank, participant) in participants.iter().enumerate() {
154 if previous.is_some_and(|value| value >= participant.participant_index)
155 || participant.participant_index >= identity_slot_limit
156 || participant.cursor > high_watermark
157 {
158 return Err(sequence_error(
159 rank_index(rank),
160 ClaimFrontierInvalidReason::LogicalOwner,
161 ));
162 }
163 previous = Some(participant.participant_index);
164 }
165 Ok(Self { participants })
166 }
167
168 #[must_use]
170 pub fn participants(&self) -> &[FrontierParticipant] {
171 &self.participants
172 }
173
174 #[must_use]
176 pub fn len(&self) -> u64 {
177 usize_to_u64(self.participants.len())
178 }
179
180 #[must_use]
182 pub fn is_empty(&self) -> bool {
183 self.participants.is_empty()
184 }
185
186 fn contains(&self, participant_index: ParticipantId) -> bool {
187 self.participants
188 .binary_search_by_key(&participant_index, |participant| {
189 participant.participant_index
190 })
191 .is_ok()
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub struct BindingTerminalOwner {
198 pub participant_index: ParticipantId,
200 pub binding_epoch: BindingEpoch,
202}
203
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub enum SequenceDirectOwner {
207 MembershipExit {
209 participant_index: ParticipantId,
211 },
212 BindingTerminal(BindingTerminalOwner),
214}
215
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct MovableSequenceClaim {
219 pub delivery_seq: DeliverySeq,
221 pub owner: SequenceDirectOwner,
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
227pub enum TerminalProductSource {
228 Binding(BindingTerminalOwner),
230 RecoveryReplacement {
232 participant_index: ParticipantId,
234 binding_epoch: BindingEpoch,
236 },
237}
238
239impl TerminalProductSource {
240 #[must_use]
242 pub const fn recovery_replacement(
243 participant_index: ParticipantId,
244 binding_epoch: BindingEpoch,
245 ) -> Self {
246 Self::RecoveryReplacement {
247 participant_index,
248 binding_epoch,
249 }
250 }
251}
252
253#[derive(Clone, Copy, Debug, PartialEq, Eq)]
255pub enum MarkerProvenance {
256 NonProductM,
258 TerminalProduct {
260 terminal: TerminalProductSource,
262 affected_participant: ParticipantId,
264 },
265 ExitProduct {
267 exit_participant: ParticipantId,
269 remaining_participant: ParticipantId,
271 },
272}
273
274impl MarkerProvenance {
275 #[must_use]
277 pub const fn terminal_product(
278 terminal: TerminalProductSource,
279 affected_participant: ParticipantId,
280 ) -> Self {
281 Self::TerminalProduct {
282 terminal,
283 affected_participant,
284 }
285 }
286
287 #[must_use]
289 pub const fn exit_product(
290 exit_participant: ParticipantId,
291 remaining_participant: ParticipantId,
292 ) -> Self {
293 Self::ExitProduct {
294 exit_participant,
295 remaining_participant,
296 }
297 }
298}
299
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302pub enum RetainedCausalRecordKind {
303 BindingTerminal(BindingTerminalOwner),
305 MembershipExit {
307 participant_index: ParticipantId,
309 },
310 AttachLifecycle {
312 participant_index: ParticipantId,
314 binding_epoch: BindingEpoch,
316 },
317 OrdinaryRecord {
319 participant_index: ParticipantId,
321 },
322 CompactionMarker {
324 participant_index: ParticipantId,
326 provenance: MarkerProvenance,
328 },
329}
330
331#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333pub struct RetainedCausalRecord {
334 pub delivery_seq: DeliverySeq,
336 pub admission_order: super::AdmissionOrder,
338 pub kind: RetainedCausalRecordKind,
340}
341
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343enum HistoricalCausalKind {
344 BindingTerminal(BindingTerminalOwner),
345 MembershipExit(ParticipantId),
346}
347
348#[derive(Clone, Copy, Debug, PartialEq, Eq)]
360pub enum HistoricalCausalFactRestore {
361 BindingTerminal {
363 conversation_id: ConversationId,
365 participant_index: ParticipantId,
367 binding_epoch: BindingEpoch,
369 admission_order: super::AdmissionOrder,
371 },
372 MembershipExit {
374 conversation_id: ConversationId,
376 participant_index: ParticipantId,
378 admission_order: super::AdmissionOrder,
380 },
381}
382
383#[derive(Clone, Copy, Debug, PartialEq, Eq)]
392pub struct HistoricalMarkerDeliveryFactRestore {
393 pub conversation_id: ConversationId,
395 pub participant_index: ParticipantId,
397 pub marker_delivery_seq: DeliverySeq,
399 pub delivered_binding_epoch: BindingEpoch,
401}
402
403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405pub(super) struct HistoricalCausalAuthority {
406 conversation_id: ConversationId,
407 admission_order: super::AdmissionOrder,
408 kind: HistoricalCausalKind,
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
412pub(super) struct HistoricalMarkerDeliveryAuthority {
413 participant_index: ParticipantId,
414 marker_delivery_seq: DeliverySeq,
415 delivered_binding_epoch: BindingEpoch,
416}
417
418impl HistoricalMarkerDeliveryAuthority {
419 pub(super) const fn participant_index(self) -> ParticipantId {
421 self.participant_index
422 }
423
424 pub(super) const fn marker_delivery_seq(self) -> DeliverySeq {
426 self.marker_delivery_seq
427 }
428
429 pub(super) const fn delivered_binding_epoch(self) -> BindingEpoch {
431 self.delivered_binding_epoch
432 }
433}
434
435impl HistoricalCausalAuthority {
436 pub(super) const fn from_committed_terminal(terminal: super::CommittedBindingTerminal) -> Self {
437 Self {
438 conversation_id: terminal.conversation_id(),
439 admission_order: terminal.admission_order(),
440 kind: HistoricalCausalKind::BindingTerminal(BindingTerminalOwner {
441 participant_index: terminal.participant_id(),
442 binding_epoch: terminal.binding_epoch(),
443 }),
444 }
445 }
446
447 pub(super) const fn from_retired<EF, V, LF>(
448 retired: &super::RetiredIdentity<EF, V, LF>,
449 ) -> Self {
450 Self {
451 conversation_id: retired.conversation_id(),
452 admission_order: retired.left_admission_order(),
453 kind: HistoricalCausalKind::MembershipExit(retired.participant_id()),
454 }
455 }
456
457 const fn from_restore(fact: HistoricalCausalFactRestore) -> Self {
458 match fact {
459 HistoricalCausalFactRestore::BindingTerminal {
460 conversation_id,
461 participant_index,
462 binding_epoch,
463 admission_order,
464 } => Self {
465 conversation_id,
466 admission_order,
467 kind: HistoricalCausalKind::BindingTerminal(BindingTerminalOwner {
468 participant_index,
469 binding_epoch,
470 }),
471 },
472 HistoricalCausalFactRestore::MembershipExit {
473 conversation_id,
474 participant_index,
475 admission_order,
476 } => Self {
477 conversation_id,
478 admission_order,
479 kind: HistoricalCausalKind::MembershipExit(participant_index),
480 },
481 }
482 }
483
484 const fn describe(self) -> HistoricalCausalFactRestore {
487 match self.kind {
488 HistoricalCausalKind::BindingTerminal(owner) => {
489 HistoricalCausalFactRestore::BindingTerminal {
490 conversation_id: self.conversation_id,
491 participant_index: owner.participant_index,
492 binding_epoch: owner.binding_epoch,
493 admission_order: self.admission_order,
494 }
495 }
496 HistoricalCausalKind::MembershipExit(participant_index) => {
497 HistoricalCausalFactRestore::MembershipExit {
498 conversation_id: self.conversation_id,
499 participant_index,
500 admission_order: self.admission_order,
501 }
502 }
503 }
504 }
505}
506
507#[derive(Debug)]
508pub(super) struct ValidatedConversationHistory {
509 causal_authorities: Vec<HistoricalCausalAuthority>,
510 binding_origins: Vec<BindingOrigin>,
511 total: bool,
512}
513
514impl ValidatedConversationHistory {
515 pub(super) const fn empty() -> Self {
516 Self {
517 causal_authorities: Vec::new(),
518 binding_origins: Vec::new(),
519 total: false,
520 }
521 }
522
523 pub(super) const fn new(
524 causal_authorities: Vec<HistoricalCausalAuthority>,
525 binding_origins: Vec<BindingOrigin>,
526 ) -> Self {
527 Self {
528 causal_authorities,
529 binding_origins,
530 total: true,
531 }
532 }
533
534 pub(super) fn ordinary_origin(
535 &self,
536 conversation_id: ConversationId,
537 participant_index: ParticipantId,
538 binding_epoch: BindingEpoch,
539 ) -> Option<&BindingOrigin> {
540 self.binding_origins.iter().find(|origin| {
541 origin.conversation_id() == conversation_id
542 && origin.participant_id() == participant_index
543 && origin.binding_epoch() == binding_epoch
544 && origin.is_unfenced()
545 })
546 }
547}
548
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
551pub enum SequenceProductClass {
552 LiveTimesTerminal,
554 LiveTimesReplacementTerminal,
556 OtherLiveTimesExit,
558}
559
560#[derive(Clone, Copy, Debug, PartialEq, Eq)]
562pub enum MarkerSequenceOwner {
563 Marker,
565 ConditionalProduct(SequenceProductClass),
567}
568
569#[derive(Clone, Copy, Debug, PartialEq, Eq)]
571pub struct MarkerCandidateAuthority {
572 pub delivery_seq: DeliverySeq,
574 pub admission_order: super::AdmissionOrder,
576 pub target_binding: FrontierBinding,
578 pub provenance: MarkerProvenance,
580 pub abandoned_after: DeliverySeq,
582 pub abandoned_through: DeliverySeq,
584 pub physical_floor_at_decision: DeliverySeq,
586 pub current_owner: MarkerSequenceOwner,
588}
589
590#[derive(Clone, Copy, Debug, PartialEq, Eq)]
592pub enum ImmutableSequenceCandidate {
593 BindingTerminal {
595 delivery_seq: DeliverySeq,
597 admission_order: super::AdmissionOrder,
599 owner: BindingTerminalOwner,
601 },
602 Marker(MarkerCandidateAuthority),
604}
605
606impl ImmutableSequenceCandidate {
607 #[must_use]
609 pub const fn delivery_seq(self) -> DeliverySeq {
610 match self {
611 Self::BindingTerminal { delivery_seq, .. } => delivery_seq,
612 Self::Marker(candidate) => candidate.delivery_seq,
613 }
614 }
615
616 #[must_use]
618 pub const fn admission_order(self) -> super::AdmissionOrder {
619 match self {
620 Self::BindingTerminal {
621 admission_order, ..
622 } => admission_order,
623 Self::Marker(candidate) => candidate.admission_order,
624 }
625 }
626}
627
628#[derive(Clone, Copy, Debug, PartialEq, Eq)]
630pub struct TerminalProductRangeRestore {
631 pub start: DeliverySeq,
633 pub length: u64,
635 pub terminal: BindingTerminalOwner,
637}
638
639#[derive(Clone, Copy, Debug, PartialEq, Eq)]
641pub struct ReplacementTerminalProductRangeRestore {
642 pub start: DeliverySeq,
644 pub length: u64,
646}
647
648#[derive(Clone, Copy, Debug, PartialEq, Eq)]
650pub struct ExitProductRangeRestore {
651 pub start: DeliverySeq,
653 pub length: u64,
655 pub exit_participant: ParticipantId,
657}
658
659#[derive(Clone, Debug, Default, PartialEq, Eq)]
661pub struct SequenceProductRangesRestore {
662 pub live_times_terminal: Vec<TerminalProductRangeRestore>,
664 pub live_times_replacement_terminal: Option<ReplacementTerminalProductRangeRestore>,
666 pub other_live_times_exit: Vec<ExitProductRangeRestore>,
668}
669
670#[derive(Clone, Copy, Debug, PartialEq, Eq)]
672pub struct TerminalProductRange {
673 start: DeliverySeq,
674 length: u64,
675 terminal: BindingTerminalOwner,
676}
677
678impl TerminalProductRange {
679 #[must_use]
681 pub const fn start(self) -> DeliverySeq {
682 self.start
683 }
684
685 #[must_use]
687 pub const fn terminal(self) -> BindingTerminalOwner {
688 self.terminal
689 }
690
691 #[must_use]
693 pub const fn length(self) -> u64 {
694 self.length
695 }
696
697 #[must_use]
699 pub fn value_at_rank(self, active_rank: usize) -> Option<DeliverySeq> {
700 if usize_to_u128(active_rank) >= u128::from(self.length) {
701 return None;
702 }
703 checked_rank_value(self.start, active_rank)
704 }
705}
706
707#[derive(Clone, Copy, Debug, PartialEq, Eq)]
709pub struct ReplacementTerminalProductRange {
710 start: DeliverySeq,
711 length: u64,
712 participant_index: ParticipantId,
713 marker_delivery_seq: DeliverySeq,
714 prior_binding_epoch: BindingEpoch,
715}
716
717impl ReplacementTerminalProductRange {
718 #[must_use]
720 pub const fn start(self) -> DeliverySeq {
721 self.start
722 }
723
724 #[must_use]
726 pub const fn length(self) -> u64 {
727 self.length
728 }
729
730 #[must_use]
732 pub const fn participant_index(self) -> ParticipantId {
733 self.participant_index
734 }
735
736 #[must_use]
738 pub const fn marker_delivery_seq(self) -> DeliverySeq {
739 self.marker_delivery_seq
740 }
741
742 #[must_use]
744 pub const fn prior_binding_epoch(self) -> BindingEpoch {
745 self.prior_binding_epoch
746 }
747
748 #[must_use]
750 pub fn value_at_rank(self, active_rank: usize) -> Option<DeliverySeq> {
751 if usize_to_u128(active_rank) >= u128::from(self.length) {
752 return None;
753 }
754 checked_rank_value(self.start, active_rank)
755 }
756}
757
758#[derive(Clone, Copy, Debug, PartialEq, Eq)]
760pub struct ExitProductRange {
761 start: DeliverySeq,
762 length: u64,
763 exit_participant: ParticipantId,
764}
765
766impl ExitProductRange {
767 #[must_use]
769 pub const fn start(self) -> DeliverySeq {
770 self.start
771 }
772
773 #[must_use]
775 pub const fn exit_participant(self) -> ParticipantId {
776 self.exit_participant
777 }
778
779 #[must_use]
781 pub const fn length(self) -> u64 {
782 self.length
783 }
784
785 #[must_use]
789 pub fn value_for_affected_rank(
790 self,
791 active_identities: &ActiveIdentityRanks,
792 affected_rank: usize,
793 ) -> Option<DeliverySeq> {
794 let affected = active_identities.participants().get(affected_rank)?;
795 if usize_to_u128(affected_rank) >= usize_to_u128(active_identities.participants.len()) {
796 return None;
797 }
798 if affected.participant_index == self.exit_participant {
799 return None;
800 }
801 let exit_rank = active_identities
802 .participants()
803 .binary_search_by_key(&self.exit_participant, |participant| {
804 participant.participant_index
805 })
806 .ok()?;
807 let compact_rank = if affected_rank < exit_rank {
808 affected_rank
809 } else {
810 affected_rank.checked_sub(1)?
811 };
812 if usize_to_u128(compact_rank) >= u128::from(self.length) {
813 return None;
814 }
815 checked_rank_value(self.start, compact_rank)
816 }
817}
818
819#[derive(Clone, Debug, Default, PartialEq, Eq)]
821pub struct SequenceProductRanges {
822 live_times_terminal: Vec<TerminalProductRange>,
823 live_times_replacement_terminal: Option<ReplacementTerminalProductRange>,
824 other_live_times_exit: Vec<ExitProductRange>,
825}
826
827impl SequenceProductRanges {
828 #[must_use]
830 pub fn live_times_terminal(&self) -> &[TerminalProductRange] {
831 &self.live_times_terminal
832 }
833
834 #[must_use]
836 pub const fn live_times_replacement_terminal(&self) -> Option<ReplacementTerminalProductRange> {
837 self.live_times_replacement_terminal
838 }
839
840 #[must_use]
842 pub fn other_live_times_exit(&self) -> &[ExitProductRange] {
843 &self.other_live_times_exit
844 }
845}
846
847#[derive(Clone, Copy, Debug, PartialEq, Eq)]
849pub struct RecoverySequenceTerminalRestore {
850 pub delivery_seq: DeliverySeq,
852 pub owner: BindingTerminalOwner,
854}
855
856#[derive(Clone, Copy, Debug, PartialEq, Eq)]
862pub struct RecoverySequenceBlockRestore {
863 pub terminal: Option<RecoverySequenceTerminalRestore>,
865 pub recovery_attach_seq: DeliverySeq,
867 pub replacement_terminal_seq: DeliverySeq,
869}
870
871#[derive(Clone, Copy, Debug, PartialEq, Eq)]
873pub struct RecoverySequenceBlock {
874 terminal: Option<RecoverySequenceTerminalRestore>,
875 recovery_attach_seq: DeliverySeq,
876 replacement_terminal_seq: DeliverySeq,
877 participant_index: ParticipantId,
878 marker_delivery_seq: DeliverySeq,
879 recovered_binding_epoch: BindingEpoch,
880}
881
882impl RecoverySequenceBlock {
883 #[must_use]
885 pub const fn terminal(self) -> Option<RecoverySequenceTerminalRestore> {
886 self.terminal
887 }
888
889 #[must_use]
891 pub const fn recovery_attach_seq(self) -> DeliverySeq {
892 self.recovery_attach_seq
893 }
894
895 #[must_use]
897 pub const fn replacement_terminal_seq(self) -> DeliverySeq {
898 self.replacement_terminal_seq
899 }
900
901 #[must_use]
903 pub const fn participant_index(self) -> ParticipantId {
904 self.participant_index
905 }
906
907 #[must_use]
909 pub const fn marker_delivery_seq(self) -> DeliverySeq {
910 self.marker_delivery_seq
911 }
912
913 #[must_use]
915 pub const fn recovered_binding_epoch(self) -> BindingEpoch {
916 self.recovered_binding_epoch
917 }
918}
919
920#[derive(Clone, Debug, Default, PartialEq, Eq)]
922pub struct SequenceClaimFrontierRestore {
923 pub movable_claims: Vec<MovableSequenceClaim>,
925 pub immutable_candidates: Vec<ImmutableSequenceCandidate>,
927 pub products: SequenceProductRangesRestore,
929 pub recovery: Option<RecoverySequenceBlockRestore>,
931}
932
933#[derive(Clone, Debug, PartialEq, Eq)]
935pub struct SequenceClaimFrontier {
936 ledger: SequenceLedger,
937 movable_claims: Vec<MovableSequenceClaim>,
938 immutable_candidates: Vec<ImmutableSequenceCandidate>,
939 products: SequenceProductRanges,
940 recovery: Option<RecoverySequenceBlock>,
941}
942
943impl SequenceClaimFrontier {
944 #[must_use]
946 pub const fn ledger(&self) -> SequenceLedger {
947 self.ledger
948 }
949
950 #[must_use]
952 pub fn movable_claims(&self) -> &[MovableSequenceClaim] {
953 &self.movable_claims
954 }
955
956 #[must_use]
958 pub fn immutable_candidates(&self) -> &[ImmutableSequenceCandidate] {
959 &self.immutable_candidates
960 }
961
962 #[must_use]
964 pub const fn products(&self) -> &SequenceProductRanges {
965 &self.products
966 }
967
968 #[must_use]
970 pub const fn recovery(&self) -> Option<RecoverySequenceBlock> {
971 self.recovery
972 }
973}
974
975#[derive(Clone, Copy, Debug, PartialEq, Eq)]
977pub enum OrderDirectOwner {
978 ActiveBindingTerminal(BindingTerminalOwner),
980 MembershipExit {
982 participant_index: ParticipantId,
984 },
985}
986
987#[derive(Clone, Copy, Debug, PartialEq, Eq)]
989pub struct MovableOrderClaim {
990 pub transaction_order: TransactionOrder,
992 pub owner: OrderDirectOwner,
994}
995
996#[derive(Clone, Debug, PartialEq, Eq)]
1001pub struct ImmutableOrderCandidateMajorRestore {
1002 pub transaction_order: TransactionOrder,
1004 pub candidate_keys: Vec<super::AdmissionOrder>,
1006}
1007
1008#[derive(Clone, Debug, PartialEq, Eq)]
1010pub struct ImmutableOrderCandidateMajor {
1011 transaction_order: TransactionOrder,
1012 candidate_keys: Vec<super::AdmissionOrder>,
1013}
1014
1015impl ImmutableOrderCandidateMajor {
1016 #[must_use]
1018 pub const fn transaction_order(&self) -> TransactionOrder {
1019 self.transaction_order
1020 }
1021
1022 #[must_use]
1024 pub fn candidate_keys(&self) -> &[super::AdmissionOrder] {
1025 &self.candidate_keys
1026 }
1027}
1028
1029#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1031pub struct RecoveryOrderActiveBindingRestore {
1032 pub transaction_order: TransactionOrder,
1034 pub owner: BindingTerminalOwner,
1036}
1037
1038#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1040pub struct RecoveryOrderBlockRestore {
1041 pub active_binding: Option<RecoveryOrderActiveBindingRestore>,
1043 pub recovery_operation_order: TransactionOrder,
1045 pub replacement_terminal_order: TransactionOrder,
1047}
1048
1049#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1051pub struct RecoveryOrderBlock {
1052 active_binding: Option<RecoveryOrderActiveBindingRestore>,
1053 recovery_operation_order: TransactionOrder,
1054 replacement_terminal_order: TransactionOrder,
1055 participant_index: ParticipantId,
1056 marker_delivery_seq: DeliverySeq,
1057 recovered_binding_epoch: BindingEpoch,
1058}
1059
1060impl RecoveryOrderBlock {
1061 #[must_use]
1063 pub const fn active_binding(self) -> Option<RecoveryOrderActiveBindingRestore> {
1064 self.active_binding
1065 }
1066
1067 #[must_use]
1069 pub const fn recovery_operation_order(self) -> TransactionOrder {
1070 self.recovery_operation_order
1071 }
1072
1073 #[must_use]
1075 pub const fn replacement_terminal_order(self) -> TransactionOrder {
1076 self.replacement_terminal_order
1077 }
1078
1079 #[must_use]
1081 pub const fn participant_index(self) -> ParticipantId {
1082 self.participant_index
1083 }
1084
1085 #[must_use]
1087 pub const fn marker_delivery_seq(self) -> DeliverySeq {
1088 self.marker_delivery_seq
1089 }
1090
1091 #[must_use]
1093 pub const fn recovered_binding_epoch(self) -> BindingEpoch {
1094 self.recovered_binding_epoch
1095 }
1096}
1097
1098#[derive(Clone, Debug, Default, PartialEq, Eq)]
1100pub struct OrderClaimFrontierRestore {
1101 pub movable_claims: Vec<MovableOrderClaim>,
1103 pub immutable_candidates: Vec<ImmutableOrderCandidateMajorRestore>,
1105 pub recovery: Option<RecoveryOrderBlockRestore>,
1107}
1108
1109#[derive(Clone, Debug, PartialEq, Eq)]
1111pub struct OrderClaimFrontier {
1112 ledger: OrderLedger,
1113 movable_claims: Vec<MovableOrderClaim>,
1114 immutable_candidates: Vec<ImmutableOrderCandidateMajor>,
1115 recovery: Option<RecoveryOrderBlock>,
1116}
1117
1118impl OrderClaimFrontier {
1119 #[must_use]
1121 pub const fn ledger(&self) -> OrderLedger {
1122 self.ledger
1123 }
1124
1125 #[must_use]
1127 pub fn movable_claims(&self) -> &[MovableOrderClaim] {
1128 &self.movable_claims
1129 }
1130
1131 #[must_use]
1133 pub fn immutable_candidates(&self) -> &[ImmutableOrderCandidateMajor] {
1134 &self.immutable_candidates
1135 }
1136
1137 #[must_use]
1139 pub const fn recovery(&self) -> Option<RecoveryOrderBlock> {
1140 self.recovery
1141 }
1142}
1143
1144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1145enum RecoveryClaimPhase {
1146 PreFate,
1147 PostFate,
1148 RecoveredBound,
1149}
1150
1151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1159pub struct RecoveryClaimProvenance {
1160 participant_index: ParticipantId,
1161 marker_delivery_seq: DeliverySeq,
1162 prior_binding_epoch: BindingEpoch,
1163 current_binding_epoch: BindingEpoch,
1164 phase: RecoveryClaimPhase,
1165}
1166
1167impl RecoveryClaimProvenance {
1168 #[must_use]
1171 pub const fn from_stored_edge(edge: super::StoredEdge) -> Option<Self> {
1172 match edge {
1173 super::StoredEdge::MarkerDelivery(delivery) => Some(Self {
1174 participant_index: delivery.participant_id(),
1175 marker_delivery_seq: delivery.marker_delivery_seq(),
1176 prior_binding_epoch: delivery.binding_epoch(),
1177 current_binding_epoch: delivery.binding_epoch(),
1178 phase: RecoveryClaimPhase::PreFate,
1179 }),
1180 super::StoredEdge::ParticipantCursorProgress(progress) => {
1181 let Some(marker_delivery_seq) = progress.marker_delivery_seq() else {
1182 return None;
1183 };
1184 Some(Self {
1185 participant_index: progress.participant_id(),
1186 marker_delivery_seq,
1187 prior_binding_epoch: progress.binding_epoch(),
1188 current_binding_epoch: progress.binding_epoch(),
1189 phase: RecoveryClaimPhase::PreFate,
1190 })
1191 }
1192 super::StoredEdge::DetachedCredentialRecovery(recovery) => Some(Self {
1193 participant_index: recovery.participant_id(),
1194 marker_delivery_seq: recovery.marker_delivery_seq(),
1195 prior_binding_epoch: recovery.prior_binding_epoch(),
1196 current_binding_epoch: recovery.prior_binding_epoch(),
1197 phase: RecoveryClaimPhase::PostFate,
1198 }),
1199 _ => None,
1200 }
1201 }
1202
1203 #[must_use]
1205 pub const fn participant_index(self) -> ParticipantId {
1206 self.participant_index
1207 }
1208
1209 #[must_use]
1211 pub const fn marker_delivery_seq(self) -> DeliverySeq {
1212 self.marker_delivery_seq
1213 }
1214
1215 #[must_use]
1217 pub const fn prior_binding_epoch(self) -> BindingEpoch {
1218 self.prior_binding_epoch
1219 }
1220}
1221
1222#[derive(Clone, Debug, PartialEq, Eq)]
1224pub struct ClaimFrontiersRestore {
1225 pub conversation_id: ConversationId,
1227 pub active_identities: Vec<FrontierParticipant>,
1229 pub identity_slot_limit: u64,
1231 pub retained_floor: u128,
1233 pub retained_record_limit: u64,
1235 pub retained_records: Vec<RetainedCausalRecord>,
1237 pub active_marker_anchors: Vec<DeliverySeq>,
1243 pub historical_marker_deliveries: Vec<HistoricalMarkerDeliveryFactRestore>,
1249 pub historical_causal_facts: Vec<HistoricalCausalFactRestore>,
1251 pub sequence: SequenceClaimFrontierRestore,
1253 pub order: OrderClaimFrontierRestore,
1255 pub recovery_marker_delivery_seq: Option<DeliverySeq>,
1261}
1262
1263#[derive(Clone, Debug, PartialEq, Eq)]
1274pub(super) struct ClaimFrontiersDescription {
1275 pub(super) restore: ClaimFrontiersRestore,
1277 pub(super) sequence_ledger: SequenceLedger,
1279 pub(super) order_ledger: OrderLedger,
1281 pub(super) fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1284}
1285
1286#[derive(Debug)]
1293pub(super) struct ClaimFrontiersPrevalidated {
1294 conversation_id: ConversationId,
1295 active_identities: ActiveIdentityRanks,
1296 identity_slot_limit: u64,
1297 retained_floor: u128,
1298 retained_records: Vec<RetainedCausalRecord>,
1299 marker_records: Vec<RetainedCausalRecord>,
1300 historical_marker_deliveries: Vec<HistoricalMarkerDeliveryAuthority>,
1301 historical_causal_authorities: Vec<HistoricalCausalAuthority>,
1302 binding_origins: Vec<BindingOrigin>,
1303 sequence_restore: SequenceClaimFrontierRestore,
1304 order_restore: OrderClaimFrontierRestore,
1305 recovery_marker_delivery_seq: Option<DeliverySeq>,
1306 sequence_ledger: SequenceLedger,
1307 order_ledger: OrderLedger,
1308 issued_marker_record: Option<MarkerRecordRequest>,
1309 fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1315}
1316
1317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1318enum MarkerRecordUse {
1319 Planned(FrontierBinding),
1320 Delivered(FrontierBinding),
1321 Recovered {
1322 prior_binding_epoch: BindingEpoch,
1323 recovered_binding_epoch: BindingEpoch,
1324 },
1325}
1326
1327#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1329pub(super) struct MarkerRecordRequest {
1330 participant_index: ParticipantId,
1331 marker_delivery_seq: DeliverySeq,
1332 use_kind: MarkerRecordUse,
1333}
1334
1335impl MarkerRecordRequest {
1336 pub(super) const fn planned(
1338 participant_index: ParticipantId,
1339 marker_delivery_seq: DeliverySeq,
1340 target: FrontierBinding,
1341 ) -> Self {
1342 Self {
1343 participant_index,
1344 marker_delivery_seq,
1345 use_kind: MarkerRecordUse::Planned(target),
1346 }
1347 }
1348
1349 pub(super) const fn delivered(
1351 participant_index: ParticipantId,
1352 marker_delivery_seq: DeliverySeq,
1353 target: FrontierBinding,
1354 ) -> Self {
1355 Self {
1356 participant_index,
1357 marker_delivery_seq,
1358 use_kind: MarkerRecordUse::Delivered(target),
1359 }
1360 }
1361
1362 pub(super) const fn recovered(
1364 participant_index: ParticipantId,
1365 marker_delivery_seq: DeliverySeq,
1366 prior_binding_epoch: BindingEpoch,
1367 recovered_binding_epoch: BindingEpoch,
1368 ) -> Self {
1369 Self {
1370 participant_index,
1371 marker_delivery_seq,
1372 use_kind: MarkerRecordUse::Recovered {
1373 prior_binding_epoch,
1374 recovered_binding_epoch,
1375 },
1376 }
1377 }
1378}
1379
1380#[derive(Debug)]
1382pub(super) struct MarkerDrainCore {
1383 frontiers: ClaimFrontiers,
1384 candidate: ValidatedMarkerCandidate,
1385 record: ValidatedMarkerRecord,
1386}
1387
1388impl MarkerDrainCore {
1389 pub(super) fn into_parts(
1392 self,
1393 ) -> (
1394 ClaimFrontiers,
1395 ValidatedMarkerCandidate,
1396 ValidatedMarkerRecord,
1397 ) {
1398 (self.frontiers, self.candidate, self.record)
1399 }
1400}
1401
1402#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1404pub(super) enum MarkerDrainCoreError {
1405 NoCandidate,
1407 BindingTerminalFirst,
1409 SequenceNotNext,
1411 CausalMajorNotAllocated,
1413 MissingOrderCandidate,
1415 ResultingLedger,
1417}
1418
1419#[derive(Debug, PartialEq, Eq)]
1421pub struct ClaimFrontiers {
1422 conversation_id: ConversationId,
1423 active_identities: ActiveIdentityRanks,
1424 identity_slot_limit: u64,
1425 retained_floor: u128,
1426 retained_records: Vec<RetainedCausalRecord>,
1427 marker_records: Vec<RetainedCausalRecord>,
1428 fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1429 sequence: SequenceClaimFrontier,
1430 order: OrderClaimFrontier,
1431 historical_marker_deliveries: Vec<HistoricalMarkerDeliveryAuthority>,
1441 historical_causal_authorities: Vec<HistoricalCausalAuthority>,
1449}
1450
1451#[derive(Debug, PartialEq, Eq)]
1457pub struct InitialEnrollmentFrontierCommit<F> {
1458 operation: InitialEnrollmentOperationCommit<F>,
1459 frontiers: ClaimFrontiers,
1460 closure_accounting: ClosureAccounting,
1461 attached_charge: ResourceVector,
1462}
1463
1464impl<F> InitialEnrollmentFrontierCommit<F> {
1465 #[must_use]
1467 pub const fn operation(&self) -> &InitialEnrollmentOperationCommit<F> {
1468 &self.operation
1469 }
1470
1471 #[must_use]
1473 pub const fn frontiers(&self) -> &ClaimFrontiers {
1474 &self.frontiers
1475 }
1476
1477 #[must_use]
1479 pub const fn closure_accounting(&self) -> ClosureAccounting {
1480 self.closure_accounting
1481 }
1482
1483 #[must_use]
1485 pub const fn attached_charge(&self) -> ResourceVector {
1486 self.attached_charge
1487 }
1488
1489 #[allow(
1491 dead_code,
1492 reason = "the next conversation event body consumes this sealed operation/frontier unit"
1493 )]
1494 pub(in crate::lifecycle) fn into_conversation_parts(
1495 self,
1496 ) -> (
1497 InitialEnrollmentOperationCommit<F>,
1498 ClaimFrontiers,
1499 ClosureAccounting,
1500 ResourceVector,
1501 ) {
1502 (
1503 self.operation,
1504 self.frontiers,
1505 self.closure_accounting,
1506 self.attached_charge,
1507 )
1508 }
1509}
1510
1511#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1513pub enum InitialEnrollmentFrontierError {
1514 AttachedChargeMismatch {
1516 expected: ResourceVector,
1518 actual: ResourceVector,
1520 },
1521 EnrollmentShape,
1523 LedgerShape,
1525 ClosureProjection,
1527 PositionOverflow,
1529 FrontierInvariant,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1535pub struct InitialEnrollmentFrontierFailure<F> {
1536 operation: InitialEnrollmentOperationCommit<F>,
1537 error: InitialEnrollmentFrontierError,
1538}
1539
1540impl<F> InitialEnrollmentFrontierFailure<F> {
1541 #[must_use]
1543 pub const fn error(&self) -> InitialEnrollmentFrontierError {
1544 self.error
1545 }
1546
1547 #[allow(
1549 dead_code,
1550 reason = "the conversation decision layer recovers or terminalizes the speculative enrollment"
1551 )]
1552 pub(in crate::lifecycle) fn into_operation(self) -> InitialEnrollmentOperationCommit<F> {
1553 self.operation
1554 }
1555}
1556
1557#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1559pub enum PrepareLeaveAuthorityError {
1560 Conversation,
1562 Identity,
1564 Binding,
1566 ImmutablePrefix,
1568 PendingCandidate,
1570 MembershipExitClaim,
1572 ActiveBindingClaim,
1574 OrderCapacity,
1576 ResultingOrderLedger,
1578}
1579
1580#[derive(Debug, PartialEq, Eq)]
1586pub struct ValidatedMarkerCandidate {
1587 conversation_id: ConversationId,
1588 candidate: MarkerCandidateAuthority,
1589 seal: MarkerAuthoritySeal,
1590}
1591
1592#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1595pub(super) struct FencedMarkerSourceRecord {
1596 pub(super) conversation_id: ConversationId,
1597 pub(super) delivery_seq: DeliverySeq,
1598 pub(super) admission_order: super::AdmissionOrder,
1599 pub(super) participant_id: ParticipantId,
1600 pub(super) provenance: MarkerProvenance,
1601 pub(super) target_binding: FrontierBinding,
1602}
1603
1604#[derive(Debug, PartialEq, Eq)]
1609pub struct ValidatedMarkerRecord {
1610 conversation_id: ConversationId,
1611 record: RetainedCausalRecord,
1612 provenance: MarkerProvenance,
1613 target_binding: FrontierBinding,
1614 occurrence: MarkerRecordOccurrence,
1615 seal: MarkerAuthoritySeal,
1616}
1617
1618#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1625pub(super) enum MarkerRecordOccurrence {
1626 Undelivered,
1628 Delivered,
1630}
1631
1632#[derive(Debug, PartialEq, Eq)]
1633enum MarkerAuthoritySeal {
1634 Validated,
1635}
1636
1637#[doc(hidden)]
1643pub fn with_validated_marker_record_type<F>(probe: F)
1644where
1645 F: FnOnce(ValidatedMarkerRecord),
1646{
1647 drop(probe);
1648}
1649
1650impl ValidatedMarkerRecord {
1651 pub(super) const fn consume(self) {
1653 match self.seal {
1654 MarkerAuthoritySeal::Validated => {}
1655 }
1656 }
1657
1658 #[must_use]
1660 pub const fn conversation_id(&self) -> ConversationId {
1661 self.conversation_id
1662 }
1663
1664 #[must_use]
1666 pub const fn participant_id(&self) -> ParticipantId {
1667 self.record.admission_order.participant_index()
1668 }
1669
1670 #[must_use]
1672 pub const fn delivery_seq(&self) -> DeliverySeq {
1673 self.record.delivery_seq
1674 }
1675
1676 #[must_use]
1678 pub const fn admission_order(&self) -> super::AdmissionOrder {
1679 self.record.admission_order
1680 }
1681
1682 #[must_use]
1684 pub const fn provenance(&self) -> MarkerProvenance {
1685 self.provenance
1686 }
1687
1688 #[must_use]
1690 pub const fn target_binding(&self) -> FrontierBinding {
1691 self.target_binding
1692 }
1693
1694 #[must_use]
1696 pub const fn binding_epoch(&self) -> BindingEpoch {
1697 binding_epoch(self.target_binding)
1698 }
1699
1700 #[must_use]
1702 pub(super) const fn occurrence(&self) -> MarkerRecordOccurrence {
1703 self.occurrence
1704 }
1705
1706 #[cfg(any(test, feature = "test-support"))]
1708 pub(super) const fn delivered_for_test(mut self) -> Self {
1709 self.occurrence = MarkerRecordOccurrence::Delivered;
1710 self
1711 }
1712}
1713
1714impl ValidatedMarkerCandidate {
1715 pub(super) const fn consume(self) {
1717 match self.seal {
1718 MarkerAuthoritySeal::Validated => {}
1719 }
1720 }
1721
1722 #[must_use]
1724 pub(super) const fn conversation_id(&self) -> ConversationId {
1725 self.conversation_id
1726 }
1727
1728 #[must_use]
1730 pub(super) const fn participant_index(&self) -> ParticipantId {
1731 self.candidate.admission_order.participant_index()
1732 }
1733
1734 #[must_use]
1736 pub(super) const fn participant_id(&self) -> ParticipantId {
1737 self.participant_index()
1738 }
1739
1740 #[must_use]
1742 pub(super) const fn delivery_seq(&self) -> DeliverySeq {
1743 self.candidate.delivery_seq
1744 }
1745
1746 #[must_use]
1748 pub(super) const fn target_binding(&self) -> FrontierBinding {
1749 self.candidate.target_binding
1750 }
1751
1752 #[must_use]
1754 pub(super) const fn provenance(&self) -> MarkerProvenance {
1755 self.candidate.provenance
1756 }
1757
1758 pub(super) const fn abandoned_after(&self) -> DeliverySeq {
1759 self.candidate.abandoned_after
1760 }
1761
1762 pub(super) const fn abandoned_through(&self) -> DeliverySeq {
1763 self.candidate.abandoned_through
1764 }
1765
1766 pub(super) const fn physical_floor_at_decision(&self) -> DeliverySeq {
1767 self.candidate.physical_floor_at_decision
1768 }
1769}
1770
1771#[derive(Clone, Copy)]
1772struct InitialEnrollmentFrontierShape {
1773 conversation_id: ConversationId,
1774 binding_epoch: BindingEpoch,
1775 identity_slot_limit: u64,
1776 retained_floor: u128,
1777 attached: AttachedLifecycleRecord,
1778 order_ledger: OrderLedger,
1779 sequence_ledger: SequenceLedger,
1780 closure_accounting: ClosureAccounting,
1781}
1782
1783#[derive(Clone, Copy)]
1784struct InitialFrontierPositions {
1785 terminal_sequence: DeliverySeq,
1786 exit_sequence: DeliverySeq,
1787 product_sequence: DeliverySeq,
1788 active_terminal_order: TransactionOrder,
1789 exit_order: TransactionOrder,
1790}
1791
1792fn initial_enrollment_frontier_shape<F>(
1793 operation: &InitialEnrollmentOperationCommit<F>,
1794 attached_charge: ResourceVector,
1795) -> Result<InitialEnrollmentFrontierShape, InitialEnrollmentFrontierError> {
1796 let projection = operation.closure_projection();
1797 let expected_charge = projection.resulting_retained_charge();
1798 if attached_charge != expected_charge {
1799 return Err(InitialEnrollmentFrontierError::AttachedChargeMismatch {
1800 expected: expected_charge,
1801 actual: attached_charge,
1802 });
1803 }
1804 let enrollment = operation.enrollment();
1805 let attached = enrollment.attached;
1806 let BindingState::Bound(binding) = enrollment.binding_state else {
1807 return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1808 };
1809 if enrollment.member.participant_id() != 0
1810 || enrollment.member.cursor() != 0
1811 || binding.participant_id != 0
1812 || attached.participant_id() != 0
1813 || binding.conversation_id != enrollment.member.conversation_id()
1814 || attached.conversation_id() != binding.conversation_id
1815 || attached.binding_epoch() != binding.binding_epoch
1816 || projection.participant_index() != 0
1817 || projection.binding_epoch() != binding.binding_epoch
1818 || projection.identity_slots() == 0
1819 {
1820 return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1821 }
1822 let admission_order = attached.admission_order();
1823 let order_ledger = operation.order().resulting();
1824 let sequence_ledger = operation.sequence().resulting();
1825 let order_claims = order_ledger.claims();
1826 let sequence_claims = sequence_ledger.claims();
1827 if operation.order().major() != admission_order.transaction_order()
1828 || !matches!(order_ledger.high(), OrderHigh::Allocated(value) if value == admission_order.transaction_order())
1829 || order_claims.active_binding_terminals() != 1
1830 || order_claims.membership_exits() != 1
1831 || order_claims.recovery_operation()
1832 || order_claims.recovery_replacement_terminal()
1833 || sequence_ledger.high_watermark() != attached.delivery_seq()
1834 || sequence_claims.live_members() != 1
1835 || sequence_claims.binding_terminals() != 1
1836 || sequence_claims.markers() != 0
1837 || sequence_claims.recovery() != RecoverySequenceReserve::None
1838 || admission_order.candidate_phase() != CandidatePhase::AttachLifecycle
1839 {
1840 return Err(InitialEnrollmentFrontierError::LedgerShape);
1841 }
1842 Ok(InitialEnrollmentFrontierShape {
1843 conversation_id: binding.conversation_id,
1844 binding_epoch: binding.binding_epoch,
1845 identity_slot_limit: projection.identity_slots(),
1846 retained_floor: projection.resulting_floor(),
1847 attached,
1848 order_ledger,
1849 sequence_ledger,
1850 closure_accounting: validate_initial_enrollment_closure(operation, projection, attached)?,
1851 })
1852}
1853
1854fn validate_initial_enrollment_closure<F>(
1855 operation: &InitialEnrollmentOperationCommit<F>,
1856 projection: &InitialEnrollmentClosureProjection,
1857 attached: AttachedLifecycleRecord,
1858) -> Result<ClosureAccounting, InitialEnrollmentFrontierError> {
1859 let accounting = projection.resulting_closure_accounting();
1860 let state_matches = match accounting.state() {
1861 ClosureState::Clear => {
1862 projection.debt().is_zero()
1863 && projection.remaining_recovery_claim() == ResourceVector::default()
1864 }
1865 ClosureState::Owed {
1866 debt,
1867 edge: StoredEdge::ObserverProjection(observer),
1868 } => {
1869 debt.value() == projection.debt()
1870 && observer == ObserverProjection::new(attached.delivery_seq())
1871 && projection.remaining_recovery_claim() == accounting.edge_k_remaining()
1872 }
1873 ClosureState::Owed { .. } => false,
1874 };
1875 if projection.resulting_floor() != 1
1876 || projection.resulting_floor() != operation.observer_floor().cap_floor()
1877 || operation.observer_floor().observer_progress() != 0
1878 || projection.recovery_quartet() != RecoveryQuartetStatus::None
1879 || !projection.new_marker_candidates().is_empty()
1880 || accounting.marker_capacity_credits() != 0
1881 || accounting.marker_anchors() != 0
1882 || accounting.edge_sequence_claims() != 0
1883 || accounting.edge_order_position_claims() != 0
1884 || accounting.baseline() != projection.resulting_baseline()
1885 || !state_matches
1886 {
1887 Err(InitialEnrollmentFrontierError::ClosureProjection)
1888 } else {
1889 Ok(accounting)
1890 }
1891}
1892
1893fn initial_frontier_positions(
1894 attached: AttachedLifecycleRecord,
1895) -> Result<InitialFrontierPositions, InitialEnrollmentFrontierError> {
1896 let terminal_sequence = attached
1897 .delivery_seq()
1898 .checked_add(1)
1899 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1900 let exit_sequence = terminal_sequence
1901 .checked_add(1)
1902 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1903 let product_sequence = exit_sequence
1904 .checked_add(1)
1905 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1906 let active_terminal_order = attached
1907 .admission_order()
1908 .transaction_order()
1909 .checked_add(1)
1910 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1911 let exit_order = active_terminal_order
1912 .checked_add(1)
1913 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1914 Ok(InitialFrontierPositions {
1915 terminal_sequence,
1916 exit_sequence,
1917 product_sequence,
1918 active_terminal_order,
1919 exit_order,
1920 })
1921}
1922
1923fn initial_sequence_frontier(
1924 shape: &InitialEnrollmentFrontierShape,
1925 positions: InitialFrontierPositions,
1926 terminal: BindingTerminalOwner,
1927) -> SequenceClaimFrontier {
1928 SequenceClaimFrontier {
1929 ledger: shape.sequence_ledger,
1930 movable_claims: alloc::vec![
1931 MovableSequenceClaim {
1932 delivery_seq: positions.terminal_sequence,
1933 owner: SequenceDirectOwner::BindingTerminal(terminal),
1934 },
1935 MovableSequenceClaim {
1936 delivery_seq: positions.exit_sequence,
1937 owner: SequenceDirectOwner::MembershipExit {
1938 participant_index: 0,
1939 },
1940 },
1941 ],
1942 immutable_candidates: Vec::new(),
1943 products: SequenceProductRanges {
1944 live_times_terminal: alloc::vec![TerminalProductRange {
1945 start: positions.product_sequence,
1946 length: 1,
1947 terminal,
1948 }],
1949 live_times_replacement_terminal: None,
1950 other_live_times_exit: Vec::new(),
1951 },
1952 recovery: None,
1953 }
1954}
1955
1956fn initial_order_frontier(
1957 shape: &InitialEnrollmentFrontierShape,
1958 positions: InitialFrontierPositions,
1959 terminal: BindingTerminalOwner,
1960) -> OrderClaimFrontier {
1961 OrderClaimFrontier {
1962 ledger: shape.order_ledger,
1963 movable_claims: alloc::vec![
1964 MovableOrderClaim {
1965 transaction_order: positions.active_terminal_order,
1966 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
1967 },
1968 MovableOrderClaim {
1969 transaction_order: positions.exit_order,
1970 owner: OrderDirectOwner::MembershipExit {
1971 participant_index: 0,
1972 },
1973 },
1974 ],
1975 immutable_candidates: Vec::new(),
1976 recovery: None,
1977 }
1978}
1979
1980fn build_initial_enrollment_frontiers(
1981 shape: &InitialEnrollmentFrontierShape,
1982) -> Result<ClaimFrontiers, InitialEnrollmentFrontierError> {
1983 let positions = initial_frontier_positions(shape.attached)?;
1984 let terminal = BindingTerminalOwner {
1985 participant_index: 0,
1986 binding_epoch: shape.binding_epoch,
1987 };
1988 let sequence = initial_sequence_frontier(shape, positions, terminal);
1989 let order = initial_order_frontier(shape, positions, terminal);
1990 validate_cross_counter(&sequence, &order)
1991 .map_err(|_| InitialEnrollmentFrontierError::FrontierInvariant)?;
1992 Ok(ClaimFrontiers {
1993 historical_marker_deliveries: alloc::vec::Vec::new(),
1995 historical_causal_authorities: alloc::vec::Vec::new(),
1997 conversation_id: shape.conversation_id,
1998 active_identities: ActiveIdentityRanks {
1999 participants: alloc::vec![FrontierParticipant::new(
2000 0,
2001 0,
2002 FrontierBinding::Bound(shape.binding_epoch),
2003 )],
2004 },
2005 identity_slot_limit: shape.identity_slot_limit,
2006 retained_floor: shape.retained_floor,
2007 retained_records: alloc::vec![RetainedCausalRecord {
2008 delivery_seq: shape.attached.delivery_seq(),
2009 admission_order: shape.attached.admission_order(),
2010 kind: RetainedCausalRecordKind::AttachLifecycle {
2011 participant_index: 0,
2012 binding_epoch: shape.binding_epoch,
2013 },
2014 }],
2015 marker_records: Vec::new(),
2016 fenced_marker_issued: None,
2017 sequence,
2018 order,
2019 })
2020}
2021
2022#[derive(Clone, Copy)]
2023enum LeaveSequenceUnit {
2024 Direct(MovableSequenceClaim),
2025 TerminalProduct(TerminalProductRange),
2026 ReplacementProduct(ReplacementTerminalProductRange),
2027 ExitProduct(ExitProductRange),
2028 Recovery(RecoverySequenceBlock),
2029}
2030
2031impl LeaveSequenceUnit {
2032 fn original_start(self) -> DeliverySeq {
2033 match self {
2034 Self::Direct(claim) => claim.delivery_seq,
2035 Self::TerminalProduct(range) => range.start,
2036 Self::ReplacementProduct(range) => range.start,
2037 Self::ExitProduct(range) => range.start,
2038 Self::Recovery(block) => block_start_validated_sequence(block),
2039 }
2040 }
2041}
2042
2043fn allocate_leave_sequence_range(
2044 cursor: &mut Option<DeliverySeq>,
2045 length: u64,
2046) -> Result<DeliverySeq, LeaveCommitError> {
2047 let Some(start) = *cursor else {
2048 return Err(LeaveCommitError::ResultingFrontier);
2049 };
2050 let end = u128::from(start)
2051 .checked_add(u128::from(length))
2052 .and_then(|value| value.checked_sub(1))
2053 .ok_or(LeaveCommitError::ResultingFrontier)?;
2054 if end > u128::from(u64::MAX) {
2055 return Err(LeaveCommitError::ResultingFrontier);
2056 }
2057 *cursor = u64::try_from(end + 1).ok();
2058 Ok(start)
2059}
2060
2061#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2075pub enum PrecedenceCondition {
2076 BindingTerminal,
2080 MarkerDrain {
2082 settlement_epoch: DeliverySeq,
2090 },
2091 FencedRecovery,
2097 Unclassified,
2101}
2102
2103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2105pub(in crate::lifecycle) enum LiveFrontierTransitionError {
2106 Authority,
2108 Precedence(PrecedenceCondition),
2113 RecordPosition,
2115 Exhausted,
2117 ResultingFrontier,
2119}
2120
2121fn precedence(
2123 frontiers: ClaimFrontiers,
2124 condition: PrecedenceCondition,
2125) -> Box<(ClaimFrontiers, LiveFrontierTransitionError)> {
2126 Box::new((
2127 frontiers,
2128 LiveFrontierTransitionError::Precedence(condition),
2129 ))
2130}
2131
2132fn select_retained_marker_records(records: &[RetainedCausalRecord]) -> Vec<RetainedCausalRecord> {
2133 records
2134 .iter()
2135 .copied()
2136 .filter(|record| {
2137 matches!(
2138 record.kind,
2139 RetainedCausalRecordKind::CompactionMarker { .. }
2140 )
2141 })
2142 .collect()
2143}
2144
2145impl ClaimFrontiers {
2146 pub(super) fn historical_marker_deliveries(&self) -> &[HistoricalMarkerDeliveryAuthority] {
2152 &self.historical_marker_deliveries
2153 }
2154
2155 #[must_use]
2168 pub(in crate::lifecycle) fn describe(
2169 &self,
2170 retained_record_limit: u64,
2171 ) -> ClaimFrontiersDescription {
2172 let conversation_id = self.conversation_id;
2173 ClaimFrontiersDescription {
2174 restore: ClaimFrontiersRestore {
2175 conversation_id,
2176 active_identities: self.active_identities.participants().to_vec(),
2177 identity_slot_limit: self.identity_slot_limit,
2178 retained_floor: self.retained_floor,
2179 retained_record_limit,
2180 retained_records: self.retained_records.clone(),
2181 active_marker_anchors: self
2182 .marker_records
2183 .iter()
2184 .map(|record| record.delivery_seq)
2185 .collect(),
2186 historical_marker_deliveries: self
2187 .historical_marker_deliveries()
2188 .iter()
2189 .map(|authority| HistoricalMarkerDeliveryFactRestore {
2190 conversation_id,
2191 participant_index: authority.participant_index(),
2192 marker_delivery_seq: authority.marker_delivery_seq(),
2193 delivered_binding_epoch: authority.delivered_binding_epoch(),
2194 })
2195 .collect(),
2196 historical_causal_facts: self
2197 .historical_causal_authorities
2198 .iter()
2199 .copied()
2200 .map(HistoricalCausalAuthority::describe)
2201 .collect(),
2202 sequence: describe_sequence_frontier(&self.sequence),
2203 order: describe_order_frontier(&self.order),
2204 recovery_marker_delivery_seq: self
2205 .sequence
2206 .recovery
2207 .map(RecoverySequenceBlock::marker_delivery_seq),
2208 },
2209 sequence_ledger: self.sequence.ledger,
2210 order_ledger: self.order.ledger,
2211 fenced_marker_issued: self.fenced_marker_issued,
2212 }
2213 }
2214
2215 pub(in crate::lifecycle) fn prevalidate_description(
2237 description: ClaimFrontiersDescription,
2238 history: &ValidatedConversationHistory,
2239 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2240 let ClaimFrontiersDescription {
2241 restore,
2242 sequence_ledger,
2243 order_ledger,
2244 fenced_marker_issued,
2245 } = description;
2246 let mut prevalidated =
2247 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, history)?;
2248 prevalidated.fenced_marker_issued = fenced_marker_issued;
2249 Ok(prevalidated)
2250 }
2251
2252 pub fn from_initial_enrollment<F>(
2267 operation: InitialEnrollmentOperationCommit<F>,
2268 attached_charge: ResourceVector,
2269 ) -> Result<InitialEnrollmentFrontierCommit<F>, Box<InitialEnrollmentFrontierFailure<F>>> {
2270 let shape = match initial_enrollment_frontier_shape(&operation, attached_charge) {
2271 Ok(shape) => shape,
2272 Err(error) => {
2273 return Err(Box::new(InitialEnrollmentFrontierFailure {
2274 operation,
2275 error,
2276 }));
2277 }
2278 };
2279 let closure_accounting = shape.closure_accounting;
2280 let frontiers = match build_initial_enrollment_frontiers(&shape) {
2281 Ok(frontiers) => frontiers,
2282 Err(error) => {
2283 return Err(Box::new(InitialEnrollmentFrontierFailure {
2284 operation,
2285 error,
2286 }));
2287 }
2288 };
2289 Ok(InitialEnrollmentFrontierCommit {
2290 operation,
2291 frontiers,
2292 closure_accounting,
2293 attached_charge,
2294 })
2295 }
2296
2297 pub fn restore(
2310 restore: ClaimFrontiersRestore,
2311 sequence_ledger: SequenceLedger,
2312 order_ledger: OrderLedger,
2313 ) -> Result<Self, ParticipantStateCorruptReason> {
2314 let history = ValidatedConversationHistory::empty();
2315 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)?
2316 .finish(None)
2317 }
2318
2319 #[cfg(any(test, feature = "test-support"))]
2322 pub(super) fn prevalidate(
2323 restore: ClaimFrontiersRestore,
2324 sequence_ledger: SequenceLedger,
2325 order_ledger: OrderLedger,
2326 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2327 let history = ValidatedConversationHistory::empty();
2328 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)
2329 }
2330
2331 pub(super) fn prevalidate_with_history(
2332 restore: ClaimFrontiersRestore,
2333 sequence_ledger: SequenceLedger,
2334 order_ledger: OrderLedger,
2335 history: &ValidatedConversationHistory,
2336 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2337 validate_sequence_numeric(&restore.sequence, sequence_ledger).map_err(corrupt_frontier)?;
2338 validate_order_numeric(&restore.order, order_ledger).map_err(corrupt_frontier)?;
2339 validate_unique_candidate_keys(
2340 &restore.sequence.immutable_candidates,
2341 &restore.retained_records,
2342 )?;
2343 validate_bounded_shape(&restore, sequence_ledger).map_err(corrupt_frontier)?;
2344 let active_identities = ActiveIdentityRanks::try_new(
2345 restore.active_identities,
2346 sequence_ledger.high_watermark(),
2347 restore.identity_slot_limit,
2348 )
2349 .map_err(corrupt_frontier)?;
2350 let retained_records = validated_retained_records(
2351 restore.retained_records,
2352 restore.retained_floor,
2353 restore.retained_record_limit,
2354 restore.identity_slot_limit,
2355 sequence_ledger,
2356 )
2357 .map_err(corrupt_frontier)?;
2358 let historical_causal_authorities = validated_historical_authorities(
2359 restore.historical_causal_facts,
2360 restore.conversation_id,
2361 restore.identity_slot_limit,
2362 sequence_ledger,
2363 history,
2364 )
2365 .map_err(corrupt_frontier)?;
2366 let retained_marker_records = select_retained_marker_records(&retained_records);
2367 let marker_records = validated_active_marker_records(
2368 &retained_marker_records,
2369 restore.active_marker_anchors,
2370 restore.identity_slot_limit,
2371 sequence_ledger,
2372 )
2373 .map_err(corrupt_frontier)?;
2374 let historical_marker_deliveries = validated_historical_marker_deliveries(
2375 restore.historical_marker_deliveries,
2376 restore.conversation_id,
2377 &active_identities,
2378 &MarkerDeliverySources {
2379 retained: &retained_records,
2380 historical: &historical_causal_authorities,
2381 candidates: &restore.sequence.immutable_candidates,
2382 },
2383 restore.retained_record_limit,
2384 sequence_ledger,
2385 )
2386 .map_err(corrupt_frontier)?;
2387 BindingOriginValidation {
2388 conversation_id: restore.conversation_id,
2389 active: &active_identities,
2390 origins: &history.binding_origins,
2391 retained_records: &retained_records,
2392 causal_authorities: &history.causal_authorities,
2393 historical_marker_deliveries: &historical_marker_deliveries,
2394 total: history.total,
2395 ledger: sequence_ledger,
2396 }
2397 .validate()
2398 .map_err(corrupt_frontier)?;
2399 validate_sequence_candidates(
2400 &active_identities,
2401 &restore.sequence.immutable_candidates,
2402 restore.retained_floor,
2403 &retained_records,
2404 &historical_causal_authorities,
2405 sequence_ledger,
2406 )
2407 .map_err(corrupt_frontier)?;
2408 validate_marker_credit_owners(
2409 &restore.sequence.immutable_candidates,
2410 &marker_records,
2411 restore.identity_slot_limit,
2412 sequence_ledger,
2413 )
2414 .map_err(corrupt_frontier)?;
2415 Ok(ClaimFrontiersPrevalidated {
2416 conversation_id: restore.conversation_id,
2417 active_identities,
2418 identity_slot_limit: restore.identity_slot_limit,
2419 retained_floor: restore.retained_floor,
2420 retained_records,
2421 marker_records,
2422 historical_marker_deliveries,
2423 historical_causal_authorities,
2424 binding_origins: history.binding_origins.clone(),
2425 sequence_restore: restore.sequence,
2426 order_restore: restore.order,
2427 recovery_marker_delivery_seq: restore.recovery_marker_delivery_seq,
2428 sequence_ledger,
2429 order_ledger,
2430 issued_marker_record: None,
2431 fenced_marker_issued: None,
2432 })
2433 }
2434
2435 #[must_use]
2437 pub const fn active_identities(&self) -> &ActiveIdentityRanks {
2438 &self.active_identities
2439 }
2440
2441 #[must_use]
2443 pub const fn identity_slot_limit(&self) -> u64 {
2444 self.identity_slot_limit
2445 }
2446
2447 #[must_use]
2449 pub const fn conversation_id(&self) -> ConversationId {
2450 self.conversation_id
2451 }
2452
2453 #[must_use]
2455 pub const fn retained_floor(&self) -> u128 {
2456 self.retained_floor
2457 }
2458
2459 #[must_use]
2461 pub fn retained_records(&self) -> &[RetainedCausalRecord] {
2462 &self.retained_records
2463 }
2464
2465 #[must_use]
2467 pub fn retained_marker_records(&self) -> &[RetainedCausalRecord] {
2468 &self.marker_records
2469 }
2470
2471 #[must_use]
2476 pub(in crate::lifecycle) fn unaccepted_marker_anchor_count(&self) -> u64 {
2477 ordinary_unaccepted_marker_anchors(self).len() as u64
2478 }
2479
2480 #[must_use]
2487 pub(in crate::lifecycle) fn pending_marker_candidate_count(&self) -> u64 {
2488 self.sequence
2489 .immutable_candidates
2490 .iter()
2491 .filter(|candidate| matches!(candidate, ImmutableSequenceCandidate::Marker(_)))
2492 .count() as u64
2493 }
2494
2495 #[must_use]
2503 pub fn project_offered_marker_progress(
2504 &self,
2505 participant_id: ParticipantId,
2506 binding_epoch: BindingEpoch,
2507 marker_delivery_seq: DeliverySeq,
2508 event: Event,
2509 ) -> Option<ParticipantCursorProgress> {
2510 let record = self
2511 .marker_records
2512 .iter()
2513 .find(|record| record.delivery_seq == marker_delivery_seq)
2514 .copied()?;
2515 let RetainedCausalRecordKind::CompactionMarker {
2516 participant_index,
2517 provenance,
2518 } = record.kind
2519 else {
2520 return None;
2521 };
2522 if participant_index != participant_id {
2523 return None;
2524 }
2525 let participant = active_participant(&self.active_identities, participant_id)?;
2526 let target_binding = FrontierBinding::Bound(binding_epoch);
2527 if participant.binding != target_binding {
2528 return None;
2529 }
2530 let authority = ValidatedMarkerRecord {
2531 conversation_id: self.conversation_id,
2532 record,
2533 provenance,
2534 target_binding,
2535 occurrence: MarkerRecordOccurrence::Undelivered,
2536 seal: MarkerAuthoritySeal::Validated,
2537 };
2538 MarkerDelivery::from_validated_record(&authority)
2539 .delivered_progress(event)
2540 .ok()
2541 }
2542
2543 pub(super) fn fenced_marker_source(
2546 &self,
2547 recovery: super::DetachedCredentialRecovery,
2548 ) -> Option<FencedMarkerSourceRecord> {
2549 if self.fenced_marker_issued.is_some() {
2550 return None;
2551 }
2552 self.unlatched_fenced_marker_source(recovery)
2553 }
2554
2555 #[must_use]
2572 pub(in crate::lifecycle) fn fenced_latch_matches(
2573 &self,
2574 edge: Option<super::StoredEdge>,
2575 ) -> bool {
2576 let Some((participant_id, marker_delivery_seq, prior_binding_epoch)) =
2577 self.fenced_marker_issued
2578 else {
2579 return true;
2580 };
2581 let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge else {
2582 return false;
2583 };
2584 recovery.participant_id() == participant_id
2585 && recovery.marker_delivery_seq() == marker_delivery_seq
2586 && recovery.prior_binding_epoch() == prior_binding_epoch
2587 && self.unlatched_fenced_marker_source(recovery).is_some()
2588 }
2589
2590 fn unlatched_fenced_marker_source(
2593 &self,
2594 recovery: super::DetachedCredentialRecovery,
2595 ) -> Option<FencedMarkerSourceRecord> {
2596 if recovery.conversation_id() != self.conversation_id {
2597 return None;
2598 }
2599 let marker_delivery_seq = recovery.marker_delivery_seq();
2600 let participant_id = recovery.participant_id();
2601 let prior_binding_epoch = recovery.prior_binding_epoch();
2602 let record = self
2603 .marker_records
2604 .iter()
2605 .find(|record| record.delivery_seq == marker_delivery_seq)
2606 .copied()?;
2607 let RetainedCausalRecordKind::CompactionMarker {
2608 participant_index,
2609 provenance,
2610 } = record.kind
2611 else {
2612 return None;
2613 };
2614 let participant = active_participant(&self.active_identities, participant_id)?;
2615 let target_binding = FrontierBinding::Detached(prior_binding_epoch);
2616 if participant_index != participant_id || participant.binding != target_binding {
2617 return None;
2618 }
2619 Some(FencedMarkerSourceRecord {
2620 conversation_id: self.conversation_id,
2621 delivery_seq: record.delivery_seq,
2622 admission_order: record.admission_order,
2623 participant_id,
2624 provenance,
2625 target_binding,
2626 })
2627 }
2628
2629 pub(in crate::lifecycle) fn take_fenced_marker_record(
2640 &mut self,
2641 recovery: super::DetachedCredentialRecovery,
2642 ) -> Option<ValidatedMarkerRecord> {
2643 let source = self.fenced_marker_source(recovery)?;
2644 self.fenced_marker_issued = Some((
2645 source.participant_id,
2646 source.delivery_seq,
2647 recovery.prior_binding_epoch(),
2648 ));
2649 Some(ValidatedMarkerRecord {
2650 conversation_id: source.conversation_id,
2651 record: RetainedCausalRecord {
2652 delivery_seq: source.delivery_seq,
2653 admission_order: source.admission_order,
2654 kind: RetainedCausalRecordKind::CompactionMarker {
2655 participant_index: source.participant_id,
2656 provenance: source.provenance,
2657 },
2658 },
2659 provenance: source.provenance,
2660 target_binding: source.target_binding,
2661 occurrence: MarkerRecordOccurrence::Delivered,
2662 seal: MarkerAuthoritySeal::Validated,
2663 })
2664 }
2665
2666 pub(in crate::lifecycle) const fn reinstall_fenced_marker_record(
2669 &mut self,
2670 record: ValidatedMarkerRecord,
2671 ) {
2672 self.fenced_marker_issued = None;
2673 record.consume();
2674 }
2675
2676 #[must_use]
2678 pub const fn sequence(&self) -> &SequenceClaimFrontier {
2679 &self.sequence
2680 }
2681
2682 #[must_use]
2684 pub const fn order(&self) -> &OrderClaimFrontier {
2685 &self.order
2686 }
2687
2688 #[cfg(test)]
2689 pub(in crate::lifecycle) fn cross_counter_valid_for_test(&self) -> bool {
2690 validate_cross_counter(&self.sequence, &self.order).is_ok()
2691 }
2692
2693 #[must_use]
2701 pub fn live_transition_blocked(&self) -> bool {
2702 !self.sequence.immutable_candidates.is_empty()
2703 || self.sequence.recovery.is_some()
2704 || !self.order.immutable_candidates.is_empty()
2705 || self.order.recovery.is_some()
2706 }
2707
2708 #[must_use]
2725 pub fn precedence_condition(&self) -> PrecedenceCondition {
2726 if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
2727 return PrecedenceCondition::FencedRecovery;
2728 }
2729 match self.sequence.immutable_candidates.first() {
2730 Some(ImmutableSequenceCandidate::Marker(marker)) => PrecedenceCondition::MarkerDrain {
2731 settlement_epoch: marker.delivery_seq,
2732 },
2733 Some(ImmutableSequenceCandidate::BindingTerminal { .. }) => {
2734 PrecedenceCondition::BindingTerminal
2735 }
2736 None => PrecedenceCondition::Unclassified,
2737 }
2738 }
2739
2740 pub(in crate::lifecycle) fn apply_live_transition(
2746 self,
2747 active_identities: Vec<FrontierParticipant>,
2748 appended_records: &[RetainedCausalRecord],
2749 sequence_ledger: SequenceLedger,
2750 order_ledger: OrderLedger,
2751 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2752 if self.live_transition_blocked() {
2753 let condition = self.precedence_condition();
2754 return Err(Box::new((
2755 self,
2756 LiveFrontierTransitionError::Precedence(condition),
2757 )));
2758 }
2759 let Ok(active) = ActiveIdentityRanks::try_new(
2760 active_identities,
2761 sequence_ledger.high_watermark(),
2762 self.identity_slot_limit,
2763 ) else {
2764 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2765 };
2766 let first_sequence = self.sequence.ledger.high_watermark().checked_add(1);
2767 if first_sequence.is_none_or(|first| {
2768 appended_records.iter().enumerate().any(|(index, record)| {
2769 u64::try_from(index)
2770 .ok()
2771 .and_then(|offset| first.checked_add(offset))
2772 != Some(record.delivery_seq)
2773 })
2774 }) || appended_records
2775 .last()
2776 .is_some_and(|record| record.delivery_seq != sequence_ledger.high_watermark())
2777 {
2778 return Err(Box::new((
2779 self,
2780 LiveFrontierTransitionError::RecordPosition,
2781 )));
2782 }
2783 let (sequence, order) =
2784 match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
2785 Ok(frontiers) => frontiers,
2786 Err(error) => return Err(Box::new((self, error))),
2787 };
2788 let Self {
2789 conversation_id,
2790 identity_slot_limit,
2791 retained_floor,
2792 mut retained_records,
2793 marker_records,
2794 fenced_marker_issued,
2795 historical_marker_deliveries,
2796 historical_causal_authorities,
2797 ..
2798 } = self;
2799 retained_records.extend_from_slice(appended_records);
2800 Ok(Self {
2801 conversation_id,
2802 active_identities: active,
2803 identity_slot_limit,
2804 retained_floor,
2805 retained_records,
2806 marker_records,
2807 fenced_marker_issued,
2808 sequence,
2809 order,
2810 historical_marker_deliveries,
2811 historical_causal_authorities,
2812 })
2813 }
2814
2815 pub(in crate::lifecycle) fn apply_pending_binding_terminal(
2819 mut self,
2820 participant_id: ParticipantId,
2821 binding_epoch: BindingEpoch,
2822 delivery_seq: DeliverySeq,
2823 admission_order: super::AdmissionOrder,
2824 order_ledger: OrderLedger,
2825 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2826 if self.live_transition_blocked() {
2827 let condition = self.precedence_condition();
2828 return Err(Box::new((
2829 self,
2830 LiveFrontierTransitionError::Precedence(condition),
2831 )));
2832 }
2833 let mut participants = self.active_identities.participants().to_vec();
2834 let Some(participant) = participants
2835 .iter_mut()
2836 .find(|participant| participant.participant_index() == participant_id)
2837 else {
2838 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2839 };
2840 if participant.binding() != FrontierBinding::Bound(binding_epoch) {
2841 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2842 }
2843 *participant = FrontierParticipant::new(
2844 participant_id,
2845 participant.cursor(),
2846 FrontierBinding::Detached(binding_epoch),
2847 );
2848 let Ok(active) = ActiveIdentityRanks::try_new(
2849 participants,
2850 self.sequence.ledger.high_watermark(),
2851 self.identity_slot_limit,
2852 ) else {
2853 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2854 };
2855 let pending_owner = BindingTerminalOwner {
2856 participant_index: participant_id,
2857 binding_epoch,
2858 };
2859 let (sequence, order) = match rebuild_pending_terminal_frontiers(
2860 &active,
2861 pending_owner,
2862 delivery_seq,
2863 admission_order,
2864 self.sequence.ledger,
2865 order_ledger,
2866 ) {
2867 Ok(frontiers) => frontiers,
2868 Err(error) => return Err(Box::new((self, error))),
2869 };
2870 self.active_identities = active;
2871 self.sequence = sequence;
2872 self.order = order;
2873 Ok(self)
2874 }
2875
2876 pub(in crate::lifecycle) fn apply_live_fenced_attach(
2884 self,
2885 participant: FrontierParticipant,
2886 prior_binding_epoch: BindingEpoch,
2887 appended_records: &[RetainedCausalRecord],
2888 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2889 let Some(current) = self
2890 .active_identities
2891 .participants()
2892 .iter()
2893 .find(|current| current.participant_index() == participant.participant_index())
2894 .copied()
2895 else {
2896 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2897 };
2898 let Some(sequence_recovery) = self.sequence.recovery else {
2899 return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2900 };
2901 let Some(order_recovery) = self.order.recovery else {
2902 return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2903 };
2904 if !Self::fenced_recovery_authority_matches(
2905 current,
2906 participant,
2907 prior_binding_epoch,
2908 sequence_recovery,
2909 order_recovery,
2910 ) {
2911 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2912 }
2913 let finalizes_pending = appended_records.len() == 2;
2914 let pending_terminal_matches = self.pending_fenced_terminal_matches(appended_records);
2915 let candidates_empty = self.sequence.immutable_candidates.is_empty()
2916 && self.order.immutable_candidates.is_empty();
2917 if !(candidates_empty || finalizes_pending && pending_terminal_matches) {
2918 let condition = self.precedence_condition();
2919 return Err(precedence(self, condition));
2920 }
2921 if !Self::fenced_records_match(
2922 participant,
2923 sequence_recovery,
2924 order_recovery,
2925 appended_records,
2926 pending_terminal_matches,
2927 ) {
2928 return Err(Box::new((
2929 self,
2930 LiveFrontierTransitionError::RecordPosition,
2931 )));
2932 }
2933 if !self.has_recovery_marker(participant) {
2934 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2935 }
2936 if self.has_duplicate_appended_record(appended_records) {
2937 return Err(Box::new((
2938 self,
2939 LiveFrontierTransitionError::RecordPosition,
2940 )));
2941 }
2942 let Some((sequence_ledger, order_ledger)) =
2943 apply_fenced_ledgers(self.sequence.ledger, self.order.ledger, finalizes_pending)
2944 else {
2945 return Err(Box::new((
2946 self,
2947 LiveFrontierTransitionError::ResultingFrontier,
2948 )));
2949 };
2950 let mut active = self.active_identities.participants().to_vec();
2951 let Some(current) = active
2952 .iter_mut()
2953 .find(|current| current.participant_index() == participant.participant_index())
2954 else {
2955 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2956 };
2957 *current = participant;
2958 let Ok(active) = ActiveIdentityRanks::try_new(
2959 active,
2960 sequence_ledger.high_watermark(),
2961 self.identity_slot_limit,
2962 ) else {
2963 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2964 };
2965 let Ok((sequence, order)) =
2966 rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger)
2967 else {
2968 return Err(Box::new((
2969 self,
2970 LiveFrontierTransitionError::ResultingFrontier,
2971 )));
2972 };
2973 let mut resulting = self;
2974 resulting.active_identities = active;
2975 resulting
2976 .retained_records
2977 .extend_from_slice(appended_records);
2978 resulting
2979 .retained_records
2980 .sort_unstable_by_key(|record| record.delivery_seq);
2981 resulting
2982 .marker_records
2983 .retain(|record| record.delivery_seq != participant.cursor());
2984 resulting.spend_fenced_latch(participant, prior_binding_epoch);
2985 resulting.sequence = sequence;
2986 resulting.order = order;
2987 Ok(resulting)
2988 }
2989
2990 fn spend_fenced_latch(
2995 &mut self,
2996 participant: FrontierParticipant,
2997 prior_binding_epoch: BindingEpoch,
2998 ) {
2999 if self.fenced_marker_issued
3000 == Some((
3001 participant.participant_index(),
3002 participant.cursor(),
3003 prior_binding_epoch,
3004 ))
3005 {
3006 self.fenced_marker_issued = None;
3007 }
3008 }
3009
3010 fn fenced_recovery_authority_matches(
3011 current: FrontierParticipant,
3012 participant: FrontierParticipant,
3013 prior_binding_epoch: BindingEpoch,
3014 sequence_recovery: RecoverySequenceBlock,
3015 order_recovery: RecoveryOrderBlock,
3016 ) -> bool {
3017 let participant_matches = sequence_recovery.participant_index()
3018 == participant.participant_index()
3019 && order_recovery.participant_index() == participant.participant_index();
3020 let marker_matches = sequence_recovery.marker_delivery_seq() == participant.cursor()
3021 && order_recovery.marker_delivery_seq() == participant.cursor();
3022 let prior_epoch_matches = sequence_recovery.recovered_binding_epoch()
3023 == prior_binding_epoch
3024 && order_recovery.recovered_binding_epoch() == prior_binding_epoch;
3025 participant_matches
3026 && marker_matches
3027 && prior_epoch_matches
3028 && current.binding() == FrontierBinding::Detached(prior_binding_epoch)
3029 && current.cursor() <= participant.cursor()
3030 && matches!(participant.binding(), FrontierBinding::Bound(_))
3031 }
3032
3033 fn fenced_records_match(
3034 participant: FrontierParticipant,
3035 sequence_recovery: RecoverySequenceBlock,
3036 order_recovery: RecoveryOrderBlock,
3037 appended_records: &[RetainedCausalRecord],
3038 pending_terminal_matches: bool,
3039 ) -> bool {
3040 let Some(attached) = appended_records.last().copied() else {
3041 return false;
3042 };
3043 let FrontierBinding::Bound(recovered_binding_epoch) = participant.binding() else {
3044 return false;
3045 };
3046 let attached_matches = attached.delivery_seq == sequence_recovery.recovery_attach_seq()
3047 && attached.admission_order.transaction_order()
3048 == order_recovery.recovery_operation_order()
3049 && attached.kind
3050 == (RetainedCausalRecordKind::AttachLifecycle {
3051 participant_index: participant.participant_index(),
3052 binding_epoch: recovered_binding_epoch,
3053 });
3054 if !attached_matches {
3055 return false;
3056 }
3057 let prefix = &appended_records[..appended_records.len() - 1];
3058 match (
3059 sequence_recovery.terminal(),
3060 order_recovery.active_binding(),
3061 prefix,
3062 ) {
3063 (None, None, []) => true,
3064 (None, None, [_]) => pending_terminal_matches,
3065 (Some(sequence_terminal), Some(order_terminal), [terminal]) => {
3066 sequence_terminal.owner == order_terminal.owner
3067 && terminal.delivery_seq == sequence_terminal.delivery_seq
3068 && terminal.admission_order.transaction_order()
3069 == order_terminal.transaction_order
3070 && terminal.kind
3071 == RetainedCausalRecordKind::BindingTerminal(sequence_terminal.owner)
3072 }
3073 _ => false,
3074 }
3075 }
3076
3077 fn pending_fenced_terminal_matches(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3078 let [terminal, _] = appended_records else {
3079 return false;
3080 };
3081 let RetainedCausalRecordKind::BindingTerminal(owner) = terminal.kind else {
3082 return false;
3083 };
3084 let sequence_matches = self.sequence.immutable_candidates.iter().any(|candidate| {
3085 matches!(
3086 candidate,
3087 ImmutableSequenceCandidate::BindingTerminal {
3088 delivery_seq,
3089 admission_order,
3090 owner: candidate_owner,
3091 } if *delivery_seq == terminal.delivery_seq
3092 && *admission_order == terminal.admission_order
3093 && *candidate_owner == owner
3094 )
3095 });
3096 let order_matches = self.order.immutable_candidates.iter().any(|candidate| {
3097 candidate.transaction_order == terminal.admission_order.transaction_order()
3098 && candidate.candidate_keys.as_slice() == [terminal.admission_order]
3099 });
3100 sequence_matches && order_matches
3101 }
3102
3103 fn has_recovery_marker(&self, participant: FrontierParticipant) -> bool {
3104 self.retained_records.iter().any(|record| {
3105 record.delivery_seq == participant.cursor()
3106 && matches!(
3107 record.kind,
3108 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3109 if participant_index == participant.participant_index()
3110 )
3111 })
3112 }
3113
3114 fn has_duplicate_appended_record(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3115 appended_records.iter().any(|row| {
3116 self.retained_records
3117 .iter()
3118 .any(|retained| retained.delivery_seq == row.delivery_seq)
3119 })
3120 }
3121
3122 pub(in crate::lifecycle) fn apply_live_identity(
3125 mut self,
3126 participant: FrontierParticipant,
3127 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
3128 let Some(current) = self
3129 .active_identities
3130 .participants
3131 .iter_mut()
3132 .find(|current| current.participant_index == participant.participant_index)
3133 else {
3134 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3135 };
3136 if participant.cursor < current.cursor
3137 || participant.cursor > self.sequence.ledger.high_watermark()
3138 {
3139 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3140 }
3141 *current = participant;
3142 Ok(self)
3143 }
3144
3145 pub fn project_ordinary_record(
3161 self,
3162 input: OrdinaryRecordProjectionInput,
3163 ) -> Result<OrdinaryRecordProjectionDecision, Box<OrdinaryRecordProjectionFailure>> {
3164 let (
3165 request,
3166 receiving_binding_epoch,
3167 encoded_record_charge,
3168 retained_charges,
3169 observer_progress,
3170 closure_accounting,
3171 limits,
3172 ) = input.as_parts();
3173 if request.conversation_id != self.conversation_id {
3174 return Err(projection_failure(
3175 self,
3176 input,
3177 OrdinaryProjectionError::Conversation,
3178 ));
3179 }
3180 let unaccepted_marker_anchors = ordinary_unaccepted_marker_anchors(&self);
3181 let kernel = match project_ordinary_fixed_point(&OrdinaryProjectionFacts {
3182 request: request.clone(),
3183 receiving_binding_epoch,
3184 encoded_record_charge,
3185 retained_records: &self.retained_records,
3186 retained_charges,
3187 active_marker_credit_records: &self.marker_records,
3188 unaccepted_marker_anchors: &unaccepted_marker_anchors,
3189 active_identities: self.active_identities.participants(),
3190 identity_slot_limit: self.identity_slot_limit,
3191 current_floor: self.retained_floor,
3192 observer_progress,
3193 order_ledger: self.order.ledger,
3194 sequence_ledger: self.sequence.ledger,
3195 immutable_candidates: &self.sequence.immutable_candidates,
3196 closure_accounting,
3197 remaining_recovery_claim: closure_accounting.edge_k_remaining(),
3198 limits,
3199 }) {
3200 Ok(value) => value,
3201 Err(error) => return Err(projection_failure(self, input, error)),
3202 };
3203 match kernel {
3204 OrdinaryProjectionKernelDecision::DrainFirst(prefix) => Ok(
3205 OrdinaryRecordProjectionDecision::DrainFirst(Box::new(OrdinaryRecordDrainFirst {
3206 frontiers: self,
3207 input,
3208 candidate: prefix.candidate(),
3209 })),
3210 ),
3211 OrdinaryProjectionKernelDecision::Projected(projected) => {
3212 let observer_floor = match check_observer_floor(
3213 ObserverCheckedOperation::RecordAdmission(request.clone()),
3214 observer_progress,
3215 projected.floor().resulting_floor,
3216 ) {
3217 ObserverFloorDecision::Eligible(permit) => permit,
3218 ObserverFloorDecision::Respond(_) => {
3219 return Err(projection_failure(
3220 self,
3221 input,
3222 OrdinaryProjectionError::ObserverSelectorInvariant,
3223 ));
3224 }
3225 };
3226 let closure = match check_remaining_closure(
3227 &ClosureCheckedEnvelope::RecordAdmission(request.clone()),
3228 closure_accounting,
3229 false,
3230 0,
3231 projected.required_capacity(),
3232 ) {
3233 RemainingClosureDecision::Eligible(permit) => *permit,
3234 RemainingClosureDecision::Respond(_) => {
3235 return Err(projection_failure(
3236 self,
3237 input,
3238 OrdinaryProjectionError::ClosureSelectorInvariant,
3239 ));
3240 }
3241 };
3242 match self.apply_ordinary_projection(*projected, observer_floor, closure) {
3243 Ok(projected) => Ok(OrdinaryRecordProjectionDecision::Projected(Box::new(
3244 projected,
3245 ))),
3246 Err(failure) => {
3247 let (frontiers, error) = *failure;
3248 Err(projection_failure(frontiers, input, error))
3249 }
3250 }
3251 }
3252 }
3253 }
3254
3255 fn apply_ordinary_projection(
3256 mut self,
3257 projected: OrdinaryFixedPointPlan,
3258 observer_floor: super::ObserverFloorPermit,
3259 closure: super::RemainingClosurePermit,
3260 ) -> Result<ProjectedOrdinaryRecord, Box<(Self, OrdinaryProjectionError)>> {
3261 let Ok(marker_count) = u64::try_from(projected.marker_candidates().len()) else {
3262 return Err(Box::new((
3263 self,
3264 OrdinaryProjectionError::SequenceRelocation,
3265 )));
3266 };
3267 let Some(sequence_delta) = marker_count.checked_add(1) else {
3268 return Err(Box::new((
3269 self,
3270 OrdinaryProjectionError::SequenceRelocation,
3271 )));
3272 };
3273 if let Err(error) = preflight_ordinary_sequence_owners(&self.sequence, sequence_delta) {
3274 return Err(Box::new((self, error)));
3275 }
3276 if let Err(error) = preflight_ordinary_order_owners(&self.order) {
3277 return Err(Box::new((self, error)));
3278 }
3279 let (
3280 floor,
3281 retained_charge,
3282 baseline,
3283 accounting,
3284 required_capacity,
3285 order,
3286 sequence,
3287 caller_record,
3288 caller_charge,
3289 retained_records,
3290 retained_charges,
3291 new_marker_candidates,
3292 ) = projected.into_parts();
3293
3294 let prior_sequence_ledger = self.sequence.ledger;
3295 let prior_order_ledger = self.order.ledger;
3296 relay_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3297 relay_ordinary_order_owners(&mut self.order);
3298
3299 self.sequence.ledger = sequence.resulting();
3300 self.sequence.immutable_candidates.extend(
3301 new_marker_candidates
3302 .iter()
3303 .copied()
3304 .map(ImmutableSequenceCandidate::Marker),
3305 );
3306 self.order.ledger = order.resulting();
3307 if !new_marker_candidates.is_empty() {
3308 self.order
3309 .immutable_candidates
3310 .push(ImmutableOrderCandidateMajor {
3311 transaction_order: order.major(),
3312 candidate_keys: new_marker_candidates
3313 .iter()
3314 .map(|candidate| candidate.admission_order)
3315 .collect(),
3316 });
3317 }
3318 if validate_cross_counter(&self.sequence, &self.order).is_err() {
3319 self.sequence.ledger = prior_sequence_ledger;
3320 self.sequence.immutable_candidates.clear();
3321 self.order.ledger = prior_order_ledger;
3322 self.order.immutable_candidates.clear();
3323 rollback_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3324 rollback_ordinary_order_owners(&mut self.order);
3325 return Err(Box::new((
3326 self,
3327 OrdinaryProjectionError::SequenceRelocation,
3328 )));
3329 }
3330 self.retained_floor = floor.resulting_floor;
3331 self.retained_records = retained_records;
3332 self.marker_records
3333 .retain(|record| u128::from(record.delivery_seq) >= floor.resulting_floor);
3334
3335 Ok(ProjectedOrdinaryRecord {
3336 frontiers: self,
3337 floor,
3338 retained_charge,
3339 baseline,
3340 accounting,
3341 required_capacity,
3342 order,
3343 sequence,
3344 observer_floor,
3345 closure,
3346 caller_record,
3347 caller_charge,
3348 retained_charges,
3349 new_marker_candidates,
3350 })
3351 }
3352
3353 pub fn planned_settled_leave_admission_order<F>(
3365 &self,
3366 member: &LiveMember<F>,
3367 binding_state: BindingState,
3368 ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3369 let (participant_id, ended_binding_epoch) =
3370 validate_settled_leave_prestate(self, member, binding_state)?;
3371 let selection = select_leave_order(&self.order, participant_id, ended_binding_epoch, None)?;
3372 Ok(super::AdmissionOrder::new(
3373 selection.selected_major,
3374 CandidatePhase::MembershipExit,
3375 participant_id,
3376 ))
3377 }
3378
3379 pub fn prepare_settled_leave_authority<F>(
3392 mut self,
3393 member: &LiveMember<F>,
3394 binding_state: BindingState,
3395 ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3396 let (participant_id, ended_binding_epoch) =
3397 validate_settled_leave_prestate(&self, member, binding_state)?;
3398 let left_transaction_order =
3399 consume_leave_order_lane(&mut self.order, participant_id, ended_binding_epoch, None)?;
3400 Ok(PreparedLeaveAuthority::settled(
3401 self,
3402 member.conversation_id(),
3403 participant_id,
3404 ended_binding_epoch,
3405 left_transaction_order,
3406 ))
3407 }
3408
3409 pub fn planned_pending_leave_admission_order<F>(
3417 &self,
3418 member: &LiveMember<F>,
3419 pending: PendingFinalization,
3420 ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3421 let (participant_id, expected_order) =
3422 validate_pending_leave_prestate(self, member, pending)?;
3423 let selection =
3424 select_leave_order(&self.order, participant_id, None, Some(expected_order))?;
3425 Ok(super::AdmissionOrder::new(
3426 selection.selected_major,
3427 CandidatePhase::MembershipExit,
3428 participant_id,
3429 ))
3430 }
3431
3432 pub fn prepare_pending_leave_authority<F>(
3446 mut self,
3447 member: &LiveMember<F>,
3448 pending: PendingFinalization,
3449 ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3450 let (participant_id, expected_order) =
3451 validate_pending_leave_prestate(&self, member, pending)?;
3452 let left_transaction_order =
3453 consume_leave_order_lane(&mut self.order, participant_id, None, Some(expected_order))?;
3454 Ok(PreparedLeaveAuthority::pending(
3455 self,
3456 member.conversation_id(),
3457 participant_id,
3458 pending.binding_epoch(),
3459 expected_order,
3460 left_transaction_order,
3461 ))
3462 }
3463
3464 #[allow(
3473 clippy::too_many_lines,
3474 reason = "the atomic Leave relay keeps membership, both ledgers, products, recovery, and retained rows visibly in one checked transition"
3475 )]
3476 pub(super) fn finish_leave_claims(
3477 mut self,
3478 participant_id: ParticipantId,
3479 ended_binding_epoch: Option<BindingEpoch>,
3480 committed_terminal: Option<CommittedBindingTerminal>,
3481 left_delivery_seq: DeliverySeq,
3482 left_transaction_order: TransactionOrder,
3483 ) -> Result<Self, LeaveCommitError> {
3484 let prior_high = self.sequence.ledger.high_watermark();
3485 let first_appended = prior_high
3486 .checked_add(1)
3487 .ok_or(LeaveCommitError::SequenceAuthority)?;
3488 let expected_left = if committed_terminal.is_some() {
3489 first_appended
3490 .checked_add(1)
3491 .ok_or(LeaveCommitError::SequenceAuthority)?
3492 } else {
3493 first_appended
3494 };
3495 if left_delivery_seq != expected_left {
3496 return Err(LeaveCommitError::SequenceAuthority);
3497 }
3498
3499 match (
3500 committed_terminal,
3501 self.sequence.immutable_candidates.as_slice(),
3502 ) {
3503 (
3504 Some(terminal),
3505 [
3506 ImmutableSequenceCandidate::BindingTerminal {
3507 delivery_seq,
3508 admission_order,
3509 owner,
3510 },
3511 ],
3512 ) if *delivery_seq == first_appended
3513 && terminal.delivery_seq() == first_appended
3514 && *admission_order == terminal.admission_order()
3515 && owner.participant_index == participant_id
3516 && owner.binding_epoch == terminal.binding_epoch() => {}
3517 (None, []) => {}
3518 (Some(_) | None, _) => return Err(LeaveCommitError::SequenceAuthority),
3519 }
3520
3521 let Some(active_index) = self
3522 .active_identities
3523 .participants
3524 .iter()
3525 .position(|participant| participant.participant_index == participant_id)
3526 else {
3527 return Err(LeaveCommitError::ResultingFrontier);
3528 };
3529 self.active_identities.participants.remove(active_index);
3530 self.marker_records.retain(|record| {
3531 !matches!(
3532 record.kind,
3533 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3534 if participant_index == participant_id
3535 )
3536 });
3537 if self
3540 .fenced_marker_issued
3541 .is_some_and(|(latched, _, _)| latched == participant_id)
3542 {
3543 self.fenced_marker_issued = None;
3544 }
3545 let resulting_live = usize_to_u64(self.active_identities.participants.len());
3546
3547 let mut exit_consumed = false;
3548 let mut terminal_consumed = ended_binding_epoch.is_none();
3549 let mut units = Vec::new();
3550 for claim in self.sequence.movable_claims.iter().copied() {
3551 match claim.owner {
3552 SequenceDirectOwner::MembershipExit {
3553 participant_index: owner,
3554 } if owner == participant_id => {
3555 if exit_consumed {
3556 return Err(LeaveCommitError::ResultingFrontier);
3557 }
3558 exit_consumed = true;
3559 }
3560 SequenceDirectOwner::BindingTerminal(owner)
3561 if owner.participant_index == participant_id
3562 && Some(owner.binding_epoch) == ended_binding_epoch =>
3563 {
3564 if terminal_consumed {
3565 return Err(LeaveCommitError::ResultingFrontier);
3566 }
3567 terminal_consumed = true;
3568 }
3569 SequenceDirectOwner::MembershipExit { .. }
3570 | SequenceDirectOwner::BindingTerminal(_) => {
3571 units.push(LeaveSequenceUnit::Direct(claim));
3572 }
3573 }
3574 }
3575 if !exit_consumed || !terminal_consumed {
3576 return Err(LeaveCommitError::ResultingFrontier);
3577 }
3578
3579 let recovery_owned = self
3580 .sequence
3581 .recovery
3582 .is_some_and(|block| block.participant_index == participant_id);
3583 for range in self.sequence.products.live_times_terminal.iter().copied() {
3584 if range.terminal.participant_index != participant_id && resulting_live != 0 {
3585 units.push(LeaveSequenceUnit::TerminalProduct(TerminalProductRange {
3586 start: range.start,
3587 length: resulting_live,
3588 terminal: range.terminal,
3589 }));
3590 }
3591 }
3592 if let Some(range) = self.sequence.products.live_times_replacement_terminal
3593 && !recovery_owned
3594 && resulting_live != 0
3595 {
3596 units.push(LeaveSequenceUnit::ReplacementProduct(
3597 ReplacementTerminalProductRange {
3598 start: range.start,
3599 length: resulting_live,
3600 participant_index: range.participant_index,
3601 marker_delivery_seq: range.marker_delivery_seq,
3602 prior_binding_epoch: range.prior_binding_epoch,
3603 },
3604 ));
3605 }
3606 let resulting_other = resulting_live.saturating_sub(1);
3607 if resulting_other != 0 {
3608 for range in self.sequence.products.other_live_times_exit.iter().copied() {
3609 if range.exit_participant != participant_id {
3610 units.push(LeaveSequenceUnit::ExitProduct(ExitProductRange {
3611 start: range.start,
3612 length: resulting_other,
3613 exit_participant: range.exit_participant,
3614 }));
3615 }
3616 }
3617 }
3618 if let Some(recovery) = self.sequence.recovery
3619 && !recovery_owned
3620 {
3621 units.push(LeaveSequenceUnit::Recovery(recovery));
3622 }
3623 units.sort_by_key(|unit| unit.original_start());
3624
3625 let mut cursor = left_delivery_seq.checked_add(1);
3626 let mut movable_claims = Vec::new();
3627 let mut terminal_products = Vec::new();
3628 let mut replacement_product = None;
3629 let mut exit_products = Vec::new();
3630 let mut recovery = None;
3631 for unit in units {
3632 match unit {
3633 LeaveSequenceUnit::Direct(mut claim) => {
3634 claim.delivery_seq = allocate_leave_sequence_range(&mut cursor, 1)?;
3635 movable_claims.push(claim);
3636 }
3637 LeaveSequenceUnit::TerminalProduct(mut range) => {
3638 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3639 terminal_products.push(range);
3640 }
3641 LeaveSequenceUnit::ReplacementProduct(mut range) => {
3642 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3643 replacement_product = Some(range);
3644 }
3645 LeaveSequenceUnit::ExitProduct(mut range) => {
3646 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3647 exit_products.push(range);
3648 }
3649 LeaveSequenceUnit::Recovery(mut block) => {
3650 let length = 2 + u64::from(block.terminal.is_some());
3651 let start = allocate_leave_sequence_range(&mut cursor, length)?;
3652 if let Some(mut terminal) = block.terminal {
3653 terminal.delivery_seq = start;
3654 block.terminal = Some(terminal);
3655 block.recovery_attach_seq = start
3656 .checked_add(1)
3657 .ok_or(LeaveCommitError::ResultingFrontier)?;
3658 } else {
3659 block.recovery_attach_seq = start;
3660 }
3661 block.replacement_terminal_seq = block
3662 .recovery_attach_seq
3663 .checked_add(1)
3664 .ok_or(LeaveCommitError::ResultingFrontier)?;
3665 recovery = Some(block);
3666 }
3667 }
3668 }
3669 movable_claims.sort_by_key(|claim| claim.delivery_seq);
3670 terminal_products.sort_by_key(|range| range.start);
3671 exit_products.sort_by_key(|range| range.start);
3672 let terminal_count = usize_to_u64(
3673 movable_claims
3674 .iter()
3675 .filter(|claim| matches!(claim.owner, SequenceDirectOwner::BindingTerminal(_)))
3676 .count(),
3677 ) + u64::from(recovery.is_some_and(|block| block.terminal.is_some()));
3678 let recovery_reserve = if recovery.is_some() {
3679 RecoverySequenceReserve::DetachedCredentialRecovery
3680 } else {
3681 RecoverySequenceReserve::None
3682 };
3683 let ledger = SequenceLedger::try_new(
3684 left_delivery_seq,
3685 SequenceClaims::new(resulting_live, terminal_count, 0, recovery_reserve),
3686 )
3687 .map_err(|_| LeaveCommitError::ResultingFrontier)?;
3688 self.sequence = SequenceClaimFrontier {
3689 ledger,
3690 movable_claims,
3691 immutable_candidates: Vec::new(),
3692 products: SequenceProductRanges {
3693 live_times_terminal: terminal_products,
3694 live_times_replacement_terminal: replacement_product,
3695 other_live_times_exit: exit_products,
3696 },
3697 recovery,
3698 };
3699
3700 if let Some(terminal) = committed_terminal {
3701 self.retained_records.push(RetainedCausalRecord {
3702 delivery_seq: terminal.delivery_seq(),
3703 admission_order: terminal.admission_order(),
3704 kind: RetainedCausalRecordKind::BindingTerminal(BindingTerminalOwner {
3705 participant_index: participant_id,
3706 binding_epoch: terminal.binding_epoch(),
3707 }),
3708 });
3709 }
3710 self.retained_records.push(RetainedCausalRecord {
3711 delivery_seq: left_delivery_seq,
3712 admission_order: super::AdmissionOrder::new(
3713 left_transaction_order,
3714 CandidatePhase::MembershipExit,
3715 participant_id,
3716 ),
3717 kind: RetainedCausalRecordKind::MembershipExit {
3718 participant_index: participant_id,
3719 },
3720 });
3721 self.retained_records
3722 .sort_by_key(|record| record.delivery_seq);
3723
3724 let order_claims = self.order.ledger.claims();
3725 if order_claims.membership_exits() != resulting_live
3726 || self.order.recovery.is_some() != self.sequence.recovery.is_some()
3727 || validate_cross_counter(&self.sequence, &self.order).is_err()
3728 {
3729 return Err(LeaveCommitError::ResultingFrontier);
3730 }
3731 Ok(self)
3732 }
3733
3734 fn marker_candidate(&self, delivery_seq: DeliverySeq) -> Option<ValidatedMarkerCandidate> {
3735 self.sequence
3736 .immutable_candidates
3737 .iter()
3738 .find_map(|candidate| match candidate {
3739 ImmutableSequenceCandidate::Marker(candidate)
3740 if candidate.delivery_seq == delivery_seq =>
3741 {
3742 Some(ValidatedMarkerCandidate {
3743 conversation_id: self.conversation_id,
3744 candidate: *candidate,
3745 seal: MarkerAuthoritySeal::Validated,
3746 })
3747 }
3748 _ => None,
3749 })
3750 }
3751
3752 pub(super) fn drain_next_marker_core(
3761 mut self,
3762 ) -> Result<MarkerDrainCore, MarkerDrainCoreError> {
3763 let Some(first) = self.sequence.immutable_candidates.first().copied() else {
3764 return Err(MarkerDrainCoreError::NoCandidate);
3765 };
3766 let ImmutableSequenceCandidate::Marker(marker) = first else {
3767 return Err(MarkerDrainCoreError::BindingTerminalFirst);
3768 };
3769 let expected_sequence = self
3770 .sequence
3771 .ledger
3772 .high_watermark()
3773 .checked_add(1)
3774 .ok_or(MarkerDrainCoreError::SequenceNotNext)?;
3775 if marker.delivery_seq != expected_sequence {
3776 return Err(MarkerDrainCoreError::SequenceNotNext);
3777 }
3778 if order_is_above_high(
3779 marker.admission_order.transaction_order(),
3780 self.order.ledger.high(),
3781 ) {
3782 return Err(MarkerDrainCoreError::CausalMajorNotAllocated);
3783 }
3784 let candidate = self
3785 .marker_candidate(marker.delivery_seq)
3786 .ok_or(MarkerDrainCoreError::NoCandidate)?;
3787
3788 let key = marker.admission_order;
3789 let Some(group_index) = self
3790 .order
3791 .immutable_candidates
3792 .iter()
3793 .position(|group| group.candidate_keys.contains(&key))
3794 else {
3795 return Err(MarkerDrainCoreError::MissingOrderCandidate);
3796 };
3797 let group = &mut self.order.immutable_candidates[group_index];
3798 let Ok(key_index) = group.candidate_keys.binary_search(&key) else {
3799 return Err(MarkerDrainCoreError::MissingOrderCandidate);
3800 };
3801 group.candidate_keys.remove(key_index);
3802 if group.candidate_keys.is_empty() {
3803 self.order.immutable_candidates.remove(group_index);
3804 }
3805 self.sequence.immutable_candidates.remove(0);
3806
3807 let claims = self.sequence.ledger.claims();
3808 let markers = claims
3809 .markers()
3810 .checked_sub(1)
3811 .ok_or(MarkerDrainCoreError::ResultingLedger)?;
3812 self.sequence.ledger = SequenceLedger::try_new(
3813 expected_sequence,
3814 super::SequenceClaims::new(
3815 claims.live_members(),
3816 claims.binding_terminals(),
3817 markers,
3818 claims.recovery(),
3819 ),
3820 )
3821 .map_err(|_| MarkerDrainCoreError::ResultingLedger)?;
3822
3823 let record = RetainedCausalRecord {
3824 delivery_seq: marker.delivery_seq,
3825 admission_order: marker.admission_order,
3826 kind: RetainedCausalRecordKind::CompactionMarker {
3827 participant_index: marker.admission_order.participant_index(),
3828 provenance: marker.provenance,
3829 },
3830 };
3831 self.marker_records.push(record);
3832 self.retained_records.push(record);
3833 Ok(MarkerDrainCore {
3834 candidate,
3835 record: ValidatedMarkerRecord {
3836 conversation_id: self.conversation_id,
3837 record,
3838 provenance: marker.provenance,
3839 target_binding: marker.target_binding,
3840 occurrence: MarkerRecordOccurrence::Undelivered,
3841 seal: MarkerAuthoritySeal::Validated,
3842 },
3843 frontiers: self,
3844 })
3845 }
3846
3847 pub(in crate::lifecycle) fn drain_first_binding_terminal(
3861 mut self,
3862 expected_owner: BindingTerminalOwner,
3863 expected_order: super::AdmissionOrder,
3864 ) -> Result<(Self, RetainedCausalRecord), Box<(Self, LiveFrontierTransitionError)>> {
3865 let (delivery_seq, admission_order, owner) =
3866 match self.validate_first_terminal_candidate(expected_owner, expected_order) {
3867 Ok(candidate) => candidate,
3868 Err(error) => return Err(Box::new((self, error))),
3869 };
3870 let claims = self.sequence.ledger.claims();
3871 let Some(sequence_ledger) =
3872 claims
3873 .binding_terminals()
3874 .checked_sub(1)
3875 .and_then(|binding_terminals| {
3876 SequenceLedger::try_new(
3877 delivery_seq,
3878 SequenceClaims::new(
3879 claims.live_members(),
3880 binding_terminals,
3881 claims.markers(),
3882 claims.recovery(),
3883 ),
3884 )
3885 .ok()
3886 })
3887 else {
3888 return Err(Box::new((
3889 self,
3890 LiveFrontierTransitionError::ResultingFrontier,
3891 )));
3892 };
3893 let order_ledger = self.order.ledger;
3894 let Ok(active) = ActiveIdentityRanks::try_new(
3895 self.active_identities.participants().to_vec(),
3896 sequence_ledger.high_watermark(),
3897 self.identity_slot_limit,
3898 ) else {
3899 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3900 };
3901 let (sequence, order) =
3902 match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
3903 Ok(frontiers) => frontiers,
3904 Err(error) => return Err(Box::new((self, error))),
3905 };
3906 let record = RetainedCausalRecord {
3907 delivery_seq,
3908 admission_order,
3909 kind: RetainedCausalRecordKind::BindingTerminal(owner),
3910 };
3911 self.active_identities = active;
3912 self.sequence = sequence;
3913 self.order = order;
3914 self.retained_records.push(record);
3915 Ok((self, record))
3916 }
3917
3918 fn validate_first_terminal_candidate(
3923 &self,
3924 expected_owner: BindingTerminalOwner,
3925 expected_order: super::AdmissionOrder,
3926 ) -> Result<
3927 (DeliverySeq, super::AdmissionOrder, BindingTerminalOwner),
3928 LiveFrontierTransitionError,
3929 > {
3930 if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
3931 return Err(LiveFrontierTransitionError::Precedence(
3932 PrecedenceCondition::FencedRecovery,
3933 ));
3934 }
3935 let [first] = self.sequence.immutable_candidates.as_slice() else {
3936 return Err(LiveFrontierTransitionError::Precedence(
3937 self.precedence_condition(),
3938 ));
3939 };
3940 let ImmutableSequenceCandidate::BindingTerminal {
3941 delivery_seq,
3942 admission_order,
3943 owner,
3944 } = *first
3945 else {
3946 return Err(LiveFrontierTransitionError::Precedence(
3947 self.precedence_condition(),
3948 ));
3949 };
3950 if owner != expected_owner || admission_order != expected_order {
3951 return Err(LiveFrontierTransitionError::Authority);
3952 }
3953 let Some(expected_sequence) = self.sequence.ledger.high_watermark().checked_add(1) else {
3954 return Err(LiveFrontierTransitionError::Exhausted);
3955 };
3956 if delivery_seq != expected_sequence
3957 || order_is_above_high(
3958 admission_order.transaction_order(),
3959 self.order.ledger.high(),
3960 )
3961 {
3962 return Err(LiveFrontierTransitionError::RecordPosition);
3963 }
3964 let [group] = self.order.immutable_candidates.as_slice() else {
3965 return Err(LiveFrontierTransitionError::RecordPosition);
3966 };
3967 if group.candidate_keys.as_slice() != [admission_order] {
3968 return Err(LiveFrontierTransitionError::RecordPosition);
3969 }
3970 let detached_matches = self
3971 .active_identities
3972 .participants()
3973 .iter()
3974 .any(|participant| {
3975 participant.participant_index == owner.participant_index
3976 && participant.binding == FrontierBinding::Detached(owner.binding_epoch)
3977 });
3978 if !detached_matches {
3979 return Err(LiveFrontierTransitionError::Authority);
3980 }
3981 Ok((delivery_seq, admission_order, owner))
3982 }
3983}
3984
3985fn apply_fenced_ledgers(
3986 sequence: SequenceLedger,
3987 order: OrderLedger,
3988 finalizes_pending: bool,
3989) -> Option<(SequenceLedger, OrderLedger)> {
3990 let sequence = if finalizes_pending {
3991 sequence.apply_fenced_recovery_finalizing_pending()
3992 } else {
3993 sequence.apply_fenced_recovery()
3994 }
3995 .ok()?;
3996 let order = if finalizes_pending {
3997 order.apply_fenced_recovery_finalizing_pending()
3998 } else {
3999 order.apply_fenced_recovery()
4000 }
4001 .ok()?;
4002 Some((sequence, order))
4003}
4004
4005fn rebuild_unreserved_frontiers(
4006 active: &ActiveIdentityRanks,
4007 sequence_ledger: SequenceLedger,
4008 order_ledger: OrderLedger,
4009) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4010 let terminal_owners: Vec<_> = active
4011 .participants()
4012 .iter()
4013 .filter_map(|participant| match participant.binding() {
4014 FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4015 participant_index: participant.participant_index(),
4016 binding_epoch,
4017 }),
4018 FrontierBinding::Detached(_) => None,
4019 })
4020 .collect();
4021 let live_count = active.len();
4022 let terminal_count =
4023 u64::try_from(terminal_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4024 let sequence_claims = sequence_ledger.claims();
4025 let order_claims = order_ledger.claims();
4026 if sequence_claims.live_members() != live_count
4027 || sequence_claims.binding_terminals() != terminal_count
4028 || sequence_claims.markers() != 0
4029 || sequence_claims.recovery() != RecoverySequenceReserve::None
4030 || order_claims.active_binding_terminals() != terminal_count
4031 || order_claims.membership_exits() != live_count
4032 || order_claims.recovery_operation()
4033 || order_claims.recovery_replacement_terminal()
4034 {
4035 return Err(LiveFrontierTransitionError::ResultingFrontier);
4036 }
4037
4038 let sequence = rebuild_unreserved_sequence(active, &terminal_owners, sequence_ledger)?;
4039 let order = rebuild_unreserved_order(active, &terminal_owners, order_ledger)?;
4040 validate_cross_counter(&sequence, &order)
4041 .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4042 Ok((sequence, order))
4043}
4044
4045fn rebuild_unreserved_sequence(
4046 active: &ActiveIdentityRanks,
4047 terminal_owners: &[BindingTerminalOwner],
4048 sequence_ledger: SequenceLedger,
4049) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4050 let live_count = active.len();
4051 let mut sequence_cursor = sequence_ledger
4052 .high_watermark()
4053 .checked_add(1)
4054 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4055 let mut movable_sequence = Vec::new();
4056 for terminal in terminal_owners {
4057 movable_sequence.push(MovableSequenceClaim {
4058 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4059 owner: SequenceDirectOwner::BindingTerminal(*terminal),
4060 });
4061 }
4062 for participant in active.participants() {
4063 movable_sequence.push(MovableSequenceClaim {
4064 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4065 owner: SequenceDirectOwner::MembershipExit {
4066 participant_index: participant.participant_index(),
4067 },
4068 });
4069 }
4070 let mut terminal_products = Vec::new();
4071 for terminal in terminal_owners {
4072 terminal_products.push(TerminalProductRange {
4073 start: take_live_sequence(&mut sequence_cursor, live_count)?,
4074 length: live_count,
4075 terminal: *terminal,
4076 });
4077 }
4078 let exit_product_length = live_count.saturating_sub(1);
4079 let mut exit_products = Vec::new();
4080 for participant in active.participants() {
4081 exit_products.push(ExitProductRange {
4082 start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4083 length: exit_product_length,
4084 exit_participant: participant.participant_index(),
4085 });
4086 }
4087 let sequence_end = u128::from(sequence_ledger.high_watermark())
4088 .checked_add(sequence_ledger.required_reserve())
4089 .and_then(|value| value.checked_add(1))
4090 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4091 if u128::from(sequence_cursor) != sequence_end {
4092 return Err(LiveFrontierTransitionError::ResultingFrontier);
4093 }
4094 Ok(SequenceClaimFrontier {
4095 ledger: sequence_ledger,
4096 movable_claims: movable_sequence,
4097 immutable_candidates: Vec::new(),
4098 products: SequenceProductRanges {
4099 live_times_terminal: terminal_products,
4100 live_times_replacement_terminal: None,
4101 other_live_times_exit: exit_products,
4102 },
4103 recovery: None,
4104 })
4105}
4106
4107fn rebuild_unreserved_order(
4108 active: &ActiveIdentityRanks,
4109 terminal_owners: &[BindingTerminalOwner],
4110 order_ledger: OrderLedger,
4111) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4112 let order_claims = order_ledger.claims();
4113 let order_start = order_frontier_start(order_ledger.high());
4114 let mut order_cursor =
4115 u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4116 let mut movable_order = Vec::new();
4117 for terminal in terminal_owners.iter().copied() {
4118 movable_order.push(MovableOrderClaim {
4119 transaction_order: take_live_order(&mut order_cursor)?,
4120 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4121 });
4122 }
4123 for participant in active.participants() {
4124 movable_order.push(MovableOrderClaim {
4125 transaction_order: take_live_order(&mut order_cursor)?,
4126 owner: OrderDirectOwner::MembershipExit {
4127 participant_index: participant.participant_index(),
4128 },
4129 });
4130 }
4131 if u128::from(order_cursor) != order_start + order_claims.total() {
4132 return Err(LiveFrontierTransitionError::ResultingFrontier);
4133 }
4134 Ok(OrderClaimFrontier {
4135 ledger: order_ledger,
4136 movable_claims: movable_order,
4137 immutable_candidates: Vec::new(),
4138 recovery: None,
4139 })
4140}
4141
4142fn rebuild_pending_terminal_frontiers(
4143 active: &ActiveIdentityRanks,
4144 pending_owner: BindingTerminalOwner,
4145 delivery_seq: DeliverySeq,
4146 admission_order: super::AdmissionOrder,
4147 sequence_ledger: SequenceLedger,
4148 order_ledger: OrderLedger,
4149) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4150 if admission_order.candidate_phase() != CandidatePhase::BindingTerminal
4151 || admission_order.participant_index() != pending_owner.participant_index
4152 || admission_order.transaction_order()
4153 != match order_ledger.high() {
4154 OrderHigh::Allocated(high) => high,
4155 OrderHigh::Empty => return Err(LiveFrontierTransitionError::RecordPosition),
4156 }
4157 || sequence_ledger.high_watermark().checked_add(1) != Some(delivery_seq)
4158 {
4159 return Err(LiveFrontierTransitionError::RecordPosition);
4160 }
4161 let bound_owners: Vec<_> = active
4162 .participants()
4163 .iter()
4164 .filter_map(|participant| match participant.binding() {
4165 FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4166 participant_index: participant.participant_index(),
4167 binding_epoch,
4168 }),
4169 FrontierBinding::Detached(_) => None,
4170 })
4171 .collect();
4172 let mut all_terminal_owners = bound_owners.clone();
4173 all_terminal_owners.push(pending_owner);
4174 all_terminal_owners.sort_unstable_by_key(|owner| owner.participant_index);
4175 let live_count = active.len();
4176 let bound_count =
4177 u64::try_from(bound_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4178 let terminal_count = u64::try_from(all_terminal_owners.len())
4179 .map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4180 let sequence_claims = sequence_ledger.claims();
4181 let order_claims = order_ledger.claims();
4182 if sequence_claims.live_members() != live_count
4183 || sequence_claims.binding_terminals() != terminal_count
4184 || sequence_claims.markers() != 0
4185 || sequence_claims.recovery() != RecoverySequenceReserve::None
4186 || order_claims.active_binding_terminals() != bound_count
4187 || order_claims.membership_exits() != live_count
4188 || order_claims.recovery_operation()
4189 || order_claims.recovery_replacement_terminal()
4190 {
4191 return Err(LiveFrontierTransitionError::ResultingFrontier);
4192 }
4193
4194 let sequence = rebuild_pending_terminal_sequence(
4195 active,
4196 pending_owner,
4197 delivery_seq,
4198 admission_order,
4199 sequence_ledger,
4200 &bound_owners,
4201 &all_terminal_owners,
4202 )?;
4203 let order =
4204 rebuild_pending_terminal_order(active, admission_order, order_ledger, bound_owners)?;
4205 validate_cross_counter(&sequence, &order)
4206 .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4207 Ok((sequence, order))
4208}
4209
4210fn rebuild_pending_terminal_sequence(
4211 active: &ActiveIdentityRanks,
4212 pending_owner: BindingTerminalOwner,
4213 delivery_seq: DeliverySeq,
4214 admission_order: super::AdmissionOrder,
4215 sequence_ledger: SequenceLedger,
4216 bound_owners: &[BindingTerminalOwner],
4217 all_terminal_owners: &[BindingTerminalOwner],
4218) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4219 let live_count = active.len();
4220 let mut sequence_cursor = delivery_seq;
4221 let candidate_sequence = take_live_sequence(&mut sequence_cursor, 1)?;
4222 let mut movable_sequence = Vec::new();
4223 for terminal in bound_owners {
4224 movable_sequence.push(MovableSequenceClaim {
4225 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4226 owner: SequenceDirectOwner::BindingTerminal(*terminal),
4227 });
4228 }
4229 for participant in active.participants() {
4230 movable_sequence.push(MovableSequenceClaim {
4231 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4232 owner: SequenceDirectOwner::MembershipExit {
4233 participant_index: participant.participant_index(),
4234 },
4235 });
4236 }
4237 let mut terminal_products = Vec::new();
4238 for terminal in all_terminal_owners {
4239 terminal_products.push(TerminalProductRange {
4240 start: take_live_sequence(&mut sequence_cursor, live_count)?,
4241 length: live_count,
4242 terminal: *terminal,
4243 });
4244 }
4245 let exit_product_length = live_count.saturating_sub(1);
4246 let mut exit_products = Vec::new();
4247 for participant in active.participants() {
4248 exit_products.push(ExitProductRange {
4249 start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4250 length: exit_product_length,
4251 exit_participant: participant.participant_index(),
4252 });
4253 }
4254 let sequence_end = u128::from(sequence_ledger.high_watermark())
4255 .checked_add(sequence_ledger.required_reserve())
4256 .and_then(|value| value.checked_add(1))
4257 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4258 if u128::from(sequence_cursor) != sequence_end {
4259 return Err(LiveFrontierTransitionError::ResultingFrontier);
4260 }
4261 Ok(SequenceClaimFrontier {
4262 ledger: sequence_ledger,
4263 movable_claims: movable_sequence,
4264 immutable_candidates: alloc::vec![ImmutableSequenceCandidate::BindingTerminal {
4265 delivery_seq: candidate_sequence,
4266 admission_order,
4267 owner: pending_owner,
4268 }],
4269 products: SequenceProductRanges {
4270 live_times_terminal: terminal_products,
4271 live_times_replacement_terminal: None,
4272 other_live_times_exit: exit_products,
4273 },
4274 recovery: None,
4275 })
4276}
4277
4278fn rebuild_pending_terminal_order(
4279 active: &ActiveIdentityRanks,
4280 admission_order: super::AdmissionOrder,
4281 order_ledger: OrderLedger,
4282 bound_owners: Vec<BindingTerminalOwner>,
4283) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4284 let order_claims = order_ledger.claims();
4285 let order_start = order_frontier_start(order_ledger.high());
4286 let mut order_cursor =
4287 u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4288 let mut movable_order = Vec::new();
4289 for terminal in bound_owners {
4290 movable_order.push(MovableOrderClaim {
4291 transaction_order: take_live_order(&mut order_cursor)?,
4292 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4293 });
4294 }
4295 for participant in active.participants() {
4296 movable_order.push(MovableOrderClaim {
4297 transaction_order: take_live_order(&mut order_cursor)?,
4298 owner: OrderDirectOwner::MembershipExit {
4299 participant_index: participant.participant_index(),
4300 },
4301 });
4302 }
4303 if u128::from(order_cursor) != order_start + order_claims.total() {
4304 return Err(LiveFrontierTransitionError::ResultingFrontier);
4305 }
4306 Ok(OrderClaimFrontier {
4307 ledger: order_ledger,
4308 movable_claims: movable_order,
4309 immutable_candidates: alloc::vec![ImmutableOrderCandidateMajor {
4310 transaction_order: admission_order.transaction_order(),
4311 candidate_keys: alloc::vec![admission_order],
4312 }],
4313 recovery: None,
4314 })
4315}
4316
4317fn take_live_sequence(
4318 cursor: &mut DeliverySeq,
4319 length: u64,
4320) -> Result<DeliverySeq, LiveFrontierTransitionError> {
4321 let start = *cursor;
4322 *cursor = cursor
4323 .checked_add(length)
4324 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4325 Ok(start)
4326}
4327
4328fn take_live_order(
4329 cursor: &mut TransactionOrder,
4330) -> Result<TransactionOrder, LiveFrontierTransitionError> {
4331 let value = *cursor;
4332 *cursor = cursor
4333 .checked_add(1)
4334 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4335 Ok(value)
4336}
4337
4338fn ordinary_unaccepted_marker_anchors(frontiers: &ClaimFrontiers) -> Vec<DeliverySeq> {
4339 frontiers
4340 .marker_records
4341 .iter()
4342 .filter_map(|record| {
4343 let RetainedCausalRecordKind::CompactionMarker {
4344 participant_index, ..
4345 } = record.kind
4346 else {
4347 return None;
4348 };
4349 active_participant(&frontiers.active_identities, participant_index)
4350 .is_some_and(|participant| participant.cursor < record.delivery_seq)
4351 .then_some(record.delivery_seq)
4352 })
4353 .collect()
4354}
4355
4356fn projection_failure(
4357 frontiers: ClaimFrontiers,
4358 input: OrdinaryRecordProjectionInput,
4359 error: OrdinaryProjectionError,
4360) -> Box<OrdinaryRecordProjectionFailure> {
4361 Box::new(OrdinaryRecordProjectionFailure {
4362 frontiers,
4363 input,
4364 error,
4365 })
4366}
4367
4368fn preflight_ordinary_sequence_owners(
4369 sequence: &SequenceClaimFrontier,
4370 delta: u64,
4371) -> Result<(), OrdinaryProjectionError> {
4372 if !sequence.immutable_candidates.is_empty() {
4373 return Err(OrdinaryProjectionError::SequenceRelocation);
4374 }
4375 for claim in &sequence.movable_claims {
4376 claim
4377 .delivery_seq
4378 .checked_add(delta)
4379 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4380 }
4381 for range in &sequence.products.live_times_terminal {
4382 range
4383 .start
4384 .checked_add(delta)
4385 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4386 }
4387 if let Some(range) = &sequence.products.live_times_replacement_terminal {
4388 range
4389 .start
4390 .checked_add(delta)
4391 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4392 }
4393 for range in &sequence.products.other_live_times_exit {
4394 range
4395 .start
4396 .checked_add(delta)
4397 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4398 }
4399 if let Some(recovery) = &sequence.recovery {
4400 if let Some(terminal) = &recovery.terminal {
4401 terminal
4402 .delivery_seq
4403 .checked_add(delta)
4404 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4405 }
4406 recovery
4407 .recovery_attach_seq
4408 .checked_add(delta)
4409 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4410 recovery
4411 .replacement_terminal_seq
4412 .checked_add(delta)
4413 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4414 }
4415 Ok(())
4416}
4417
4418fn relay_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4419 for claim in &mut sequence.movable_claims {
4420 claim.delivery_seq = claim.delivery_seq.wrapping_add(delta);
4421 }
4422 for range in &mut sequence.products.live_times_terminal {
4423 range.start = range.start.wrapping_add(delta);
4424 }
4425 if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4426 range.start = range.start.wrapping_add(delta);
4427 }
4428 for range in &mut sequence.products.other_live_times_exit {
4429 range.start = range.start.wrapping_add(delta);
4430 }
4431 if let Some(recovery) = &mut sequence.recovery {
4432 if let Some(terminal) = &mut recovery.terminal {
4433 terminal.delivery_seq = terminal.delivery_seq.wrapping_add(delta);
4434 }
4435 recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_add(delta);
4436 recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_add(delta);
4437 }
4438}
4439
4440fn rollback_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4441 for claim in &mut sequence.movable_claims {
4442 claim.delivery_seq = claim.delivery_seq.wrapping_sub(delta);
4443 }
4444 for range in &mut sequence.products.live_times_terminal {
4445 range.start = range.start.wrapping_sub(delta);
4446 }
4447 if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4448 range.start = range.start.wrapping_sub(delta);
4449 }
4450 for range in &mut sequence.products.other_live_times_exit {
4451 range.start = range.start.wrapping_sub(delta);
4452 }
4453 if let Some(recovery) = &mut sequence.recovery {
4454 if let Some(terminal) = &mut recovery.terminal {
4455 terminal.delivery_seq = terminal.delivery_seq.wrapping_sub(delta);
4456 }
4457 recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_sub(delta);
4458 recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_sub(delta);
4459 }
4460}
4461
4462fn preflight_ordinary_order_owners(
4463 order: &OrderClaimFrontier,
4464) -> Result<(), OrdinaryProjectionError> {
4465 if !order.immutable_candidates.is_empty() {
4466 return Err(OrdinaryProjectionError::OrderRelocation);
4467 }
4468 for claim in &order.movable_claims {
4469 claim
4470 .transaction_order
4471 .checked_add(1)
4472 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4473 }
4474 if let Some(recovery) = &order.recovery {
4475 if let Some(active_binding) = &recovery.active_binding {
4476 active_binding
4477 .transaction_order
4478 .checked_add(1)
4479 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4480 }
4481 recovery
4482 .recovery_operation_order
4483 .checked_add(1)
4484 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4485 recovery
4486 .replacement_terminal_order
4487 .checked_add(1)
4488 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4489 }
4490 Ok(())
4491}
4492
4493fn relay_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4494 for claim in &mut order.movable_claims {
4495 claim.transaction_order = claim.transaction_order.wrapping_add(1);
4496 }
4497 if let Some(recovery) = &mut order.recovery {
4498 if let Some(active_binding) = &mut recovery.active_binding {
4499 active_binding.transaction_order = active_binding.transaction_order.wrapping_add(1);
4500 }
4501 recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_add(1);
4502 recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_add(1);
4503 }
4504}
4505
4506fn rollback_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4507 for claim in &mut order.movable_claims {
4508 claim.transaction_order = claim.transaction_order.wrapping_sub(1);
4509 }
4510 if let Some(recovery) = &mut order.recovery {
4511 if let Some(active_binding) = &mut recovery.active_binding {
4512 active_binding.transaction_order = active_binding.transaction_order.wrapping_sub(1);
4513 }
4514 recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_sub(1);
4515 recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_sub(1);
4516 }
4517}
4518
4519fn validate_leave_identity<F>(
4537 frontiers: &ClaimFrontiers,
4538 member: &LiveMember<F>,
4539 settled_detached: bool,
4540) -> Result<(), PrepareLeaveAuthorityError> {
4541 if member.conversation_id() != frontiers.conversation_id {
4542 return Err(PrepareLeaveAuthorityError::Conversation);
4543 }
4544 let Some(participant) =
4545 active_participant(&frontiers.active_identities, member.participant_id())
4546 else {
4547 return Err(PrepareLeaveAuthorityError::Identity);
4548 };
4549 let cursor_matches = if settled_detached {
4550 participant.cursor >= member.cursor()
4551 } else {
4552 participant.cursor == member.cursor()
4553 };
4554 if !cursor_matches {
4555 return Err(PrepareLeaveAuthorityError::Identity);
4556 }
4557 Ok(())
4558}
4559
4560fn validate_settled_leave_prestate<F>(
4561 frontiers: &ClaimFrontiers,
4562 member: &LiveMember<F>,
4563 binding_state: BindingState,
4564) -> Result<(ParticipantId, Option<BindingEpoch>), PrepareLeaveAuthorityError> {
4565 let participant_id = member.participant_id();
4566 validate_leave_identity(
4567 frontiers,
4568 member,
4569 matches!(binding_state, BindingState::Detached),
4570 )?;
4571 if !frontiers.order.immutable_candidates.is_empty() {
4572 return Err(PrepareLeaveAuthorityError::ImmutablePrefix);
4573 }
4574 let ended_binding_epoch = match binding_state {
4575 BindingState::Detached => {
4576 let Some(participant) =
4577 active_participant(&frontiers.active_identities, participant_id)
4578 else {
4579 return Err(PrepareLeaveAuthorityError::Identity);
4580 };
4581 if !matches!(participant.binding, FrontierBinding::Detached(_)) {
4582 return Err(PrepareLeaveAuthorityError::Binding);
4583 }
4584 None
4585 }
4586 BindingState::Bound(binding)
4587 if binding.conversation_id == frontiers.conversation_id
4588 && binding.participant_id == participant_id =>
4589 {
4590 let Some(participant) =
4591 active_participant(&frontiers.active_identities, participant_id)
4592 else {
4593 return Err(PrepareLeaveAuthorityError::Identity);
4594 };
4595 if participant.binding != FrontierBinding::Bound(binding.binding_epoch) {
4596 return Err(PrepareLeaveAuthorityError::Binding);
4597 }
4598 Some(binding.binding_epoch)
4599 }
4600 BindingState::Bound(_) | BindingState::PendingFinalization(_) => {
4601 return Err(PrepareLeaveAuthorityError::Binding);
4602 }
4603 };
4604 Ok((participant_id, ended_binding_epoch))
4605}
4606
4607fn validate_pending_leave_prestate<F>(
4608 frontiers: &ClaimFrontiers,
4609 member: &LiveMember<F>,
4610 pending: PendingFinalization,
4611) -> Result<(ParticipantId, super::AdmissionOrder), PrepareLeaveAuthorityError> {
4612 let participant_id = member.participant_id();
4613 validate_leave_identity(frontiers, member, false)?;
4616 if pending.conversation_id() != frontiers.conversation_id
4617 || pending.participant_id() != participant_id
4618 {
4619 return Err(PrepareLeaveAuthorityError::Binding);
4620 }
4621 let Some(participant) = active_participant(&frontiers.active_identities, participant_id) else {
4622 return Err(PrepareLeaveAuthorityError::Identity);
4623 };
4624 if participant.binding != FrontierBinding::Detached(pending.binding_epoch()) {
4625 return Err(PrepareLeaveAuthorityError::Binding);
4626 }
4627 let expected_order = pending.admission_order();
4628 let exact_sequence_candidate = matches!(
4629 frontiers.sequence.immutable_candidates.as_slice(),
4630 [ImmutableSequenceCandidate::BindingTerminal {
4631 admission_order,
4632 owner,
4633 ..
4634 }] if *admission_order == expected_order
4635 && owner.participant_index == participant_id
4636 && owner.binding_epoch == pending.binding_epoch()
4637 );
4638 let exact_order_candidate = matches!(
4639 frontiers.order.immutable_candidates.as_slice(),
4640 [ImmutableOrderCandidateMajor {
4641 transaction_order,
4642 candidate_keys,
4643 }] if *transaction_order == expected_order.transaction_order()
4644 && candidate_keys.as_slice() == [expected_order]
4645 );
4646 if !exact_sequence_candidate || !exact_order_candidate {
4647 return Err(PrepareLeaveAuthorityError::PendingCandidate);
4648 }
4649 Ok((participant_id, expected_order))
4650}
4651
4652#[derive(Clone, Copy)]
4653enum LeaveRelayUnit {
4654 Direct(MovableOrderClaim),
4655 Recovery(RecoveryOrderBlock),
4656}
4657
4658impl LeaveRelayUnit {
4659 fn start(self) -> TransactionOrder {
4660 match self {
4661 Self::Direct(claim) => claim.transaction_order,
4662 Self::Recovery(block) => block_start_validated_order(block),
4663 }
4664 }
4665
4666 const fn len(self) -> u64 {
4667 match self {
4668 Self::Direct(_) => 1,
4669 Self::Recovery(block) => match block.active_binding {
4670 Some(_) => 3,
4671 None => 2,
4672 },
4673 }
4674 }
4675}
4676
4677struct LeaveOrderSelection {
4678 units: Vec<LeaveRelayUnit>,
4679 selected_major: TransactionOrder,
4680}
4681
4682fn select_leave_order(
4683 order: &OrderClaimFrontier,
4684 participant_id: ParticipantId,
4685 ended_binding_epoch: Option<BindingEpoch>,
4686 pending_order: Option<super::AdmissionOrder>,
4687) -> Result<LeaveOrderSelection, PrepareLeaveAuthorityError> {
4688 let Some(exit_index) = order.movable_claims.iter().position(|claim| {
4689 claim.owner
4690 == OrderDirectOwner::MembershipExit {
4691 participant_index: participant_id,
4692 }
4693 }) else {
4694 return Err(PrepareLeaveAuthorityError::MembershipExitClaim);
4695 };
4696 let exit_claim = order.movable_claims[exit_index];
4697 if pending_order
4698 .is_some_and(|pending| pending.transaction_order() >= exit_claim.transaction_order)
4699 {
4700 return Err(PrepareLeaveAuthorityError::PendingCandidate);
4701 }
4702 let active_index = matching_active_claim(order, participant_id, ended_binding_epoch)?;
4703 let mut units = Vec::new();
4704 for (index, claim) in order.movable_claims.iter().copied().enumerate() {
4705 if index != exit_index && Some(index) != active_index {
4706 units.push(LeaveRelayUnit::Direct(claim));
4707 }
4708 }
4709 if let Some(recovery) = order
4710 .recovery
4711 .filter(|recovery| recovery.participant_index != participant_id)
4712 {
4713 units.push(LeaveRelayUnit::Recovery(recovery));
4714 }
4715 units.sort_by_key(|unit| unit.start());
4716 let surviving_handles: u128 = units.iter().map(|unit| u128::from(unit.len())).sum();
4717 let exit_major = exit_claim.transaction_order;
4718 let later_handle_fits = u128::from(u64::MAX - exit_major) >= surviving_handles;
4719 let selected_major = if later_handle_fits {
4720 exit_major
4721 } else if pending_order.is_some() {
4722 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4723 } else {
4724 u64::try_from(order_frontier_start(order.ledger.high()))
4725 .map_err(|_| PrepareLeaveAuthorityError::OrderCapacity)?
4726 };
4727 Ok(LeaveOrderSelection {
4728 units,
4729 selected_major,
4730 })
4731}
4732
4733fn matching_active_claim(
4734 order: &OrderClaimFrontier,
4735 participant_id: ParticipantId,
4736 ended_binding_epoch: Option<BindingEpoch>,
4737) -> Result<Option<usize>, PrepareLeaveAuthorityError> {
4738 let Some(binding_epoch) = ended_binding_epoch else {
4739 return Ok(None);
4740 };
4741 let expected = OrderDirectOwner::ActiveBindingTerminal(BindingTerminalOwner {
4742 participant_index: participant_id,
4743 binding_epoch,
4744 });
4745 order
4746 .movable_claims
4747 .iter()
4748 .position(|claim| claim.owner == expected)
4749 .map(Some)
4750 .ok_or(PrepareLeaveAuthorityError::ActiveBindingClaim)
4751}
4752
4753fn relay_leave_order_units(
4754 units: Vec<LeaveRelayUnit>,
4755 selected_major: TransactionOrder,
4756) -> Result<(Vec<MovableOrderClaim>, Option<RecoveryOrderBlock>), PrepareLeaveAuthorityError> {
4757 let mut cursor = selected_major.checked_add(1);
4758 let mut movable_claims = Vec::new();
4759 let mut recovery = None;
4760 for unit in units {
4761 match unit {
4762 LeaveRelayUnit::Direct(mut claim) => {
4763 let Some(position) = cursor else {
4764 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4765 };
4766 claim.transaction_order = position;
4767 movable_claims.push(claim);
4768 cursor = position.checked_add(1);
4769 }
4770 LeaveRelayUnit::Recovery(block) => {
4771 let (relayed, next) = relay_recovery_block(block, cursor)?;
4772 recovery = Some(relayed);
4773 cursor = next;
4774 }
4775 }
4776 }
4777 movable_claims.sort_by_key(|claim| claim.transaction_order);
4778 Ok((movable_claims, recovery))
4779}
4780
4781const fn relay_recovery_block(
4782 block: RecoveryOrderBlock,
4783 cursor: Option<TransactionOrder>,
4784) -> Result<(RecoveryOrderBlock, Option<TransactionOrder>), PrepareLeaveAuthorityError> {
4785 let mut next = cursor;
4786 let active_binding = if let Some(mut active) = block.active_binding {
4787 let Some(position) = next else {
4788 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4789 };
4790 active.transaction_order = position;
4791 next = position.checked_add(1);
4792 Some(active)
4793 } else {
4794 None
4795 };
4796 let Some(recovery_operation_order) = next else {
4797 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4798 };
4799 let Some(replacement_terminal_order) = recovery_operation_order.checked_add(1) else {
4800 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4801 };
4802 Ok((
4803 RecoveryOrderBlock {
4804 active_binding,
4805 recovery_operation_order,
4806 replacement_terminal_order,
4807 participant_index: block.participant_index,
4808 marker_delivery_seq: block.marker_delivery_seq,
4809 recovered_binding_epoch: block.recovered_binding_epoch,
4810 },
4811 replacement_terminal_order.checked_add(1),
4812 ))
4813}
4814
4815fn leave_resulting_order_ledger(
4816 selected_major: TransactionOrder,
4817 movable_claims: &[MovableOrderClaim],
4818 recovery: Option<RecoveryOrderBlock>,
4819) -> Result<OrderLedger, PrepareLeaveAuthorityError> {
4820 let active_binding_terminals =
4821 usize_to_u64(
4822 movable_claims
4823 .iter()
4824 .filter(|claim| matches!(claim.owner, OrderDirectOwner::ActiveBindingTerminal(_)))
4825 .count(),
4826 ) + u64::from(recovery.is_some_and(|block| block.active_binding.is_some()));
4827 let membership_exits = usize_to_u64(
4828 movable_claims
4829 .iter()
4830 .filter(|claim| matches!(claim.owner, OrderDirectOwner::MembershipExit { .. }))
4831 .count(),
4832 );
4833 let has_recovery = recovery.is_some();
4834 let resulting_claims = OrderClaims::new(
4835 active_binding_terminals,
4836 membership_exits,
4837 has_recovery,
4838 has_recovery,
4839 )
4840 .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)?;
4841 OrderLedger::try_new(OrderHigh::Allocated(selected_major), resulting_claims)
4842 .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)
4843}
4844
4845fn consume_leave_order_lane(
4846 order: &mut OrderClaimFrontier,
4847 participant_id: ParticipantId,
4848 ended_binding_epoch: Option<BindingEpoch>,
4849 pending_order: Option<super::AdmissionOrder>,
4850) -> Result<TransactionOrder, PrepareLeaveAuthorityError> {
4851 let selection = select_leave_order(order, participant_id, ended_binding_epoch, pending_order)?;
4852 let (movable_claims, recovery) =
4853 relay_leave_order_units(selection.units, selection.selected_major)?;
4854 let ledger = leave_resulting_order_ledger(selection.selected_major, &movable_claims, recovery)?;
4855 *order = OrderClaimFrontier {
4856 ledger,
4857 movable_claims,
4858 immutable_candidates: Vec::new(),
4859 recovery,
4860 };
4861 Ok(selection.selected_major)
4862}
4863
4864impl ClaimFrontiersPrevalidated {
4865 #[must_use]
4867 pub(super) const fn conversation_id(&self) -> ConversationId {
4868 self.conversation_id
4869 }
4870
4871 pub(super) fn take_marker_record(
4877 &mut self,
4878 request: MarkerRecordRequest,
4879 ) -> Option<ValidatedMarkerRecord> {
4880 if self.issued_marker_record.is_some() {
4881 return None;
4882 }
4883 let record = self
4884 .retained_records
4885 .iter()
4886 .find(|record| record.delivery_seq == request.marker_delivery_seq)
4887 .copied()?;
4888 let RetainedCausalRecordKind::CompactionMarker {
4889 participant_index,
4890 provenance,
4891 } = record.kind
4892 else {
4893 return None;
4894 };
4895 if participant_index != request.participant_index {
4896 return None;
4897 }
4898 let participant = active_participant(&self.active_identities, participant_index)?;
4899 let historical_delivery = self
4900 .historical_marker_deliveries
4901 .iter()
4902 .find(|authority| authority.marker_delivery_seq == request.marker_delivery_seq);
4903 let (target_binding, occurrence) = match request.use_kind {
4904 MarkerRecordUse::Planned(target) => {
4905 if participant.binding != target || historical_delivery.is_some() {
4906 return None;
4907 }
4908 (target, MarkerRecordOccurrence::Undelivered)
4909 }
4910 MarkerRecordUse::Delivered(target) => {
4911 let delivered_binding_epoch = binding_epoch(target);
4912 if participant.binding != target
4913 || !historical_delivery.is_some_and(|authority| {
4914 authority.participant_index == participant_index
4915 && authority.delivered_binding_epoch == delivered_binding_epoch
4916 })
4917 {
4918 return None;
4919 }
4920 (target, MarkerRecordOccurrence::Delivered)
4921 }
4922 MarkerRecordUse::Recovered {
4923 prior_binding_epoch,
4924 recovered_binding_epoch,
4925 } => {
4926 if participant.binding != FrontierBinding::Detached(recovered_binding_epoch)
4927 || !historical_delivery.is_some_and(|authority| {
4928 authority.participant_index == participant_index
4929 && authority.delivered_binding_epoch == prior_binding_epoch
4930 })
4931 {
4932 return None;
4933 }
4934 (
4935 FrontierBinding::Detached(prior_binding_epoch),
4936 MarkerRecordOccurrence::Delivered,
4937 )
4938 }
4939 };
4940 self.issued_marker_record = Some(request);
4941 Some(ValidatedMarkerRecord {
4942 conversation_id: self.conversation_id,
4943 record,
4944 provenance,
4945 target_binding,
4946 occurrence,
4947 seal: MarkerAuthoritySeal::Validated,
4948 })
4949 }
4950
4951 pub(super) fn finish(
4954 self,
4955 edge: Option<super::StoredEdge>,
4956 ) -> Result<ClaimFrontiers, ParticipantStateCorruptReason> {
4957 self.validate_current_marker_edge(edge)?;
4958 self.validate_historical_delivery_consumers(edge)?;
4959 let recovery_provenance = self.resolve_recovery_provenance(edge)?;
4960 let sequence = restore_sequence_frontier(
4961 &self.active_identities,
4962 self.sequence_restore,
4963 self.retained_floor,
4964 self.sequence_ledger,
4965 recovery_provenance,
4966 &self.retained_records,
4967 &self.historical_causal_authorities,
4968 )
4969 .map_err(corrupt_frontier)?;
4970 let order = restore_order_frontier(
4971 &self.active_identities,
4972 self.order_restore,
4973 self.order_ledger,
4974 recovery_provenance,
4975 )
4976 .map_err(corrupt_frontier)?;
4977 validate_cross_counter(&sequence, &order).map_err(corrupt_frontier)?;
4978 Ok(ClaimFrontiers {
4979 conversation_id: self.conversation_id,
4980 active_identities: self.active_identities,
4981 identity_slot_limit: self.identity_slot_limit,
4982 retained_floor: self.retained_floor,
4983 retained_records: self.retained_records,
4984 marker_records: self.marker_records,
4985 fenced_marker_issued: self.fenced_marker_issued,
4986 sequence,
4987 order,
4988 historical_marker_deliveries: self.historical_marker_deliveries,
4989 historical_causal_authorities: self.historical_causal_authorities,
4990 })
4991 }
4992
4993 fn validate_historical_delivery_consumers(
4994 &self,
4995 edge: Option<super::StoredEdge>,
4996 ) -> Result<(), ParticipantStateCorruptReason> {
4997 for history in &self.historical_marker_deliveries {
4998 let recovered_origin = self.binding_origins.iter().any(|origin| {
4999 origin.participant_id() == history.participant_index
5000 && origin.recovered_marker()
5001 == Some((history.marker_delivery_seq, history.delivered_binding_epoch))
5002 });
5003 let current_marker_edge = match edge {
5004 Some(super::StoredEdge::ParticipantCursorProgress(progress)) => {
5005 progress.participant_id() == history.participant_index
5006 && progress.marker_delivery_seq() == Some(history.marker_delivery_seq)
5007 && progress.binding_epoch() == history.delivered_binding_epoch
5008 }
5009 Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) => {
5010 recovery.participant_id() == history.participant_index
5011 && recovery.marker_delivery_seq() == history.marker_delivery_seq
5012 && recovery.prior_binding_epoch() == history.delivered_binding_epoch
5013 }
5014 Some(
5015 super::StoredEdge::ObserverProjection(_)
5016 | super::StoredEdge::PhysicalCompaction(_)
5017 | super::StoredEdge::MarkerDelivery(_)
5018 | super::StoredEdge::DetachedMarkerRelease(_)
5019 | super::StoredEdge::DetachedCursorRelease(_),
5020 )
5021 | None => false,
5022 };
5023 if !recovered_origin && !current_marker_edge {
5024 return Err(self.marker_corruption(history.marker_delivery_seq));
5025 }
5026 }
5027 Ok(())
5028 }
5029
5030 fn validate_current_marker_edge(
5031 &self,
5032 edge: Option<super::StoredEdge>,
5033 ) -> Result<(), ParticipantStateCorruptReason> {
5034 if let Some(MarkerRecordRequest {
5035 participant_index,
5036 marker_delivery_seq,
5037 use_kind:
5038 MarkerRecordUse::Recovered {
5039 recovered_binding_epoch,
5040 ..
5041 },
5042 }) = self.issued_marker_record
5043 {
5044 let recovered_edge_matches = matches!(
5045 edge,
5046 Some(super::StoredEdge::DetachedCursorRelease(release))
5047 if release.participant_id() == participant_index
5048 && release.last_dead_binding_epoch() == recovered_binding_epoch
5049 );
5050 if !recovered_edge_matches {
5051 return Err(self.marker_corruption(marker_delivery_seq));
5052 }
5053 return Ok(());
5054 }
5055 let Some(context) = edge.and_then(marker_edge_context) else {
5056 return Ok(());
5057 };
5058 if self
5059 .issued_marker_record
5060 .is_none_or(|request| request.marker_delivery_seq != context.marker_delivery_seq)
5061 || !self.marker_context_matches(context)
5062 {
5063 return Err(self.marker_corruption(context.marker_delivery_seq));
5064 }
5065 Ok(())
5066 }
5067
5068 fn resolve_recovery_provenance(
5069 &self,
5070 edge: Option<super::StoredEdge>,
5071 ) -> Result<Option<RecoveryClaimProvenance>, ParticipantStateCorruptReason> {
5072 let Some(marker_delivery_seq) = self.recovery_marker_delivery_seq else {
5073 return Ok(None);
5074 };
5075
5076 if let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge {
5077 return self
5078 .resolve_postfate_recovery_provenance(marker_delivery_seq, recovery)
5079 .map(Some);
5080 }
5081
5082 let candidate =
5083 self.sequence_restore.immutable_candidates.iter().find_map(
5084 |candidate| match candidate {
5085 ImmutableSequenceCandidate::Marker(marker)
5086 if marker.delivery_seq == marker_delivery_seq
5087 && matches!(marker.target_binding, FrontierBinding::Bound(_)) =>
5088 {
5089 Some(*marker)
5090 }
5091 _ => None,
5092 },
5093 );
5094 let retained = self.retained_records.iter().find_map(|record| {
5095 let RetainedCausalRecordKind::CompactionMarker {
5096 participant_index, ..
5097 } = record.kind
5098 else {
5099 return None;
5100 };
5101 if record.delivery_seq != marker_delivery_seq {
5102 return None;
5103 }
5104 let participant = active_participant(&self.active_identities, participant_index)?;
5105 let FrontierBinding::Bound(binding_epoch) = participant.binding else {
5106 return None;
5107 };
5108 let historical = self.historical_marker_deliveries.iter().find(|history| {
5109 history.participant_index == participant_index
5110 && history.marker_delivery_seq == marker_delivery_seq
5111 })?;
5112 Some((
5113 participant_index,
5114 binding_epoch,
5115 historical.delivered_binding_epoch,
5116 ))
5117 });
5118 let provenance = match (candidate, retained) {
5119 (Some(marker), None) => RecoveryClaimProvenance {
5120 participant_index: marker.admission_order.participant_index(),
5121 marker_delivery_seq,
5122 prior_binding_epoch: binding_epoch(marker.target_binding),
5123 current_binding_epoch: binding_epoch(marker.target_binding),
5124 phase: RecoveryClaimPhase::PreFate,
5125 },
5126 (None, Some((participant_index, current_binding_epoch, prior_binding_epoch))) => {
5127 let phase = if current_binding_epoch == prior_binding_epoch {
5128 let Some(context) = edge.and_then(marker_edge_context) else {
5129 return Err(self.marker_corruption(marker_delivery_seq));
5130 };
5131 if context.marker_delivery_seq != marker_delivery_seq
5132 || context.participant_index != participant_index
5133 || context.binding_epoch != prior_binding_epoch
5134 || context.target_binding != FrontierBinding::Bound(prior_binding_epoch)
5135 {
5136 return Err(self.marker_corruption(marker_delivery_seq));
5137 }
5138 RecoveryClaimPhase::PreFate
5139 } else {
5140 let recovered_origin_matches = self.binding_origins.iter().any(|origin| {
5141 origin.participant_id() == participant_index
5142 && origin.binding_epoch() == current_binding_epoch
5143 && origin.recovered_marker()
5144 == Some((marker_delivery_seq, prior_binding_epoch))
5145 });
5146 if !recovered_origin_matches
5147 || !matches!(
5148 edge,
5149 Some(
5150 super::StoredEdge::ObserverProjection(_)
5151 | super::StoredEdge::PhysicalCompaction(_)
5152 )
5153 )
5154 {
5155 return Err(self.marker_corruption(marker_delivery_seq));
5156 }
5157 RecoveryClaimPhase::RecoveredBound
5158 };
5159 RecoveryClaimProvenance {
5160 participant_index,
5161 marker_delivery_seq,
5162 prior_binding_epoch,
5163 current_binding_epoch,
5164 phase,
5165 }
5166 }
5167 (Some(_), Some(_)) | (None, None) => {
5168 return Err(self.marker_corruption(marker_delivery_seq));
5169 }
5170 };
5171 Ok(Some(provenance))
5172 }
5173
5174 fn resolve_postfate_recovery_provenance(
5175 &self,
5176 marker_delivery_seq: DeliverySeq,
5177 recovery: super::DetachedCredentialRecovery,
5178 ) -> Result<RecoveryClaimProvenance, ParticipantStateCorruptReason> {
5179 let provenance = RecoveryClaimProvenance {
5180 participant_index: recovery.participant_id(),
5181 marker_delivery_seq: recovery.marker_delivery_seq(),
5182 prior_binding_epoch: recovery.prior_binding_epoch(),
5183 current_binding_epoch: recovery.prior_binding_epoch(),
5184 phase: RecoveryClaimPhase::PostFate,
5185 };
5186 let context = MarkerEdgeContext {
5187 participant_index: provenance.participant_index,
5188 marker_delivery_seq: provenance.marker_delivery_seq,
5189 binding_epoch: provenance.prior_binding_epoch,
5190 target_binding: FrontierBinding::Detached(provenance.prior_binding_epoch),
5191 };
5192 if marker_delivery_seq != provenance.marker_delivery_seq
5193 || self
5194 .issued_marker_record
5195 .is_none_or(|request| request.marker_delivery_seq != marker_delivery_seq)
5196 || !self.marker_context_matches(context)
5197 {
5198 return Err(self.marker_corruption(marker_delivery_seq));
5199 }
5200 Ok(provenance)
5201 }
5202
5203 fn marker_context_matches(&self, context: MarkerEdgeContext) -> bool {
5204 self.marker_records.iter().any(|record| {
5205 matches!(
5206 record.kind,
5207 RetainedCausalRecordKind::CompactionMarker {
5208 participant_index,
5209 ..
5210 } if participant_index == context.participant_index
5211 ) && record.delivery_seq == context.marker_delivery_seq
5212 && active_participant(&self.active_identities, context.participant_index)
5213 .is_some_and(|participant| participant.binding == context.target_binding)
5214 })
5215 }
5216
5217 fn marker_corruption(&self, delivery_seq: DeliverySeq) -> ParticipantStateCorruptReason {
5218 corrupt_frontier(sequence_error(
5219 sequence_ordinal(self.sequence_ledger, delivery_seq),
5220 ClaimFrontierInvalidReason::RecoveryBlock,
5221 ))
5222 }
5223}
5224
5225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5226struct MarkerEdgeContext {
5227 participant_index: ParticipantId,
5228 marker_delivery_seq: DeliverySeq,
5229 binding_epoch: BindingEpoch,
5230 target_binding: FrontierBinding,
5231}
5232
5233fn marker_edge_context(edge: super::StoredEdge) -> Option<MarkerEdgeContext> {
5234 match edge {
5235 super::StoredEdge::MarkerDelivery(delivery) => Some(MarkerEdgeContext {
5236 participant_index: delivery.participant_id(),
5237 marker_delivery_seq: delivery.marker_delivery_seq(),
5238 binding_epoch: delivery.binding_epoch(),
5239 target_binding: FrontierBinding::Bound(delivery.binding_epoch()),
5240 }),
5241 super::StoredEdge::ParticipantCursorProgress(progress) => {
5242 let marker_delivery_seq = progress.marker_delivery_seq()?;
5243 Some(MarkerEdgeContext {
5244 participant_index: progress.participant_id(),
5245 marker_delivery_seq,
5246 binding_epoch: progress.binding_epoch(),
5247 target_binding: FrontierBinding::Bound(progress.binding_epoch()),
5248 })
5249 }
5250 super::StoredEdge::DetachedCredentialRecovery(recovery) => Some(MarkerEdgeContext {
5251 participant_index: recovery.participant_id(),
5252 marker_delivery_seq: recovery.marker_delivery_seq(),
5253 binding_epoch: recovery.prior_binding_epoch(),
5254 target_binding: FrontierBinding::Detached(recovery.prior_binding_epoch()),
5255 }),
5256 super::StoredEdge::DetachedMarkerRelease(release) => Some(MarkerEdgeContext {
5257 participant_index: release.participant_id(),
5258 marker_delivery_seq: release.marker_delivery_seq(),
5259 binding_epoch: release.last_dead_binding_epoch(),
5260 target_binding: FrontierBinding::Detached(release.last_dead_binding_epoch()),
5261 }),
5262 super::StoredEdge::ObserverProjection(_)
5263 | super::StoredEdge::PhysicalCompaction(_)
5264 | super::StoredEdge::DetachedCursorRelease(_) => None,
5265 }
5266}
5267
5268#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5269#[repr(u8)]
5270enum SequenceClass {
5271 Exit = 0,
5272 Terminal = 1,
5273 Marker = 2,
5274 RecoveryAttach = 3,
5275 RecoveryReplacementTerminal = 4,
5276 LiveTimesTerminal = 5,
5277 LiveTimesReplacementTerminal = 6,
5278 OtherLiveTimesExit = 7,
5279}
5280
5281#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5282#[repr(u8)]
5283enum OrderClass {
5284 ActiveBindingTerminal = 0,
5285 MembershipExit = 1,
5286 RecoveryOperation = 2,
5287 RecoveryReplacementTerminal = 3,
5288}
5289
5290#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5291struct NumericSegment<C> {
5292 start: u128,
5293 length: u128,
5294 class: Option<C>,
5295 immutable: bool,
5296}
5297
5298fn first_duplicate_candidate_key(
5299 candidates: &[ImmutableSequenceCandidate],
5300 retained_records: &[RetainedCausalRecord],
5301) -> Option<super::AdmissionOrder> {
5302 let mut keys: Vec<_> = candidates
5303 .iter()
5304 .map(|candidate| candidate.admission_order())
5305 .chain(retained_records.iter().map(|record| record.admission_order))
5306 .collect();
5307 keys.sort_unstable();
5308 let mut previous = None;
5309 for key in keys {
5310 if previous == Some(key) {
5311 return Some(key);
5312 }
5313 previous = Some(key);
5314 }
5315 None
5316}
5317
5318fn validate_unique_candidate_keys(
5319 candidates: &[ImmutableSequenceCandidate],
5320 retained_records: &[RetainedCausalRecord],
5321) -> Result<(), ParticipantStateCorruptReason> {
5322 let Some(order) = first_duplicate_candidate_key(candidates, retained_records) else {
5323 return Ok(());
5324 };
5325 Err(ParticipantStateCorruptReason::DuplicateCandidateKey {
5326 transaction_order: order.transaction_order(),
5327 candidate_phase: order.candidate_phase(),
5328 participant_index: order.participant_index(),
5329 })
5330}
5331
5332fn validate_sequence_numeric(
5333 restore: &SequenceClaimFrontierRestore,
5334 ledger: SequenceLedger,
5335) -> Result<(), ClaimFrontierError> {
5336 let mut segments = sequence_segments(restore);
5337 validate_numeric_segments(
5338 ClaimFrontierCounter::DeliverySequence,
5339 u128::from(ledger.high_watermark()) + 1,
5340 ledger.required_reserve(),
5341 &mut segments,
5342 &sequence_expected_counts(ledger),
5343 )
5344}
5345
5346fn validate_order_numeric(
5347 restore: &OrderClaimFrontierRestore,
5348 ledger: OrderLedger,
5349) -> Result<(), ClaimFrontierError> {
5350 let mut segments = order_segments(restore, ledger.high());
5351 validate_numeric_segments(
5352 ClaimFrontierCounter::TransactionOrder,
5353 order_frontier_start(ledger.high()),
5354 order_frontier_candidate_count(restore, ledger.high()) + ledger.claims().total(),
5355 &mut segments,
5356 &order_expected_counts(ledger),
5357 )
5358}
5359
5360fn validate_bounded_shape(
5361 restore: &ClaimFrontiersRestore,
5362 sequence_ledger: SequenceLedger,
5363) -> Result<(), ClaimFrontierError> {
5364 let identity_limit = u128::from(restore.identity_slot_limit);
5365 let twice_identity_limit = identity_limit.saturating_mul(2);
5366 let order_candidate_keys = restore
5367 .order
5368 .immutable_candidates
5369 .iter()
5370 .fold(0_u128, |count, candidate| {
5371 count.saturating_add(usize_to_u128(candidate.candidate_keys.len()))
5372 });
5373 let bounded = usize_to_u128(restore.active_identities.len()) <= identity_limit
5374 && usize_to_u128(restore.sequence.movable_claims.len()) <= twice_identity_limit
5375 && usize_to_u128(restore.sequence.immutable_candidates.len()) <= twice_identity_limit
5376 && usize_to_u128(restore.sequence.products.live_times_terminal.len()) <= identity_limit
5377 && usize_to_u128(restore.sequence.products.other_live_times_exit.len()) <= identity_limit
5378 && usize_to_u128(restore.historical_marker_deliveries.len())
5379 <= u128::from(restore.retained_record_limit)
5380 && usize_to_u128(restore.historical_causal_facts.len()) <= twice_identity_limit
5381 && usize_to_u128(restore.order.movable_claims.len()) <= twice_identity_limit
5382 && usize_to_u128(restore.order.immutable_candidates.len()) <= twice_identity_limit
5383 && order_candidate_keys <= twice_identity_limit;
5384 if bounded {
5385 Ok(())
5386 } else {
5387 Err(sequence_error(
5388 sequence_ledger.required_reserve(),
5389 ClaimFrontierInvalidReason::LogicalOwner,
5390 ))
5391 }
5392}
5393
5394fn validated_retained_records(
5395 mut records: Vec<RetainedCausalRecord>,
5396 retained_floor: u128,
5397 retained_record_limit: u64,
5398 identity_slot_limit: u64,
5399 ledger: SequenceLedger,
5400) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5401 let high_end = u128::from(ledger.high_watermark()) + 1;
5402 let expected_count = high_end.saturating_sub(retained_floor);
5403 if retained_floor > high_end
5404 || usize_to_u128(records.len()) > u128::from(retained_record_limit)
5405 || usize_to_u128(records.len()) != expected_count
5406 {
5407 return Err(sequence_error(
5408 usize_to_u128(records.len()).min(expected_count),
5409 ClaimFrontierInvalidReason::LogicalOwner,
5410 ));
5411 }
5412 records.sort_by_key(|record| record.delivery_seq);
5413 let mut previous_admission_order = None;
5414 for (record_index, record) in records.iter().enumerate() {
5415 let (participant_index, valid_kind) = match record.kind {
5416 RetainedCausalRecordKind::BindingTerminal(owner) => (
5417 owner.participant_index,
5418 record.admission_order.candidate_phase() == CandidatePhase::BindingTerminal
5419 && record.admission_order.participant_index() == owner.participant_index,
5420 ),
5421 RetainedCausalRecordKind::MembershipExit { participant_index } => (
5422 participant_index,
5423 record.admission_order.candidate_phase() == CandidatePhase::MembershipExit
5424 && record.admission_order.participant_index() == participant_index,
5425 ),
5426 RetainedCausalRecordKind::AttachLifecycle {
5427 participant_index, ..
5428 } => (
5429 participant_index,
5430 record.admission_order.candidate_phase() == CandidatePhase::AttachLifecycle
5431 && record.admission_order.participant_index() == participant_index,
5432 ),
5433 RetainedCausalRecordKind::OrdinaryRecord { participant_index } => (
5434 participant_index,
5435 record.admission_order.candidate_phase() == CandidatePhase::OrdinaryRecord
5436 && record.admission_order.participant_index() == participant_index,
5437 ),
5438 RetainedCausalRecordKind::CompactionMarker {
5439 participant_index,
5440 provenance,
5441 } => (
5442 participant_index,
5443 record.admission_order.candidate_phase() == CandidatePhase::CompactionMarker
5444 && record.admission_order.participant_index() == participant_index
5445 && marker_provenance_targets(provenance, participant_index),
5446 ),
5447 };
5448 let expected_sequence = retained_floor + rank_index(record_index);
5449 if participant_index >= identity_slot_limit
5450 || !valid_kind
5451 || previous_admission_order.is_some_and(|previous| previous >= record.admission_order)
5452 || u128::from(record.delivery_seq) != expected_sequence
5453 {
5454 return Err(sequence_error(
5455 rank_index(record_index),
5456 ClaimFrontierInvalidReason::CandidateKey,
5457 ));
5458 }
5459 previous_admission_order = Some(record.admission_order);
5460 }
5461 Ok(records)
5462}
5463
5464fn validated_active_marker_records(
5465 retained_markers: &[RetainedCausalRecord],
5466 mut active_marker_anchors: Vec<DeliverySeq>,
5467 identity_slot_limit: u64,
5468 ledger: SequenceLedger,
5469) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5470 if usize_to_u128(active_marker_anchors.len()) > u128::from(identity_slot_limit) {
5471 return Err(sequence_error(
5472 ledger.required_reserve(),
5473 ClaimFrontierInvalidReason::LogicalOwner,
5474 ));
5475 }
5476 active_marker_anchors.sort_unstable();
5477 let mut previous_sequence = None;
5478 let mut owners = Vec::new();
5479 let mut active_records = Vec::new();
5480 for delivery_seq in active_marker_anchors {
5481 let Some(record) = retained_markers
5482 .iter()
5483 .find(|record| record.delivery_seq == delivery_seq)
5484 .copied()
5485 else {
5486 return Err(sequence_error(
5487 sequence_ordinal(ledger, delivery_seq),
5488 ClaimFrontierInvalidReason::LogicalOwner,
5489 ));
5490 };
5491 let RetainedCausalRecordKind::CompactionMarker {
5492 participant_index, ..
5493 } = record.kind
5494 else {
5495 return Err(sequence_error(
5496 sequence_ordinal(ledger, delivery_seq),
5497 ClaimFrontierInvalidReason::LogicalOwner,
5498 ));
5499 };
5500 if previous_sequence == Some(delivery_seq) || owners.contains(&participant_index) {
5501 return Err(sequence_error(
5502 sequence_ordinal(ledger, delivery_seq),
5503 ClaimFrontierInvalidReason::LogicalOwner,
5504 ));
5505 }
5506 previous_sequence = Some(delivery_seq);
5507 owners.push(participant_index);
5508 active_records.push(record);
5509 }
5510 Ok(active_records)
5511}
5512
5513fn validated_historical_marker_deliveries(
5514 mut facts: Vec<HistoricalMarkerDeliveryFactRestore>,
5515 conversation_id: ConversationId,
5516 active: &ActiveIdentityRanks,
5517 sources: &MarkerDeliverySources<'_>,
5518 retained_record_limit: u64,
5519 ledger: SequenceLedger,
5520) -> Result<Vec<HistoricalMarkerDeliveryAuthority>, ClaimFrontierError> {
5521 let MarkerDeliverySources {
5522 retained: retained_records,
5523 historical: historical_causal_authorities,
5524 candidates: immutable_candidates,
5525 } = *sources;
5526 if usize_to_u128(facts.len()) > u128::from(retained_record_limit) {
5527 return Err(sequence_error(
5528 ledger.required_reserve(),
5529 ClaimFrontierInvalidReason::LogicalOwner,
5530 ));
5531 }
5532 facts.sort_by_key(|fact| fact.marker_delivery_seq);
5533 let mut previous_sequence = None;
5534 let mut authorities = Vec::new();
5535 for fact in facts {
5536 let matching_record = retained_records.iter().find(|record| {
5537 record.delivery_seq == fact.marker_delivery_seq
5538 && matches!(
5539 record.kind,
5540 RetainedCausalRecordKind::CompactionMarker {
5541 participant_index,
5542 ..
5543 } if participant_index == fact.participant_index
5544 )
5545 });
5546 let current_bound =
5547 active_participant(active, fact.participant_index).is_some_and(|participant| {
5548 participant.binding == FrontierBinding::Bound(fact.delivered_binding_epoch)
5549 });
5550 let terminal_order = historical_causal_authorities
5551 .iter()
5552 .find_map(|authority| match authority.kind {
5553 HistoricalCausalKind::BindingTerminal(owner)
5554 if binding_terminal_matches_delivery(owner, fact) =>
5555 {
5556 Some(authority.admission_order)
5557 }
5558 HistoricalCausalKind::BindingTerminal(_)
5559 | HistoricalCausalKind::MembershipExit(_) => None,
5560 })
5561 .or_else(|| {
5562 retained_records
5563 .iter()
5564 .find_map(|record| match record.kind {
5565 RetainedCausalRecordKind::BindingTerminal(owner)
5566 if binding_terminal_matches_delivery(owner, fact) =>
5567 {
5568 Some(record.admission_order)
5569 }
5570 RetainedCausalRecordKind::BindingTerminal(_)
5571 | RetainedCausalRecordKind::MembershipExit { .. }
5572 | RetainedCausalRecordKind::AttachLifecycle { .. }
5573 | RetainedCausalRecordKind::OrdinaryRecord { .. }
5574 | RetainedCausalRecordKind::CompactionMarker { .. } => None,
5575 })
5576 })
5577 .or_else(|| {
5578 immutable_candidates
5579 .iter()
5580 .find_map(|candidate| match candidate {
5581 ImmutableSequenceCandidate::BindingTerminal {
5582 admission_order,
5583 owner,
5584 ..
5585 } if binding_terminal_matches_delivery(*owner, fact) => {
5586 Some(*admission_order)
5587 }
5588 ImmutableSequenceCandidate::BindingTerminal { .. }
5589 | ImmutableSequenceCandidate::Marker(_) => None,
5590 })
5591 });
5592 let historical_epoch_is_backed = matching_record.is_some_and(|marker_record| {
5593 current_bound
5594 || terminal_order
5595 .is_some_and(|terminal_order| terminal_order > marker_record.admission_order)
5596 });
5597 if fact.conversation_id != conversation_id
5598 || previous_sequence == Some(fact.marker_delivery_seq)
5599 || !historical_epoch_is_backed
5600 {
5601 return Err(sequence_error(
5602 sequence_ordinal(ledger, fact.marker_delivery_seq),
5603 ClaimFrontierInvalidReason::LogicalOwner,
5604 ));
5605 }
5606 previous_sequence = Some(fact.marker_delivery_seq);
5607 authorities.push(HistoricalMarkerDeliveryAuthority {
5608 participant_index: fact.participant_index,
5609 marker_delivery_seq: fact.marker_delivery_seq,
5610 delivered_binding_epoch: fact.delivered_binding_epoch,
5611 });
5612 }
5613 Ok(authorities)
5614}
5615
5616fn binding_terminal_matches_delivery(
5617 owner: BindingTerminalOwner,
5618 fact: HistoricalMarkerDeliveryFactRestore,
5619) -> bool {
5620 (owner.participant_index, owner.binding_epoch)
5621 == (fact.participant_index, fact.delivered_binding_epoch)
5622}
5623
5624struct MarkerDeliverySources<'a> {
5625 retained: &'a [RetainedCausalRecord],
5626 historical: &'a [HistoricalCausalAuthority],
5627 candidates: &'a [ImmutableSequenceCandidate],
5628}
5629
5630struct BindingOriginValidation<'a> {
5631 conversation_id: ConversationId,
5632 active: &'a ActiveIdentityRanks,
5633 origins: &'a [BindingOrigin],
5634 retained_records: &'a [RetainedCausalRecord],
5635 causal_authorities: &'a [HistoricalCausalAuthority],
5636 historical_marker_deliveries: &'a [HistoricalMarkerDeliveryAuthority],
5637 total: bool,
5638 ledger: SequenceLedger,
5639}
5640
5641impl BindingOriginValidation<'_> {
5642 fn validate(&self) -> Result<(), ClaimFrontierError> {
5643 if !self.total {
5644 return if self.origins.is_empty() {
5645 Ok(())
5646 } else {
5647 Err(self.logical_owner_error())
5648 };
5649 }
5650 if self.origins.len() != self.active.participants.len() {
5651 return Err(self.logical_owner_error());
5652 }
5653 for participant in &self.active.participants {
5654 let mut matching = self
5655 .origins
5656 .iter()
5657 .filter(|origin| origin.participant_id() == participant.participant_index);
5658 let Some(origin) = matching.next() else {
5659 return Err(self.logical_owner_error());
5660 };
5661 if matching.next().is_some() {
5662 return Err(self.logical_owner_error());
5663 }
5664 self.validate_origin(*participant, *origin)?;
5665 }
5666 Ok(())
5667 }
5668
5669 fn validate_origin(
5670 &self,
5671 participant: FrontierParticipant,
5672 origin: BindingOrigin,
5673 ) -> Result<(), ClaimFrontierError> {
5674 let current_epoch = binding_epoch(participant.binding);
5675 let attached = origin.attached();
5676 if origin.conversation_id() != self.conversation_id
5677 || origin.binding_epoch() != current_epoch
5678 || attached.conversation_id() != self.conversation_id
5679 || attached.participant_id() != participant.participant_index
5680 || attached.binding_epoch() != current_epoch
5681 || attached.admission_order().candidate_phase() != CandidatePhase::AttachLifecycle
5682 {
5683 return Err(self.logical_owner_error());
5684 }
5685 let mut retained_attach_for_binding = self.retained_records.iter().filter(|record| {
5686 matches!(
5687 record.kind,
5688 RetainedCausalRecordKind::AttachLifecycle {
5689 participant_index,
5690 binding_epoch,
5691 } if participant_index == participant.participant_index
5692 && binding_epoch == current_epoch
5693 )
5694 });
5695 let retained_attach_matches = retained_attach_for_binding.clone().any(|record| {
5696 record.delivery_seq == attached.delivery_seq()
5697 && record.admission_order == attached.admission_order()
5698 });
5699 if retained_attach_for_binding.next().is_some() && !retained_attach_matches {
5700 return Err(self.logical_owner_error());
5701 }
5702 if let Some((marker_delivery_seq, prior_binding_epoch)) = origin.recovered_marker() {
5703 let generation_is_next = prior_binding_epoch
5704 .capability_generation
5705 .get()
5706 .checked_add(1)
5707 == Some(current_epoch.capability_generation.get());
5708 let marker_history_matches = self.historical_marker_deliveries.iter().any(|history| {
5709 history.participant_index == participant.participant_index
5710 && history.marker_delivery_seq == marker_delivery_seq
5711 && history.delivered_binding_epoch == prior_binding_epoch
5712 });
5713 if !generation_is_next || !marker_history_matches {
5714 return Err(sequence_error(
5715 sequence_ordinal(self.ledger, marker_delivery_seq),
5716 ClaimFrontierInvalidReason::RecoveryBlock,
5717 ));
5718 }
5719 } else if matches!(participant.binding, FrontierBinding::Detached(_))
5720 && !binding_terminal_exists(
5721 participant.participant_index,
5722 current_epoch,
5723 self.retained_records,
5724 self.causal_authorities,
5725 )
5726 {
5727 return Err(self.logical_owner_error());
5728 }
5729 Ok(())
5730 }
5731
5732 const fn logical_owner_error(&self) -> ClaimFrontierError {
5733 sequence_error(
5734 self.ledger.required_reserve(),
5735 ClaimFrontierInvalidReason::LogicalOwner,
5736 )
5737 }
5738}
5739
5740fn binding_terminal_exists(
5741 participant_index: ParticipantId,
5742 binding_epoch: BindingEpoch,
5743 retained_records: &[RetainedCausalRecord],
5744 historical_causal_authorities: &[HistoricalCausalAuthority],
5745) -> bool {
5746 retained_records.iter().any(|record| {
5747 matches!(
5748 record.kind,
5749 RetainedCausalRecordKind::BindingTerminal(owner)
5750 if owner.participant_index == participant_index
5751 && owner.binding_epoch == binding_epoch
5752 )
5753 }) || historical_causal_authorities.iter().any(|authority| {
5754 matches!(
5755 authority.kind,
5756 HistoricalCausalKind::BindingTerminal(owner)
5757 if owner.participant_index == participant_index
5758 && owner.binding_epoch == binding_epoch
5759 )
5760 })
5761}
5762
5763fn validate_marker_credit_owners(
5764 candidates: &[ImmutableSequenceCandidate],
5765 marker_records: &[RetainedCausalRecord],
5766 identity_slot_limit: u64,
5767 ledger: SequenceLedger,
5768) -> Result<(), ClaimFrontierError> {
5769 let mut owners = Vec::new();
5770 for record in marker_records {
5771 let RetainedCausalRecordKind::CompactionMarker {
5772 participant_index, ..
5773 } = record.kind
5774 else {
5775 continue;
5776 };
5777 if owners.contains(&participant_index) {
5778 return Err(sequence_error(
5779 ledger.required_reserve(),
5780 ClaimFrontierInvalidReason::LogicalOwner,
5781 ));
5782 }
5783 owners.push(participant_index);
5784 }
5785 for candidate in candidates {
5786 let ImmutableSequenceCandidate::Marker(marker) = candidate else {
5787 continue;
5788 };
5789 let participant_index = marker.admission_order.participant_index();
5790 if owners.contains(&participant_index) {
5791 return Err(sequence_error(
5792 sequence_ordinal(ledger, marker.delivery_seq),
5793 ClaimFrontierInvalidReason::LogicalOwner,
5794 ));
5795 }
5796 owners.push(participant_index);
5797 }
5798 if usize_to_u128(owners.len()) > u128::from(identity_slot_limit) {
5799 return Err(sequence_error(
5800 ledger.required_reserve(),
5801 ClaimFrontierInvalidReason::LogicalOwner,
5802 ));
5803 }
5804 Ok(())
5805}
5806
5807fn validated_historical_authorities(
5808 facts: Vec<HistoricalCausalFactRestore>,
5809 conversation_id: ConversationId,
5810 identity_slot_limit: u64,
5811 ledger: SequenceLedger,
5812 history: &ValidatedConversationHistory,
5813) -> Result<Vec<HistoricalCausalAuthority>, ClaimFrontierError> {
5814 if usize_to_u128(facts.len()) > u128::from(identity_slot_limit).saturating_mul(2) {
5815 return Err(sequence_error(
5816 ledger.required_reserve(),
5817 ClaimFrontierInvalidReason::LogicalOwner,
5818 ));
5819 }
5820 let authorities: Vec<_> = facts
5821 .into_iter()
5822 .map(HistoricalCausalAuthority::from_restore)
5823 .collect();
5824 let mut seen = Vec::new();
5825 for authority in &authorities {
5826 let (participant_index, phase) = match authority.kind {
5827 HistoricalCausalKind::BindingTerminal(owner) => {
5828 (owner.participant_index, CandidatePhase::BindingTerminal)
5829 }
5830 HistoricalCausalKind::MembershipExit(participant_index) => {
5831 (participant_index, CandidatePhase::MembershipExit)
5832 }
5833 };
5834 if authority.conversation_id != conversation_id
5835 || participant_index >= identity_slot_limit
5836 || authority.admission_order.participant_index() != participant_index
5837 || authority.admission_order.candidate_phase() != phase
5838 || seen.contains(authority)
5839 || !history.causal_authorities.contains(authority)
5840 {
5841 return Err(sequence_error(
5842 ledger.required_reserve(),
5843 ClaimFrontierInvalidReason::LogicalOwner,
5844 ));
5845 }
5846 seen.push(*authority);
5847 }
5848 Ok(authorities)
5849}
5850
5851const fn corrupt_frontier(error: ClaimFrontierError) -> ParticipantStateCorruptReason {
5852 ParticipantStateCorruptReason::ClaimFrontierInvalid {
5853 counter: match error.counter {
5854 ClaimFrontierCounter::DeliverySequence => ClaimCounter::DeliverySeq,
5855 ClaimFrontierCounter::TransactionOrder => ClaimCounter::TransactionOrder,
5856 },
5857 first_bad_position: error.first_bad_position,
5858 }
5859}
5860
5861fn sequence_segments(restore: &SequenceClaimFrontierRestore) -> Vec<NumericSegment<SequenceClass>> {
5862 let mut segments = Vec::new();
5863 for claim in &restore.movable_claims {
5864 segments.push(NumericSegment {
5865 start: u128::from(claim.delivery_seq),
5866 length: 1,
5867 class: Some(match claim.owner {
5868 SequenceDirectOwner::MembershipExit { .. } => SequenceClass::Exit,
5869 SequenceDirectOwner::BindingTerminal(_) => SequenceClass::Terminal,
5870 }),
5871 immutable: false,
5872 });
5873 }
5874 for candidate in &restore.immutable_candidates {
5875 segments.push(NumericSegment {
5876 start: u128::from(candidate.delivery_seq()),
5877 length: 1,
5878 class: Some(sequence_candidate_class(*candidate)),
5879 immutable: true,
5880 });
5881 }
5882 for range in &restore.products.live_times_terminal {
5883 segments.push(NumericSegment {
5884 start: u128::from(range.start),
5885 length: u128::from(range.length),
5886 class: Some(SequenceClass::LiveTimesTerminal),
5887 immutable: false,
5888 });
5889 }
5890 if let Some(range) = restore.products.live_times_replacement_terminal {
5891 segments.push(NumericSegment {
5892 start: u128::from(range.start),
5893 length: u128::from(range.length),
5894 class: Some(SequenceClass::LiveTimesReplacementTerminal),
5895 immutable: false,
5896 });
5897 }
5898 for range in &restore.products.other_live_times_exit {
5899 segments.push(NumericSegment {
5900 start: u128::from(range.start),
5901 length: u128::from(range.length),
5902 class: Some(SequenceClass::OtherLiveTimesExit),
5903 immutable: false,
5904 });
5905 }
5906 if let Some(recovery) = restore.recovery {
5907 if let Some(terminal) = recovery.terminal {
5908 segments.push(NumericSegment {
5909 start: u128::from(terminal.delivery_seq),
5910 length: 1,
5911 class: Some(SequenceClass::Terminal),
5912 immutable: false,
5913 });
5914 }
5915 segments.push(NumericSegment {
5916 start: u128::from(recovery.recovery_attach_seq),
5917 length: 1,
5918 class: Some(SequenceClass::RecoveryAttach),
5919 immutable: false,
5920 });
5921 segments.push(NumericSegment {
5922 start: u128::from(recovery.replacement_terminal_seq),
5923 length: 1,
5924 class: Some(SequenceClass::RecoveryReplacementTerminal),
5925 immutable: false,
5926 });
5927 }
5928 segments
5929}
5930
5931fn order_segments(
5932 restore: &OrderClaimFrontierRestore,
5933 high: OrderHigh,
5934) -> Vec<NumericSegment<OrderClass>> {
5935 let mut segments = Vec::new();
5936 for claim in &restore.movable_claims {
5937 segments.push(NumericSegment {
5938 start: u128::from(claim.transaction_order),
5939 length: 1,
5940 class: Some(match claim.owner {
5941 OrderDirectOwner::ActiveBindingTerminal(_) => OrderClass::ActiveBindingTerminal,
5942 OrderDirectOwner::MembershipExit { .. } => OrderClass::MembershipExit,
5943 }),
5944 immutable: false,
5945 });
5946 }
5947 for candidate in restore
5948 .immutable_candidates
5949 .iter()
5950 .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
5951 {
5952 segments.push(NumericSegment {
5953 start: u128::from(candidate.transaction_order),
5954 length: 1,
5955 class: None,
5956 immutable: true,
5957 });
5958 }
5959 if let Some(recovery) = restore.recovery {
5960 if let Some(active_binding) = recovery.active_binding {
5961 segments.push(NumericSegment {
5962 start: u128::from(active_binding.transaction_order),
5963 length: 1,
5964 class: Some(OrderClass::ActiveBindingTerminal),
5965 immutable: false,
5966 });
5967 }
5968 segments.push(NumericSegment {
5969 start: u128::from(recovery.recovery_operation_order),
5970 length: 1,
5971 class: Some(OrderClass::RecoveryOperation),
5972 immutable: false,
5973 });
5974 segments.push(NumericSegment {
5975 start: u128::from(recovery.replacement_terminal_order),
5976 length: 1,
5977 class: Some(OrderClass::RecoveryReplacementTerminal),
5978 immutable: false,
5979 });
5980 }
5981 segments
5982}
5983
5984fn restore_sequence_frontier(
5985 active: &ActiveIdentityRanks,
5986 restore: SequenceClaimFrontierRestore,
5987 retained_floor: u128,
5988 ledger: SequenceLedger,
5989 recovery_provenance: Option<RecoveryClaimProvenance>,
5990 retained_records: &[RetainedCausalRecord],
5991 historical_causal_authorities: &[HistoricalCausalAuthority],
5992) -> Result<SequenceClaimFrontier, ClaimFrontierError> {
5993 let mut segments = sequence_segments(&restore);
5994
5995 let expected_counts = sequence_expected_counts(ledger);
5996 validate_numeric_segments(
5997 ClaimFrontierCounter::DeliverySequence,
5998 u128::from(ledger.high_watermark()) + 1,
5999 ledger.required_reserve(),
6000 &mut segments,
6001 &expected_counts,
6002 )?;
6003 validate_sequence_recovery(
6004 active,
6005 restore.recovery,
6006 recovery_provenance,
6007 ledger,
6008 ledger.required_reserve(),
6009 )?;
6010 validate_sequence_candidates(
6011 active,
6012 &restore.immutable_candidates,
6013 retained_floor,
6014 retained_records,
6015 historical_causal_authorities,
6016 ledger,
6017 )?;
6018 let terminal_owners = validate_sequence_direct_owners(
6019 active,
6020 &restore.movable_claims,
6021 &restore.immutable_candidates,
6022 restore.recovery,
6023 ledger,
6024 )?;
6025 let products = validate_sequence_products(
6026 active,
6027 restore.products,
6028 &terminal_owners,
6029 restore.recovery,
6030 recovery_provenance,
6031 ledger,
6032 )?;
6033 let recovery = restore
6034 .recovery
6035 .zip(recovery_provenance)
6036 .map(|(value, provenance)| RecoverySequenceBlock {
6037 terminal: value.terminal,
6038 recovery_attach_seq: value.recovery_attach_seq,
6039 replacement_terminal_seq: value.replacement_terminal_seq,
6040 participant_index: provenance.participant_index,
6041 marker_delivery_seq: provenance.marker_delivery_seq,
6042 recovered_binding_epoch: provenance.prior_binding_epoch,
6043 });
6044
6045 let mut movable_claims = restore.movable_claims;
6046 movable_claims.sort_by_key(|claim| claim.delivery_seq);
6047 let mut immutable_candidates = restore.immutable_candidates;
6048 immutable_candidates.sort_by_key(|candidate| candidate.delivery_seq());
6049
6050 Ok(SequenceClaimFrontier {
6051 ledger,
6052 movable_claims,
6053 immutable_candidates,
6054 products,
6055 recovery,
6056 })
6057}
6058
6059fn restore_order_frontier(
6060 active: &ActiveIdentityRanks,
6061 restore: OrderClaimFrontierRestore,
6062 ledger: OrderLedger,
6063 recovery_provenance: Option<RecoveryClaimProvenance>,
6064) -> Result<OrderClaimFrontier, ClaimFrontierError> {
6065 let mut segments = order_segments(&restore, ledger.high());
6066
6067 let candidate_count = order_frontier_candidate_count(&restore, ledger.high());
6068 let expected_length = candidate_count + ledger.claims().total();
6069 let expected_counts = order_expected_counts(ledger);
6070 validate_numeric_segments(
6071 ClaimFrontierCounter::TransactionOrder,
6072 order_frontier_start(ledger.high()),
6073 expected_length,
6074 &mut segments,
6075 &expected_counts,
6076 )?;
6077 validate_order_recovery(
6078 active,
6079 restore.recovery,
6080 recovery_provenance,
6081 ledger,
6082 expected_length,
6083 )?;
6084 let immutable_candidates = validate_order_candidates(&restore.immutable_candidates, ledger)?;
6085 validate_order_direct_owners(active, &restore.movable_claims, restore.recovery, ledger)?;
6086 let recovery = restore
6087 .recovery
6088 .zip(recovery_provenance)
6089 .map(|(value, provenance)| RecoveryOrderBlock {
6090 active_binding: value.active_binding,
6091 recovery_operation_order: value.recovery_operation_order,
6092 replacement_terminal_order: value.replacement_terminal_order,
6093 participant_index: provenance.participant_index,
6094 marker_delivery_seq: provenance.marker_delivery_seq,
6095 recovered_binding_epoch: provenance.prior_binding_epoch,
6096 });
6097
6098 let mut movable_claims = restore.movable_claims;
6099 movable_claims.sort_by_key(|claim| claim.transaction_order);
6100
6101 Ok(OrderClaimFrontier {
6102 ledger,
6103 movable_claims,
6104 immutable_candidates,
6105 recovery,
6106 })
6107}
6108
6109fn describe_sequence_frontier(frontier: &SequenceClaimFrontier) -> SequenceClaimFrontierRestore {
6116 SequenceClaimFrontierRestore {
6117 movable_claims: frontier.movable_claims.clone(),
6118 immutable_candidates: frontier.immutable_candidates.clone(),
6119 products: SequenceProductRangesRestore {
6120 live_times_terminal: frontier
6121 .products
6122 .live_times_terminal
6123 .iter()
6124 .map(|range| TerminalProductRangeRestore {
6125 start: range.start,
6126 length: range.length,
6127 terminal: range.terminal,
6128 })
6129 .collect(),
6130 live_times_replacement_terminal: frontier.products.live_times_replacement_terminal.map(
6131 |range| ReplacementTerminalProductRangeRestore {
6132 start: range.start,
6133 length: range.length,
6134 },
6135 ),
6136 other_live_times_exit: frontier
6137 .products
6138 .other_live_times_exit
6139 .iter()
6140 .map(|range| ExitProductRangeRestore {
6141 start: range.start,
6142 length: range.length,
6143 exit_participant: range.exit_participant,
6144 })
6145 .collect(),
6146 },
6147 recovery: frontier.recovery.map(|block| RecoverySequenceBlockRestore {
6148 terminal: block.terminal,
6149 recovery_attach_seq: block.recovery_attach_seq,
6150 replacement_terminal_seq: block.replacement_terminal_seq,
6151 }),
6152 }
6153}
6154
6155fn describe_order_frontier(frontier: &OrderClaimFrontier) -> OrderClaimFrontierRestore {
6162 OrderClaimFrontierRestore {
6163 movable_claims: frontier.movable_claims.clone(),
6164 immutable_candidates: frontier
6165 .immutable_candidates
6166 .iter()
6167 .map(|major| ImmutableOrderCandidateMajorRestore {
6168 transaction_order: major.transaction_order,
6169 candidate_keys: major.candidate_keys.clone(),
6170 })
6171 .collect(),
6172 recovery: frontier.recovery.map(|block| RecoveryOrderBlockRestore {
6173 active_binding: block.active_binding,
6174 recovery_operation_order: block.recovery_operation_order,
6175 replacement_terminal_order: block.replacement_terminal_order,
6176 }),
6177 }
6178}
6179
6180fn validate_numeric_segments<C: Copy + Into<usize>>(
6181 counter: ClaimFrontierCounter,
6182 first_value: u128,
6183 expected_length: u128,
6184 segments: &mut [NumericSegment<C>],
6185 expected_counts: &[u128],
6186) -> Result<(), ClaimFrontierError> {
6187 segments.sort_by_key(|segment| segment.start);
6188 let mut events = numeric_events(counter, first_value, segments)?;
6189 let emitted = scan_numeric_events(counter, first_value, &mut events)?;
6190 if emitted != expected_length {
6191 return Err(frontier_error(
6192 counter,
6193 emitted.min(expected_length),
6194 ClaimFrontierInvalidReason::AggregateLedger,
6195 ));
6196 }
6197 validate_immutable_prefix(counter, first_value, segments)?;
6198 validate_segment_class_counts(counter, expected_length, segments, expected_counts)
6199}
6200
6201fn numeric_events<C>(
6202 counter: ClaimFrontierCounter,
6203 first_value: u128,
6204 segments: &[NumericSegment<C>],
6205) -> Result<Vec<(u128, i8)>, ClaimFrontierError> {
6206 let mut events = Vec::new();
6207 let counter_limit = u128::from(u64::MAX) + 1;
6208 for segment in segments {
6209 if segment.length == 0 {
6210 continue;
6211 }
6212 let Some(end) = segment.start.checked_add(segment.length) else {
6213 return Err(frontier_error(
6214 counter,
6215 counter_limit.saturating_sub(first_value),
6216 ClaimFrontierInvalidReason::NumericPosition,
6217 ));
6218 };
6219 if segment.start < first_value {
6220 return Err(frontier_error(
6221 counter,
6222 0,
6223 ClaimFrontierInvalidReason::NumericPosition,
6224 ));
6225 }
6226 if end > counter_limit {
6227 return Err(frontier_error(
6228 counter,
6229 counter_limit.saturating_sub(first_value),
6230 ClaimFrontierInvalidReason::NumericPosition,
6231 ));
6232 }
6233 events.push((segment.start, 1_i8));
6234 events.push((end, -1_i8));
6235 }
6236 Ok(events)
6237}
6238
6239fn scan_numeric_events(
6240 counter: ClaimFrontierCounter,
6241 first_value: u128,
6242 events: &mut [(u128, i8)],
6243) -> Result<u128, ClaimFrontierError> {
6244 events.sort_unstable_by_key(|event| event.0);
6245 let mut event_index = 0_usize;
6246 let mut coordinate = first_value;
6247 let mut coverage = 0_i128;
6248 let mut emitted = 0_u128;
6249 while let Some((event_coordinate, _)) = events.get(event_index).copied() {
6250 if event_coordinate > coordinate {
6251 if coverage == 0 {
6252 return Err(frontier_error(
6253 counter,
6254 emitted,
6255 ClaimFrontierInvalidReason::NumericPosition,
6256 ));
6257 }
6258 if coverage > 1 {
6259 return Err(frontier_error(
6260 counter,
6261 emitted.saturating_add(1),
6262 ClaimFrontierInvalidReason::NumericPosition,
6263 ));
6264 }
6265 emitted = emitted.saturating_add(event_coordinate - coordinate);
6266 coordinate = event_coordinate;
6267 }
6268 while let Some((same_coordinate, delta)) = events.get(event_index).copied() {
6269 if same_coordinate != coordinate {
6270 break;
6271 }
6272 coverage += i128::from(delta);
6273 event_index += 1;
6274 }
6275 }
6276 if coverage != 0 {
6277 return Err(frontier_error(
6278 counter,
6279 emitted,
6280 ClaimFrontierInvalidReason::NumericPosition,
6281 ));
6282 }
6283 Ok(emitted)
6284}
6285
6286fn validate_immutable_prefix<C>(
6287 counter: ClaimFrontierCounter,
6288 first_value: u128,
6289 segments: &[NumericSegment<C>],
6290) -> Result<(), ClaimFrontierError> {
6291 let mut first_movable = None;
6292 for segment in segments.iter().filter(|segment| segment.length != 0) {
6293 if segment.immutable {
6294 if let Some(first_movable) = first_movable {
6295 return Err(frontier_error(
6296 counter,
6297 first_movable,
6298 ClaimFrontierInvalidReason::NumericPosition,
6299 ));
6300 }
6301 } else if first_movable.is_none() {
6302 first_movable = Some(segment.start - first_value);
6303 }
6304 }
6305 Ok(())
6306}
6307
6308fn validate_segment_class_counts<C: Copy + Into<usize>>(
6309 counter: ClaimFrontierCounter,
6310 expected_length: u128,
6311 segments: &[NumericSegment<C>],
6312 expected_counts: &[u128],
6313) -> Result<(), ClaimFrontierError> {
6314 let mut actual_counts = core::iter::repeat_n(0_u128, expected_counts.len()).collect::<Vec<_>>();
6315 let mut class_ordinal = 0_u128;
6316 for segment in segments {
6317 if segment.length == 0 {
6318 continue;
6319 }
6320 if let Some(class) = segment.class {
6321 let index = class.into();
6322 let prior = actual_counts[index];
6323 let Some(resulting) = prior.checked_add(segment.length) else {
6324 return Err(frontier_error(
6325 counter,
6326 class_ordinal,
6327 ClaimFrontierInvalidReason::AggregateLedger,
6328 ));
6329 };
6330 if resulting > expected_counts[index] {
6331 return Err(frontier_error(
6332 counter,
6333 class_ordinal + expected_counts[index].saturating_sub(prior),
6334 ClaimFrontierInvalidReason::AggregateLedger,
6335 ));
6336 }
6337 actual_counts[index] = resulting;
6338 }
6339 class_ordinal += segment.length;
6340 }
6341 if actual_counts != expected_counts {
6342 return Err(frontier_error(
6343 counter,
6344 expected_length,
6345 ClaimFrontierInvalidReason::AggregateLedger,
6346 ));
6347 }
6348 Ok(())
6349}
6350
6351#[cfg(test)]
6352pub(super) fn validate_numeric_union_for_test(
6353 first_value: u128,
6354 expected_length: u128,
6355 ranges: &[(u128, u128)],
6356) -> Result<(), ClaimFrontierError> {
6357 let mut segments: Vec<_> = ranges
6358 .iter()
6359 .map(|(start, length)| NumericSegment {
6360 start: *start,
6361 length: *length,
6362 class: Some(SequenceClass::Exit),
6363 immutable: false,
6364 })
6365 .collect();
6366 validate_numeric_segments(
6367 ClaimFrontierCounter::DeliverySequence,
6368 first_value,
6369 expected_length,
6370 &mut segments,
6371 &[expected_length, 0, 0, 0, 0, 0, 0, 0],
6372 )
6373}
6374
6375impl From<SequenceClass> for usize {
6376 fn from(value: SequenceClass) -> Self {
6377 value as Self
6378 }
6379}
6380
6381impl From<OrderClass> for usize {
6382 fn from(value: OrderClass) -> Self {
6383 value as Self
6384 }
6385}
6386
6387const fn sequence_candidate_class(candidate: ImmutableSequenceCandidate) -> SequenceClass {
6388 match candidate {
6389 ImmutableSequenceCandidate::BindingTerminal { .. } => SequenceClass::Terminal,
6390 ImmutableSequenceCandidate::Marker(marker) => match marker.current_owner {
6391 MarkerSequenceOwner::Marker => SequenceClass::Marker,
6392 MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::LiveTimesTerminal) => {
6393 SequenceClass::LiveTimesTerminal
6394 }
6395 MarkerSequenceOwner::ConditionalProduct(
6396 SequenceProductClass::LiveTimesReplacementTerminal,
6397 ) => SequenceClass::LiveTimesReplacementTerminal,
6398 MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::OtherLiveTimesExit) => {
6399 SequenceClass::OtherLiveTimesExit
6400 }
6401 },
6402 }
6403}
6404
6405fn sequence_expected_counts(ledger: SequenceLedger) -> [u128; 8] {
6406 let budget = ledger.budget();
6407 [
6408 u128::from(budget.e),
6409 u128::from(budget.t),
6410 u128::from(budget.m),
6411 u128::from(budget.rs),
6412 u128::from(budget.rt),
6413 budget.l_times_t,
6414 budget.l_times_rt,
6415 budget.l_other_times_e,
6416 ]
6417}
6418
6419fn order_expected_counts(ledger: OrderLedger) -> [u128; 4] {
6420 let claims = ledger.claims();
6421 [
6422 u128::from(claims.active_binding_terminals()),
6423 u128::from(claims.membership_exits()),
6424 u128::from(claims.recovery_operation()),
6425 u128::from(claims.recovery_replacement_terminal()),
6426 ]
6427}
6428
6429fn validate_sequence_recovery(
6430 active: &ActiveIdentityRanks,
6431 recovery: Option<RecoverySequenceBlockRestore>,
6432 provenance: Option<RecoveryClaimProvenance>,
6433 ledger: SequenceLedger,
6434 frontier_length: u128,
6435) -> Result<(), ClaimFrontierError> {
6436 let expected = ledger.claims().recovery();
6437 match (expected, recovery, provenance) {
6438 (RecoverySequenceReserve::None, None, None) => Ok(()),
6439 (RecoverySequenceReserve::DetachedCredentialRecovery, None, _)
6440 | (RecoverySequenceReserve::DetachedCredentialRecovery, Some(_), None)
6441 | (RecoverySequenceReserve::None, None, Some(_)) => Err(sequence_error(
6442 frontier_length,
6443 ClaimFrontierInvalidReason::RecoveryBlock,
6444 )),
6445 (RecoverySequenceReserve::None, Some(block), _) => Err(sequence_error(
6446 sequence_ordinal(ledger, block_start_sequence(block)),
6447 ClaimFrontierInvalidReason::RecoveryBlock,
6448 )),
6449 (RecoverySequenceReserve::DetachedCredentialRecovery, Some(block), Some(provenance)) => {
6450 let block_ordinal = sequence_ordinal(ledger, block_start_sequence(block));
6451 let expected_recovery_attach = block
6452 .terminal
6453 .map_or(Some(block.recovery_attach_seq), |terminal| {
6454 terminal.delivery_seq.checked_add(1)
6455 });
6456 if expected_recovery_attach != Some(block.recovery_attach_seq) {
6457 return Err(sequence_error(
6458 block_ordinal + 1,
6459 ClaimFrontierInvalidReason::RecoveryBlock,
6460 ));
6461 }
6462 if block.recovery_attach_seq.checked_add(1) != Some(block.replacement_terminal_seq) {
6463 return Err(sequence_error(
6464 block_ordinal + u128::from(block.terminal.is_some()) + 1,
6465 ClaimFrontierInvalidReason::RecoveryBlock,
6466 ));
6467 }
6468 let Some(participant) = active_participant(active, provenance.participant_index) else {
6469 return Err(sequence_error(
6470 block_ordinal,
6471 ClaimFrontierInvalidReason::LogicalOwner,
6472 ));
6473 };
6474 let expected_binding = match provenance.phase {
6475 RecoveryClaimPhase::PreFate => {
6476 FrontierBinding::Bound(provenance.prior_binding_epoch)
6477 }
6478 RecoveryClaimPhase::PostFate => {
6479 FrontierBinding::Detached(provenance.prior_binding_epoch)
6480 }
6481 RecoveryClaimPhase::RecoveredBound => {
6482 FrontierBinding::Bound(provenance.current_binding_epoch)
6483 }
6484 };
6485 if participant.binding != expected_binding {
6486 return Err(sequence_error(
6487 block_ordinal,
6488 ClaimFrontierInvalidReason::LogicalOwner,
6489 ));
6490 }
6491 let terminal_valid = match (provenance.phase, block.terminal) {
6492 (RecoveryClaimPhase::PreFate, Some(terminal)) => {
6493 terminal.owner.participant_index == provenance.participant_index
6494 && terminal.owner.binding_epoch == provenance.prior_binding_epoch
6495 }
6496 (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
6497 _ => false,
6498 };
6499 if !terminal_valid {
6500 return Err(sequence_error(
6501 block_ordinal,
6502 ClaimFrontierInvalidReason::RecoveryBlock,
6503 ));
6504 }
6505 Ok(())
6506 }
6507 }
6508}
6509
6510fn validate_sequence_candidates(
6511 active: &ActiveIdentityRanks,
6512 candidates: &[ImmutableSequenceCandidate],
6513 retained_floor: u128,
6514 retained_records: &[RetainedCausalRecord],
6515 historical_causal_authorities: &[HistoricalCausalAuthority],
6516 ledger: SequenceLedger,
6517) -> Result<(), ClaimFrontierError> {
6518 let mut seen_keys = Vec::new();
6519 let mut previous_sequence = None;
6520 let mut previous_order = retained_records.last().map(|record| record.admission_order);
6521 for candidate in candidates {
6522 let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6523 let order = candidate.admission_order();
6524 if previous_sequence.is_some_and(|previous| previous >= candidate.delivery_seq())
6525 || previous_order.is_some_and(|previous| previous >= order)
6526 || seen_keys.contains(&order)
6527 {
6528 return Err(sequence_error(
6529 ordinal,
6530 ClaimFrontierInvalidReason::CandidateKey,
6531 ));
6532 }
6533 previous_sequence = Some(candidate.delivery_seq());
6534 previous_order = Some(order);
6535 seen_keys.push(order);
6536 match candidate {
6537 ImmutableSequenceCandidate::BindingTerminal { owner, .. } => {
6538 if order.candidate_phase() != CandidatePhase::BindingTerminal
6539 || order.participant_index() != owner.participant_index
6540 || !terminal_matches_active(active, *owner)
6541 {
6542 return Err(sequence_error(
6543 ordinal,
6544 ClaimFrontierInvalidReason::CandidateKey,
6545 ));
6546 }
6547 }
6548 ImmutableSequenceCandidate::Marker(marker) => {
6549 let Some(participant) = active_participant(active, order.participant_index())
6550 else {
6551 return Err(sequence_error(
6552 ordinal,
6553 ClaimFrontierInvalidReason::LogicalOwner,
6554 ));
6555 };
6556 if order.candidate_phase() != CandidatePhase::CompactionMarker
6557 || marker.current_owner != MarkerSequenceOwner::Marker
6558 || marker.target_binding != participant.binding
6559 || marker.abandoned_after != participant.cursor
6560 || marker.abandoned_after > marker.abandoned_through
6561 || u128::from(marker.physical_floor_at_decision) != retained_floor
6562 || u128::from(marker.physical_floor_at_decision)
6563 > u128::from(marker.abandoned_through) + 1
6564 || marker.abandoned_through >= marker.delivery_seq
6565 || !marker_provenance_targets(marker.provenance, order.participant_index())
6566 || !marker_has_causal_authority(
6567 *marker,
6568 retained_records,
6569 historical_causal_authorities,
6570 )
6571 {
6572 return Err(sequence_error(
6573 ordinal,
6574 ClaimFrontierInvalidReason::CandidateKey,
6575 ));
6576 }
6577 }
6578 }
6579 }
6580 Ok(())
6581}
6582
6583#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6584enum TerminalOccurrenceKind {
6585 Movable,
6586 Candidate,
6587}
6588
6589#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6590struct TerminalOccurrence {
6591 owner: BindingTerminalOwner,
6592 ordinal: u128,
6593 kind: TerminalOccurrenceKind,
6594}
6595
6596pub(in crate::lifecycle) const fn terminal_owner_sort_key(
6601 owner: &BindingTerminalOwner,
6602) -> (ParticipantId, (ConnectionIncarnation, Generation)) {
6603 (
6604 owner.participant_index,
6605 owner.binding_epoch.canonical_sort_key(),
6606 )
6607}
6608
6609fn validate_sequence_direct_owners(
6610 active: &ActiveIdentityRanks,
6611 movable: &[MovableSequenceClaim],
6612 candidates: &[ImmutableSequenceCandidate],
6613 recovery: Option<RecoverySequenceBlockRestore>,
6614 ledger: SequenceLedger,
6615) -> Result<Vec<BindingTerminalOwner>, ClaimFrontierError> {
6616 let (mut exit_owners, terminal_occurrences) =
6617 collect_sequence_direct_owners(active, movable, candidates, recovery, ledger)?;
6618 validate_sequence_exit_owners(active, &mut exit_owners, ledger)?;
6619 validate_sequence_terminal_owners(active, &terminal_occurrences, ledger)?;
6620 let mut owners: Vec<_> = terminal_occurrences
6621 .into_iter()
6622 .map(|occurrence| occurrence.owner)
6623 .collect();
6624 owners.sort_by_key(terminal_owner_sort_key);
6625 Ok(owners)
6626}
6627
6628fn collect_sequence_direct_owners(
6629 active: &ActiveIdentityRanks,
6630 movable: &[MovableSequenceClaim],
6631 candidates: &[ImmutableSequenceCandidate],
6632 recovery: Option<RecoverySequenceBlockRestore>,
6633 ledger: SequenceLedger,
6634) -> Result<(Vec<ParticipantId>, Vec<TerminalOccurrence>), ClaimFrontierError> {
6635 let mut ordered_movable = movable.to_vec();
6636 ordered_movable.sort_by_key(|claim| claim.delivery_seq);
6637 let mut exit_owners = Vec::new();
6638 let mut terminal_occurrences = Vec::new();
6639 for claim in ordered_movable {
6640 let ordinal = sequence_ordinal(ledger, claim.delivery_seq);
6641 match claim.owner {
6642 SequenceDirectOwner::MembershipExit { participant_index } => {
6643 if !active.contains(participant_index) || exit_owners.contains(&participant_index) {
6644 return Err(sequence_error(
6645 ordinal,
6646 ClaimFrontierInvalidReason::LogicalOwner,
6647 ));
6648 }
6649 exit_owners.push(participant_index);
6650 }
6651 SequenceDirectOwner::BindingTerminal(owner) => {
6652 if !terminal_matches_bound(active, owner) {
6653 return Err(sequence_error(
6654 ordinal,
6655 ClaimFrontierInvalidReason::LogicalOwner,
6656 ));
6657 }
6658 push_terminal_occurrence(
6659 &mut terminal_occurrences,
6660 owner,
6661 ordinal,
6662 TerminalOccurrenceKind::Movable,
6663 )?;
6664 }
6665 }
6666 }
6667 for candidate in candidates {
6668 if let ImmutableSequenceCandidate::BindingTerminal { owner, .. } = candidate {
6669 let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6670 push_terminal_occurrence(
6671 &mut terminal_occurrences,
6672 *owner,
6673 ordinal,
6674 TerminalOccurrenceKind::Candidate,
6675 )?;
6676 }
6677 }
6678 if let Some(terminal) = recovery.and_then(|block| block.terminal) {
6679 let ordinal = sequence_ordinal(ledger, terminal.delivery_seq);
6680 push_terminal_occurrence(
6681 &mut terminal_occurrences,
6682 terminal.owner,
6683 ordinal,
6684 TerminalOccurrenceKind::Movable,
6685 )?;
6686 }
6687 Ok((exit_owners, terminal_occurrences))
6688}
6689
6690fn push_terminal_occurrence(
6691 occurrences: &mut Vec<TerminalOccurrence>,
6692 owner: BindingTerminalOwner,
6693 ordinal: u128,
6694 kind: TerminalOccurrenceKind,
6695) -> Result<(), ClaimFrontierError> {
6696 if occurrences
6697 .iter()
6698 .any(|occurrence| occurrence.owner == owner)
6699 {
6700 return Err(sequence_error(
6701 ordinal,
6702 ClaimFrontierInvalidReason::LogicalOwner,
6703 ));
6704 }
6705 occurrences.push(TerminalOccurrence {
6706 owner,
6707 ordinal,
6708 kind,
6709 });
6710 Ok(())
6711}
6712
6713fn validate_sequence_exit_owners(
6714 active: &ActiveIdentityRanks,
6715 exit_owners: &mut [ParticipantId],
6716 ledger: SequenceLedger,
6717) -> Result<(), ClaimFrontierError> {
6718 exit_owners.sort_unstable();
6719 if exit_owners.len() != active.participants.len()
6720 || !exit_owners.iter().copied().eq(active
6721 .participants
6722 .iter()
6723 .map(|participant| participant.participant_index))
6724 {
6725 return Err(sequence_error(
6726 ledger.required_reserve(),
6727 ClaimFrontierInvalidReason::LogicalOwner,
6728 ));
6729 }
6730 Ok(())
6731}
6732
6733fn validate_sequence_terminal_owners(
6734 active: &ActiveIdentityRanks,
6735 terminal_occurrences: &[TerminalOccurrence],
6736 ledger: SequenceLedger,
6737) -> Result<(), ClaimFrontierError> {
6738 for participant in &active.participants {
6739 let matching: Vec<_> = terminal_occurrences
6740 .iter()
6741 .filter(|occurrence| {
6742 occurrence.owner.participant_index == participant.participant_index
6743 })
6744 .copied()
6745 .collect();
6746 match participant.binding {
6747 FrontierBinding::Bound(epoch) => {
6748 if !matches!(matching.as_slice(), [occurrence] if occurrence.owner.binding_epoch == epoch)
6749 {
6750 return Err(sequence_error(
6751 matching.first().map_or_else(
6752 || ledger.required_reserve(),
6753 |occurrence| occurrence.ordinal,
6754 ),
6755 ClaimFrontierInvalidReason::LogicalOwner,
6756 ));
6757 }
6758 }
6759 FrontierBinding::Detached(epoch) => {
6760 if matching.len() > 1
6761 || matching.first().is_some_and(|occurrence| {
6762 occurrence.owner.binding_epoch != epoch
6763 || occurrence.kind != TerminalOccurrenceKind::Candidate
6764 })
6765 {
6766 return Err(sequence_error(
6767 matching.first().map_or_else(
6768 || ledger.required_reserve(),
6769 |occurrence| occurrence.ordinal,
6770 ),
6771 ClaimFrontierInvalidReason::LogicalOwner,
6772 ));
6773 }
6774 }
6775 }
6776 }
6777 Ok(())
6778}
6779
6780fn validate_sequence_products(
6781 active: &ActiveIdentityRanks,
6782 restore: SequenceProductRangesRestore,
6783 terminal_owners: &[BindingTerminalOwner],
6784 recovery: Option<RecoverySequenceBlockRestore>,
6785 recovery_provenance: Option<RecoveryClaimProvenance>,
6786 ledger: SequenceLedger,
6787) -> Result<SequenceProductRanges, ClaimFrontierError> {
6788 let live_count = usize_to_u64(active.participants.len());
6789 let other_count = live_count.saturating_sub(1);
6790 let live_times_terminal = validate_terminal_product_ranges(
6791 restore.live_times_terminal,
6792 terminal_owners,
6793 live_count,
6794 ledger,
6795 )?;
6796 let live_times_replacement_terminal = validate_replacement_product_range(
6797 restore.live_times_replacement_terminal,
6798 recovery,
6799 recovery_provenance,
6800 live_count,
6801 ledger,
6802 )?;
6803 let other_live_times_exit =
6804 validate_exit_product_ranges(active, restore.other_live_times_exit, other_count, ledger)?;
6805 Ok(SequenceProductRanges {
6806 live_times_terminal,
6807 live_times_replacement_terminal,
6808 other_live_times_exit,
6809 })
6810}
6811
6812fn validate_terminal_product_ranges(
6813 mut ranges: Vec<TerminalProductRangeRestore>,
6814 terminal_owners: &[BindingTerminalOwner],
6815 live_count: u64,
6816 ledger: SequenceLedger,
6817) -> Result<Vec<TerminalProductRange>, ClaimFrontierError> {
6818 ranges.sort_by_key(|range| range.start);
6819 let mut seen_terminals = Vec::new();
6820 let mut live_times_terminal = Vec::new();
6821 for range in ranges {
6822 let ordinal = sequence_ordinal(ledger, range.start);
6823 if range.length != live_count
6824 || !terminal_owners.contains(&range.terminal)
6825 || seen_terminals.contains(&range.terminal)
6826 {
6827 return Err(sequence_error(
6828 ordinal,
6829 ClaimFrontierInvalidReason::ProductRange,
6830 ));
6831 }
6832 seen_terminals.push(range.terminal);
6833 live_times_terminal.push(TerminalProductRange {
6834 start: range.start,
6835 length: range.length,
6836 terminal: range.terminal,
6837 });
6838 }
6839 if seen_terminals.len() != terminal_owners.len() {
6840 return Err(sequence_error(
6841 ledger.required_reserve(),
6842 ClaimFrontierInvalidReason::ProductRange,
6843 ));
6844 }
6845 Ok(live_times_terminal)
6846}
6847
6848fn validate_replacement_product_range(
6849 range: Option<ReplacementTerminalProductRangeRestore>,
6850 recovery: Option<RecoverySequenceBlockRestore>,
6851 recovery_provenance: Option<RecoveryClaimProvenance>,
6852 live_count: u64,
6853 ledger: SequenceLedger,
6854) -> Result<Option<ReplacementTerminalProductRange>, ClaimFrontierError> {
6855 let validated = match (range, recovery, recovery_provenance) {
6856 (None, None, None) => None,
6857 (Some(range), Some(_), Some(provenance)) if range.length == live_count => {
6858 Some(ReplacementTerminalProductRange {
6859 start: range.start,
6860 length: range.length,
6861 participant_index: provenance.participant_index,
6862 marker_delivery_seq: provenance.marker_delivery_seq,
6863 prior_binding_epoch: provenance.prior_binding_epoch,
6864 })
6865 }
6866 (Some(range), _, _) => {
6867 return Err(sequence_error(
6868 sequence_ordinal(ledger, range.start),
6869 ClaimFrontierInvalidReason::ProductRange,
6870 ));
6871 }
6872 (None, Some(_), _) | (None, None, Some(_)) => {
6873 return Err(sequence_error(
6874 ledger.required_reserve(),
6875 ClaimFrontierInvalidReason::ProductRange,
6876 ));
6877 }
6878 };
6879 Ok(validated)
6880}
6881
6882fn validate_exit_product_ranges(
6883 active: &ActiveIdentityRanks,
6884 mut ranges: Vec<ExitProductRangeRestore>,
6885 other_count: u64,
6886 ledger: SequenceLedger,
6887) -> Result<Vec<ExitProductRange>, ClaimFrontierError> {
6888 ranges.sort_by_key(|range| range.start);
6889 let mut seen_exits = Vec::new();
6890 let mut other_live_times_exit = Vec::new();
6891 if other_count == 0 && !ranges.is_empty() {
6892 return Err(sequence_error(
6893 ledger.required_reserve(),
6894 ClaimFrontierInvalidReason::ProductRange,
6895 ));
6896 }
6897 for range in ranges {
6898 let ordinal = sequence_ordinal(ledger, range.start);
6899 if range.length != other_count
6900 || !active.contains(range.exit_participant)
6901 || seen_exits.contains(&range.exit_participant)
6902 {
6903 return Err(sequence_error(
6904 ordinal,
6905 ClaimFrontierInvalidReason::ProductRange,
6906 ));
6907 }
6908 seen_exits.push(range.exit_participant);
6909 other_live_times_exit.push(ExitProductRange {
6910 start: range.start,
6911 length: range.length,
6912 exit_participant: range.exit_participant,
6913 });
6914 }
6915 seen_exits.sort_unstable();
6916 let expected_exit_ranges = if other_count == 0 {
6917 0
6918 } else {
6919 active.participants.len()
6920 };
6921 if seen_exits.len() != expected_exit_ranges
6922 || !seen_exits.iter().copied().eq(active
6923 .participants
6924 .iter()
6925 .take(expected_exit_ranges)
6926 .map(|participant| participant.participant_index))
6927 {
6928 return Err(sequence_error(
6929 ledger.required_reserve(),
6930 ClaimFrontierInvalidReason::ProductRange,
6931 ));
6932 }
6933 Ok(other_live_times_exit)
6934}
6935
6936fn validate_order_recovery(
6937 active: &ActiveIdentityRanks,
6938 recovery: Option<RecoveryOrderBlockRestore>,
6939 provenance: Option<RecoveryClaimProvenance>,
6940 ledger: OrderLedger,
6941 frontier_length: u128,
6942) -> Result<(), ClaimFrontierError> {
6943 let claims = ledger.claims();
6944 let expected = claims.recovery_operation() && claims.recovery_replacement_terminal();
6945 match (expected, recovery, provenance) {
6946 (false, None, None) => Ok(()),
6947 (true, None, _) | (true, Some(_), None) | (false, None, Some(_)) => Err(order_error(
6948 frontier_length,
6949 ClaimFrontierInvalidReason::RecoveryBlock,
6950 )),
6951 (false, Some(block), _) => Err(order_error(
6952 order_ordinal(ledger, block_start_order(block)),
6953 ClaimFrontierInvalidReason::RecoveryBlock,
6954 )),
6955 (true, Some(block), Some(provenance)) => {
6956 let block_ordinal = order_ordinal(ledger, block_start_order(block));
6957 let expected_recovery_operation = block
6958 .active_binding
6959 .map_or(Some(block.recovery_operation_order), |active_binding| {
6960 active_binding.transaction_order.checked_add(1)
6961 });
6962 if expected_recovery_operation != Some(block.recovery_operation_order) {
6963 return Err(order_error(
6964 block_ordinal + 1,
6965 ClaimFrontierInvalidReason::RecoveryBlock,
6966 ));
6967 }
6968 if block.recovery_operation_order.checked_add(1)
6969 != Some(block.replacement_terminal_order)
6970 {
6971 return Err(order_error(
6972 block_ordinal + u128::from(block.active_binding.is_some()) + 1,
6973 ClaimFrontierInvalidReason::RecoveryBlock,
6974 ));
6975 }
6976 let Some(participant) = active_participant(active, provenance.participant_index) else {
6977 return Err(order_error(
6978 block_ordinal,
6979 ClaimFrontierInvalidReason::LogicalOwner,
6980 ));
6981 };
6982 let expected_binding = match provenance.phase {
6983 RecoveryClaimPhase::PreFate => {
6984 FrontierBinding::Bound(provenance.prior_binding_epoch)
6985 }
6986 RecoveryClaimPhase::PostFate => {
6987 FrontierBinding::Detached(provenance.prior_binding_epoch)
6988 }
6989 RecoveryClaimPhase::RecoveredBound => {
6990 FrontierBinding::Bound(provenance.current_binding_epoch)
6991 }
6992 };
6993 if participant.binding != expected_binding {
6994 return Err(order_error(
6995 block_ordinal,
6996 ClaimFrontierInvalidReason::LogicalOwner,
6997 ));
6998 }
6999 let active_binding_valid = match (provenance.phase, block.active_binding) {
7000 (RecoveryClaimPhase::PreFate, Some(active_binding)) => {
7001 active_binding.owner.participant_index == provenance.participant_index
7002 && active_binding.owner.binding_epoch == provenance.prior_binding_epoch
7003 }
7004 (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
7005 _ => false,
7006 };
7007 if !active_binding_valid {
7008 return Err(order_error(
7009 block_ordinal,
7010 ClaimFrontierInvalidReason::RecoveryBlock,
7011 ));
7012 }
7013 Ok(())
7014 }
7015 }
7016}
7017
7018fn validate_order_candidates(
7019 restore: &[ImmutableOrderCandidateMajorRestore],
7020 ledger: OrderLedger,
7021) -> Result<Vec<ImmutableOrderCandidateMajor>, ClaimFrontierError> {
7022 let mut groups = restore.to_vec();
7023 groups.sort_by_key(|group| group.transaction_order);
7024 let mut seen_keys = Vec::new();
7025 let mut previous_major = None;
7026 let mut validated = Vec::new();
7027 for group in groups {
7028 let ordinal = order_ordinal(ledger, group.transaction_order);
7029 let below_allocated_high = matches!(
7030 ledger.high(),
7031 OrderHigh::Allocated(high) if group.transaction_order < high
7032 );
7033 if group.candidate_keys.is_empty()
7034 || previous_major == Some(group.transaction_order)
7035 || below_allocated_high
7036 {
7037 return Err(order_error(
7038 ordinal,
7039 ClaimFrontierInvalidReason::CandidateKey,
7040 ));
7041 }
7042 previous_major = Some(group.transaction_order);
7043 let mut previous = None;
7044 for key in &group.candidate_keys {
7045 if key.transaction_order() != group.transaction_order
7046 || previous.is_some_and(|previous| previous >= *key)
7047 || seen_keys.contains(key)
7048 {
7049 return Err(order_error(
7050 ordinal,
7051 ClaimFrontierInvalidReason::CandidateKey,
7052 ));
7053 }
7054 previous = Some(*key);
7055 seen_keys.push(*key);
7056 }
7057 validated.push(ImmutableOrderCandidateMajor {
7058 transaction_order: group.transaction_order,
7059 candidate_keys: group.candidate_keys,
7060 });
7061 }
7062 Ok(validated)
7063}
7064
7065fn validate_order_direct_owners(
7066 active: &ActiveIdentityRanks,
7067 movable: &[MovableOrderClaim],
7068 recovery: Option<RecoveryOrderBlockRestore>,
7069 ledger: OrderLedger,
7070) -> Result<(), ClaimFrontierError> {
7071 let mut ordered = movable.to_vec();
7072 ordered.sort_by_key(|claim| claim.transaction_order);
7073 let mut exits = Vec::new();
7074 let mut terminals = Vec::new();
7075 for claim in ordered {
7076 let ordinal = order_ordinal(ledger, claim.transaction_order);
7077 match claim.owner {
7078 OrderDirectOwner::MembershipExit { participant_index } => {
7079 if !active.contains(participant_index) || exits.contains(&participant_index) {
7080 return Err(order_error(
7081 ordinal,
7082 ClaimFrontierInvalidReason::LogicalOwner,
7083 ));
7084 }
7085 exits.push(participant_index);
7086 }
7087 OrderDirectOwner::ActiveBindingTerminal(owner) => {
7088 if !terminal_matches_bound(active, owner) || terminals.contains(&owner) {
7089 return Err(order_error(
7090 ordinal,
7091 ClaimFrontierInvalidReason::LogicalOwner,
7092 ));
7093 }
7094 terminals.push(owner);
7095 }
7096 }
7097 }
7098 if let Some(active_binding) = recovery.and_then(|block| block.active_binding) {
7099 let ordinal = order_ordinal(ledger, active_binding.transaction_order);
7100 if !terminal_matches_bound(active, active_binding.owner)
7101 || terminals.contains(&active_binding.owner)
7102 {
7103 return Err(order_error(
7104 ordinal,
7105 ClaimFrontierInvalidReason::LogicalOwner,
7106 ));
7107 }
7108 terminals.push(active_binding.owner);
7109 }
7110 exits.sort_unstable();
7111 if exits.len() != active.participants.len()
7112 || !exits.iter().copied().eq(active
7113 .participants
7114 .iter()
7115 .map(|participant| participant.participant_index))
7116 {
7117 return Err(order_error(
7118 ledger.claims().total(),
7119 ClaimFrontierInvalidReason::LogicalOwner,
7120 ));
7121 }
7122 terminals.sort_by_key(terminal_owner_sort_key);
7123 if usize_to_u128(terminals.len()) != u128::from(ledger.claims().active_binding_terminals()) {
7124 return Err(order_error(
7125 ledger.claims().total(),
7126 ClaimFrontierInvalidReason::LogicalOwner,
7127 ));
7128 }
7129 Ok(())
7130}
7131
7132fn validate_cross_counter(
7133 sequence: &SequenceClaimFrontier,
7134 order: &OrderClaimFrontier,
7135) -> Result<(), ClaimFrontierError> {
7136 match (sequence.recovery, order.recovery) {
7137 (None, None) => {}
7138 (Some(sequence_block), Some(order_block))
7139 if sequence_block.participant_index == order_block.participant_index
7140 && sequence_block.marker_delivery_seq == order_block.marker_delivery_seq
7141 && sequence_block.recovered_binding_epoch
7142 == order_block.recovered_binding_epoch
7143 && sequence_block.terminal.map(|terminal| terminal.owner)
7144 == order_block.active_binding.map(|active| active.owner) => {}
7145 (Some(sequence_block), _) => {
7146 return Err(sequence_error(
7147 sequence_ordinal(
7148 sequence.ledger,
7149 block_start_validated_sequence(sequence_block),
7150 ),
7151 ClaimFrontierInvalidReason::RecoveryBlock,
7152 ));
7153 }
7154 (None, Some(order_block)) => {
7155 return Err(order_error(
7156 order_ordinal(order.ledger, block_start_validated_order(order_block)),
7157 ClaimFrontierInvalidReason::RecoveryBlock,
7158 ));
7159 }
7160 }
7161
7162 let mut order_candidate_keys = Vec::new();
7163 for group in &order.immutable_candidates {
7164 order_candidate_keys.extend(group.candidate_keys.iter().copied());
7165 }
7166 for candidate in &sequence.immutable_candidates {
7167 let key = candidate.admission_order();
7168 if !order_candidate_keys.contains(&key) {
7169 return Err(sequence_error(
7170 sequence_ordinal(sequence.ledger, candidate.delivery_seq()),
7171 ClaimFrontierInvalidReason::CandidateKey,
7172 ));
7173 }
7174 }
7175 for group in &order.immutable_candidates {
7176 for key in &group.candidate_keys {
7177 if !sequence
7178 .immutable_candidates
7179 .iter()
7180 .any(|candidate| candidate.admission_order() == *key)
7181 {
7182 return Err(order_error(
7183 order_ordinal(order.ledger, group.transaction_order),
7184 ClaimFrontierInvalidReason::CandidateKey,
7185 ));
7186 }
7187 }
7188 }
7189
7190 let mut sequence_movable_terminals = Vec::new();
7191 for claim in &sequence.movable_claims {
7192 if let SequenceDirectOwner::BindingTerminal(owner) = claim.owner {
7193 sequence_movable_terminals.push(owner);
7194 }
7195 }
7196 if let Some(terminal) = sequence.recovery.and_then(|block| block.terminal) {
7197 sequence_movable_terminals.push(terminal.owner);
7198 }
7199 let mut order_movable_terminals = Vec::new();
7200 for claim in &order.movable_claims {
7201 if let OrderDirectOwner::ActiveBindingTerminal(owner) = claim.owner {
7202 order_movable_terminals.push(owner);
7203 }
7204 }
7205 if let Some(active_binding) = order.recovery.and_then(|block| block.active_binding) {
7206 order_movable_terminals.push(active_binding.owner);
7207 }
7208 sequence_movable_terminals.sort_by_key(terminal_owner_sort_key);
7209 order_movable_terminals.sort_by_key(terminal_owner_sort_key);
7210 if sequence_movable_terminals != order_movable_terminals {
7211 return Err(sequence_error(
7212 sequence.ledger.required_reserve(),
7213 ClaimFrontierInvalidReason::LogicalOwner,
7214 ));
7215 }
7216 Ok(())
7217}
7218
7219const fn marker_provenance_targets(provenance: MarkerProvenance, target: ParticipantId) -> bool {
7220 match provenance {
7221 MarkerProvenance::NonProductM => true,
7222 MarkerProvenance::TerminalProduct {
7223 affected_participant,
7224 ..
7225 } => affected_participant == target,
7226 MarkerProvenance::ExitProduct {
7227 exit_participant,
7228 remaining_participant,
7229 } => exit_participant != remaining_participant && remaining_participant == target,
7230 }
7231}
7232
7233fn marker_has_causal_authority(
7234 marker: MarkerCandidateAuthority,
7235 records: &[RetainedCausalRecord],
7236 historical: &[HistoricalCausalAuthority],
7237) -> bool {
7238 if marker.provenance == MarkerProvenance::NonProductM {
7239 return true;
7240 }
7241 let retained_match = records.iter().any(|record| {
7242 if record.admission_order.transaction_order() != marker.admission_order.transaction_order()
7243 {
7244 return false;
7245 }
7246 match marker.provenance {
7247 MarkerProvenance::NonProductM => true,
7248 MarkerProvenance::TerminalProduct {
7249 terminal: TerminalProductSource::Binding(owner),
7250 ..
7251 } => matches!(
7252 record.kind,
7253 RetainedCausalRecordKind::BindingTerminal(actual) if actual == owner
7254 ),
7255 MarkerProvenance::TerminalProduct {
7256 terminal:
7257 TerminalProductSource::RecoveryReplacement {
7258 participant_index,
7259 binding_epoch,
7260 },
7261 ..
7262 } => matches!(
7263 record.kind,
7264 RetainedCausalRecordKind::BindingTerminal(owner)
7265 if owner.participant_index == participant_index
7266 && owner.binding_epoch == binding_epoch
7267 ),
7268 MarkerProvenance::ExitProduct {
7269 exit_participant, ..
7270 } => matches!(
7271 record.kind,
7272 RetainedCausalRecordKind::MembershipExit { participant_index }
7273 if participant_index == exit_participant
7274 ),
7275 }
7276 });
7277 retained_match
7278 || historical
7279 .iter()
7280 .any(|authority| match (marker.provenance, authority.kind) {
7281 (
7282 MarkerProvenance::TerminalProduct {
7283 terminal: TerminalProductSource::Binding(expected),
7284 ..
7285 },
7286 HistoricalCausalKind::BindingTerminal(owner),
7287 ) => {
7288 owner == expected
7289 && authority.admission_order.transaction_order()
7290 == marker.admission_order.transaction_order()
7291 }
7292 (
7293 MarkerProvenance::TerminalProduct {
7294 terminal:
7295 TerminalProductSource::RecoveryReplacement {
7296 participant_index,
7297 binding_epoch,
7298 },
7299 ..
7300 },
7301 HistoricalCausalKind::BindingTerminal(owner),
7302 ) => {
7303 owner.participant_index == participant_index
7304 && owner.binding_epoch == binding_epoch
7305 && authority.admission_order.transaction_order()
7306 == marker.admission_order.transaction_order()
7307 }
7308 (
7309 MarkerProvenance::ExitProduct {
7310 exit_participant, ..
7311 },
7312 HistoricalCausalKind::MembershipExit(participant_index),
7313 ) => {
7314 participant_index == exit_participant
7315 && authority.admission_order.transaction_order()
7316 == marker.admission_order.transaction_order()
7317 }
7318 _ => false,
7319 })
7320}
7321
7322fn terminal_matches_active(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7323 active_participant(active, owner.participant_index)
7324 .is_some_and(|participant| binding_epoch(participant.binding) == owner.binding_epoch)
7325}
7326
7327fn terminal_matches_bound(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7328 active_participant(active, owner.participant_index).is_some_and(|participant| {
7329 participant.binding == FrontierBinding::Bound(owner.binding_epoch)
7330 })
7331}
7332
7333fn active_participant(
7334 active: &ActiveIdentityRanks,
7335 participant_index: ParticipantId,
7336) -> Option<FrontierParticipant> {
7337 active
7338 .participants
7339 .binary_search_by_key(&participant_index, |participant| {
7340 participant.participant_index
7341 })
7342 .ok()
7343 .and_then(|index| active.participants.get(index))
7344 .copied()
7345}
7346
7347const fn binding_epoch(binding: FrontierBinding) -> BindingEpoch {
7348 match binding {
7349 FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
7350 }
7351}
7352
7353fn block_start_sequence(block: RecoverySequenceBlockRestore) -> DeliverySeq {
7354 block
7355 .terminal
7356 .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7357}
7358
7359fn block_start_validated_sequence(block: RecoverySequenceBlock) -> DeliverySeq {
7360 block
7361 .terminal
7362 .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7363}
7364
7365fn block_start_order(block: RecoveryOrderBlockRestore) -> TransactionOrder {
7366 block
7367 .active_binding
7368 .map_or(block.recovery_operation_order, |active_binding| {
7369 active_binding.transaction_order
7370 })
7371}
7372
7373fn block_start_validated_order(block: RecoveryOrderBlock) -> TransactionOrder {
7374 block
7375 .active_binding
7376 .map_or(block.recovery_operation_order, |active_binding| {
7377 active_binding.transaction_order
7378 })
7379}
7380
7381fn order_frontier_start(high: OrderHigh) -> u128 {
7382 match high {
7383 OrderHigh::Empty => 0,
7384 OrderHigh::Allocated(high) => u128::from(high) + 1,
7385 }
7386}
7387
7388const fn order_is_above_high(value: TransactionOrder, high: OrderHigh) -> bool {
7389 match high {
7390 OrderHigh::Empty => true,
7391 OrderHigh::Allocated(high) => value > high,
7392 }
7393}
7394
7395fn order_frontier_candidate_count(restore: &OrderClaimFrontierRestore, high: OrderHigh) -> u128 {
7396 usize_to_u128(
7397 restore
7398 .immutable_candidates
7399 .iter()
7400 .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
7401 .count(),
7402 )
7403}
7404
7405fn sequence_ordinal(ledger: SequenceLedger, value: DeliverySeq) -> u128 {
7406 u128::from(value).saturating_sub(u128::from(ledger.high_watermark()) + 1)
7407}
7408
7409fn order_ordinal(ledger: OrderLedger, value: TransactionOrder) -> u128 {
7410 u128::from(value).saturating_sub(order_frontier_start(ledger.high()))
7411}
7412
7413fn checked_rank_value(start: DeliverySeq, active_rank: usize) -> Option<DeliverySeq> {
7414 let rank = u64::try_from(active_rank).ok()?;
7415 start.checked_add(rank)
7416}
7417
7418fn usize_to_u64(value: usize) -> u64 {
7419 u64::try_from(value).unwrap_or(u64::MAX)
7420}
7421
7422fn usize_to_u128(value: usize) -> u128 {
7423 u64::try_from(value).map_or(u128::MAX, u128::from)
7424}
7425
7426fn rank_index(rank: usize) -> u128 {
7427 usize_to_u128(rank)
7428}
7429
7430const fn frontier_error(
7431 counter: ClaimFrontierCounter,
7432 first_bad_position: u128,
7433 reason: ClaimFrontierInvalidReason,
7434) -> ClaimFrontierError {
7435 ClaimFrontierError {
7436 counter,
7437 first_bad_position,
7438 reason,
7439 }
7440}
7441
7442const fn sequence_error(
7443 first_bad_position: u128,
7444 reason: ClaimFrontierInvalidReason,
7445) -> ClaimFrontierError {
7446 frontier_error(
7447 ClaimFrontierCounter::DeliverySequence,
7448 first_bad_position,
7449 reason,
7450 )
7451}
7452
7453const fn order_error(
7454 first_bad_position: u128,
7455 reason: ClaimFrontierInvalidReason,
7456) -> ClaimFrontierError {
7457 frontier_error(
7458 ClaimFrontierCounter::TransactionOrder,
7459 first_bad_position,
7460 reason,
7461 )
7462}