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, Eq)]
1421pub struct ClaimFrontiers {
1422 ordinary_cursor_release: Option<Box<super::storage::OrdinaryBindingFateRestore>>,
1423 conversation_id: ConversationId,
1424 active_identities: ActiveIdentityRanks,
1425 identity_slot_limit: u64,
1426 retained_floor: u128,
1427 retained_records: Vec<RetainedCausalRecord>,
1428 marker_records: Vec<RetainedCausalRecord>,
1429 fenced_marker_issued: Option<(ParticipantId, DeliverySeq, BindingEpoch)>,
1430 sequence: SequenceClaimFrontier,
1431 order: OrderClaimFrontier,
1432 historical_marker_deliveries: Vec<HistoricalMarkerDeliveryAuthority>,
1442 historical_causal_authorities: Vec<HistoricalCausalAuthority>,
1450}
1451
1452impl PartialEq for ClaimFrontiers {
1453 fn eq(&self, other: &Self) -> bool {
1454 (
1456 &self.conversation_id,
1457 &self.active_identities,
1458 &self.identity_slot_limit,
1459 &self.retained_floor,
1460 &self.retained_records,
1461 &self.marker_records,
1462 &self.fenced_marker_issued,
1463 &self.sequence,
1464 &self.order,
1465 &self.historical_marker_deliveries,
1466 &self.historical_causal_authorities,
1467 ) == (
1468 &other.conversation_id,
1469 &other.active_identities,
1470 &other.identity_slot_limit,
1471 &other.retained_floor,
1472 &other.retained_records,
1473 &other.marker_records,
1474 &other.fenced_marker_issued,
1475 &other.sequence,
1476 &other.order,
1477 &other.historical_marker_deliveries,
1478 &other.historical_causal_authorities,
1479 )
1480 }
1481}
1482
1483#[derive(Debug, PartialEq, Eq)]
1489pub struct InitialEnrollmentFrontierCommit<F> {
1490 operation: InitialEnrollmentOperationCommit<F>,
1491 frontiers: ClaimFrontiers,
1492 closure_accounting: ClosureAccounting,
1493 attached_charge: ResourceVector,
1494}
1495
1496impl<F> InitialEnrollmentFrontierCommit<F> {
1497 #[must_use]
1499 pub const fn operation(&self) -> &InitialEnrollmentOperationCommit<F> {
1500 &self.operation
1501 }
1502
1503 #[must_use]
1505 pub const fn frontiers(&self) -> &ClaimFrontiers {
1506 &self.frontiers
1507 }
1508
1509 #[must_use]
1511 pub const fn closure_accounting(&self) -> ClosureAccounting {
1512 self.closure_accounting
1513 }
1514
1515 #[must_use]
1517 pub const fn attached_charge(&self) -> ResourceVector {
1518 self.attached_charge
1519 }
1520
1521 #[allow(
1523 dead_code,
1524 reason = "the next conversation event body consumes this sealed operation/frontier unit"
1525 )]
1526 pub(in crate::lifecycle) fn into_conversation_parts(
1527 self,
1528 ) -> (
1529 InitialEnrollmentOperationCommit<F>,
1530 ClaimFrontiers,
1531 ClosureAccounting,
1532 ResourceVector,
1533 ) {
1534 (
1535 self.operation,
1536 self.frontiers,
1537 self.closure_accounting,
1538 self.attached_charge,
1539 )
1540 }
1541}
1542
1543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1545pub enum InitialEnrollmentFrontierError {
1546 AttachedChargeMismatch {
1548 expected: ResourceVector,
1550 actual: ResourceVector,
1552 },
1553 EnrollmentShape,
1555 LedgerShape,
1557 ClosureProjection,
1559 PositionOverflow,
1561 FrontierInvariant,
1563}
1564
1565#[derive(Debug, PartialEq, Eq)]
1567pub struct InitialEnrollmentFrontierFailure<F> {
1568 operation: InitialEnrollmentOperationCommit<F>,
1569 error: InitialEnrollmentFrontierError,
1570}
1571
1572impl<F> InitialEnrollmentFrontierFailure<F> {
1573 #[must_use]
1575 pub const fn error(&self) -> InitialEnrollmentFrontierError {
1576 self.error
1577 }
1578
1579 #[allow(
1581 dead_code,
1582 reason = "the conversation decision layer recovers or terminalizes the speculative enrollment"
1583 )]
1584 pub(in crate::lifecycle) fn into_operation(self) -> InitialEnrollmentOperationCommit<F> {
1585 self.operation
1586 }
1587}
1588
1589#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1591pub enum PrepareLeaveAuthorityError {
1592 Conversation,
1594 Identity,
1596 Binding,
1598 ImmutablePrefix,
1600 PendingCandidate,
1602 MembershipExitClaim,
1604 ActiveBindingClaim,
1606 OrderCapacity,
1608 ResultingOrderLedger,
1610}
1611
1612#[derive(Debug, PartialEq, Eq)]
1618pub struct ValidatedMarkerCandidate {
1619 conversation_id: ConversationId,
1620 candidate: MarkerCandidateAuthority,
1621 seal: MarkerAuthoritySeal,
1622}
1623
1624#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1627pub(super) struct FencedMarkerSourceRecord {
1628 pub(super) conversation_id: ConversationId,
1629 pub(super) delivery_seq: DeliverySeq,
1630 pub(super) admission_order: super::AdmissionOrder,
1631 pub(super) participant_id: ParticipantId,
1632 pub(super) provenance: MarkerProvenance,
1633 pub(super) target_binding: FrontierBinding,
1634}
1635
1636#[derive(Debug, PartialEq, Eq)]
1641pub struct ValidatedMarkerRecord {
1642 conversation_id: ConversationId,
1643 record: RetainedCausalRecord,
1644 provenance: MarkerProvenance,
1645 target_binding: FrontierBinding,
1646 occurrence: MarkerRecordOccurrence,
1647 seal: MarkerAuthoritySeal,
1648}
1649
1650#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1657pub(super) enum MarkerRecordOccurrence {
1658 Undelivered,
1660 Delivered,
1662}
1663
1664#[derive(Debug, PartialEq, Eq)]
1665enum MarkerAuthoritySeal {
1666 Validated,
1667}
1668
1669#[doc(hidden)]
1675pub fn with_validated_marker_record_type<F>(probe: F)
1676where
1677 F: FnOnce(ValidatedMarkerRecord),
1678{
1679 drop(probe);
1680}
1681
1682impl ValidatedMarkerRecord {
1683 pub(super) const fn consume(self) {
1685 match self.seal {
1686 MarkerAuthoritySeal::Validated => {}
1687 }
1688 }
1689
1690 #[must_use]
1692 pub const fn conversation_id(&self) -> ConversationId {
1693 self.conversation_id
1694 }
1695
1696 #[must_use]
1698 pub const fn participant_id(&self) -> ParticipantId {
1699 self.record.admission_order.participant_index()
1700 }
1701
1702 #[must_use]
1704 pub const fn delivery_seq(&self) -> DeliverySeq {
1705 self.record.delivery_seq
1706 }
1707
1708 #[must_use]
1710 pub const fn admission_order(&self) -> super::AdmissionOrder {
1711 self.record.admission_order
1712 }
1713
1714 #[must_use]
1716 pub const fn provenance(&self) -> MarkerProvenance {
1717 self.provenance
1718 }
1719
1720 #[must_use]
1722 pub const fn target_binding(&self) -> FrontierBinding {
1723 self.target_binding
1724 }
1725
1726 #[must_use]
1728 pub const fn binding_epoch(&self) -> BindingEpoch {
1729 binding_epoch(self.target_binding)
1730 }
1731
1732 #[must_use]
1734 pub(super) const fn occurrence(&self) -> MarkerRecordOccurrence {
1735 self.occurrence
1736 }
1737
1738 #[cfg(any(test, feature = "test-support"))]
1740 pub(super) const fn delivered_for_test(mut self) -> Self {
1741 self.occurrence = MarkerRecordOccurrence::Delivered;
1742 self
1743 }
1744}
1745
1746impl ValidatedMarkerCandidate {
1747 pub(super) const fn consume(self) {
1749 match self.seal {
1750 MarkerAuthoritySeal::Validated => {}
1751 }
1752 }
1753
1754 #[must_use]
1756 pub(super) const fn conversation_id(&self) -> ConversationId {
1757 self.conversation_id
1758 }
1759
1760 #[must_use]
1762 pub(super) const fn participant_index(&self) -> ParticipantId {
1763 self.candidate.admission_order.participant_index()
1764 }
1765
1766 #[must_use]
1768 pub(super) const fn participant_id(&self) -> ParticipantId {
1769 self.participant_index()
1770 }
1771
1772 #[must_use]
1774 pub(super) const fn delivery_seq(&self) -> DeliverySeq {
1775 self.candidate.delivery_seq
1776 }
1777
1778 #[must_use]
1780 pub(super) const fn target_binding(&self) -> FrontierBinding {
1781 self.candidate.target_binding
1782 }
1783
1784 #[must_use]
1786 pub(super) const fn provenance(&self) -> MarkerProvenance {
1787 self.candidate.provenance
1788 }
1789
1790 pub(super) const fn abandoned_after(&self) -> DeliverySeq {
1791 self.candidate.abandoned_after
1792 }
1793
1794 pub(super) const fn abandoned_through(&self) -> DeliverySeq {
1795 self.candidate.abandoned_through
1796 }
1797
1798 pub(super) const fn physical_floor_at_decision(&self) -> DeliverySeq {
1799 self.candidate.physical_floor_at_decision
1800 }
1801}
1802
1803#[derive(Clone, Copy)]
1804struct InitialEnrollmentFrontierShape {
1805 conversation_id: ConversationId,
1806 binding_epoch: BindingEpoch,
1807 identity_slot_limit: u64,
1808 retained_floor: u128,
1809 attached: AttachedLifecycleRecord,
1810 order_ledger: OrderLedger,
1811 sequence_ledger: SequenceLedger,
1812 closure_accounting: ClosureAccounting,
1813}
1814
1815#[derive(Clone, Copy)]
1816struct InitialFrontierPositions {
1817 terminal_sequence: DeliverySeq,
1818 exit_sequence: DeliverySeq,
1819 product_sequence: DeliverySeq,
1820 active_terminal_order: TransactionOrder,
1821 exit_order: TransactionOrder,
1822}
1823
1824fn initial_enrollment_frontier_shape<F>(
1825 operation: &InitialEnrollmentOperationCommit<F>,
1826 attached_charge: ResourceVector,
1827) -> Result<InitialEnrollmentFrontierShape, InitialEnrollmentFrontierError> {
1828 let projection = operation.closure_projection();
1829 let expected_charge = projection.resulting_retained_charge();
1830 if attached_charge != expected_charge {
1831 return Err(InitialEnrollmentFrontierError::AttachedChargeMismatch {
1832 expected: expected_charge,
1833 actual: attached_charge,
1834 });
1835 }
1836 let enrollment = operation.enrollment();
1837 let attached = enrollment.attached;
1838 let BindingState::Bound(binding) = enrollment.binding_state else {
1839 return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1840 };
1841 if enrollment.member.participant_id() != 0
1842 || enrollment.member.cursor() != 0
1843 || binding.participant_id != 0
1844 || attached.participant_id() != 0
1845 || binding.conversation_id != enrollment.member.conversation_id()
1846 || attached.conversation_id() != binding.conversation_id
1847 || attached.binding_epoch() != binding.binding_epoch
1848 || projection.participant_index() != 0
1849 || projection.binding_epoch() != binding.binding_epoch
1850 || projection.identity_slots() == 0
1851 {
1852 return Err(InitialEnrollmentFrontierError::EnrollmentShape);
1853 }
1854 let admission_order = attached.admission_order();
1855 let order_ledger = operation.order().resulting();
1856 let sequence_ledger = operation.sequence().resulting();
1857 let order_claims = order_ledger.claims();
1858 let sequence_claims = sequence_ledger.claims();
1859 if operation.order().major() != admission_order.transaction_order()
1860 || !matches!(order_ledger.high(), OrderHigh::Allocated(value) if value == admission_order.transaction_order())
1861 || order_claims.active_binding_terminals() != 1
1862 || order_claims.membership_exits() != 1
1863 || order_claims.recovery_operation()
1864 || order_claims.recovery_replacement_terminal()
1865 || sequence_ledger.high_watermark() != attached.delivery_seq()
1866 || sequence_claims.live_members() != 1
1867 || sequence_claims.binding_terminals() != 1
1868 || sequence_claims.markers() != 0
1869 || sequence_claims.recovery() != RecoverySequenceReserve::None
1870 || admission_order.candidate_phase() != CandidatePhase::AttachLifecycle
1871 {
1872 return Err(InitialEnrollmentFrontierError::LedgerShape);
1873 }
1874 Ok(InitialEnrollmentFrontierShape {
1875 conversation_id: binding.conversation_id,
1876 binding_epoch: binding.binding_epoch,
1877 identity_slot_limit: projection.identity_slots(),
1878 retained_floor: projection.resulting_floor(),
1879 attached,
1880 order_ledger,
1881 sequence_ledger,
1882 closure_accounting: validate_initial_enrollment_closure(operation, projection, attached)?,
1883 })
1884}
1885
1886fn validate_initial_enrollment_closure<F>(
1887 operation: &InitialEnrollmentOperationCommit<F>,
1888 projection: &InitialEnrollmentClosureProjection,
1889 attached: AttachedLifecycleRecord,
1890) -> Result<ClosureAccounting, InitialEnrollmentFrontierError> {
1891 let accounting = projection.resulting_closure_accounting();
1892 let state_matches = match accounting.state() {
1893 ClosureState::Clear => {
1894 projection.debt().is_zero()
1895 && projection.remaining_recovery_claim() == ResourceVector::default()
1896 }
1897 ClosureState::Owed {
1898 debt,
1899 edge: StoredEdge::ObserverProjection(observer),
1900 } => {
1901 debt.value() == projection.debt()
1902 && observer == ObserverProjection::new(attached.delivery_seq())
1903 && projection.remaining_recovery_claim() == accounting.edge_k_remaining()
1904 }
1905 ClosureState::Owed { .. } => false,
1906 };
1907 if projection.resulting_floor() != 1
1908 || projection.resulting_floor() != operation.observer_floor().cap_floor()
1909 || operation.observer_floor().observer_progress() != 0
1910 || projection.recovery_quartet() != RecoveryQuartetStatus::None
1911 || !projection.new_marker_candidates().is_empty()
1912 || accounting.marker_capacity_credits() != 0
1913 || accounting.marker_anchors() != 0
1914 || accounting.edge_sequence_claims() != 0
1915 || accounting.edge_order_position_claims() != 0
1916 || accounting.baseline() != projection.resulting_baseline()
1917 || !state_matches
1918 {
1919 Err(InitialEnrollmentFrontierError::ClosureProjection)
1920 } else {
1921 Ok(accounting)
1922 }
1923}
1924
1925fn initial_frontier_positions(
1926 attached: AttachedLifecycleRecord,
1927) -> Result<InitialFrontierPositions, InitialEnrollmentFrontierError> {
1928 let terminal_sequence = attached
1929 .delivery_seq()
1930 .checked_add(1)
1931 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1932 let exit_sequence = terminal_sequence
1933 .checked_add(1)
1934 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1935 let product_sequence = exit_sequence
1936 .checked_add(1)
1937 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1938 let active_terminal_order = attached
1939 .admission_order()
1940 .transaction_order()
1941 .checked_add(1)
1942 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1943 let exit_order = active_terminal_order
1944 .checked_add(1)
1945 .ok_or(InitialEnrollmentFrontierError::PositionOverflow)?;
1946 Ok(InitialFrontierPositions {
1947 terminal_sequence,
1948 exit_sequence,
1949 product_sequence,
1950 active_terminal_order,
1951 exit_order,
1952 })
1953}
1954
1955fn initial_sequence_frontier(
1956 shape: &InitialEnrollmentFrontierShape,
1957 positions: InitialFrontierPositions,
1958 terminal: BindingTerminalOwner,
1959) -> SequenceClaimFrontier {
1960 SequenceClaimFrontier {
1961 ledger: shape.sequence_ledger,
1962 movable_claims: alloc::vec![
1963 MovableSequenceClaim {
1964 delivery_seq: positions.terminal_sequence,
1965 owner: SequenceDirectOwner::BindingTerminal(terminal),
1966 },
1967 MovableSequenceClaim {
1968 delivery_seq: positions.exit_sequence,
1969 owner: SequenceDirectOwner::MembershipExit {
1970 participant_index: 0,
1971 },
1972 },
1973 ],
1974 immutable_candidates: Vec::new(),
1975 products: SequenceProductRanges {
1976 live_times_terminal: alloc::vec![TerminalProductRange {
1977 start: positions.product_sequence,
1978 length: 1,
1979 terminal,
1980 }],
1981 live_times_replacement_terminal: None,
1982 other_live_times_exit: Vec::new(),
1983 },
1984 recovery: None,
1985 }
1986}
1987
1988fn initial_order_frontier(
1989 shape: &InitialEnrollmentFrontierShape,
1990 positions: InitialFrontierPositions,
1991 terminal: BindingTerminalOwner,
1992) -> OrderClaimFrontier {
1993 OrderClaimFrontier {
1994 ledger: shape.order_ledger,
1995 movable_claims: alloc::vec![
1996 MovableOrderClaim {
1997 transaction_order: positions.active_terminal_order,
1998 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
1999 },
2000 MovableOrderClaim {
2001 transaction_order: positions.exit_order,
2002 owner: OrderDirectOwner::MembershipExit {
2003 participant_index: 0,
2004 },
2005 },
2006 ],
2007 immutable_candidates: Vec::new(),
2008 recovery: None,
2009 }
2010}
2011
2012fn build_initial_enrollment_frontiers(
2013 shape: &InitialEnrollmentFrontierShape,
2014) -> Result<ClaimFrontiers, InitialEnrollmentFrontierError> {
2015 let positions = initial_frontier_positions(shape.attached)?;
2016 let terminal = BindingTerminalOwner {
2017 participant_index: 0,
2018 binding_epoch: shape.binding_epoch,
2019 };
2020 let sequence = initial_sequence_frontier(shape, positions, terminal);
2021 let order = initial_order_frontier(shape, positions, terminal);
2022 validate_cross_counter(&sequence, &order)
2023 .map_err(|_| InitialEnrollmentFrontierError::FrontierInvariant)?;
2024 Ok(ClaimFrontiers {
2025 ordinary_cursor_release: None,
2026 historical_marker_deliveries: alloc::vec::Vec::new(),
2028 historical_causal_authorities: alloc::vec::Vec::new(),
2030 conversation_id: shape.conversation_id,
2031 active_identities: ActiveIdentityRanks {
2032 participants: alloc::vec![FrontierParticipant::new(
2033 0,
2034 0,
2035 FrontierBinding::Bound(shape.binding_epoch),
2036 )],
2037 },
2038 identity_slot_limit: shape.identity_slot_limit,
2039 retained_floor: shape.retained_floor,
2040 retained_records: alloc::vec![RetainedCausalRecord {
2041 delivery_seq: shape.attached.delivery_seq(),
2042 admission_order: shape.attached.admission_order(),
2043 kind: RetainedCausalRecordKind::AttachLifecycle {
2044 participant_index: 0,
2045 binding_epoch: shape.binding_epoch,
2046 },
2047 }],
2048 marker_records: Vec::new(),
2049 fenced_marker_issued: None,
2050 sequence,
2051 order,
2052 })
2053}
2054
2055#[derive(Clone, Copy)]
2056enum LeaveSequenceUnit {
2057 Direct(MovableSequenceClaim),
2058 TerminalProduct(TerminalProductRange),
2059 ReplacementProduct(ReplacementTerminalProductRange),
2060 ExitProduct(ExitProductRange),
2061 Recovery(RecoverySequenceBlock),
2062}
2063
2064impl LeaveSequenceUnit {
2065 fn original_start(self) -> DeliverySeq {
2066 match self {
2067 Self::Direct(claim) => claim.delivery_seq,
2068 Self::TerminalProduct(range) => range.start,
2069 Self::ReplacementProduct(range) => range.start,
2070 Self::ExitProduct(range) => range.start,
2071 Self::Recovery(block) => block_start_validated_sequence(block),
2072 }
2073 }
2074}
2075
2076fn allocate_leave_sequence_range(
2077 cursor: &mut Option<DeliverySeq>,
2078 length: u64,
2079) -> Result<DeliverySeq, LeaveCommitError> {
2080 let Some(start) = *cursor else {
2081 return Err(LeaveCommitError::ResultingFrontier);
2082 };
2083 let end = u128::from(start)
2084 .checked_add(u128::from(length))
2085 .and_then(|value| value.checked_sub(1))
2086 .ok_or(LeaveCommitError::ResultingFrontier)?;
2087 if end > u128::from(u64::MAX) {
2088 return Err(LeaveCommitError::ResultingFrontier);
2089 }
2090 *cursor = u64::try_from(end + 1).ok();
2091 Ok(start)
2092}
2093
2094#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2108pub enum PrecedenceCondition {
2109 BindingTerminal,
2113 MarkerDrain {
2115 settlement_epoch: DeliverySeq,
2123 },
2124 FencedRecovery,
2130 Unclassified,
2134}
2135
2136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2138pub(in crate::lifecycle) enum LiveFrontierTransitionError {
2139 Authority,
2141 Precedence(PrecedenceCondition),
2146 RecordPosition,
2148 Exhausted,
2150 ResultingFrontier,
2152}
2153
2154fn precedence(
2156 frontiers: ClaimFrontiers,
2157 condition: PrecedenceCondition,
2158) -> Box<(ClaimFrontiers, LiveFrontierTransitionError)> {
2159 Box::new((
2160 frontiers,
2161 LiveFrontierTransitionError::Precedence(condition),
2162 ))
2163}
2164
2165fn select_retained_marker_records(records: &[RetainedCausalRecord]) -> Vec<RetainedCausalRecord> {
2166 records
2167 .iter()
2168 .copied()
2169 .filter(|record| {
2170 matches!(
2171 record.kind,
2172 RetainedCausalRecordKind::CompactionMarker { .. }
2173 )
2174 })
2175 .collect()
2176}
2177
2178impl ClaimFrontiers {
2179 pub(super) fn retain_ordinary_cursor_release(
2180 &mut self,
2181 source: Box<super::storage::OrdinaryBindingFateRestore>,
2182 ) {
2183 self.ordinary_cursor_release = Some(source);
2184 }
2185
2186 pub(super) fn ordinary_cursor_release(
2187 &self,
2188 ) -> Option<&super::storage::OrdinaryBindingFateRestore> {
2189 self.ordinary_cursor_release.as_deref()
2190 }
2191
2192 pub(super) fn historical_marker_deliveries(&self) -> &[HistoricalMarkerDeliveryAuthority] {
2198 &self.historical_marker_deliveries
2199 }
2200
2201 #[must_use]
2214 pub(in crate::lifecycle) fn describe(
2215 &self,
2216 retained_record_limit: u64,
2217 ) -> ClaimFrontiersDescription {
2218 let conversation_id = self.conversation_id;
2219 ClaimFrontiersDescription {
2220 restore: ClaimFrontiersRestore {
2221 conversation_id,
2222 active_identities: self.active_identities.participants().to_vec(),
2223 identity_slot_limit: self.identity_slot_limit,
2224 retained_floor: self.retained_floor,
2225 retained_record_limit,
2226 retained_records: self.retained_records.clone(),
2227 active_marker_anchors: self
2228 .marker_records
2229 .iter()
2230 .map(|record| record.delivery_seq)
2231 .collect(),
2232 historical_marker_deliveries: self
2233 .historical_marker_deliveries()
2234 .iter()
2235 .map(|authority| HistoricalMarkerDeliveryFactRestore {
2236 conversation_id,
2237 participant_index: authority.participant_index(),
2238 marker_delivery_seq: authority.marker_delivery_seq(),
2239 delivered_binding_epoch: authority.delivered_binding_epoch(),
2240 })
2241 .collect(),
2242 historical_causal_facts: self
2243 .historical_causal_authorities
2244 .iter()
2245 .copied()
2246 .map(HistoricalCausalAuthority::describe)
2247 .collect(),
2248 sequence: describe_sequence_frontier(&self.sequence),
2249 order: describe_order_frontier(&self.order),
2250 recovery_marker_delivery_seq: self
2251 .sequence
2252 .recovery
2253 .map(RecoverySequenceBlock::marker_delivery_seq),
2254 },
2255 sequence_ledger: self.sequence.ledger,
2256 order_ledger: self.order.ledger,
2257 fenced_marker_issued: self.fenced_marker_issued,
2258 }
2259 }
2260
2261 pub(in crate::lifecycle) fn prevalidate_description(
2283 description: ClaimFrontiersDescription,
2284 history: &ValidatedConversationHistory,
2285 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2286 let ClaimFrontiersDescription {
2287 restore,
2288 sequence_ledger,
2289 order_ledger,
2290 fenced_marker_issued,
2291 } = description;
2292 let mut prevalidated =
2293 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, history)?;
2294 prevalidated.fenced_marker_issued = fenced_marker_issued;
2295 Ok(prevalidated)
2296 }
2297
2298 pub fn from_initial_enrollment<F>(
2313 operation: InitialEnrollmentOperationCommit<F>,
2314 attached_charge: ResourceVector,
2315 ) -> Result<InitialEnrollmentFrontierCommit<F>, Box<InitialEnrollmentFrontierFailure<F>>> {
2316 let shape = match initial_enrollment_frontier_shape(&operation, attached_charge) {
2317 Ok(shape) => shape,
2318 Err(error) => {
2319 return Err(Box::new(InitialEnrollmentFrontierFailure {
2320 operation,
2321 error,
2322 }));
2323 }
2324 };
2325 let closure_accounting = shape.closure_accounting;
2326 let frontiers = match build_initial_enrollment_frontiers(&shape) {
2327 Ok(frontiers) => frontiers,
2328 Err(error) => {
2329 return Err(Box::new(InitialEnrollmentFrontierFailure {
2330 operation,
2331 error,
2332 }));
2333 }
2334 };
2335 Ok(InitialEnrollmentFrontierCommit {
2336 operation,
2337 frontiers,
2338 closure_accounting,
2339 attached_charge,
2340 })
2341 }
2342
2343 pub fn restore(
2356 restore: ClaimFrontiersRestore,
2357 sequence_ledger: SequenceLedger,
2358 order_ledger: OrderLedger,
2359 ) -> Result<Self, ParticipantStateCorruptReason> {
2360 let history = ValidatedConversationHistory::empty();
2361 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)?
2362 .finish(None)
2363 }
2364
2365 #[cfg(any(test, feature = "test-support"))]
2368 pub(super) fn prevalidate(
2369 restore: ClaimFrontiersRestore,
2370 sequence_ledger: SequenceLedger,
2371 order_ledger: OrderLedger,
2372 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2373 let history = ValidatedConversationHistory::empty();
2374 Self::prevalidate_with_history(restore, sequence_ledger, order_ledger, &history)
2375 }
2376
2377 pub(super) fn prevalidate_with_history(
2378 restore: ClaimFrontiersRestore,
2379 sequence_ledger: SequenceLedger,
2380 order_ledger: OrderLedger,
2381 history: &ValidatedConversationHistory,
2382 ) -> Result<ClaimFrontiersPrevalidated, ParticipantStateCorruptReason> {
2383 validate_sequence_numeric(&restore.sequence, sequence_ledger).map_err(corrupt_frontier)?;
2384 validate_order_numeric(&restore.order, order_ledger).map_err(corrupt_frontier)?;
2385 validate_unique_candidate_keys(
2386 &restore.sequence.immutable_candidates,
2387 &restore.retained_records,
2388 )?;
2389 validate_bounded_shape(&restore, sequence_ledger).map_err(corrupt_frontier)?;
2390 let active_identities = ActiveIdentityRanks::try_new(
2391 restore.active_identities,
2392 sequence_ledger.high_watermark(),
2393 restore.identity_slot_limit,
2394 )
2395 .map_err(corrupt_frontier)?;
2396 let retained_records = validated_retained_records(
2397 restore.retained_records,
2398 restore.retained_floor,
2399 restore.retained_record_limit,
2400 restore.identity_slot_limit,
2401 sequence_ledger,
2402 )
2403 .map_err(corrupt_frontier)?;
2404 let historical_causal_authorities = validated_historical_authorities(
2405 restore.historical_causal_facts,
2406 restore.conversation_id,
2407 restore.identity_slot_limit,
2408 sequence_ledger,
2409 history,
2410 )
2411 .map_err(corrupt_frontier)?;
2412 let retained_marker_records = select_retained_marker_records(&retained_records);
2413 let marker_records = validated_active_marker_records(
2414 &retained_marker_records,
2415 restore.active_marker_anchors,
2416 restore.identity_slot_limit,
2417 sequence_ledger,
2418 )
2419 .map_err(corrupt_frontier)?;
2420 let historical_marker_deliveries = validated_historical_marker_deliveries(
2421 restore.historical_marker_deliveries,
2422 restore.conversation_id,
2423 &active_identities,
2424 &MarkerDeliverySources {
2425 retained: &retained_records,
2426 historical: &historical_causal_authorities,
2427 candidates: &restore.sequence.immutable_candidates,
2428 },
2429 restore.retained_record_limit,
2430 sequence_ledger,
2431 )
2432 .map_err(corrupt_frontier)?;
2433 BindingOriginValidation {
2434 conversation_id: restore.conversation_id,
2435 active: &active_identities,
2436 origins: &history.binding_origins,
2437 retained_records: &retained_records,
2438 causal_authorities: &history.causal_authorities,
2439 historical_marker_deliveries: &historical_marker_deliveries,
2440 total: history.total,
2441 ledger: sequence_ledger,
2442 }
2443 .validate()
2444 .map_err(corrupt_frontier)?;
2445 validate_sequence_candidates(
2446 &active_identities,
2447 &restore.sequence.immutable_candidates,
2448 restore.retained_floor,
2449 &retained_records,
2450 &historical_causal_authorities,
2451 sequence_ledger,
2452 )
2453 .map_err(corrupt_frontier)?;
2454 validate_marker_credit_owners(
2455 &restore.sequence.immutable_candidates,
2456 &marker_records,
2457 restore.identity_slot_limit,
2458 sequence_ledger,
2459 )
2460 .map_err(corrupt_frontier)?;
2461 Ok(ClaimFrontiersPrevalidated {
2462 conversation_id: restore.conversation_id,
2463 active_identities,
2464 identity_slot_limit: restore.identity_slot_limit,
2465 retained_floor: restore.retained_floor,
2466 retained_records,
2467 marker_records,
2468 historical_marker_deliveries,
2469 historical_causal_authorities,
2470 binding_origins: history.binding_origins.clone(),
2471 sequence_restore: restore.sequence,
2472 order_restore: restore.order,
2473 recovery_marker_delivery_seq: restore.recovery_marker_delivery_seq,
2474 sequence_ledger,
2475 order_ledger,
2476 issued_marker_record: None,
2477 fenced_marker_issued: None,
2478 })
2479 }
2480
2481 #[must_use]
2483 pub const fn active_identities(&self) -> &ActiveIdentityRanks {
2484 &self.active_identities
2485 }
2486
2487 #[must_use]
2489 pub const fn identity_slot_limit(&self) -> u64 {
2490 self.identity_slot_limit
2491 }
2492
2493 #[must_use]
2495 pub const fn conversation_id(&self) -> ConversationId {
2496 self.conversation_id
2497 }
2498
2499 #[must_use]
2501 pub const fn retained_floor(&self) -> u128 {
2502 self.retained_floor
2503 }
2504
2505 #[must_use]
2507 pub fn retained_records(&self) -> &[RetainedCausalRecord] {
2508 &self.retained_records
2509 }
2510
2511 pub(super) fn checkpoint_recovered_marker_record(
2512 &self,
2513 origin: BindingOrigin,
2514 ) -> Option<ValidatedMarkerRecord> {
2515 let (marker_delivery_seq, prior_binding_epoch) = origin.recovered_marker()?;
2516 if origin.conversation_id() != self.conversation_id
2517 || !self.historical_marker_deliveries.iter().any(|delivery| {
2518 delivery.participant_index == origin.participant_id()
2519 && delivery.marker_delivery_seq == marker_delivery_seq
2520 && delivery.delivered_binding_epoch == prior_binding_epoch
2521 })
2522 {
2523 return None;
2524 }
2525 let record = self
2526 .retained_records
2527 .iter()
2528 .find(|record| record.delivery_seq == marker_delivery_seq)
2529 .copied()?;
2530 let RetainedCausalRecordKind::CompactionMarker {
2531 participant_index,
2532 provenance,
2533 } = record.kind
2534 else {
2535 return None;
2536 };
2537 if participant_index != origin.participant_id() {
2538 return None;
2539 }
2540 Some(ValidatedMarkerRecord {
2541 conversation_id: self.conversation_id,
2542 record,
2543 provenance,
2544 target_binding: FrontierBinding::Detached(prior_binding_epoch),
2545 occurrence: MarkerRecordOccurrence::Delivered,
2546 seal: MarkerAuthoritySeal::Validated,
2547 })
2548 }
2549
2550 #[must_use]
2552 pub fn retained_marker_records(&self) -> &[RetainedCausalRecord] {
2553 &self.marker_records
2554 }
2555
2556 #[must_use]
2561 pub(in crate::lifecycle) fn unaccepted_marker_anchor_count(&self) -> u64 {
2562 ordinary_unaccepted_marker_anchors(self).len() as u64
2563 }
2564
2565 #[must_use]
2572 pub(in crate::lifecycle) fn pending_marker_candidate_count(&self) -> u64 {
2573 self.sequence
2574 .immutable_candidates
2575 .iter()
2576 .filter(|candidate| matches!(candidate, ImmutableSequenceCandidate::Marker(_)))
2577 .count() as u64
2578 }
2579
2580 #[must_use]
2588 pub fn project_offered_marker_progress(
2589 &self,
2590 participant_id: ParticipantId,
2591 binding_epoch: BindingEpoch,
2592 marker_delivery_seq: DeliverySeq,
2593 event: Event,
2594 ) -> Option<ParticipantCursorProgress> {
2595 let record = self
2596 .marker_records
2597 .iter()
2598 .find(|record| record.delivery_seq == marker_delivery_seq)
2599 .copied()?;
2600 let RetainedCausalRecordKind::CompactionMarker {
2601 participant_index,
2602 provenance,
2603 } = record.kind
2604 else {
2605 return None;
2606 };
2607 if participant_index != participant_id {
2608 return None;
2609 }
2610 let participant = active_participant(&self.active_identities, participant_id)?;
2611 let target_binding = FrontierBinding::Bound(binding_epoch);
2612 if participant.binding != target_binding {
2613 return None;
2614 }
2615 let authority = ValidatedMarkerRecord {
2616 conversation_id: self.conversation_id,
2617 record,
2618 provenance,
2619 target_binding,
2620 occurrence: MarkerRecordOccurrence::Undelivered,
2621 seal: MarkerAuthoritySeal::Validated,
2622 };
2623 MarkerDelivery::from_validated_record(&authority)
2624 .delivered_progress(event)
2625 .ok()
2626 }
2627
2628 pub(super) fn fenced_marker_source(
2631 &self,
2632 recovery: super::DetachedCredentialRecovery,
2633 ) -> Option<FencedMarkerSourceRecord> {
2634 if self.fenced_marker_issued.is_some() {
2635 return None;
2636 }
2637 self.unlatched_fenced_marker_source(recovery)
2638 }
2639
2640 #[must_use]
2657 pub(in crate::lifecycle) fn fenced_latch_matches(
2658 &self,
2659 edge: Option<super::StoredEdge>,
2660 ) -> bool {
2661 let Some((participant_id, marker_delivery_seq, prior_binding_epoch)) =
2662 self.fenced_marker_issued
2663 else {
2664 return true;
2665 };
2666 let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge else {
2667 return false;
2668 };
2669 recovery.participant_id() == participant_id
2670 && recovery.marker_delivery_seq() == marker_delivery_seq
2671 && recovery.prior_binding_epoch() == prior_binding_epoch
2672 && self.unlatched_fenced_marker_source(recovery).is_some()
2673 }
2674
2675 fn unlatched_fenced_marker_source(
2678 &self,
2679 recovery: super::DetachedCredentialRecovery,
2680 ) -> Option<FencedMarkerSourceRecord> {
2681 if recovery.conversation_id() != self.conversation_id {
2682 return None;
2683 }
2684 let marker_delivery_seq = recovery.marker_delivery_seq();
2685 let participant_id = recovery.participant_id();
2686 let prior_binding_epoch = recovery.prior_binding_epoch();
2687 let record = self
2688 .marker_records
2689 .iter()
2690 .find(|record| record.delivery_seq == marker_delivery_seq)
2691 .copied()?;
2692 let RetainedCausalRecordKind::CompactionMarker {
2693 participant_index,
2694 provenance,
2695 } = record.kind
2696 else {
2697 return None;
2698 };
2699 let participant = active_participant(&self.active_identities, participant_id)?;
2700 let target_binding = FrontierBinding::Detached(prior_binding_epoch);
2701 if participant_index != participant_id || participant.binding != target_binding {
2702 return None;
2703 }
2704 Some(FencedMarkerSourceRecord {
2705 conversation_id: self.conversation_id,
2706 delivery_seq: record.delivery_seq,
2707 admission_order: record.admission_order,
2708 participant_id,
2709 provenance,
2710 target_binding,
2711 })
2712 }
2713
2714 pub(in crate::lifecycle) fn take_fenced_marker_record(
2725 &mut self,
2726 recovery: super::DetachedCredentialRecovery,
2727 ) -> Option<ValidatedMarkerRecord> {
2728 let source = self.fenced_marker_source(recovery)?;
2729 self.fenced_marker_issued = Some((
2730 source.participant_id,
2731 source.delivery_seq,
2732 recovery.prior_binding_epoch(),
2733 ));
2734 Some(ValidatedMarkerRecord {
2735 conversation_id: source.conversation_id,
2736 record: RetainedCausalRecord {
2737 delivery_seq: source.delivery_seq,
2738 admission_order: source.admission_order,
2739 kind: RetainedCausalRecordKind::CompactionMarker {
2740 participant_index: source.participant_id,
2741 provenance: source.provenance,
2742 },
2743 },
2744 provenance: source.provenance,
2745 target_binding: source.target_binding,
2746 occurrence: MarkerRecordOccurrence::Delivered,
2747 seal: MarkerAuthoritySeal::Validated,
2748 })
2749 }
2750
2751 pub(in crate::lifecycle) const fn reinstall_fenced_marker_record(
2754 &mut self,
2755 record: ValidatedMarkerRecord,
2756 ) {
2757 self.fenced_marker_issued = None;
2758 record.consume();
2759 }
2760
2761 #[must_use]
2763 pub const fn sequence(&self) -> &SequenceClaimFrontier {
2764 &self.sequence
2765 }
2766
2767 #[must_use]
2769 pub const fn order(&self) -> &OrderClaimFrontier {
2770 &self.order
2771 }
2772
2773 #[cfg(test)]
2774 pub(in crate::lifecycle) fn cross_counter_valid_for_test(&self) -> bool {
2775 validate_cross_counter(&self.sequence, &self.order).is_ok()
2776 }
2777
2778 #[must_use]
2786 pub fn live_transition_blocked(&self) -> bool {
2787 !self.sequence.immutable_candidates.is_empty()
2788 || self.sequence.recovery.is_some()
2789 || !self.order.immutable_candidates.is_empty()
2790 || self.order.recovery.is_some()
2791 }
2792
2793 #[must_use]
2810 pub fn precedence_condition(&self) -> PrecedenceCondition {
2811 if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
2812 return PrecedenceCondition::FencedRecovery;
2813 }
2814 match self.sequence.immutable_candidates.first() {
2815 Some(ImmutableSequenceCandidate::Marker(marker)) => PrecedenceCondition::MarkerDrain {
2816 settlement_epoch: marker.delivery_seq,
2817 },
2818 Some(ImmutableSequenceCandidate::BindingTerminal { .. }) => {
2819 PrecedenceCondition::BindingTerminal
2820 }
2821 None => PrecedenceCondition::Unclassified,
2822 }
2823 }
2824
2825 pub(in crate::lifecycle) fn apply_live_transition(
2831 self,
2832 active_identities: Vec<FrontierParticipant>,
2833 appended_records: &[RetainedCausalRecord],
2834 sequence_ledger: SequenceLedger,
2835 order_ledger: OrderLedger,
2836 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2837 if self.live_transition_blocked() {
2838 let condition = self.precedence_condition();
2839 return Err(Box::new((
2840 self,
2841 LiveFrontierTransitionError::Precedence(condition),
2842 )));
2843 }
2844 let Ok(active) = ActiveIdentityRanks::try_new(
2845 active_identities,
2846 sequence_ledger.high_watermark(),
2847 self.identity_slot_limit,
2848 ) else {
2849 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2850 };
2851 let first_sequence = self.sequence.ledger.high_watermark().checked_add(1);
2852 if first_sequence.is_none_or(|first| {
2853 appended_records.iter().enumerate().any(|(index, record)| {
2854 u64::try_from(index)
2855 .ok()
2856 .and_then(|offset| first.checked_add(offset))
2857 != Some(record.delivery_seq)
2858 })
2859 }) || appended_records
2860 .last()
2861 .is_some_and(|record| record.delivery_seq != sequence_ledger.high_watermark())
2862 {
2863 return Err(Box::new((
2864 self,
2865 LiveFrontierTransitionError::RecordPosition,
2866 )));
2867 }
2868 let (sequence, order) =
2869 match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
2870 Ok(frontiers) => frontiers,
2871 Err(error) => return Err(Box::new((self, error))),
2872 };
2873 let Self {
2874 ordinary_cursor_release,
2875 conversation_id,
2876 identity_slot_limit,
2877 retained_floor,
2878 mut retained_records,
2879 marker_records,
2880 fenced_marker_issued,
2881 historical_marker_deliveries,
2882 historical_causal_authorities,
2883 ..
2884 } = self;
2885 retained_records.extend_from_slice(appended_records);
2886 Ok(Self {
2887 ordinary_cursor_release,
2888 conversation_id,
2889 active_identities: active,
2890 identity_slot_limit,
2891 retained_floor,
2892 retained_records,
2893 marker_records,
2894 fenced_marker_issued,
2895 sequence,
2896 order,
2897 historical_marker_deliveries,
2898 historical_causal_authorities,
2899 })
2900 }
2901
2902 pub(in crate::lifecycle) fn apply_pending_binding_terminal(
2906 mut self,
2907 participant_id: ParticipantId,
2908 binding_epoch: BindingEpoch,
2909 delivery_seq: DeliverySeq,
2910 admission_order: super::AdmissionOrder,
2911 order_ledger: OrderLedger,
2912 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2913 if self.live_transition_blocked() {
2914 let condition = self.precedence_condition();
2915 return Err(Box::new((
2916 self,
2917 LiveFrontierTransitionError::Precedence(condition),
2918 )));
2919 }
2920 let mut participants = self.active_identities.participants().to_vec();
2921 let Some(participant) = participants
2922 .iter_mut()
2923 .find(|participant| participant.participant_index() == participant_id)
2924 else {
2925 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2926 };
2927 if participant.binding() != FrontierBinding::Bound(binding_epoch) {
2928 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2929 }
2930 *participant = FrontierParticipant::new(
2931 participant_id,
2932 participant.cursor(),
2933 FrontierBinding::Detached(binding_epoch),
2934 );
2935 let Ok(active) = ActiveIdentityRanks::try_new(
2936 participants,
2937 self.sequence.ledger.high_watermark(),
2938 self.identity_slot_limit,
2939 ) else {
2940 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2941 };
2942 let pending_owner = BindingTerminalOwner {
2943 participant_index: participant_id,
2944 binding_epoch,
2945 };
2946 let (sequence, order) = match rebuild_pending_terminal_frontiers(
2947 &active,
2948 pending_owner,
2949 delivery_seq,
2950 admission_order,
2951 self.sequence.ledger,
2952 order_ledger,
2953 ) {
2954 Ok(frontiers) => frontiers,
2955 Err(error) => return Err(Box::new((self, error))),
2956 };
2957 self.active_identities = active;
2958 self.sequence = sequence;
2959 self.order = order;
2960 Ok(self)
2961 }
2962
2963 pub(in crate::lifecycle) fn apply_live_fenced_attach(
2971 self,
2972 participant: FrontierParticipant,
2973 prior_binding_epoch: BindingEpoch,
2974 appended_records: &[RetainedCausalRecord],
2975 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
2976 let Some(current) = self
2977 .active_identities
2978 .participants()
2979 .iter()
2980 .find(|current| current.participant_index() == participant.participant_index())
2981 .copied()
2982 else {
2983 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2984 };
2985 let Some(sequence_recovery) = self.sequence.recovery else {
2986 return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2987 };
2988 let Some(order_recovery) = self.order.recovery else {
2989 return Err(precedence(self, PrecedenceCondition::FencedRecovery));
2990 };
2991 if !Self::fenced_recovery_authority_matches(
2992 current,
2993 participant,
2994 prior_binding_epoch,
2995 sequence_recovery,
2996 order_recovery,
2997 ) {
2998 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
2999 }
3000 let finalizes_pending = appended_records.len() == 2;
3001 let pending_terminal_matches = self.pending_fenced_terminal_matches(appended_records);
3002 let candidates_empty = self.sequence.immutable_candidates.is_empty()
3003 && self.order.immutable_candidates.is_empty();
3004 if !(candidates_empty || finalizes_pending && pending_terminal_matches) {
3005 let condition = self.precedence_condition();
3006 return Err(precedence(self, condition));
3007 }
3008 if !Self::fenced_records_match(
3009 participant,
3010 sequence_recovery,
3011 order_recovery,
3012 appended_records,
3013 pending_terminal_matches,
3014 ) {
3015 return Err(Box::new((
3016 self,
3017 LiveFrontierTransitionError::RecordPosition,
3018 )));
3019 }
3020 if !self.has_recovery_marker(participant) {
3021 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3022 }
3023 if self.has_duplicate_appended_record(appended_records) {
3024 return Err(Box::new((
3025 self,
3026 LiveFrontierTransitionError::RecordPosition,
3027 )));
3028 }
3029 let Some((sequence_ledger, order_ledger)) =
3030 apply_fenced_ledgers(self.sequence.ledger, self.order.ledger, finalizes_pending)
3031 else {
3032 return Err(Box::new((
3033 self,
3034 LiveFrontierTransitionError::ResultingFrontier,
3035 )));
3036 };
3037 let mut active = self.active_identities.participants().to_vec();
3038 let Some(current) = active
3039 .iter_mut()
3040 .find(|current| current.participant_index() == participant.participant_index())
3041 else {
3042 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3043 };
3044 *current = participant;
3045 let Ok(active) = ActiveIdentityRanks::try_new(
3046 active,
3047 sequence_ledger.high_watermark(),
3048 self.identity_slot_limit,
3049 ) else {
3050 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3051 };
3052 let Ok((sequence, order)) =
3053 rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger)
3054 else {
3055 return Err(Box::new((
3056 self,
3057 LiveFrontierTransitionError::ResultingFrontier,
3058 )));
3059 };
3060 let mut resulting = self;
3061 resulting.active_identities = active;
3062 resulting
3063 .retained_records
3064 .extend_from_slice(appended_records);
3065 resulting
3066 .retained_records
3067 .sort_unstable_by_key(|record| record.delivery_seq);
3068 resulting
3069 .marker_records
3070 .retain(|record| record.delivery_seq != participant.cursor());
3071 resulting.spend_fenced_latch(participant, prior_binding_epoch);
3072 resulting.sequence = sequence;
3073 resulting.order = order;
3074 Ok(resulting)
3075 }
3076
3077 fn spend_fenced_latch(
3082 &mut self,
3083 participant: FrontierParticipant,
3084 prior_binding_epoch: BindingEpoch,
3085 ) {
3086 if self.fenced_marker_issued
3087 == Some((
3088 participant.participant_index(),
3089 participant.cursor(),
3090 prior_binding_epoch,
3091 ))
3092 {
3093 self.fenced_marker_issued = None;
3094 }
3095 }
3096
3097 fn fenced_recovery_authority_matches(
3098 current: FrontierParticipant,
3099 participant: FrontierParticipant,
3100 prior_binding_epoch: BindingEpoch,
3101 sequence_recovery: RecoverySequenceBlock,
3102 order_recovery: RecoveryOrderBlock,
3103 ) -> bool {
3104 let participant_matches = sequence_recovery.participant_index()
3105 == participant.participant_index()
3106 && order_recovery.participant_index() == participant.participant_index();
3107 let marker_matches = sequence_recovery.marker_delivery_seq() == participant.cursor()
3108 && order_recovery.marker_delivery_seq() == participant.cursor();
3109 let prior_epoch_matches = sequence_recovery.recovered_binding_epoch()
3110 == prior_binding_epoch
3111 && order_recovery.recovered_binding_epoch() == prior_binding_epoch;
3112 participant_matches
3113 && marker_matches
3114 && prior_epoch_matches
3115 && current.binding() == FrontierBinding::Detached(prior_binding_epoch)
3116 && current.cursor() <= participant.cursor()
3117 && matches!(participant.binding(), FrontierBinding::Bound(_))
3118 }
3119
3120 fn fenced_records_match(
3121 participant: FrontierParticipant,
3122 sequence_recovery: RecoverySequenceBlock,
3123 order_recovery: RecoveryOrderBlock,
3124 appended_records: &[RetainedCausalRecord],
3125 pending_terminal_matches: bool,
3126 ) -> bool {
3127 let Some(attached) = appended_records.last().copied() else {
3128 return false;
3129 };
3130 let FrontierBinding::Bound(recovered_binding_epoch) = participant.binding() else {
3131 return false;
3132 };
3133 let attached_matches = attached.delivery_seq == sequence_recovery.recovery_attach_seq()
3134 && attached.admission_order.transaction_order()
3135 == order_recovery.recovery_operation_order()
3136 && attached.kind
3137 == (RetainedCausalRecordKind::AttachLifecycle {
3138 participant_index: participant.participant_index(),
3139 binding_epoch: recovered_binding_epoch,
3140 });
3141 if !attached_matches {
3142 return false;
3143 }
3144 let prefix = &appended_records[..appended_records.len() - 1];
3145 match (
3146 sequence_recovery.terminal(),
3147 order_recovery.active_binding(),
3148 prefix,
3149 ) {
3150 (None, None, []) => true,
3151 (None, None, [_]) => pending_terminal_matches,
3152 (Some(sequence_terminal), Some(order_terminal), [terminal]) => {
3153 sequence_terminal.owner == order_terminal.owner
3154 && terminal.delivery_seq == sequence_terminal.delivery_seq
3155 && terminal.admission_order.transaction_order()
3156 == order_terminal.transaction_order
3157 && terminal.kind
3158 == RetainedCausalRecordKind::BindingTerminal(sequence_terminal.owner)
3159 }
3160 _ => false,
3161 }
3162 }
3163
3164 fn pending_fenced_terminal_matches(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3165 let [terminal, _] = appended_records else {
3166 return false;
3167 };
3168 let RetainedCausalRecordKind::BindingTerminal(owner) = terminal.kind else {
3169 return false;
3170 };
3171 let sequence_matches = self.sequence.immutable_candidates.iter().any(|candidate| {
3172 matches!(
3173 candidate,
3174 ImmutableSequenceCandidate::BindingTerminal {
3175 delivery_seq,
3176 admission_order,
3177 owner: candidate_owner,
3178 } if *delivery_seq == terminal.delivery_seq
3179 && *admission_order == terminal.admission_order
3180 && *candidate_owner == owner
3181 )
3182 });
3183 let order_matches = self.order.immutable_candidates.iter().any(|candidate| {
3184 candidate.transaction_order == terminal.admission_order.transaction_order()
3185 && candidate.candidate_keys.as_slice() == [terminal.admission_order]
3186 });
3187 sequence_matches && order_matches
3188 }
3189
3190 fn has_recovery_marker(&self, participant: FrontierParticipant) -> bool {
3191 self.retained_records.iter().any(|record| {
3192 record.delivery_seq == participant.cursor()
3193 && matches!(
3194 record.kind,
3195 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3196 if participant_index == participant.participant_index()
3197 )
3198 })
3199 }
3200
3201 fn has_duplicate_appended_record(&self, appended_records: &[RetainedCausalRecord]) -> bool {
3202 appended_records.iter().any(|row| {
3203 self.retained_records
3204 .iter()
3205 .any(|retained| retained.delivery_seq == row.delivery_seq)
3206 })
3207 }
3208
3209 pub(in crate::lifecycle) fn apply_live_identity(
3212 mut self,
3213 participant: FrontierParticipant,
3214 ) -> Result<Self, Box<(Self, LiveFrontierTransitionError)>> {
3215 let Some(current) = self
3216 .active_identities
3217 .participants
3218 .iter_mut()
3219 .find(|current| current.participant_index == participant.participant_index)
3220 else {
3221 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3222 };
3223 if participant.cursor < current.cursor
3224 || participant.cursor > self.sequence.ledger.high_watermark()
3225 {
3226 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3227 }
3228 *current = participant;
3229 Ok(self)
3230 }
3231
3232 pub fn project_ordinary_record(
3248 self,
3249 input: OrdinaryRecordProjectionInput,
3250 ) -> Result<OrdinaryRecordProjectionDecision, Box<OrdinaryRecordProjectionFailure>> {
3251 let (
3252 request,
3253 receiving_binding_epoch,
3254 encoded_record_charge,
3255 retained_charges,
3256 observer_progress,
3257 closure_accounting,
3258 limits,
3259 ) = input.as_parts();
3260 if request.conversation_id != self.conversation_id {
3261 return Err(projection_failure(
3262 self,
3263 input,
3264 OrdinaryProjectionError::Conversation,
3265 ));
3266 }
3267 let unaccepted_marker_anchors = ordinary_unaccepted_marker_anchors(&self);
3268 let kernel = match project_ordinary_fixed_point(&OrdinaryProjectionFacts {
3269 request: request.clone(),
3270 receiving_binding_epoch,
3271 encoded_record_charge,
3272 retained_records: &self.retained_records,
3273 retained_charges,
3274 active_marker_credit_records: &self.marker_records,
3275 unaccepted_marker_anchors: &unaccepted_marker_anchors,
3276 active_identities: self.active_identities.participants(),
3277 identity_slot_limit: self.identity_slot_limit,
3278 current_floor: self.retained_floor,
3279 observer_progress,
3280 order_ledger: self.order.ledger,
3281 sequence_ledger: self.sequence.ledger,
3282 immutable_candidates: &self.sequence.immutable_candidates,
3283 closure_accounting,
3284 remaining_recovery_claim: closure_accounting.edge_k_remaining(),
3285 limits,
3286 }) {
3287 Ok(value) => value,
3288 Err(error) => return Err(projection_failure(self, input, error)),
3289 };
3290 match kernel {
3291 OrdinaryProjectionKernelDecision::DrainFirst(prefix) => Ok(
3292 OrdinaryRecordProjectionDecision::DrainFirst(Box::new(OrdinaryRecordDrainFirst {
3293 frontiers: self,
3294 input,
3295 candidate: prefix.candidate(),
3296 })),
3297 ),
3298 OrdinaryProjectionKernelDecision::Projected(projected) => {
3299 let observer_floor = match check_observer_floor(
3300 ObserverCheckedOperation::RecordAdmission(request.clone()),
3301 observer_progress,
3302 projected.floor().resulting_floor,
3303 ) {
3304 ObserverFloorDecision::Eligible(permit) => permit,
3305 ObserverFloorDecision::Respond(_) => {
3306 return Err(projection_failure(
3307 self,
3308 input,
3309 OrdinaryProjectionError::ObserverSelectorInvariant,
3310 ));
3311 }
3312 };
3313 let closure = match check_remaining_closure(
3314 &ClosureCheckedEnvelope::RecordAdmission(request.clone()),
3315 closure_accounting,
3316 false,
3317 0,
3318 projected.required_capacity(),
3319 ) {
3320 RemainingClosureDecision::Eligible(permit) => *permit,
3321 RemainingClosureDecision::Respond(_) => {
3322 return Err(projection_failure(
3323 self,
3324 input,
3325 OrdinaryProjectionError::ClosureSelectorInvariant,
3326 ));
3327 }
3328 };
3329 match self.apply_ordinary_projection(*projected, observer_floor, closure) {
3330 Ok(projected) => Ok(OrdinaryRecordProjectionDecision::Projected(Box::new(
3331 projected,
3332 ))),
3333 Err(failure) => {
3334 let (frontiers, error) = *failure;
3335 Err(projection_failure(frontiers, input, error))
3336 }
3337 }
3338 }
3339 }
3340 }
3341
3342 fn apply_ordinary_projection(
3343 mut self,
3344 projected: OrdinaryFixedPointPlan,
3345 observer_floor: super::ObserverFloorPermit,
3346 closure: super::RemainingClosurePermit,
3347 ) -> Result<ProjectedOrdinaryRecord, Box<(Self, OrdinaryProjectionError)>> {
3348 let Ok(marker_count) = u64::try_from(projected.marker_candidates().len()) else {
3349 return Err(Box::new((
3350 self,
3351 OrdinaryProjectionError::SequenceRelocation,
3352 )));
3353 };
3354 let Some(sequence_delta) = marker_count.checked_add(1) else {
3355 return Err(Box::new((
3356 self,
3357 OrdinaryProjectionError::SequenceRelocation,
3358 )));
3359 };
3360 if let Err(error) = preflight_ordinary_sequence_owners(&self.sequence, sequence_delta) {
3361 return Err(Box::new((self, error)));
3362 }
3363 if let Err(error) = preflight_ordinary_order_owners(&self.order) {
3364 return Err(Box::new((self, error)));
3365 }
3366 let (
3367 floor,
3368 retained_charge,
3369 baseline,
3370 accounting,
3371 required_capacity,
3372 order,
3373 sequence,
3374 caller_record,
3375 caller_charge,
3376 retained_records,
3377 retained_charges,
3378 new_marker_candidates,
3379 ) = projected.into_parts();
3380
3381 let prior_sequence_ledger = self.sequence.ledger;
3382 let prior_order_ledger = self.order.ledger;
3383 relay_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3384 relay_ordinary_order_owners(&mut self.order);
3385
3386 self.sequence.ledger = sequence.resulting();
3387 self.sequence.immutable_candidates.extend(
3388 new_marker_candidates
3389 .iter()
3390 .copied()
3391 .map(ImmutableSequenceCandidate::Marker),
3392 );
3393 self.order.ledger = order.resulting();
3394 if !new_marker_candidates.is_empty() {
3395 self.order
3396 .immutable_candidates
3397 .push(ImmutableOrderCandidateMajor {
3398 transaction_order: order.major(),
3399 candidate_keys: new_marker_candidates
3400 .iter()
3401 .map(|candidate| candidate.admission_order)
3402 .collect(),
3403 });
3404 }
3405 if validate_cross_counter(&self.sequence, &self.order).is_err() {
3406 self.sequence.ledger = prior_sequence_ledger;
3407 self.sequence.immutable_candidates.clear();
3408 self.order.ledger = prior_order_ledger;
3409 self.order.immutable_candidates.clear();
3410 rollback_ordinary_sequence_owners(&mut self.sequence, sequence_delta);
3411 rollback_ordinary_order_owners(&mut self.order);
3412 return Err(Box::new((
3413 self,
3414 OrdinaryProjectionError::SequenceRelocation,
3415 )));
3416 }
3417 self.retained_floor = floor.resulting_floor;
3418 self.retained_records = retained_records;
3419 self.marker_records
3420 .retain(|record| u128::from(record.delivery_seq) >= floor.resulting_floor);
3421
3422 Ok(ProjectedOrdinaryRecord {
3423 frontiers: self,
3424 floor,
3425 retained_charge,
3426 baseline,
3427 accounting,
3428 required_capacity,
3429 order,
3430 sequence,
3431 observer_floor,
3432 closure,
3433 caller_record,
3434 caller_charge,
3435 retained_charges,
3436 new_marker_candidates,
3437 })
3438 }
3439
3440 pub fn planned_settled_leave_admission_order<F>(
3452 &self,
3453 member: &LiveMember<F>,
3454 binding_state: BindingState,
3455 ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3456 let (participant_id, ended_binding_epoch) =
3457 validate_settled_leave_prestate(self, member, binding_state)?;
3458 let selection = select_leave_order(&self.order, participant_id, ended_binding_epoch, None)?;
3459 Ok(super::AdmissionOrder::new(
3460 selection.selected_major,
3461 CandidatePhase::MembershipExit,
3462 participant_id,
3463 ))
3464 }
3465
3466 pub fn prepare_settled_leave_authority<F>(
3479 mut self,
3480 member: &LiveMember<F>,
3481 binding_state: BindingState,
3482 ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3483 let (participant_id, ended_binding_epoch) =
3484 validate_settled_leave_prestate(&self, member, binding_state)?;
3485 let left_transaction_order =
3486 consume_leave_order_lane(&mut self.order, participant_id, ended_binding_epoch, None)?;
3487 Ok(PreparedLeaveAuthority::settled(
3488 self,
3489 member.conversation_id(),
3490 participant_id,
3491 ended_binding_epoch,
3492 left_transaction_order,
3493 ))
3494 }
3495
3496 pub fn planned_pending_leave_admission_order<F>(
3504 &self,
3505 member: &LiveMember<F>,
3506 pending: PendingFinalization,
3507 ) -> Result<super::AdmissionOrder, PrepareLeaveAuthorityError> {
3508 let (participant_id, expected_order) =
3509 validate_pending_leave_prestate(self, member, pending)?;
3510 let selection =
3511 select_leave_order(&self.order, participant_id, None, Some(expected_order))?;
3512 Ok(super::AdmissionOrder::new(
3513 selection.selected_major,
3514 CandidatePhase::MembershipExit,
3515 participant_id,
3516 ))
3517 }
3518
3519 pub fn prepare_pending_leave_authority<F>(
3533 mut self,
3534 member: &LiveMember<F>,
3535 pending: PendingFinalization,
3536 ) -> Result<PreparedLeaveAuthority, PrepareLeaveAuthorityError> {
3537 let (participant_id, expected_order) =
3538 validate_pending_leave_prestate(&self, member, pending)?;
3539 let left_transaction_order =
3540 consume_leave_order_lane(&mut self.order, participant_id, None, Some(expected_order))?;
3541 Ok(PreparedLeaveAuthority::pending(
3542 self,
3543 member.conversation_id(),
3544 participant_id,
3545 pending.binding_epoch(),
3546 expected_order,
3547 left_transaction_order,
3548 ))
3549 }
3550
3551 #[allow(
3560 clippy::too_many_lines,
3561 reason = "the atomic Leave relay keeps membership, both ledgers, products, recovery, and retained rows visibly in one checked transition"
3562 )]
3563 pub(super) fn finish_leave_claims(
3564 mut self,
3565 participant_id: ParticipantId,
3566 ended_binding_epoch: Option<BindingEpoch>,
3567 committed_terminal: Option<CommittedBindingTerminal>,
3568 left_delivery_seq: DeliverySeq,
3569 left_transaction_order: TransactionOrder,
3570 ) -> Result<Self, LeaveCommitError> {
3571 let prior_high = self.sequence.ledger.high_watermark();
3572 let first_appended = prior_high
3573 .checked_add(1)
3574 .ok_or(LeaveCommitError::SequenceAuthority)?;
3575 let expected_left = if committed_terminal.is_some() {
3576 first_appended
3577 .checked_add(1)
3578 .ok_or(LeaveCommitError::SequenceAuthority)?
3579 } else {
3580 first_appended
3581 };
3582 if left_delivery_seq != expected_left {
3583 return Err(LeaveCommitError::SequenceAuthority);
3584 }
3585
3586 match (
3587 committed_terminal,
3588 self.sequence.immutable_candidates.as_slice(),
3589 ) {
3590 (
3591 Some(terminal),
3592 [
3593 ImmutableSequenceCandidate::BindingTerminal {
3594 delivery_seq,
3595 admission_order,
3596 owner,
3597 },
3598 ],
3599 ) if *delivery_seq == first_appended
3600 && terminal.delivery_seq() == first_appended
3601 && *admission_order == terminal.admission_order()
3602 && owner.participant_index == participant_id
3603 && owner.binding_epoch == terminal.binding_epoch() => {}
3604 (None, []) => {}
3605 (Some(_) | None, _) => return Err(LeaveCommitError::SequenceAuthority),
3606 }
3607
3608 let Some(active_index) = self
3609 .active_identities
3610 .participants
3611 .iter()
3612 .position(|participant| participant.participant_index == participant_id)
3613 else {
3614 return Err(LeaveCommitError::ResultingFrontier);
3615 };
3616 self.active_identities.participants.remove(active_index);
3617 self.marker_records.retain(|record| {
3618 !matches!(
3619 record.kind,
3620 RetainedCausalRecordKind::CompactionMarker { participant_index, .. }
3621 if participant_index == participant_id
3622 )
3623 });
3624 if self
3627 .fenced_marker_issued
3628 .is_some_and(|(latched, _, _)| latched == participant_id)
3629 {
3630 self.fenced_marker_issued = None;
3631 }
3632 let resulting_live = usize_to_u64(self.active_identities.participants.len());
3633
3634 let mut exit_consumed = false;
3635 let mut terminal_consumed = ended_binding_epoch.is_none();
3636 let mut units = Vec::new();
3637 for claim in self.sequence.movable_claims.iter().copied() {
3638 match claim.owner {
3639 SequenceDirectOwner::MembershipExit {
3640 participant_index: owner,
3641 } if owner == participant_id => {
3642 if exit_consumed {
3643 return Err(LeaveCommitError::ResultingFrontier);
3644 }
3645 exit_consumed = true;
3646 }
3647 SequenceDirectOwner::BindingTerminal(owner)
3648 if owner.participant_index == participant_id
3649 && Some(owner.binding_epoch) == ended_binding_epoch =>
3650 {
3651 if terminal_consumed {
3652 return Err(LeaveCommitError::ResultingFrontier);
3653 }
3654 terminal_consumed = true;
3655 }
3656 SequenceDirectOwner::MembershipExit { .. }
3657 | SequenceDirectOwner::BindingTerminal(_) => {
3658 units.push(LeaveSequenceUnit::Direct(claim));
3659 }
3660 }
3661 }
3662 if !exit_consumed || !terminal_consumed {
3663 return Err(LeaveCommitError::ResultingFrontier);
3664 }
3665
3666 let recovery_owned = self
3667 .sequence
3668 .recovery
3669 .is_some_and(|block| block.participant_index == participant_id);
3670 for range in self.sequence.products.live_times_terminal.iter().copied() {
3671 if range.terminal.participant_index != participant_id && resulting_live != 0 {
3672 units.push(LeaveSequenceUnit::TerminalProduct(TerminalProductRange {
3673 start: range.start,
3674 length: resulting_live,
3675 terminal: range.terminal,
3676 }));
3677 }
3678 }
3679 if let Some(range) = self.sequence.products.live_times_replacement_terminal
3680 && !recovery_owned
3681 && resulting_live != 0
3682 {
3683 units.push(LeaveSequenceUnit::ReplacementProduct(
3684 ReplacementTerminalProductRange {
3685 start: range.start,
3686 length: resulting_live,
3687 participant_index: range.participant_index,
3688 marker_delivery_seq: range.marker_delivery_seq,
3689 prior_binding_epoch: range.prior_binding_epoch,
3690 },
3691 ));
3692 }
3693 let resulting_other = resulting_live.saturating_sub(1);
3694 if resulting_other != 0 {
3695 for range in self.sequence.products.other_live_times_exit.iter().copied() {
3696 if range.exit_participant != participant_id {
3697 units.push(LeaveSequenceUnit::ExitProduct(ExitProductRange {
3698 start: range.start,
3699 length: resulting_other,
3700 exit_participant: range.exit_participant,
3701 }));
3702 }
3703 }
3704 }
3705 if let Some(recovery) = self.sequence.recovery
3706 && !recovery_owned
3707 {
3708 units.push(LeaveSequenceUnit::Recovery(recovery));
3709 }
3710 units.sort_by_key(|unit| unit.original_start());
3711
3712 let mut cursor = left_delivery_seq.checked_add(1);
3713 let mut movable_claims = Vec::new();
3714 let mut terminal_products = Vec::new();
3715 let mut replacement_product = None;
3716 let mut exit_products = Vec::new();
3717 let mut recovery = None;
3718 for unit in units {
3719 match unit {
3720 LeaveSequenceUnit::Direct(mut claim) => {
3721 claim.delivery_seq = allocate_leave_sequence_range(&mut cursor, 1)?;
3722 movable_claims.push(claim);
3723 }
3724 LeaveSequenceUnit::TerminalProduct(mut range) => {
3725 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3726 terminal_products.push(range);
3727 }
3728 LeaveSequenceUnit::ReplacementProduct(mut range) => {
3729 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3730 replacement_product = Some(range);
3731 }
3732 LeaveSequenceUnit::ExitProduct(mut range) => {
3733 range.start = allocate_leave_sequence_range(&mut cursor, range.length)?;
3734 exit_products.push(range);
3735 }
3736 LeaveSequenceUnit::Recovery(mut block) => {
3737 let length = 2 + u64::from(block.terminal.is_some());
3738 let start = allocate_leave_sequence_range(&mut cursor, length)?;
3739 if let Some(mut terminal) = block.terminal {
3740 terminal.delivery_seq = start;
3741 block.terminal = Some(terminal);
3742 block.recovery_attach_seq = start
3743 .checked_add(1)
3744 .ok_or(LeaveCommitError::ResultingFrontier)?;
3745 } else {
3746 block.recovery_attach_seq = start;
3747 }
3748 block.replacement_terminal_seq = block
3749 .recovery_attach_seq
3750 .checked_add(1)
3751 .ok_or(LeaveCommitError::ResultingFrontier)?;
3752 recovery = Some(block);
3753 }
3754 }
3755 }
3756 movable_claims.sort_by_key(|claim| claim.delivery_seq);
3757 terminal_products.sort_by_key(|range| range.start);
3758 exit_products.sort_by_key(|range| range.start);
3759 let terminal_count = usize_to_u64(
3760 movable_claims
3761 .iter()
3762 .filter(|claim| matches!(claim.owner, SequenceDirectOwner::BindingTerminal(_)))
3763 .count(),
3764 ) + u64::from(recovery.is_some_and(|block| block.terminal.is_some()));
3765 let recovery_reserve = if recovery.is_some() {
3766 RecoverySequenceReserve::DetachedCredentialRecovery
3767 } else {
3768 RecoverySequenceReserve::None
3769 };
3770 let ledger = SequenceLedger::try_new(
3771 left_delivery_seq,
3772 SequenceClaims::new(resulting_live, terminal_count, 0, recovery_reserve),
3773 )
3774 .map_err(|_| LeaveCommitError::ResultingFrontier)?;
3775 self.sequence = SequenceClaimFrontier {
3776 ledger,
3777 movable_claims,
3778 immutable_candidates: Vec::new(),
3779 products: SequenceProductRanges {
3780 live_times_terminal: terminal_products,
3781 live_times_replacement_terminal: replacement_product,
3782 other_live_times_exit: exit_products,
3783 },
3784 recovery,
3785 };
3786
3787 if let Some(terminal) = committed_terminal {
3788 self.retained_records.push(RetainedCausalRecord {
3789 delivery_seq: terminal.delivery_seq(),
3790 admission_order: terminal.admission_order(),
3791 kind: RetainedCausalRecordKind::BindingTerminal(BindingTerminalOwner {
3792 participant_index: participant_id,
3793 binding_epoch: terminal.binding_epoch(),
3794 }),
3795 });
3796 }
3797 self.retained_records.push(RetainedCausalRecord {
3798 delivery_seq: left_delivery_seq,
3799 admission_order: super::AdmissionOrder::new(
3800 left_transaction_order,
3801 CandidatePhase::MembershipExit,
3802 participant_id,
3803 ),
3804 kind: RetainedCausalRecordKind::MembershipExit {
3805 participant_index: participant_id,
3806 },
3807 });
3808 self.retained_records
3809 .sort_by_key(|record| record.delivery_seq);
3810
3811 let order_claims = self.order.ledger.claims();
3812 if order_claims.membership_exits() != resulting_live
3813 || self.order.recovery.is_some() != self.sequence.recovery.is_some()
3814 || validate_cross_counter(&self.sequence, &self.order).is_err()
3815 {
3816 return Err(LeaveCommitError::ResultingFrontier);
3817 }
3818 Ok(self)
3819 }
3820
3821 fn marker_candidate(&self, delivery_seq: DeliverySeq) -> Option<ValidatedMarkerCandidate> {
3822 self.sequence
3823 .immutable_candidates
3824 .iter()
3825 .find_map(|candidate| match candidate {
3826 ImmutableSequenceCandidate::Marker(candidate)
3827 if candidate.delivery_seq == delivery_seq =>
3828 {
3829 Some(ValidatedMarkerCandidate {
3830 conversation_id: self.conversation_id,
3831 candidate: *candidate,
3832 seal: MarkerAuthoritySeal::Validated,
3833 })
3834 }
3835 _ => None,
3836 })
3837 }
3838
3839 pub(super) fn drain_next_marker_core(
3848 mut self,
3849 ) -> Result<MarkerDrainCore, MarkerDrainCoreError> {
3850 let Some(first) = self.sequence.immutable_candidates.first().copied() else {
3851 return Err(MarkerDrainCoreError::NoCandidate);
3852 };
3853 let ImmutableSequenceCandidate::Marker(marker) = first else {
3854 return Err(MarkerDrainCoreError::BindingTerminalFirst);
3855 };
3856 let expected_sequence = self
3857 .sequence
3858 .ledger
3859 .high_watermark()
3860 .checked_add(1)
3861 .ok_or(MarkerDrainCoreError::SequenceNotNext)?;
3862 if marker.delivery_seq != expected_sequence {
3863 return Err(MarkerDrainCoreError::SequenceNotNext);
3864 }
3865 if order_is_above_high(
3866 marker.admission_order.transaction_order(),
3867 self.order.ledger.high(),
3868 ) {
3869 return Err(MarkerDrainCoreError::CausalMajorNotAllocated);
3870 }
3871 let candidate = self
3872 .marker_candidate(marker.delivery_seq)
3873 .ok_or(MarkerDrainCoreError::NoCandidate)?;
3874
3875 let key = marker.admission_order;
3876 let Some(group_index) = self
3877 .order
3878 .immutable_candidates
3879 .iter()
3880 .position(|group| group.candidate_keys.contains(&key))
3881 else {
3882 return Err(MarkerDrainCoreError::MissingOrderCandidate);
3883 };
3884 let group = &mut self.order.immutable_candidates[group_index];
3885 let Ok(key_index) = group.candidate_keys.binary_search(&key) else {
3886 return Err(MarkerDrainCoreError::MissingOrderCandidate);
3887 };
3888 group.candidate_keys.remove(key_index);
3889 if group.candidate_keys.is_empty() {
3890 self.order.immutable_candidates.remove(group_index);
3891 }
3892 self.sequence.immutable_candidates.remove(0);
3893
3894 let claims = self.sequence.ledger.claims();
3895 let markers = claims
3896 .markers()
3897 .checked_sub(1)
3898 .ok_or(MarkerDrainCoreError::ResultingLedger)?;
3899 self.sequence.ledger = SequenceLedger::try_new(
3900 expected_sequence,
3901 super::SequenceClaims::new(
3902 claims.live_members(),
3903 claims.binding_terminals(),
3904 markers,
3905 claims.recovery(),
3906 ),
3907 )
3908 .map_err(|_| MarkerDrainCoreError::ResultingLedger)?;
3909
3910 let record = RetainedCausalRecord {
3911 delivery_seq: marker.delivery_seq,
3912 admission_order: marker.admission_order,
3913 kind: RetainedCausalRecordKind::CompactionMarker {
3914 participant_index: marker.admission_order.participant_index(),
3915 provenance: marker.provenance,
3916 },
3917 };
3918 self.marker_records.push(record);
3919 self.retained_records.push(record);
3920 Ok(MarkerDrainCore {
3921 candidate,
3922 record: ValidatedMarkerRecord {
3923 conversation_id: self.conversation_id,
3924 record,
3925 provenance: marker.provenance,
3926 target_binding: marker.target_binding,
3927 occurrence: MarkerRecordOccurrence::Undelivered,
3928 seal: MarkerAuthoritySeal::Validated,
3929 },
3930 frontiers: self,
3931 })
3932 }
3933
3934 pub(in crate::lifecycle) fn drain_first_binding_terminal(
3948 mut self,
3949 expected_owner: BindingTerminalOwner,
3950 expected_order: super::AdmissionOrder,
3951 ) -> Result<(Self, RetainedCausalRecord), Box<(Self, LiveFrontierTransitionError)>> {
3952 let (delivery_seq, admission_order, owner) =
3953 match self.validate_first_terminal_candidate(expected_owner, expected_order) {
3954 Ok(candidate) => candidate,
3955 Err(error) => return Err(Box::new((self, error))),
3956 };
3957 let claims = self.sequence.ledger.claims();
3958 let Some(sequence_ledger) =
3959 claims
3960 .binding_terminals()
3961 .checked_sub(1)
3962 .and_then(|binding_terminals| {
3963 SequenceLedger::try_new(
3964 delivery_seq,
3965 SequenceClaims::new(
3966 claims.live_members(),
3967 binding_terminals,
3968 claims.markers(),
3969 claims.recovery(),
3970 ),
3971 )
3972 .ok()
3973 })
3974 else {
3975 return Err(Box::new((
3976 self,
3977 LiveFrontierTransitionError::ResultingFrontier,
3978 )));
3979 };
3980 let order_ledger = self.order.ledger;
3981 let Ok(active) = ActiveIdentityRanks::try_new(
3982 self.active_identities.participants().to_vec(),
3983 sequence_ledger.high_watermark(),
3984 self.identity_slot_limit,
3985 ) else {
3986 return Err(Box::new((self, LiveFrontierTransitionError::Authority)));
3987 };
3988 let (sequence, order) =
3989 match rebuild_unreserved_frontiers(&active, sequence_ledger, order_ledger) {
3990 Ok(frontiers) => frontiers,
3991 Err(error) => return Err(Box::new((self, error))),
3992 };
3993 let record = RetainedCausalRecord {
3994 delivery_seq,
3995 admission_order,
3996 kind: RetainedCausalRecordKind::BindingTerminal(owner),
3997 };
3998 self.active_identities = active;
3999 self.sequence = sequence;
4000 self.order = order;
4001 self.retained_records.push(record);
4002 Ok((self, record))
4003 }
4004
4005 fn validate_first_terminal_candidate(
4010 &self,
4011 expected_owner: BindingTerminalOwner,
4012 expected_order: super::AdmissionOrder,
4013 ) -> Result<
4014 (DeliverySeq, super::AdmissionOrder, BindingTerminalOwner),
4015 LiveFrontierTransitionError,
4016 > {
4017 if self.sequence.recovery.is_some() || self.order.recovery.is_some() {
4018 return Err(LiveFrontierTransitionError::Precedence(
4019 PrecedenceCondition::FencedRecovery,
4020 ));
4021 }
4022 let [first] = self.sequence.immutable_candidates.as_slice() else {
4023 return Err(LiveFrontierTransitionError::Precedence(
4024 self.precedence_condition(),
4025 ));
4026 };
4027 let ImmutableSequenceCandidate::BindingTerminal {
4028 delivery_seq,
4029 admission_order,
4030 owner,
4031 } = *first
4032 else {
4033 return Err(LiveFrontierTransitionError::Precedence(
4034 self.precedence_condition(),
4035 ));
4036 };
4037 if owner != expected_owner || admission_order != expected_order {
4038 return Err(LiveFrontierTransitionError::Authority);
4039 }
4040 let Some(expected_sequence) = self.sequence.ledger.high_watermark().checked_add(1) else {
4041 return Err(LiveFrontierTransitionError::Exhausted);
4042 };
4043 if delivery_seq != expected_sequence
4044 || order_is_above_high(
4045 admission_order.transaction_order(),
4046 self.order.ledger.high(),
4047 )
4048 {
4049 return Err(LiveFrontierTransitionError::RecordPosition);
4050 }
4051 let [group] = self.order.immutable_candidates.as_slice() else {
4052 return Err(LiveFrontierTransitionError::RecordPosition);
4053 };
4054 if group.candidate_keys.as_slice() != [admission_order] {
4055 return Err(LiveFrontierTransitionError::RecordPosition);
4056 }
4057 let detached_matches = self
4058 .active_identities
4059 .participants()
4060 .iter()
4061 .any(|participant| {
4062 participant.participant_index == owner.participant_index
4063 && participant.binding == FrontierBinding::Detached(owner.binding_epoch)
4064 });
4065 if !detached_matches {
4066 return Err(LiveFrontierTransitionError::Authority);
4067 }
4068 Ok((delivery_seq, admission_order, owner))
4069 }
4070}
4071
4072fn apply_fenced_ledgers(
4073 sequence: SequenceLedger,
4074 order: OrderLedger,
4075 finalizes_pending: bool,
4076) -> Option<(SequenceLedger, OrderLedger)> {
4077 let sequence = if finalizes_pending {
4078 sequence.apply_fenced_recovery_finalizing_pending()
4079 } else {
4080 sequence.apply_fenced_recovery()
4081 }
4082 .ok()?;
4083 let order = if finalizes_pending {
4084 order.apply_fenced_recovery_finalizing_pending()
4085 } else {
4086 order.apply_fenced_recovery()
4087 }
4088 .ok()?;
4089 Some((sequence, order))
4090}
4091
4092fn rebuild_unreserved_frontiers(
4093 active: &ActiveIdentityRanks,
4094 sequence_ledger: SequenceLedger,
4095 order_ledger: OrderLedger,
4096) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4097 let terminal_owners: Vec<_> = active
4098 .participants()
4099 .iter()
4100 .filter_map(|participant| match participant.binding() {
4101 FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4102 participant_index: participant.participant_index(),
4103 binding_epoch,
4104 }),
4105 FrontierBinding::Detached(_) => None,
4106 })
4107 .collect();
4108 let live_count = active.len();
4109 let terminal_count =
4110 u64::try_from(terminal_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4111 let sequence_claims = sequence_ledger.claims();
4112 let order_claims = order_ledger.claims();
4113 if sequence_claims.live_members() != live_count
4114 || sequence_claims.binding_terminals() != terminal_count
4115 || sequence_claims.markers() != 0
4116 || sequence_claims.recovery() != RecoverySequenceReserve::None
4117 || order_claims.active_binding_terminals() != terminal_count
4118 || order_claims.membership_exits() != live_count
4119 || order_claims.recovery_operation()
4120 || order_claims.recovery_replacement_terminal()
4121 {
4122 return Err(LiveFrontierTransitionError::ResultingFrontier);
4123 }
4124
4125 let sequence = rebuild_unreserved_sequence(active, &terminal_owners, sequence_ledger)?;
4126 let order = rebuild_unreserved_order(active, &terminal_owners, order_ledger)?;
4127 validate_cross_counter(&sequence, &order)
4128 .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4129 Ok((sequence, order))
4130}
4131
4132fn rebuild_unreserved_sequence(
4133 active: &ActiveIdentityRanks,
4134 terminal_owners: &[BindingTerminalOwner],
4135 sequence_ledger: SequenceLedger,
4136) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4137 let live_count = active.len();
4138 let mut sequence_cursor = sequence_ledger
4139 .high_watermark()
4140 .checked_add(1)
4141 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4142 let mut movable_sequence = Vec::new();
4143 for terminal in terminal_owners {
4144 movable_sequence.push(MovableSequenceClaim {
4145 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4146 owner: SequenceDirectOwner::BindingTerminal(*terminal),
4147 });
4148 }
4149 for participant in active.participants() {
4150 movable_sequence.push(MovableSequenceClaim {
4151 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4152 owner: SequenceDirectOwner::MembershipExit {
4153 participant_index: participant.participant_index(),
4154 },
4155 });
4156 }
4157 let mut terminal_products = Vec::new();
4158 for terminal in terminal_owners {
4159 terminal_products.push(TerminalProductRange {
4160 start: take_live_sequence(&mut sequence_cursor, live_count)?,
4161 length: live_count,
4162 terminal: *terminal,
4163 });
4164 }
4165 let exit_product_length = live_count.saturating_sub(1);
4166 let mut exit_products = Vec::new();
4167 if live_count > 1 {
4168 for participant in active.participants() {
4169 exit_products.push(ExitProductRange {
4170 start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4171 length: exit_product_length,
4172 exit_participant: participant.participant_index(),
4173 });
4174 }
4175 }
4176 let sequence_end = u128::from(sequence_ledger.high_watermark())
4177 .checked_add(sequence_ledger.required_reserve())
4178 .and_then(|value| value.checked_add(1))
4179 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4180 if u128::from(sequence_cursor) != sequence_end {
4181 return Err(LiveFrontierTransitionError::ResultingFrontier);
4182 }
4183 Ok(SequenceClaimFrontier {
4184 ledger: sequence_ledger,
4185 movable_claims: movable_sequence,
4186 immutable_candidates: Vec::new(),
4187 products: SequenceProductRanges {
4188 live_times_terminal: terminal_products,
4189 live_times_replacement_terminal: None,
4190 other_live_times_exit: exit_products,
4191 },
4192 recovery: None,
4193 })
4194}
4195
4196fn rebuild_unreserved_order(
4197 active: &ActiveIdentityRanks,
4198 terminal_owners: &[BindingTerminalOwner],
4199 order_ledger: OrderLedger,
4200) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4201 let order_claims = order_ledger.claims();
4202 let order_start = order_frontier_start(order_ledger.high());
4203 let mut order_cursor =
4204 u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4205 let mut movable_order = Vec::new();
4206 for terminal in terminal_owners.iter().copied() {
4207 movable_order.push(MovableOrderClaim {
4208 transaction_order: take_live_order(&mut order_cursor)?,
4209 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4210 });
4211 }
4212 for participant in active.participants() {
4213 movable_order.push(MovableOrderClaim {
4214 transaction_order: take_live_order(&mut order_cursor)?,
4215 owner: OrderDirectOwner::MembershipExit {
4216 participant_index: participant.participant_index(),
4217 },
4218 });
4219 }
4220 if u128::from(order_cursor) != order_start + order_claims.total() {
4221 return Err(LiveFrontierTransitionError::ResultingFrontier);
4222 }
4223 Ok(OrderClaimFrontier {
4224 ledger: order_ledger,
4225 movable_claims: movable_order,
4226 immutable_candidates: Vec::new(),
4227 recovery: None,
4228 })
4229}
4230
4231fn rebuild_pending_terminal_frontiers(
4232 active: &ActiveIdentityRanks,
4233 pending_owner: BindingTerminalOwner,
4234 delivery_seq: DeliverySeq,
4235 admission_order: super::AdmissionOrder,
4236 sequence_ledger: SequenceLedger,
4237 order_ledger: OrderLedger,
4238) -> Result<(SequenceClaimFrontier, OrderClaimFrontier), LiveFrontierTransitionError> {
4239 if admission_order.candidate_phase() != CandidatePhase::BindingTerminal
4240 || admission_order.participant_index() != pending_owner.participant_index
4241 || admission_order.transaction_order()
4242 != match order_ledger.high() {
4243 OrderHigh::Allocated(high) => high,
4244 OrderHigh::Empty => return Err(LiveFrontierTransitionError::RecordPosition),
4245 }
4246 || sequence_ledger.high_watermark().checked_add(1) != Some(delivery_seq)
4247 {
4248 return Err(LiveFrontierTransitionError::RecordPosition);
4249 }
4250 let bound_owners: Vec<_> = active
4251 .participants()
4252 .iter()
4253 .filter_map(|participant| match participant.binding() {
4254 FrontierBinding::Bound(binding_epoch) => Some(BindingTerminalOwner {
4255 participant_index: participant.participant_index(),
4256 binding_epoch,
4257 }),
4258 FrontierBinding::Detached(_) => None,
4259 })
4260 .collect();
4261 let mut all_terminal_owners = bound_owners.clone();
4262 all_terminal_owners.push(pending_owner);
4263 all_terminal_owners.sort_unstable_by_key(|owner| owner.participant_index);
4264 let live_count = active.len();
4265 let bound_count =
4266 u64::try_from(bound_owners.len()).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4267 let terminal_count = u64::try_from(all_terminal_owners.len())
4268 .map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4269 let sequence_claims = sequence_ledger.claims();
4270 let order_claims = order_ledger.claims();
4271 if sequence_claims.live_members() != live_count
4272 || sequence_claims.binding_terminals() != terminal_count
4273 || sequence_claims.markers() != 0
4274 || sequence_claims.recovery() != RecoverySequenceReserve::None
4275 || order_claims.active_binding_terminals() != bound_count
4276 || order_claims.membership_exits() != live_count
4277 || order_claims.recovery_operation()
4278 || order_claims.recovery_replacement_terminal()
4279 {
4280 return Err(LiveFrontierTransitionError::ResultingFrontier);
4281 }
4282
4283 let sequence = rebuild_pending_terminal_sequence(
4284 active,
4285 pending_owner,
4286 delivery_seq,
4287 admission_order,
4288 sequence_ledger,
4289 &bound_owners,
4290 &all_terminal_owners,
4291 )?;
4292 let order =
4293 rebuild_pending_terminal_order(active, admission_order, order_ledger, bound_owners)?;
4294 validate_cross_counter(&sequence, &order)
4295 .map_err(|_| LiveFrontierTransitionError::ResultingFrontier)?;
4296 Ok((sequence, order))
4297}
4298
4299fn rebuild_pending_terminal_sequence(
4300 active: &ActiveIdentityRanks,
4301 pending_owner: BindingTerminalOwner,
4302 delivery_seq: DeliverySeq,
4303 admission_order: super::AdmissionOrder,
4304 sequence_ledger: SequenceLedger,
4305 bound_owners: &[BindingTerminalOwner],
4306 all_terminal_owners: &[BindingTerminalOwner],
4307) -> Result<SequenceClaimFrontier, LiveFrontierTransitionError> {
4308 let live_count = active.len();
4309 let mut sequence_cursor = delivery_seq;
4310 let candidate_sequence = take_live_sequence(&mut sequence_cursor, 1)?;
4311 let mut movable_sequence = Vec::new();
4312 for terminal in bound_owners {
4313 movable_sequence.push(MovableSequenceClaim {
4314 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4315 owner: SequenceDirectOwner::BindingTerminal(*terminal),
4316 });
4317 }
4318 for participant in active.participants() {
4319 movable_sequence.push(MovableSequenceClaim {
4320 delivery_seq: take_live_sequence(&mut sequence_cursor, 1)?,
4321 owner: SequenceDirectOwner::MembershipExit {
4322 participant_index: participant.participant_index(),
4323 },
4324 });
4325 }
4326 let mut terminal_products = Vec::new();
4327 for terminal in all_terminal_owners {
4328 terminal_products.push(TerminalProductRange {
4329 start: take_live_sequence(&mut sequence_cursor, live_count)?,
4330 length: live_count,
4331 terminal: *terminal,
4332 });
4333 }
4334 let exit_product_length = live_count.saturating_sub(1);
4335 let mut exit_products = Vec::new();
4336 if live_count > 1 {
4337 for participant in active.participants() {
4338 exit_products.push(ExitProductRange {
4339 start: take_live_sequence(&mut sequence_cursor, exit_product_length)?,
4340 length: exit_product_length,
4341 exit_participant: participant.participant_index(),
4342 });
4343 }
4344 }
4345 let sequence_end = u128::from(sequence_ledger.high_watermark())
4346 .checked_add(sequence_ledger.required_reserve())
4347 .and_then(|value| value.checked_add(1))
4348 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4349 if u128::from(sequence_cursor) != sequence_end {
4350 return Err(LiveFrontierTransitionError::ResultingFrontier);
4351 }
4352 Ok(SequenceClaimFrontier {
4353 ledger: sequence_ledger,
4354 movable_claims: movable_sequence,
4355 immutable_candidates: alloc::vec![ImmutableSequenceCandidate::BindingTerminal {
4356 delivery_seq: candidate_sequence,
4357 admission_order,
4358 owner: pending_owner,
4359 }],
4360 products: SequenceProductRanges {
4361 live_times_terminal: terminal_products,
4362 live_times_replacement_terminal: None,
4363 other_live_times_exit: exit_products,
4364 },
4365 recovery: None,
4366 })
4367}
4368
4369fn rebuild_pending_terminal_order(
4370 active: &ActiveIdentityRanks,
4371 admission_order: super::AdmissionOrder,
4372 order_ledger: OrderLedger,
4373 bound_owners: Vec<BindingTerminalOwner>,
4374) -> Result<OrderClaimFrontier, LiveFrontierTransitionError> {
4375 let order_claims = order_ledger.claims();
4376 let order_start = order_frontier_start(order_ledger.high());
4377 let mut order_cursor =
4378 u64::try_from(order_start).map_err(|_| LiveFrontierTransitionError::Exhausted)?;
4379 let mut movable_order = Vec::new();
4380 for terminal in bound_owners {
4381 movable_order.push(MovableOrderClaim {
4382 transaction_order: take_live_order(&mut order_cursor)?,
4383 owner: OrderDirectOwner::ActiveBindingTerminal(terminal),
4384 });
4385 }
4386 for participant in active.participants() {
4387 movable_order.push(MovableOrderClaim {
4388 transaction_order: take_live_order(&mut order_cursor)?,
4389 owner: OrderDirectOwner::MembershipExit {
4390 participant_index: participant.participant_index(),
4391 },
4392 });
4393 }
4394 if u128::from(order_cursor) != order_start + order_claims.total() {
4395 return Err(LiveFrontierTransitionError::ResultingFrontier);
4396 }
4397 Ok(OrderClaimFrontier {
4398 ledger: order_ledger,
4399 movable_claims: movable_order,
4400 immutable_candidates: alloc::vec![ImmutableOrderCandidateMajor {
4401 transaction_order: admission_order.transaction_order(),
4402 candidate_keys: alloc::vec![admission_order],
4403 }],
4404 recovery: None,
4405 })
4406}
4407
4408fn take_live_sequence(
4409 cursor: &mut DeliverySeq,
4410 length: u64,
4411) -> Result<DeliverySeq, LiveFrontierTransitionError> {
4412 let start = *cursor;
4413 *cursor = cursor
4414 .checked_add(length)
4415 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4416 Ok(start)
4417}
4418
4419fn take_live_order(
4420 cursor: &mut TransactionOrder,
4421) -> Result<TransactionOrder, LiveFrontierTransitionError> {
4422 let value = *cursor;
4423 *cursor = cursor
4424 .checked_add(1)
4425 .ok_or(LiveFrontierTransitionError::Exhausted)?;
4426 Ok(value)
4427}
4428
4429fn ordinary_unaccepted_marker_anchors(frontiers: &ClaimFrontiers) -> Vec<DeliverySeq> {
4430 frontiers
4431 .marker_records
4432 .iter()
4433 .filter_map(|record| {
4434 let RetainedCausalRecordKind::CompactionMarker {
4435 participant_index, ..
4436 } = record.kind
4437 else {
4438 return None;
4439 };
4440 active_participant(&frontiers.active_identities, participant_index)
4441 .is_some_and(|participant| participant.cursor < record.delivery_seq)
4442 .then_some(record.delivery_seq)
4443 })
4444 .collect()
4445}
4446
4447fn projection_failure(
4448 frontiers: ClaimFrontiers,
4449 input: OrdinaryRecordProjectionInput,
4450 error: OrdinaryProjectionError,
4451) -> Box<OrdinaryRecordProjectionFailure> {
4452 Box::new(OrdinaryRecordProjectionFailure {
4453 frontiers,
4454 input,
4455 error,
4456 })
4457}
4458
4459fn preflight_ordinary_sequence_owners(
4460 sequence: &SequenceClaimFrontier,
4461 delta: u64,
4462) -> Result<(), OrdinaryProjectionError> {
4463 if !sequence.immutable_candidates.is_empty() {
4464 return Err(OrdinaryProjectionError::SequenceRelocation);
4465 }
4466 for claim in &sequence.movable_claims {
4467 claim
4468 .delivery_seq
4469 .checked_add(delta)
4470 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4471 }
4472 for range in &sequence.products.live_times_terminal {
4473 range
4474 .start
4475 .checked_add(delta)
4476 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4477 }
4478 if let Some(range) = &sequence.products.live_times_replacement_terminal {
4479 range
4480 .start
4481 .checked_add(delta)
4482 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4483 }
4484 for range in &sequence.products.other_live_times_exit {
4485 range
4486 .start
4487 .checked_add(delta)
4488 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4489 }
4490 if let Some(recovery) = &sequence.recovery {
4491 if let Some(terminal) = &recovery.terminal {
4492 terminal
4493 .delivery_seq
4494 .checked_add(delta)
4495 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4496 }
4497 recovery
4498 .recovery_attach_seq
4499 .checked_add(delta)
4500 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4501 recovery
4502 .replacement_terminal_seq
4503 .checked_add(delta)
4504 .ok_or(OrdinaryProjectionError::SequenceRelocation)?;
4505 }
4506 Ok(())
4507}
4508
4509fn relay_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4510 for claim in &mut sequence.movable_claims {
4511 claim.delivery_seq = claim.delivery_seq.wrapping_add(delta);
4512 }
4513 for range in &mut sequence.products.live_times_terminal {
4514 range.start = range.start.wrapping_add(delta);
4515 }
4516 if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4517 range.start = range.start.wrapping_add(delta);
4518 }
4519 for range in &mut sequence.products.other_live_times_exit {
4520 range.start = range.start.wrapping_add(delta);
4521 }
4522 if let Some(recovery) = &mut sequence.recovery {
4523 if let Some(terminal) = &mut recovery.terminal {
4524 terminal.delivery_seq = terminal.delivery_seq.wrapping_add(delta);
4525 }
4526 recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_add(delta);
4527 recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_add(delta);
4528 }
4529}
4530
4531fn rollback_ordinary_sequence_owners(sequence: &mut SequenceClaimFrontier, delta: u64) {
4532 for claim in &mut sequence.movable_claims {
4533 claim.delivery_seq = claim.delivery_seq.wrapping_sub(delta);
4534 }
4535 for range in &mut sequence.products.live_times_terminal {
4536 range.start = range.start.wrapping_sub(delta);
4537 }
4538 if let Some(range) = &mut sequence.products.live_times_replacement_terminal {
4539 range.start = range.start.wrapping_sub(delta);
4540 }
4541 for range in &mut sequence.products.other_live_times_exit {
4542 range.start = range.start.wrapping_sub(delta);
4543 }
4544 if let Some(recovery) = &mut sequence.recovery {
4545 if let Some(terminal) = &mut recovery.terminal {
4546 terminal.delivery_seq = terminal.delivery_seq.wrapping_sub(delta);
4547 }
4548 recovery.recovery_attach_seq = recovery.recovery_attach_seq.wrapping_sub(delta);
4549 recovery.replacement_terminal_seq = recovery.replacement_terminal_seq.wrapping_sub(delta);
4550 }
4551}
4552
4553fn preflight_ordinary_order_owners(
4554 order: &OrderClaimFrontier,
4555) -> Result<(), OrdinaryProjectionError> {
4556 if !order.immutable_candidates.is_empty() {
4557 return Err(OrdinaryProjectionError::OrderRelocation);
4558 }
4559 for claim in &order.movable_claims {
4560 claim
4561 .transaction_order
4562 .checked_add(1)
4563 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4564 }
4565 if let Some(recovery) = &order.recovery {
4566 if let Some(active_binding) = &recovery.active_binding {
4567 active_binding
4568 .transaction_order
4569 .checked_add(1)
4570 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4571 }
4572 recovery
4573 .recovery_operation_order
4574 .checked_add(1)
4575 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4576 recovery
4577 .replacement_terminal_order
4578 .checked_add(1)
4579 .ok_or(OrdinaryProjectionError::OrderRelocation)?;
4580 }
4581 Ok(())
4582}
4583
4584fn relay_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4585 for claim in &mut order.movable_claims {
4586 claim.transaction_order = claim.transaction_order.wrapping_add(1);
4587 }
4588 if let Some(recovery) = &mut order.recovery {
4589 if let Some(active_binding) = &mut recovery.active_binding {
4590 active_binding.transaction_order = active_binding.transaction_order.wrapping_add(1);
4591 }
4592 recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_add(1);
4593 recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_add(1);
4594 }
4595}
4596
4597fn rollback_ordinary_order_owners(order: &mut OrderClaimFrontier) {
4598 for claim in &mut order.movable_claims {
4599 claim.transaction_order = claim.transaction_order.wrapping_sub(1);
4600 }
4601 if let Some(recovery) = &mut order.recovery {
4602 if let Some(active_binding) = &mut recovery.active_binding {
4603 active_binding.transaction_order = active_binding.transaction_order.wrapping_sub(1);
4604 }
4605 recovery.recovery_operation_order = recovery.recovery_operation_order.wrapping_sub(1);
4606 recovery.replacement_terminal_order = recovery.replacement_terminal_order.wrapping_sub(1);
4607 }
4608}
4609
4610fn validate_leave_identity<F>(
4628 frontiers: &ClaimFrontiers,
4629 member: &LiveMember<F>,
4630 settled_detached: bool,
4631) -> Result<(), PrepareLeaveAuthorityError> {
4632 if member.conversation_id() != frontiers.conversation_id {
4633 return Err(PrepareLeaveAuthorityError::Conversation);
4634 }
4635 let Some(participant) =
4636 active_participant(&frontiers.active_identities, member.participant_id())
4637 else {
4638 return Err(PrepareLeaveAuthorityError::Identity);
4639 };
4640 let cursor_matches = if settled_detached {
4641 participant.cursor >= member.cursor()
4642 } else {
4643 participant.cursor == member.cursor()
4644 };
4645 if !cursor_matches {
4646 return Err(PrepareLeaveAuthorityError::Identity);
4647 }
4648 Ok(())
4649}
4650
4651fn validate_settled_leave_prestate<F>(
4652 frontiers: &ClaimFrontiers,
4653 member: &LiveMember<F>,
4654 binding_state: BindingState,
4655) -> Result<(ParticipantId, Option<BindingEpoch>), PrepareLeaveAuthorityError> {
4656 let participant_id = member.participant_id();
4657 validate_leave_identity(
4658 frontiers,
4659 member,
4660 matches!(binding_state, BindingState::Detached),
4661 )?;
4662 if !frontiers.order.immutable_candidates.is_empty() {
4663 return Err(PrepareLeaveAuthorityError::ImmutablePrefix);
4664 }
4665 let ended_binding_epoch = match binding_state {
4666 BindingState::Detached => {
4667 let Some(participant) =
4668 active_participant(&frontiers.active_identities, participant_id)
4669 else {
4670 return Err(PrepareLeaveAuthorityError::Identity);
4671 };
4672 if !matches!(participant.binding, FrontierBinding::Detached(_)) {
4673 return Err(PrepareLeaveAuthorityError::Binding);
4674 }
4675 None
4676 }
4677 BindingState::Bound(binding)
4678 if binding.conversation_id == frontiers.conversation_id
4679 && binding.participant_id == participant_id =>
4680 {
4681 let Some(participant) =
4682 active_participant(&frontiers.active_identities, participant_id)
4683 else {
4684 return Err(PrepareLeaveAuthorityError::Identity);
4685 };
4686 if participant.binding != FrontierBinding::Bound(binding.binding_epoch) {
4687 return Err(PrepareLeaveAuthorityError::Binding);
4688 }
4689 Some(binding.binding_epoch)
4690 }
4691 BindingState::Bound(_) | BindingState::PendingFinalization(_) => {
4692 return Err(PrepareLeaveAuthorityError::Binding);
4693 }
4694 };
4695 Ok((participant_id, ended_binding_epoch))
4696}
4697
4698fn validate_pending_leave_prestate<F>(
4699 frontiers: &ClaimFrontiers,
4700 member: &LiveMember<F>,
4701 pending: PendingFinalization,
4702) -> Result<(ParticipantId, super::AdmissionOrder), PrepareLeaveAuthorityError> {
4703 let participant_id = member.participant_id();
4704 validate_leave_identity(frontiers, member, false)?;
4707 if pending.conversation_id() != frontiers.conversation_id
4708 || pending.participant_id() != participant_id
4709 {
4710 return Err(PrepareLeaveAuthorityError::Binding);
4711 }
4712 let Some(participant) = active_participant(&frontiers.active_identities, participant_id) else {
4713 return Err(PrepareLeaveAuthorityError::Identity);
4714 };
4715 if participant.binding != FrontierBinding::Detached(pending.binding_epoch()) {
4716 return Err(PrepareLeaveAuthorityError::Binding);
4717 }
4718 let expected_order = pending.admission_order();
4719 let exact_sequence_candidate = matches!(
4720 frontiers.sequence.immutable_candidates.as_slice(),
4721 [ImmutableSequenceCandidate::BindingTerminal {
4722 admission_order,
4723 owner,
4724 ..
4725 }] if *admission_order == expected_order
4726 && owner.participant_index == participant_id
4727 && owner.binding_epoch == pending.binding_epoch()
4728 );
4729 let exact_order_candidate = matches!(
4730 frontiers.order.immutable_candidates.as_slice(),
4731 [ImmutableOrderCandidateMajor {
4732 transaction_order,
4733 candidate_keys,
4734 }] if *transaction_order == expected_order.transaction_order()
4735 && candidate_keys.as_slice() == [expected_order]
4736 );
4737 if !exact_sequence_candidate || !exact_order_candidate {
4738 return Err(PrepareLeaveAuthorityError::PendingCandidate);
4739 }
4740 Ok((participant_id, expected_order))
4741}
4742
4743#[derive(Clone, Copy)]
4744enum LeaveRelayUnit {
4745 Direct(MovableOrderClaim),
4746 Recovery(RecoveryOrderBlock),
4747}
4748
4749impl LeaveRelayUnit {
4750 fn start(self) -> TransactionOrder {
4751 match self {
4752 Self::Direct(claim) => claim.transaction_order,
4753 Self::Recovery(block) => block_start_validated_order(block),
4754 }
4755 }
4756
4757 const fn len(self) -> u64 {
4758 match self {
4759 Self::Direct(_) => 1,
4760 Self::Recovery(block) => match block.active_binding {
4761 Some(_) => 3,
4762 None => 2,
4763 },
4764 }
4765 }
4766}
4767
4768struct LeaveOrderSelection {
4769 units: Vec<LeaveRelayUnit>,
4770 selected_major: TransactionOrder,
4771}
4772
4773fn select_leave_order(
4774 order: &OrderClaimFrontier,
4775 participant_id: ParticipantId,
4776 ended_binding_epoch: Option<BindingEpoch>,
4777 pending_order: Option<super::AdmissionOrder>,
4778) -> Result<LeaveOrderSelection, PrepareLeaveAuthorityError> {
4779 let Some(exit_index) = order.movable_claims.iter().position(|claim| {
4780 claim.owner
4781 == OrderDirectOwner::MembershipExit {
4782 participant_index: participant_id,
4783 }
4784 }) else {
4785 return Err(PrepareLeaveAuthorityError::MembershipExitClaim);
4786 };
4787 let exit_claim = order.movable_claims[exit_index];
4788 if pending_order
4789 .is_some_and(|pending| pending.transaction_order() >= exit_claim.transaction_order)
4790 {
4791 return Err(PrepareLeaveAuthorityError::PendingCandidate);
4792 }
4793 let active_index = matching_active_claim(order, participant_id, ended_binding_epoch)?;
4794 let mut units = Vec::new();
4795 for (index, claim) in order.movable_claims.iter().copied().enumerate() {
4796 if index != exit_index && Some(index) != active_index {
4797 units.push(LeaveRelayUnit::Direct(claim));
4798 }
4799 }
4800 if let Some(recovery) = order
4801 .recovery
4802 .filter(|recovery| recovery.participant_index != participant_id)
4803 {
4804 units.push(LeaveRelayUnit::Recovery(recovery));
4805 }
4806 units.sort_by_key(|unit| unit.start());
4807 let surviving_handles: u128 = units.iter().map(|unit| u128::from(unit.len())).sum();
4808 let exit_major = exit_claim.transaction_order;
4809 let later_handle_fits = u128::from(u64::MAX - exit_major) >= surviving_handles;
4810 let selected_major = if later_handle_fits {
4811 exit_major
4812 } else if pending_order.is_some() {
4813 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4814 } else {
4815 u64::try_from(order_frontier_start(order.ledger.high()))
4816 .map_err(|_| PrepareLeaveAuthorityError::OrderCapacity)?
4817 };
4818 Ok(LeaveOrderSelection {
4819 units,
4820 selected_major,
4821 })
4822}
4823
4824fn matching_active_claim(
4825 order: &OrderClaimFrontier,
4826 participant_id: ParticipantId,
4827 ended_binding_epoch: Option<BindingEpoch>,
4828) -> Result<Option<usize>, PrepareLeaveAuthorityError> {
4829 let Some(binding_epoch) = ended_binding_epoch else {
4830 return Ok(None);
4831 };
4832 let expected = OrderDirectOwner::ActiveBindingTerminal(BindingTerminalOwner {
4833 participant_index: participant_id,
4834 binding_epoch,
4835 });
4836 order
4837 .movable_claims
4838 .iter()
4839 .position(|claim| claim.owner == expected)
4840 .map(Some)
4841 .ok_or(PrepareLeaveAuthorityError::ActiveBindingClaim)
4842}
4843
4844fn relay_leave_order_units(
4845 units: Vec<LeaveRelayUnit>,
4846 selected_major: TransactionOrder,
4847) -> Result<(Vec<MovableOrderClaim>, Option<RecoveryOrderBlock>), PrepareLeaveAuthorityError> {
4848 let mut cursor = selected_major.checked_add(1);
4849 let mut movable_claims = Vec::new();
4850 let mut recovery = None;
4851 for unit in units {
4852 match unit {
4853 LeaveRelayUnit::Direct(mut claim) => {
4854 let Some(position) = cursor else {
4855 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4856 };
4857 claim.transaction_order = position;
4858 movable_claims.push(claim);
4859 cursor = position.checked_add(1);
4860 }
4861 LeaveRelayUnit::Recovery(block) => {
4862 let (relayed, next) = relay_recovery_block(block, cursor)?;
4863 recovery = Some(relayed);
4864 cursor = next;
4865 }
4866 }
4867 }
4868 movable_claims.sort_by_key(|claim| claim.transaction_order);
4869 Ok((movable_claims, recovery))
4870}
4871
4872const fn relay_recovery_block(
4873 block: RecoveryOrderBlock,
4874 cursor: Option<TransactionOrder>,
4875) -> Result<(RecoveryOrderBlock, Option<TransactionOrder>), PrepareLeaveAuthorityError> {
4876 let mut next = cursor;
4877 let active_binding = if let Some(mut active) = block.active_binding {
4878 let Some(position) = next else {
4879 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4880 };
4881 active.transaction_order = position;
4882 next = position.checked_add(1);
4883 Some(active)
4884 } else {
4885 None
4886 };
4887 let Some(recovery_operation_order) = next else {
4888 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4889 };
4890 let Some(replacement_terminal_order) = recovery_operation_order.checked_add(1) else {
4891 return Err(PrepareLeaveAuthorityError::OrderCapacity);
4892 };
4893 Ok((
4894 RecoveryOrderBlock {
4895 active_binding,
4896 recovery_operation_order,
4897 replacement_terminal_order,
4898 participant_index: block.participant_index,
4899 marker_delivery_seq: block.marker_delivery_seq,
4900 recovered_binding_epoch: block.recovered_binding_epoch,
4901 },
4902 replacement_terminal_order.checked_add(1),
4903 ))
4904}
4905
4906fn leave_resulting_order_ledger(
4907 selected_major: TransactionOrder,
4908 movable_claims: &[MovableOrderClaim],
4909 recovery: Option<RecoveryOrderBlock>,
4910) -> Result<OrderLedger, PrepareLeaveAuthorityError> {
4911 let active_binding_terminals =
4912 usize_to_u64(
4913 movable_claims
4914 .iter()
4915 .filter(|claim| matches!(claim.owner, OrderDirectOwner::ActiveBindingTerminal(_)))
4916 .count(),
4917 ) + u64::from(recovery.is_some_and(|block| block.active_binding.is_some()));
4918 let membership_exits = usize_to_u64(
4919 movable_claims
4920 .iter()
4921 .filter(|claim| matches!(claim.owner, OrderDirectOwner::MembershipExit { .. }))
4922 .count(),
4923 );
4924 let has_recovery = recovery.is_some();
4925 let resulting_claims = OrderClaims::new(
4926 active_binding_terminals,
4927 membership_exits,
4928 has_recovery,
4929 has_recovery,
4930 )
4931 .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)?;
4932 OrderLedger::try_new(OrderHigh::Allocated(selected_major), resulting_claims)
4933 .map_err(|_| PrepareLeaveAuthorityError::ResultingOrderLedger)
4934}
4935
4936fn consume_leave_order_lane(
4937 order: &mut OrderClaimFrontier,
4938 participant_id: ParticipantId,
4939 ended_binding_epoch: Option<BindingEpoch>,
4940 pending_order: Option<super::AdmissionOrder>,
4941) -> Result<TransactionOrder, PrepareLeaveAuthorityError> {
4942 let selection = select_leave_order(order, participant_id, ended_binding_epoch, pending_order)?;
4943 let (movable_claims, recovery) =
4944 relay_leave_order_units(selection.units, selection.selected_major)?;
4945 let ledger = leave_resulting_order_ledger(selection.selected_major, &movable_claims, recovery)?;
4946 *order = OrderClaimFrontier {
4947 ledger,
4948 movable_claims,
4949 immutable_candidates: Vec::new(),
4950 recovery,
4951 };
4952 Ok(selection.selected_major)
4953}
4954
4955impl ClaimFrontiersPrevalidated {
4956 #[must_use]
4958 pub(super) const fn conversation_id(&self) -> ConversationId {
4959 self.conversation_id
4960 }
4961
4962 pub(super) fn take_marker_record(
4968 &mut self,
4969 request: MarkerRecordRequest,
4970 ) -> Option<ValidatedMarkerRecord> {
4971 if self.issued_marker_record.is_some() {
4972 return None;
4973 }
4974 let record = self
4975 .retained_records
4976 .iter()
4977 .find(|record| record.delivery_seq == request.marker_delivery_seq)
4978 .copied()?;
4979 let RetainedCausalRecordKind::CompactionMarker {
4980 participant_index,
4981 provenance,
4982 } = record.kind
4983 else {
4984 return None;
4985 };
4986 if participant_index != request.participant_index {
4987 return None;
4988 }
4989 let participant = active_participant(&self.active_identities, participant_index)?;
4990 let historical_delivery = self
4991 .historical_marker_deliveries
4992 .iter()
4993 .find(|authority| authority.marker_delivery_seq == request.marker_delivery_seq);
4994 let (target_binding, occurrence) = match request.use_kind {
4995 MarkerRecordUse::Planned(target) => {
4996 if participant.binding != target || historical_delivery.is_some() {
4997 return None;
4998 }
4999 (target, MarkerRecordOccurrence::Undelivered)
5000 }
5001 MarkerRecordUse::Delivered(target) => {
5002 let delivered_binding_epoch = binding_epoch(target);
5003 if participant.binding != target
5004 || !historical_delivery.is_some_and(|authority| {
5005 authority.participant_index == participant_index
5006 && authority.delivered_binding_epoch == delivered_binding_epoch
5007 })
5008 {
5009 return None;
5010 }
5011 (target, MarkerRecordOccurrence::Delivered)
5012 }
5013 MarkerRecordUse::Recovered {
5014 prior_binding_epoch,
5015 recovered_binding_epoch,
5016 } => {
5017 if participant.binding != FrontierBinding::Detached(recovered_binding_epoch)
5018 || !historical_delivery.is_some_and(|authority| {
5019 authority.participant_index == participant_index
5020 && authority.delivered_binding_epoch == prior_binding_epoch
5021 })
5022 {
5023 return None;
5024 }
5025 (
5026 FrontierBinding::Detached(prior_binding_epoch),
5027 MarkerRecordOccurrence::Delivered,
5028 )
5029 }
5030 };
5031 self.issued_marker_record = Some(request);
5032 Some(ValidatedMarkerRecord {
5033 conversation_id: self.conversation_id,
5034 record,
5035 provenance,
5036 target_binding,
5037 occurrence,
5038 seal: MarkerAuthoritySeal::Validated,
5039 })
5040 }
5041
5042 pub(super) fn finish(
5045 self,
5046 edge: Option<super::StoredEdge>,
5047 ) -> Result<ClaimFrontiers, ParticipantStateCorruptReason> {
5048 self.validate_current_marker_edge(edge)?;
5049 self.validate_historical_delivery_consumers(edge)?;
5050 let recovery_provenance = self.resolve_recovery_provenance(edge)?;
5051 let sequence = restore_sequence_frontier(
5052 &self.active_identities,
5053 self.sequence_restore,
5054 self.retained_floor,
5055 self.sequence_ledger,
5056 recovery_provenance,
5057 &self.retained_records,
5058 &self.historical_causal_authorities,
5059 )
5060 .map_err(corrupt_frontier)?;
5061 let order = restore_order_frontier(
5062 &self.active_identities,
5063 self.order_restore,
5064 self.order_ledger,
5065 recovery_provenance,
5066 )
5067 .map_err(corrupt_frontier)?;
5068 validate_cross_counter(&sequence, &order).map_err(corrupt_frontier)?;
5069 Ok(ClaimFrontiers {
5070 ordinary_cursor_release: None,
5071 conversation_id: self.conversation_id,
5072 active_identities: self.active_identities,
5073 identity_slot_limit: self.identity_slot_limit,
5074 retained_floor: self.retained_floor,
5075 retained_records: self.retained_records,
5076 marker_records: self.marker_records,
5077 fenced_marker_issued: self.fenced_marker_issued,
5078 sequence,
5079 order,
5080 historical_marker_deliveries: self.historical_marker_deliveries,
5081 historical_causal_authorities: self.historical_causal_authorities,
5082 })
5083 }
5084
5085 fn validate_historical_delivery_consumers(
5086 &self,
5087 edge: Option<super::StoredEdge>,
5088 ) -> Result<(), ParticipantStateCorruptReason> {
5089 for history in &self.historical_marker_deliveries {
5090 let recovered_origin = self.binding_origins.iter().any(|origin| {
5091 origin.participant_id() == history.participant_index
5092 && origin.recovered_marker()
5093 == Some((history.marker_delivery_seq, history.delivered_binding_epoch))
5094 });
5095 let current_marker_edge = match edge {
5096 Some(super::StoredEdge::ParticipantCursorProgress(progress)) => {
5097 progress.participant_id() == history.participant_index
5098 && progress.marker_delivery_seq() == Some(history.marker_delivery_seq)
5099 && progress.binding_epoch() == history.delivered_binding_epoch
5100 }
5101 Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) => {
5102 recovery.participant_id() == history.participant_index
5103 && recovery.marker_delivery_seq() == history.marker_delivery_seq
5104 && recovery.prior_binding_epoch() == history.delivered_binding_epoch
5105 }
5106 Some(
5107 super::StoredEdge::ObserverProjection(_)
5108 | super::StoredEdge::PhysicalCompaction(_)
5109 | super::StoredEdge::MarkerDelivery(_)
5110 | super::StoredEdge::DetachedMarkerRelease(_)
5111 | super::StoredEdge::DetachedCursorRelease(_),
5112 )
5113 | None => false,
5114 };
5115 if !recovered_origin && !current_marker_edge {
5116 return Err(self.marker_corruption(history.marker_delivery_seq));
5117 }
5118 }
5119 Ok(())
5120 }
5121
5122 fn validate_current_marker_edge(
5123 &self,
5124 edge: Option<super::StoredEdge>,
5125 ) -> Result<(), ParticipantStateCorruptReason> {
5126 if let Some(MarkerRecordRequest {
5127 participant_index,
5128 marker_delivery_seq,
5129 use_kind:
5130 MarkerRecordUse::Recovered {
5131 recovered_binding_epoch,
5132 ..
5133 },
5134 }) = self.issued_marker_record
5135 {
5136 let recovered_edge_matches = matches!(
5137 edge,
5138 Some(super::StoredEdge::DetachedCursorRelease(release))
5139 if release.participant_id() == participant_index
5140 && release.last_dead_binding_epoch() == recovered_binding_epoch
5141 );
5142 if !recovered_edge_matches {
5143 return Err(self.marker_corruption(marker_delivery_seq));
5144 }
5145 return Ok(());
5146 }
5147 let Some(context) = edge.and_then(marker_edge_context) else {
5148 return Ok(());
5149 };
5150 if self
5151 .issued_marker_record
5152 .is_none_or(|request| request.marker_delivery_seq != context.marker_delivery_seq)
5153 || !self.marker_context_matches(context)
5154 {
5155 return Err(self.marker_corruption(context.marker_delivery_seq));
5156 }
5157 Ok(())
5158 }
5159
5160 fn resolve_recovery_provenance(
5161 &self,
5162 edge: Option<super::StoredEdge>,
5163 ) -> Result<Option<RecoveryClaimProvenance>, ParticipantStateCorruptReason> {
5164 let Some(marker_delivery_seq) = self.recovery_marker_delivery_seq else {
5165 return Ok(None);
5166 };
5167
5168 if let Some(super::StoredEdge::DetachedCredentialRecovery(recovery)) = edge {
5169 return self
5170 .resolve_postfate_recovery_provenance(marker_delivery_seq, recovery)
5171 .map(Some);
5172 }
5173
5174 let candidate =
5175 self.sequence_restore.immutable_candidates.iter().find_map(
5176 |candidate| match candidate {
5177 ImmutableSequenceCandidate::Marker(marker)
5178 if marker.delivery_seq == marker_delivery_seq
5179 && matches!(marker.target_binding, FrontierBinding::Bound(_)) =>
5180 {
5181 Some(*marker)
5182 }
5183 _ => None,
5184 },
5185 );
5186 let retained = self.retained_records.iter().find_map(|record| {
5187 let RetainedCausalRecordKind::CompactionMarker {
5188 participant_index, ..
5189 } = record.kind
5190 else {
5191 return None;
5192 };
5193 if record.delivery_seq != marker_delivery_seq {
5194 return None;
5195 }
5196 let participant = active_participant(&self.active_identities, participant_index)?;
5197 let FrontierBinding::Bound(binding_epoch) = participant.binding else {
5198 return None;
5199 };
5200 let historical = self.historical_marker_deliveries.iter().find(|history| {
5201 history.participant_index == participant_index
5202 && history.marker_delivery_seq == marker_delivery_seq
5203 })?;
5204 Some((
5205 participant_index,
5206 binding_epoch,
5207 historical.delivered_binding_epoch,
5208 ))
5209 });
5210 let provenance = match (candidate, retained) {
5211 (Some(marker), None) => RecoveryClaimProvenance {
5212 participant_index: marker.admission_order.participant_index(),
5213 marker_delivery_seq,
5214 prior_binding_epoch: binding_epoch(marker.target_binding),
5215 current_binding_epoch: binding_epoch(marker.target_binding),
5216 phase: RecoveryClaimPhase::PreFate,
5217 },
5218 (None, Some((participant_index, current_binding_epoch, prior_binding_epoch))) => {
5219 let phase = if current_binding_epoch == prior_binding_epoch {
5220 let Some(context) = edge.and_then(marker_edge_context) else {
5221 return Err(self.marker_corruption(marker_delivery_seq));
5222 };
5223 if context.marker_delivery_seq != marker_delivery_seq
5224 || context.participant_index != participant_index
5225 || context.binding_epoch != prior_binding_epoch
5226 || context.target_binding != FrontierBinding::Bound(prior_binding_epoch)
5227 {
5228 return Err(self.marker_corruption(marker_delivery_seq));
5229 }
5230 RecoveryClaimPhase::PreFate
5231 } else {
5232 let recovered_origin_matches = self.binding_origins.iter().any(|origin| {
5233 origin.participant_id() == participant_index
5234 && origin.binding_epoch() == current_binding_epoch
5235 && origin.recovered_marker()
5236 == Some((marker_delivery_seq, prior_binding_epoch))
5237 });
5238 if !recovered_origin_matches
5239 || !matches!(
5240 edge,
5241 Some(
5242 super::StoredEdge::ObserverProjection(_)
5243 | super::StoredEdge::PhysicalCompaction(_)
5244 )
5245 )
5246 {
5247 return Err(self.marker_corruption(marker_delivery_seq));
5248 }
5249 RecoveryClaimPhase::RecoveredBound
5250 };
5251 RecoveryClaimProvenance {
5252 participant_index,
5253 marker_delivery_seq,
5254 prior_binding_epoch,
5255 current_binding_epoch,
5256 phase,
5257 }
5258 }
5259 (Some(_), Some(_)) | (None, None) => {
5260 return Err(self.marker_corruption(marker_delivery_seq));
5261 }
5262 };
5263 Ok(Some(provenance))
5264 }
5265
5266 fn resolve_postfate_recovery_provenance(
5267 &self,
5268 marker_delivery_seq: DeliverySeq,
5269 recovery: super::DetachedCredentialRecovery,
5270 ) -> Result<RecoveryClaimProvenance, ParticipantStateCorruptReason> {
5271 let provenance = RecoveryClaimProvenance {
5272 participant_index: recovery.participant_id(),
5273 marker_delivery_seq: recovery.marker_delivery_seq(),
5274 prior_binding_epoch: recovery.prior_binding_epoch(),
5275 current_binding_epoch: recovery.prior_binding_epoch(),
5276 phase: RecoveryClaimPhase::PostFate,
5277 };
5278 let context = MarkerEdgeContext {
5279 participant_index: provenance.participant_index,
5280 marker_delivery_seq: provenance.marker_delivery_seq,
5281 binding_epoch: provenance.prior_binding_epoch,
5282 target_binding: FrontierBinding::Detached(provenance.prior_binding_epoch),
5283 };
5284 if marker_delivery_seq != provenance.marker_delivery_seq
5285 || self
5286 .issued_marker_record
5287 .is_none_or(|request| request.marker_delivery_seq != marker_delivery_seq)
5288 || !self.marker_context_matches(context)
5289 {
5290 return Err(self.marker_corruption(marker_delivery_seq));
5291 }
5292 Ok(provenance)
5293 }
5294
5295 fn marker_context_matches(&self, context: MarkerEdgeContext) -> bool {
5296 self.marker_records.iter().any(|record| {
5297 matches!(
5298 record.kind,
5299 RetainedCausalRecordKind::CompactionMarker {
5300 participant_index,
5301 ..
5302 } if participant_index == context.participant_index
5303 ) && record.delivery_seq == context.marker_delivery_seq
5304 && active_participant(&self.active_identities, context.participant_index)
5305 .is_some_and(|participant| participant.binding == context.target_binding)
5306 })
5307 }
5308
5309 fn marker_corruption(&self, delivery_seq: DeliverySeq) -> ParticipantStateCorruptReason {
5310 corrupt_frontier(sequence_error(
5311 sequence_ordinal(self.sequence_ledger, delivery_seq),
5312 ClaimFrontierInvalidReason::RecoveryBlock,
5313 ))
5314 }
5315}
5316
5317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5318struct MarkerEdgeContext {
5319 participant_index: ParticipantId,
5320 marker_delivery_seq: DeliverySeq,
5321 binding_epoch: BindingEpoch,
5322 target_binding: FrontierBinding,
5323}
5324
5325fn marker_edge_context(edge: super::StoredEdge) -> Option<MarkerEdgeContext> {
5326 match edge {
5327 super::StoredEdge::MarkerDelivery(delivery) => Some(MarkerEdgeContext {
5328 participant_index: delivery.participant_id(),
5329 marker_delivery_seq: delivery.marker_delivery_seq(),
5330 binding_epoch: delivery.binding_epoch(),
5331 target_binding: FrontierBinding::Bound(delivery.binding_epoch()),
5332 }),
5333 super::StoredEdge::ParticipantCursorProgress(progress) => {
5334 let marker_delivery_seq = progress.marker_delivery_seq()?;
5335 Some(MarkerEdgeContext {
5336 participant_index: progress.participant_id(),
5337 marker_delivery_seq,
5338 binding_epoch: progress.binding_epoch(),
5339 target_binding: FrontierBinding::Bound(progress.binding_epoch()),
5340 })
5341 }
5342 super::StoredEdge::DetachedCredentialRecovery(recovery) => Some(MarkerEdgeContext {
5343 participant_index: recovery.participant_id(),
5344 marker_delivery_seq: recovery.marker_delivery_seq(),
5345 binding_epoch: recovery.prior_binding_epoch(),
5346 target_binding: FrontierBinding::Detached(recovery.prior_binding_epoch()),
5347 }),
5348 super::StoredEdge::DetachedMarkerRelease(release) => Some(MarkerEdgeContext {
5349 participant_index: release.participant_id(),
5350 marker_delivery_seq: release.marker_delivery_seq(),
5351 binding_epoch: release.last_dead_binding_epoch(),
5352 target_binding: FrontierBinding::Detached(release.last_dead_binding_epoch()),
5353 }),
5354 super::StoredEdge::ObserverProjection(_)
5355 | super::StoredEdge::PhysicalCompaction(_)
5356 | super::StoredEdge::DetachedCursorRelease(_) => None,
5357 }
5358}
5359
5360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5361#[repr(u8)]
5362enum SequenceClass {
5363 Exit = 0,
5364 Terminal = 1,
5365 Marker = 2,
5366 RecoveryAttach = 3,
5367 RecoveryReplacementTerminal = 4,
5368 LiveTimesTerminal = 5,
5369 LiveTimesReplacementTerminal = 6,
5370 OtherLiveTimesExit = 7,
5371}
5372
5373#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5374#[repr(u8)]
5375enum OrderClass {
5376 ActiveBindingTerminal = 0,
5377 MembershipExit = 1,
5378 RecoveryOperation = 2,
5379 RecoveryReplacementTerminal = 3,
5380}
5381
5382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5383struct NumericSegment<C> {
5384 start: u128,
5385 length: u128,
5386 class: Option<C>,
5387 immutable: bool,
5388}
5389
5390fn first_duplicate_candidate_key(
5391 candidates: &[ImmutableSequenceCandidate],
5392 retained_records: &[RetainedCausalRecord],
5393) -> Option<super::AdmissionOrder> {
5394 let mut keys: Vec<_> = candidates
5395 .iter()
5396 .map(|candidate| candidate.admission_order())
5397 .chain(retained_records.iter().map(|record| record.admission_order))
5398 .collect();
5399 keys.sort_unstable();
5400 let mut previous = None;
5401 for key in keys {
5402 if previous == Some(key) {
5403 return Some(key);
5404 }
5405 previous = Some(key);
5406 }
5407 None
5408}
5409
5410fn validate_unique_candidate_keys(
5411 candidates: &[ImmutableSequenceCandidate],
5412 retained_records: &[RetainedCausalRecord],
5413) -> Result<(), ParticipantStateCorruptReason> {
5414 let Some(order) = first_duplicate_candidate_key(candidates, retained_records) else {
5415 return Ok(());
5416 };
5417 Err(ParticipantStateCorruptReason::DuplicateCandidateKey {
5418 transaction_order: order.transaction_order(),
5419 candidate_phase: order.candidate_phase(),
5420 participant_index: order.participant_index(),
5421 })
5422}
5423
5424fn validate_sequence_numeric(
5425 restore: &SequenceClaimFrontierRestore,
5426 ledger: SequenceLedger,
5427) -> Result<(), ClaimFrontierError> {
5428 let mut segments = sequence_segments(restore);
5429 validate_numeric_segments(
5430 ClaimFrontierCounter::DeliverySequence,
5431 u128::from(ledger.high_watermark()) + 1,
5432 ledger.required_reserve(),
5433 &mut segments,
5434 &sequence_expected_counts(ledger),
5435 )
5436}
5437
5438fn validate_order_numeric(
5439 restore: &OrderClaimFrontierRestore,
5440 ledger: OrderLedger,
5441) -> Result<(), ClaimFrontierError> {
5442 let mut segments = order_segments(restore, ledger.high());
5443 validate_numeric_segments(
5444 ClaimFrontierCounter::TransactionOrder,
5445 order_frontier_start(ledger.high()),
5446 order_frontier_candidate_count(restore, ledger.high()) + ledger.claims().total(),
5447 &mut segments,
5448 &order_expected_counts(ledger),
5449 )
5450}
5451
5452fn validate_bounded_shape(
5453 restore: &ClaimFrontiersRestore,
5454 sequence_ledger: SequenceLedger,
5455) -> Result<(), ClaimFrontierError> {
5456 let identity_limit = u128::from(restore.identity_slot_limit);
5457 let twice_identity_limit = identity_limit.saturating_mul(2);
5458 let order_candidate_keys = restore
5459 .order
5460 .immutable_candidates
5461 .iter()
5462 .fold(0_u128, |count, candidate| {
5463 count.saturating_add(usize_to_u128(candidate.candidate_keys.len()))
5464 });
5465 let bounded = usize_to_u128(restore.active_identities.len()) <= identity_limit
5466 && usize_to_u128(restore.sequence.movable_claims.len()) <= twice_identity_limit
5467 && usize_to_u128(restore.sequence.immutable_candidates.len()) <= twice_identity_limit
5468 && usize_to_u128(restore.sequence.products.live_times_terminal.len()) <= identity_limit
5469 && usize_to_u128(restore.sequence.products.other_live_times_exit.len()) <= identity_limit
5470 && usize_to_u128(restore.historical_marker_deliveries.len())
5471 <= u128::from(restore.retained_record_limit)
5472 && usize_to_u128(restore.historical_causal_facts.len()) <= twice_identity_limit
5473 && usize_to_u128(restore.order.movable_claims.len()) <= twice_identity_limit
5474 && usize_to_u128(restore.order.immutable_candidates.len()) <= twice_identity_limit
5475 && order_candidate_keys <= twice_identity_limit;
5476 if bounded {
5477 Ok(())
5478 } else {
5479 Err(sequence_error(
5480 sequence_ledger.required_reserve(),
5481 ClaimFrontierInvalidReason::LogicalOwner,
5482 ))
5483 }
5484}
5485
5486fn validated_retained_records(
5487 mut records: Vec<RetainedCausalRecord>,
5488 retained_floor: u128,
5489 retained_record_limit: u64,
5490 identity_slot_limit: u64,
5491 ledger: SequenceLedger,
5492) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5493 let high_end = u128::from(ledger.high_watermark()) + 1;
5494 let expected_count = high_end.saturating_sub(retained_floor);
5495 if retained_floor > high_end
5496 || usize_to_u128(records.len()) > u128::from(retained_record_limit)
5497 || usize_to_u128(records.len()) != expected_count
5498 {
5499 return Err(sequence_error(
5500 usize_to_u128(records.len()).min(expected_count),
5501 ClaimFrontierInvalidReason::LogicalOwner,
5502 ));
5503 }
5504 records.sort_by_key(|record| record.delivery_seq);
5505 let mut previous_admission_order = None;
5506 for (record_index, record) in records.iter().enumerate() {
5507 let (participant_index, valid_kind) = match record.kind {
5508 RetainedCausalRecordKind::BindingTerminal(owner) => (
5509 owner.participant_index,
5510 record.admission_order.candidate_phase() == CandidatePhase::BindingTerminal
5511 && record.admission_order.participant_index() == owner.participant_index,
5512 ),
5513 RetainedCausalRecordKind::MembershipExit { participant_index } => (
5514 participant_index,
5515 record.admission_order.candidate_phase() == CandidatePhase::MembershipExit
5516 && record.admission_order.participant_index() == participant_index,
5517 ),
5518 RetainedCausalRecordKind::AttachLifecycle {
5519 participant_index, ..
5520 } => (
5521 participant_index,
5522 record.admission_order.candidate_phase() == CandidatePhase::AttachLifecycle
5523 && record.admission_order.participant_index() == participant_index,
5524 ),
5525 RetainedCausalRecordKind::OrdinaryRecord { participant_index } => (
5526 participant_index,
5527 record.admission_order.candidate_phase() == CandidatePhase::OrdinaryRecord
5528 && record.admission_order.participant_index() == participant_index,
5529 ),
5530 RetainedCausalRecordKind::CompactionMarker {
5531 participant_index,
5532 provenance,
5533 } => (
5534 participant_index,
5535 record.admission_order.candidate_phase() == CandidatePhase::CompactionMarker
5536 && record.admission_order.participant_index() == participant_index
5537 && marker_provenance_targets(provenance, participant_index),
5538 ),
5539 };
5540 let expected_sequence = retained_floor + rank_index(record_index);
5541 if participant_index >= identity_slot_limit
5542 || !valid_kind
5543 || previous_admission_order.is_some_and(|previous| previous >= record.admission_order)
5544 || u128::from(record.delivery_seq) != expected_sequence
5545 {
5546 return Err(sequence_error(
5547 rank_index(record_index),
5548 ClaimFrontierInvalidReason::CandidateKey,
5549 ));
5550 }
5551 previous_admission_order = Some(record.admission_order);
5552 }
5553 Ok(records)
5554}
5555
5556fn validated_active_marker_records(
5557 retained_markers: &[RetainedCausalRecord],
5558 mut active_marker_anchors: Vec<DeliverySeq>,
5559 identity_slot_limit: u64,
5560 ledger: SequenceLedger,
5561) -> Result<Vec<RetainedCausalRecord>, ClaimFrontierError> {
5562 if usize_to_u128(active_marker_anchors.len()) > u128::from(identity_slot_limit) {
5563 return Err(sequence_error(
5564 ledger.required_reserve(),
5565 ClaimFrontierInvalidReason::LogicalOwner,
5566 ));
5567 }
5568 active_marker_anchors.sort_unstable();
5569 let mut previous_sequence = None;
5570 let mut owners = Vec::new();
5571 let mut active_records = Vec::new();
5572 for delivery_seq in active_marker_anchors {
5573 let Some(record) = retained_markers
5574 .iter()
5575 .find(|record| record.delivery_seq == delivery_seq)
5576 .copied()
5577 else {
5578 return Err(sequence_error(
5579 sequence_ordinal(ledger, delivery_seq),
5580 ClaimFrontierInvalidReason::LogicalOwner,
5581 ));
5582 };
5583 let RetainedCausalRecordKind::CompactionMarker {
5584 participant_index, ..
5585 } = record.kind
5586 else {
5587 return Err(sequence_error(
5588 sequence_ordinal(ledger, delivery_seq),
5589 ClaimFrontierInvalidReason::LogicalOwner,
5590 ));
5591 };
5592 if previous_sequence == Some(delivery_seq) || owners.contains(&participant_index) {
5593 return Err(sequence_error(
5594 sequence_ordinal(ledger, delivery_seq),
5595 ClaimFrontierInvalidReason::LogicalOwner,
5596 ));
5597 }
5598 previous_sequence = Some(delivery_seq);
5599 owners.push(participant_index);
5600 active_records.push(record);
5601 }
5602 Ok(active_records)
5603}
5604
5605fn validated_historical_marker_deliveries(
5606 mut facts: Vec<HistoricalMarkerDeliveryFactRestore>,
5607 conversation_id: ConversationId,
5608 active: &ActiveIdentityRanks,
5609 sources: &MarkerDeliverySources<'_>,
5610 retained_record_limit: u64,
5611 ledger: SequenceLedger,
5612) -> Result<Vec<HistoricalMarkerDeliveryAuthority>, ClaimFrontierError> {
5613 let MarkerDeliverySources {
5614 retained: retained_records,
5615 historical: historical_causal_authorities,
5616 candidates: immutable_candidates,
5617 } = *sources;
5618 if usize_to_u128(facts.len()) > u128::from(retained_record_limit) {
5619 return Err(sequence_error(
5620 ledger.required_reserve(),
5621 ClaimFrontierInvalidReason::LogicalOwner,
5622 ));
5623 }
5624 facts.sort_by_key(|fact| fact.marker_delivery_seq);
5625 let mut previous_sequence = None;
5626 let mut authorities = Vec::new();
5627 for fact in facts {
5628 let matching_record = retained_records.iter().find(|record| {
5629 record.delivery_seq == fact.marker_delivery_seq
5630 && matches!(
5631 record.kind,
5632 RetainedCausalRecordKind::CompactionMarker {
5633 participant_index,
5634 ..
5635 } if participant_index == fact.participant_index
5636 )
5637 });
5638 let current_bound =
5639 active_participant(active, fact.participant_index).is_some_and(|participant| {
5640 participant.binding == FrontierBinding::Bound(fact.delivered_binding_epoch)
5641 });
5642 let terminal_order = historical_causal_authorities
5643 .iter()
5644 .find_map(|authority| match authority.kind {
5645 HistoricalCausalKind::BindingTerminal(owner)
5646 if binding_terminal_matches_delivery(owner, fact) =>
5647 {
5648 Some(authority.admission_order)
5649 }
5650 HistoricalCausalKind::BindingTerminal(_)
5651 | HistoricalCausalKind::MembershipExit(_) => None,
5652 })
5653 .or_else(|| {
5654 retained_records
5655 .iter()
5656 .find_map(|record| match record.kind {
5657 RetainedCausalRecordKind::BindingTerminal(owner)
5658 if binding_terminal_matches_delivery(owner, fact) =>
5659 {
5660 Some(record.admission_order)
5661 }
5662 RetainedCausalRecordKind::BindingTerminal(_)
5663 | RetainedCausalRecordKind::MembershipExit { .. }
5664 | RetainedCausalRecordKind::AttachLifecycle { .. }
5665 | RetainedCausalRecordKind::OrdinaryRecord { .. }
5666 | RetainedCausalRecordKind::CompactionMarker { .. } => None,
5667 })
5668 })
5669 .or_else(|| {
5670 immutable_candidates
5671 .iter()
5672 .find_map(|candidate| match candidate {
5673 ImmutableSequenceCandidate::BindingTerminal {
5674 admission_order,
5675 owner,
5676 ..
5677 } if binding_terminal_matches_delivery(*owner, fact) => {
5678 Some(*admission_order)
5679 }
5680 ImmutableSequenceCandidate::BindingTerminal { .. }
5681 | ImmutableSequenceCandidate::Marker(_) => None,
5682 })
5683 });
5684 let historical_epoch_is_backed = matching_record.is_some_and(|marker_record| {
5685 current_bound
5686 || terminal_order
5687 .is_some_and(|terminal_order| terminal_order > marker_record.admission_order)
5688 });
5689 if fact.conversation_id != conversation_id
5690 || previous_sequence == Some(fact.marker_delivery_seq)
5691 || !historical_epoch_is_backed
5692 {
5693 return Err(sequence_error(
5694 sequence_ordinal(ledger, fact.marker_delivery_seq),
5695 ClaimFrontierInvalidReason::LogicalOwner,
5696 ));
5697 }
5698 previous_sequence = Some(fact.marker_delivery_seq);
5699 authorities.push(HistoricalMarkerDeliveryAuthority {
5700 participant_index: fact.participant_index,
5701 marker_delivery_seq: fact.marker_delivery_seq,
5702 delivered_binding_epoch: fact.delivered_binding_epoch,
5703 });
5704 }
5705 Ok(authorities)
5706}
5707
5708fn binding_terminal_matches_delivery(
5709 owner: BindingTerminalOwner,
5710 fact: HistoricalMarkerDeliveryFactRestore,
5711) -> bool {
5712 (owner.participant_index, owner.binding_epoch)
5713 == (fact.participant_index, fact.delivered_binding_epoch)
5714}
5715
5716struct MarkerDeliverySources<'a> {
5717 retained: &'a [RetainedCausalRecord],
5718 historical: &'a [HistoricalCausalAuthority],
5719 candidates: &'a [ImmutableSequenceCandidate],
5720}
5721
5722struct BindingOriginValidation<'a> {
5723 conversation_id: ConversationId,
5724 active: &'a ActiveIdentityRanks,
5725 origins: &'a [BindingOrigin],
5726 retained_records: &'a [RetainedCausalRecord],
5727 causal_authorities: &'a [HistoricalCausalAuthority],
5728 historical_marker_deliveries: &'a [HistoricalMarkerDeliveryAuthority],
5729 total: bool,
5730 ledger: SequenceLedger,
5731}
5732
5733impl BindingOriginValidation<'_> {
5734 fn validate(&self) -> Result<(), ClaimFrontierError> {
5735 if !self.total {
5736 return if self.origins.is_empty() {
5737 Ok(())
5738 } else {
5739 Err(self.logical_owner_error())
5740 };
5741 }
5742 if self.origins.len() != self.active.participants.len() {
5743 return Err(self.logical_owner_error());
5744 }
5745 for participant in &self.active.participants {
5746 let mut matching = self
5747 .origins
5748 .iter()
5749 .filter(|origin| origin.participant_id() == participant.participant_index);
5750 let Some(origin) = matching.next() else {
5751 return Err(self.logical_owner_error());
5752 };
5753 if matching.next().is_some() {
5754 return Err(self.logical_owner_error());
5755 }
5756 self.validate_origin(*participant, *origin)?;
5757 }
5758 Ok(())
5759 }
5760
5761 fn validate_origin(
5762 &self,
5763 participant: FrontierParticipant,
5764 origin: BindingOrigin,
5765 ) -> Result<(), ClaimFrontierError> {
5766 let current_epoch = binding_epoch(participant.binding);
5767 let attached = origin.attached();
5768 if origin.conversation_id() != self.conversation_id
5769 || origin.binding_epoch() != current_epoch
5770 || attached.conversation_id() != self.conversation_id
5771 || attached.participant_id() != participant.participant_index
5772 || attached.binding_epoch() != current_epoch
5773 || attached.admission_order().candidate_phase() != CandidatePhase::AttachLifecycle
5774 {
5775 return Err(self.logical_owner_error());
5776 }
5777 let mut retained_attach_for_binding = self.retained_records.iter().filter(|record| {
5778 matches!(
5779 record.kind,
5780 RetainedCausalRecordKind::AttachLifecycle {
5781 participant_index,
5782 binding_epoch,
5783 } if participant_index == participant.participant_index
5784 && binding_epoch == current_epoch
5785 )
5786 });
5787 let retained_attach_matches = retained_attach_for_binding.clone().any(|record| {
5788 record.delivery_seq == attached.delivery_seq()
5789 && record.admission_order == attached.admission_order()
5790 });
5791 if retained_attach_for_binding.next().is_some() && !retained_attach_matches {
5792 return Err(self.logical_owner_error());
5793 }
5794 if let Some((marker_delivery_seq, prior_binding_epoch)) = origin.recovered_marker() {
5795 let generation_is_next = prior_binding_epoch
5796 .capability_generation
5797 .get()
5798 .checked_add(1)
5799 == Some(current_epoch.capability_generation.get());
5800 let marker_history_matches = self.historical_marker_deliveries.iter().any(|history| {
5801 history.participant_index == participant.participant_index
5802 && history.marker_delivery_seq == marker_delivery_seq
5803 && history.delivered_binding_epoch == prior_binding_epoch
5804 });
5805 if !generation_is_next || !marker_history_matches {
5806 return Err(sequence_error(
5807 sequence_ordinal(self.ledger, marker_delivery_seq),
5808 ClaimFrontierInvalidReason::RecoveryBlock,
5809 ));
5810 }
5811 } else if matches!(participant.binding, FrontierBinding::Detached(_))
5812 && !binding_terminal_exists(
5813 participant.participant_index,
5814 current_epoch,
5815 self.retained_records,
5816 self.causal_authorities,
5817 )
5818 {
5819 return Err(self.logical_owner_error());
5820 }
5821 Ok(())
5822 }
5823
5824 const fn logical_owner_error(&self) -> ClaimFrontierError {
5825 sequence_error(
5826 self.ledger.required_reserve(),
5827 ClaimFrontierInvalidReason::LogicalOwner,
5828 )
5829 }
5830}
5831
5832fn binding_terminal_exists(
5833 participant_index: ParticipantId,
5834 binding_epoch: BindingEpoch,
5835 retained_records: &[RetainedCausalRecord],
5836 historical_causal_authorities: &[HistoricalCausalAuthority],
5837) -> bool {
5838 retained_records.iter().any(|record| {
5839 matches!(
5840 record.kind,
5841 RetainedCausalRecordKind::BindingTerminal(owner)
5842 if owner.participant_index == participant_index
5843 && owner.binding_epoch == binding_epoch
5844 )
5845 }) || historical_causal_authorities.iter().any(|authority| {
5846 matches!(
5847 authority.kind,
5848 HistoricalCausalKind::BindingTerminal(owner)
5849 if owner.participant_index == participant_index
5850 && owner.binding_epoch == binding_epoch
5851 )
5852 })
5853}
5854
5855fn validate_marker_credit_owners(
5856 candidates: &[ImmutableSequenceCandidate],
5857 marker_records: &[RetainedCausalRecord],
5858 identity_slot_limit: u64,
5859 ledger: SequenceLedger,
5860) -> Result<(), ClaimFrontierError> {
5861 let mut owners = Vec::new();
5862 for record in marker_records {
5863 let RetainedCausalRecordKind::CompactionMarker {
5864 participant_index, ..
5865 } = record.kind
5866 else {
5867 continue;
5868 };
5869 if owners.contains(&participant_index) {
5870 return Err(sequence_error(
5871 ledger.required_reserve(),
5872 ClaimFrontierInvalidReason::LogicalOwner,
5873 ));
5874 }
5875 owners.push(participant_index);
5876 }
5877 for candidate in candidates {
5878 let ImmutableSequenceCandidate::Marker(marker) = candidate else {
5879 continue;
5880 };
5881 let participant_index = marker.admission_order.participant_index();
5882 if owners.contains(&participant_index) {
5883 return Err(sequence_error(
5884 sequence_ordinal(ledger, marker.delivery_seq),
5885 ClaimFrontierInvalidReason::LogicalOwner,
5886 ));
5887 }
5888 owners.push(participant_index);
5889 }
5890 if usize_to_u128(owners.len()) > u128::from(identity_slot_limit) {
5891 return Err(sequence_error(
5892 ledger.required_reserve(),
5893 ClaimFrontierInvalidReason::LogicalOwner,
5894 ));
5895 }
5896 Ok(())
5897}
5898
5899fn validated_historical_authorities(
5900 facts: Vec<HistoricalCausalFactRestore>,
5901 conversation_id: ConversationId,
5902 identity_slot_limit: u64,
5903 ledger: SequenceLedger,
5904 history: &ValidatedConversationHistory,
5905) -> Result<Vec<HistoricalCausalAuthority>, ClaimFrontierError> {
5906 if usize_to_u128(facts.len()) > u128::from(identity_slot_limit).saturating_mul(2) {
5907 return Err(sequence_error(
5908 ledger.required_reserve(),
5909 ClaimFrontierInvalidReason::LogicalOwner,
5910 ));
5911 }
5912 let authorities: Vec<_> = facts
5913 .into_iter()
5914 .map(HistoricalCausalAuthority::from_restore)
5915 .collect();
5916 let mut seen = Vec::new();
5917 for authority in &authorities {
5918 let (participant_index, phase) = match authority.kind {
5919 HistoricalCausalKind::BindingTerminal(owner) => {
5920 (owner.participant_index, CandidatePhase::BindingTerminal)
5921 }
5922 HistoricalCausalKind::MembershipExit(participant_index) => {
5923 (participant_index, CandidatePhase::MembershipExit)
5924 }
5925 };
5926 if authority.conversation_id != conversation_id
5927 || participant_index >= identity_slot_limit
5928 || authority.admission_order.participant_index() != participant_index
5929 || authority.admission_order.candidate_phase() != phase
5930 || seen.contains(authority)
5931 || !history.causal_authorities.contains(authority)
5932 {
5933 return Err(sequence_error(
5934 ledger.required_reserve(),
5935 ClaimFrontierInvalidReason::LogicalOwner,
5936 ));
5937 }
5938 seen.push(*authority);
5939 }
5940 Ok(authorities)
5941}
5942
5943const fn corrupt_frontier(error: ClaimFrontierError) -> ParticipantStateCorruptReason {
5944 ParticipantStateCorruptReason::ClaimFrontierInvalid {
5945 counter: match error.counter {
5946 ClaimFrontierCounter::DeliverySequence => ClaimCounter::DeliverySeq,
5947 ClaimFrontierCounter::TransactionOrder => ClaimCounter::TransactionOrder,
5948 },
5949 first_bad_position: error.first_bad_position,
5950 }
5951}
5952
5953fn sequence_segments(restore: &SequenceClaimFrontierRestore) -> Vec<NumericSegment<SequenceClass>> {
5954 let mut segments = Vec::new();
5955 for claim in &restore.movable_claims {
5956 segments.push(NumericSegment {
5957 start: u128::from(claim.delivery_seq),
5958 length: 1,
5959 class: Some(match claim.owner {
5960 SequenceDirectOwner::MembershipExit { .. } => SequenceClass::Exit,
5961 SequenceDirectOwner::BindingTerminal(_) => SequenceClass::Terminal,
5962 }),
5963 immutable: false,
5964 });
5965 }
5966 for candidate in &restore.immutable_candidates {
5967 segments.push(NumericSegment {
5968 start: u128::from(candidate.delivery_seq()),
5969 length: 1,
5970 class: Some(sequence_candidate_class(*candidate)),
5971 immutable: true,
5972 });
5973 }
5974 for range in &restore.products.live_times_terminal {
5975 segments.push(NumericSegment {
5976 start: u128::from(range.start),
5977 length: u128::from(range.length),
5978 class: Some(SequenceClass::LiveTimesTerminal),
5979 immutable: false,
5980 });
5981 }
5982 if let Some(range) = restore.products.live_times_replacement_terminal {
5983 segments.push(NumericSegment {
5984 start: u128::from(range.start),
5985 length: u128::from(range.length),
5986 class: Some(SequenceClass::LiveTimesReplacementTerminal),
5987 immutable: false,
5988 });
5989 }
5990 for range in &restore.products.other_live_times_exit {
5991 segments.push(NumericSegment {
5992 start: u128::from(range.start),
5993 length: u128::from(range.length),
5994 class: Some(SequenceClass::OtherLiveTimesExit),
5995 immutable: false,
5996 });
5997 }
5998 if let Some(recovery) = restore.recovery {
5999 if let Some(terminal) = recovery.terminal {
6000 segments.push(NumericSegment {
6001 start: u128::from(terminal.delivery_seq),
6002 length: 1,
6003 class: Some(SequenceClass::Terminal),
6004 immutable: false,
6005 });
6006 }
6007 segments.push(NumericSegment {
6008 start: u128::from(recovery.recovery_attach_seq),
6009 length: 1,
6010 class: Some(SequenceClass::RecoveryAttach),
6011 immutable: false,
6012 });
6013 segments.push(NumericSegment {
6014 start: u128::from(recovery.replacement_terminal_seq),
6015 length: 1,
6016 class: Some(SequenceClass::RecoveryReplacementTerminal),
6017 immutable: false,
6018 });
6019 }
6020 segments
6021}
6022
6023fn order_segments(
6024 restore: &OrderClaimFrontierRestore,
6025 high: OrderHigh,
6026) -> Vec<NumericSegment<OrderClass>> {
6027 let mut segments = Vec::new();
6028 for claim in &restore.movable_claims {
6029 segments.push(NumericSegment {
6030 start: u128::from(claim.transaction_order),
6031 length: 1,
6032 class: Some(match claim.owner {
6033 OrderDirectOwner::ActiveBindingTerminal(_) => OrderClass::ActiveBindingTerminal,
6034 OrderDirectOwner::MembershipExit { .. } => OrderClass::MembershipExit,
6035 }),
6036 immutable: false,
6037 });
6038 }
6039 for candidate in restore
6040 .immutable_candidates
6041 .iter()
6042 .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
6043 {
6044 segments.push(NumericSegment {
6045 start: u128::from(candidate.transaction_order),
6046 length: 1,
6047 class: None,
6048 immutable: true,
6049 });
6050 }
6051 if let Some(recovery) = restore.recovery {
6052 if let Some(active_binding) = recovery.active_binding {
6053 segments.push(NumericSegment {
6054 start: u128::from(active_binding.transaction_order),
6055 length: 1,
6056 class: Some(OrderClass::ActiveBindingTerminal),
6057 immutable: false,
6058 });
6059 }
6060 segments.push(NumericSegment {
6061 start: u128::from(recovery.recovery_operation_order),
6062 length: 1,
6063 class: Some(OrderClass::RecoveryOperation),
6064 immutable: false,
6065 });
6066 segments.push(NumericSegment {
6067 start: u128::from(recovery.replacement_terminal_order),
6068 length: 1,
6069 class: Some(OrderClass::RecoveryReplacementTerminal),
6070 immutable: false,
6071 });
6072 }
6073 segments
6074}
6075
6076fn restore_sequence_frontier(
6077 active: &ActiveIdentityRanks,
6078 restore: SequenceClaimFrontierRestore,
6079 retained_floor: u128,
6080 ledger: SequenceLedger,
6081 recovery_provenance: Option<RecoveryClaimProvenance>,
6082 retained_records: &[RetainedCausalRecord],
6083 historical_causal_authorities: &[HistoricalCausalAuthority],
6084) -> Result<SequenceClaimFrontier, ClaimFrontierError> {
6085 let mut segments = sequence_segments(&restore);
6086
6087 let expected_counts = sequence_expected_counts(ledger);
6088 validate_numeric_segments(
6089 ClaimFrontierCounter::DeliverySequence,
6090 u128::from(ledger.high_watermark()) + 1,
6091 ledger.required_reserve(),
6092 &mut segments,
6093 &expected_counts,
6094 )?;
6095 validate_sequence_recovery(
6096 active,
6097 restore.recovery,
6098 recovery_provenance,
6099 ledger,
6100 ledger.required_reserve(),
6101 )?;
6102 validate_sequence_candidates(
6103 active,
6104 &restore.immutable_candidates,
6105 retained_floor,
6106 retained_records,
6107 historical_causal_authorities,
6108 ledger,
6109 )?;
6110 let terminal_owners = validate_sequence_direct_owners(
6111 active,
6112 &restore.movable_claims,
6113 &restore.immutable_candidates,
6114 restore.recovery,
6115 ledger,
6116 )?;
6117 let products = validate_sequence_products(
6118 active,
6119 restore.products,
6120 &terminal_owners,
6121 restore.recovery,
6122 recovery_provenance,
6123 ledger,
6124 )?;
6125 let recovery = restore
6126 .recovery
6127 .zip(recovery_provenance)
6128 .map(|(value, provenance)| RecoverySequenceBlock {
6129 terminal: value.terminal,
6130 recovery_attach_seq: value.recovery_attach_seq,
6131 replacement_terminal_seq: value.replacement_terminal_seq,
6132 participant_index: provenance.participant_index,
6133 marker_delivery_seq: provenance.marker_delivery_seq,
6134 recovered_binding_epoch: provenance.prior_binding_epoch,
6135 });
6136
6137 let mut movable_claims = restore.movable_claims;
6138 movable_claims.sort_by_key(|claim| claim.delivery_seq);
6139 let mut immutable_candidates = restore.immutable_candidates;
6140 immutable_candidates.sort_by_key(|candidate| candidate.delivery_seq());
6141
6142 Ok(SequenceClaimFrontier {
6143 ledger,
6144 movable_claims,
6145 immutable_candidates,
6146 products,
6147 recovery,
6148 })
6149}
6150
6151fn restore_order_frontier(
6152 active: &ActiveIdentityRanks,
6153 restore: OrderClaimFrontierRestore,
6154 ledger: OrderLedger,
6155 recovery_provenance: Option<RecoveryClaimProvenance>,
6156) -> Result<OrderClaimFrontier, ClaimFrontierError> {
6157 let mut segments = order_segments(&restore, ledger.high());
6158
6159 let candidate_count = order_frontier_candidate_count(&restore, ledger.high());
6160 let expected_length = candidate_count + ledger.claims().total();
6161 let expected_counts = order_expected_counts(ledger);
6162 validate_numeric_segments(
6163 ClaimFrontierCounter::TransactionOrder,
6164 order_frontier_start(ledger.high()),
6165 expected_length,
6166 &mut segments,
6167 &expected_counts,
6168 )?;
6169 validate_order_recovery(
6170 active,
6171 restore.recovery,
6172 recovery_provenance,
6173 ledger,
6174 expected_length,
6175 )?;
6176 let immutable_candidates = validate_order_candidates(&restore.immutable_candidates, ledger)?;
6177 validate_order_direct_owners(active, &restore.movable_claims, restore.recovery, ledger)?;
6178 let recovery = restore
6179 .recovery
6180 .zip(recovery_provenance)
6181 .map(|(value, provenance)| RecoveryOrderBlock {
6182 active_binding: value.active_binding,
6183 recovery_operation_order: value.recovery_operation_order,
6184 replacement_terminal_order: value.replacement_terminal_order,
6185 participant_index: provenance.participant_index,
6186 marker_delivery_seq: provenance.marker_delivery_seq,
6187 recovered_binding_epoch: provenance.prior_binding_epoch,
6188 });
6189
6190 let mut movable_claims = restore.movable_claims;
6191 movable_claims.sort_by_key(|claim| claim.transaction_order);
6192
6193 Ok(OrderClaimFrontier {
6194 ledger,
6195 movable_claims,
6196 immutable_candidates,
6197 recovery,
6198 })
6199}
6200
6201fn describe_sequence_frontier(frontier: &SequenceClaimFrontier) -> SequenceClaimFrontierRestore {
6208 SequenceClaimFrontierRestore {
6209 movable_claims: frontier.movable_claims.clone(),
6210 immutable_candidates: frontier.immutable_candidates.clone(),
6211 products: SequenceProductRangesRestore {
6212 live_times_terminal: frontier
6213 .products
6214 .live_times_terminal
6215 .iter()
6216 .map(|range| TerminalProductRangeRestore {
6217 start: range.start,
6218 length: range.length,
6219 terminal: range.terminal,
6220 })
6221 .collect(),
6222 live_times_replacement_terminal: frontier.products.live_times_replacement_terminal.map(
6223 |range| ReplacementTerminalProductRangeRestore {
6224 start: range.start,
6225 length: range.length,
6226 },
6227 ),
6228 other_live_times_exit: frontier
6229 .products
6230 .other_live_times_exit
6231 .iter()
6232 .map(|range| ExitProductRangeRestore {
6233 start: range.start,
6234 length: range.length,
6235 exit_participant: range.exit_participant,
6236 })
6237 .collect(),
6238 },
6239 recovery: frontier.recovery.map(|block| RecoverySequenceBlockRestore {
6240 terminal: block.terminal,
6241 recovery_attach_seq: block.recovery_attach_seq,
6242 replacement_terminal_seq: block.replacement_terminal_seq,
6243 }),
6244 }
6245}
6246
6247fn describe_order_frontier(frontier: &OrderClaimFrontier) -> OrderClaimFrontierRestore {
6254 OrderClaimFrontierRestore {
6255 movable_claims: frontier.movable_claims.clone(),
6256 immutable_candidates: frontier
6257 .immutable_candidates
6258 .iter()
6259 .map(|major| ImmutableOrderCandidateMajorRestore {
6260 transaction_order: major.transaction_order,
6261 candidate_keys: major.candidate_keys.clone(),
6262 })
6263 .collect(),
6264 recovery: frontier.recovery.map(|block| RecoveryOrderBlockRestore {
6265 active_binding: block.active_binding,
6266 recovery_operation_order: block.recovery_operation_order,
6267 replacement_terminal_order: block.replacement_terminal_order,
6268 }),
6269 }
6270}
6271
6272fn validate_numeric_segments<C: Copy + Into<usize>>(
6273 counter: ClaimFrontierCounter,
6274 first_value: u128,
6275 expected_length: u128,
6276 segments: &mut [NumericSegment<C>],
6277 expected_counts: &[u128],
6278) -> Result<(), ClaimFrontierError> {
6279 segments.sort_by_key(|segment| segment.start);
6280 let mut events = numeric_events(counter, first_value, segments)?;
6281 let emitted = scan_numeric_events(counter, first_value, &mut events)?;
6282 if emitted != expected_length {
6283 return Err(frontier_error(
6284 counter,
6285 emitted.min(expected_length),
6286 ClaimFrontierInvalidReason::AggregateLedger,
6287 ));
6288 }
6289 validate_immutable_prefix(counter, first_value, segments)?;
6290 validate_segment_class_counts(counter, expected_length, segments, expected_counts)
6291}
6292
6293fn numeric_events<C>(
6294 counter: ClaimFrontierCounter,
6295 first_value: u128,
6296 segments: &[NumericSegment<C>],
6297) -> Result<Vec<(u128, i8)>, ClaimFrontierError> {
6298 let mut events = Vec::new();
6299 let counter_limit = u128::from(u64::MAX) + 1;
6300 for segment in segments {
6301 if segment.length == 0 {
6302 continue;
6303 }
6304 let Some(end) = segment.start.checked_add(segment.length) else {
6305 return Err(frontier_error(
6306 counter,
6307 counter_limit.saturating_sub(first_value),
6308 ClaimFrontierInvalidReason::NumericPosition,
6309 ));
6310 };
6311 if segment.start < first_value {
6312 return Err(frontier_error(
6313 counter,
6314 0,
6315 ClaimFrontierInvalidReason::NumericPosition,
6316 ));
6317 }
6318 if end > counter_limit {
6319 return Err(frontier_error(
6320 counter,
6321 counter_limit.saturating_sub(first_value),
6322 ClaimFrontierInvalidReason::NumericPosition,
6323 ));
6324 }
6325 events.push((segment.start, 1_i8));
6326 events.push((end, -1_i8));
6327 }
6328 Ok(events)
6329}
6330
6331fn scan_numeric_events(
6332 counter: ClaimFrontierCounter,
6333 first_value: u128,
6334 events: &mut [(u128, i8)],
6335) -> Result<u128, ClaimFrontierError> {
6336 events.sort_unstable_by_key(|event| event.0);
6337 let mut event_index = 0_usize;
6338 let mut coordinate = first_value;
6339 let mut coverage = 0_i128;
6340 let mut emitted = 0_u128;
6341 while let Some((event_coordinate, _)) = events.get(event_index).copied() {
6342 if event_coordinate > coordinate {
6343 if coverage == 0 {
6344 return Err(frontier_error(
6345 counter,
6346 emitted,
6347 ClaimFrontierInvalidReason::NumericPosition,
6348 ));
6349 }
6350 if coverage > 1 {
6351 return Err(frontier_error(
6352 counter,
6353 emitted.saturating_add(1),
6354 ClaimFrontierInvalidReason::NumericPosition,
6355 ));
6356 }
6357 emitted = emitted.saturating_add(event_coordinate - coordinate);
6358 coordinate = event_coordinate;
6359 }
6360 while let Some((same_coordinate, delta)) = events.get(event_index).copied() {
6361 if same_coordinate != coordinate {
6362 break;
6363 }
6364 coverage += i128::from(delta);
6365 event_index += 1;
6366 }
6367 }
6368 if coverage != 0 {
6369 return Err(frontier_error(
6370 counter,
6371 emitted,
6372 ClaimFrontierInvalidReason::NumericPosition,
6373 ));
6374 }
6375 Ok(emitted)
6376}
6377
6378fn validate_immutable_prefix<C>(
6379 counter: ClaimFrontierCounter,
6380 first_value: u128,
6381 segments: &[NumericSegment<C>],
6382) -> Result<(), ClaimFrontierError> {
6383 let mut first_movable = None;
6384 for segment in segments.iter().filter(|segment| segment.length != 0) {
6385 if segment.immutable {
6386 if let Some(first_movable) = first_movable {
6387 return Err(frontier_error(
6388 counter,
6389 first_movable,
6390 ClaimFrontierInvalidReason::NumericPosition,
6391 ));
6392 }
6393 } else if first_movable.is_none() {
6394 first_movable = Some(segment.start - first_value);
6395 }
6396 }
6397 Ok(())
6398}
6399
6400fn validate_segment_class_counts<C: Copy + Into<usize>>(
6401 counter: ClaimFrontierCounter,
6402 expected_length: u128,
6403 segments: &[NumericSegment<C>],
6404 expected_counts: &[u128],
6405) -> Result<(), ClaimFrontierError> {
6406 let mut actual_counts = core::iter::repeat_n(0_u128, expected_counts.len()).collect::<Vec<_>>();
6407 let mut class_ordinal = 0_u128;
6408 for segment in segments {
6409 if segment.length == 0 {
6410 continue;
6411 }
6412 if let Some(class) = segment.class {
6413 let index = class.into();
6414 let prior = actual_counts[index];
6415 let Some(resulting) = prior.checked_add(segment.length) else {
6416 return Err(frontier_error(
6417 counter,
6418 class_ordinal,
6419 ClaimFrontierInvalidReason::AggregateLedger,
6420 ));
6421 };
6422 if resulting > expected_counts[index] {
6423 return Err(frontier_error(
6424 counter,
6425 class_ordinal + expected_counts[index].saturating_sub(prior),
6426 ClaimFrontierInvalidReason::AggregateLedger,
6427 ));
6428 }
6429 actual_counts[index] = resulting;
6430 }
6431 class_ordinal += segment.length;
6432 }
6433 if actual_counts != expected_counts {
6434 return Err(frontier_error(
6435 counter,
6436 expected_length,
6437 ClaimFrontierInvalidReason::AggregateLedger,
6438 ));
6439 }
6440 Ok(())
6441}
6442
6443#[cfg(test)]
6444pub(super) fn validate_numeric_union_for_test(
6445 first_value: u128,
6446 expected_length: u128,
6447 ranges: &[(u128, u128)],
6448) -> Result<(), ClaimFrontierError> {
6449 let mut segments: Vec<_> = ranges
6450 .iter()
6451 .map(|(start, length)| NumericSegment {
6452 start: *start,
6453 length: *length,
6454 class: Some(SequenceClass::Exit),
6455 immutable: false,
6456 })
6457 .collect();
6458 validate_numeric_segments(
6459 ClaimFrontierCounter::DeliverySequence,
6460 first_value,
6461 expected_length,
6462 &mut segments,
6463 &[expected_length, 0, 0, 0, 0, 0, 0, 0],
6464 )
6465}
6466
6467impl From<SequenceClass> for usize {
6468 fn from(value: SequenceClass) -> Self {
6469 value as Self
6470 }
6471}
6472
6473impl From<OrderClass> for usize {
6474 fn from(value: OrderClass) -> Self {
6475 value as Self
6476 }
6477}
6478
6479const fn sequence_candidate_class(candidate: ImmutableSequenceCandidate) -> SequenceClass {
6480 match candidate {
6481 ImmutableSequenceCandidate::BindingTerminal { .. } => SequenceClass::Terminal,
6482 ImmutableSequenceCandidate::Marker(marker) => match marker.current_owner {
6483 MarkerSequenceOwner::Marker => SequenceClass::Marker,
6484 MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::LiveTimesTerminal) => {
6485 SequenceClass::LiveTimesTerminal
6486 }
6487 MarkerSequenceOwner::ConditionalProduct(
6488 SequenceProductClass::LiveTimesReplacementTerminal,
6489 ) => SequenceClass::LiveTimesReplacementTerminal,
6490 MarkerSequenceOwner::ConditionalProduct(SequenceProductClass::OtherLiveTimesExit) => {
6491 SequenceClass::OtherLiveTimesExit
6492 }
6493 },
6494 }
6495}
6496
6497fn sequence_expected_counts(ledger: SequenceLedger) -> [u128; 8] {
6498 let budget = ledger.budget();
6499 [
6500 u128::from(budget.e),
6501 u128::from(budget.t),
6502 u128::from(budget.m),
6503 u128::from(budget.rs),
6504 u128::from(budget.rt),
6505 budget.l_times_t,
6506 budget.l_times_rt,
6507 budget.l_other_times_e,
6508 ]
6509}
6510
6511fn order_expected_counts(ledger: OrderLedger) -> [u128; 4] {
6512 let claims = ledger.claims();
6513 [
6514 u128::from(claims.active_binding_terminals()),
6515 u128::from(claims.membership_exits()),
6516 u128::from(claims.recovery_operation()),
6517 u128::from(claims.recovery_replacement_terminal()),
6518 ]
6519}
6520
6521fn validate_sequence_recovery(
6522 active: &ActiveIdentityRanks,
6523 recovery: Option<RecoverySequenceBlockRestore>,
6524 provenance: Option<RecoveryClaimProvenance>,
6525 ledger: SequenceLedger,
6526 frontier_length: u128,
6527) -> Result<(), ClaimFrontierError> {
6528 let expected = ledger.claims().recovery();
6529 match (expected, recovery, provenance) {
6530 (RecoverySequenceReserve::None, None, None) => Ok(()),
6531 (RecoverySequenceReserve::DetachedCredentialRecovery, None, _)
6532 | (RecoverySequenceReserve::DetachedCredentialRecovery, Some(_), None)
6533 | (RecoverySequenceReserve::None, None, Some(_)) => Err(sequence_error(
6534 frontier_length,
6535 ClaimFrontierInvalidReason::RecoveryBlock,
6536 )),
6537 (RecoverySequenceReserve::None, Some(block), _) => Err(sequence_error(
6538 sequence_ordinal(ledger, block_start_sequence(block)),
6539 ClaimFrontierInvalidReason::RecoveryBlock,
6540 )),
6541 (RecoverySequenceReserve::DetachedCredentialRecovery, Some(block), Some(provenance)) => {
6542 let block_ordinal = sequence_ordinal(ledger, block_start_sequence(block));
6543 let expected_recovery_attach = block
6544 .terminal
6545 .map_or(Some(block.recovery_attach_seq), |terminal| {
6546 terminal.delivery_seq.checked_add(1)
6547 });
6548 if expected_recovery_attach != Some(block.recovery_attach_seq) {
6549 return Err(sequence_error(
6550 block_ordinal + 1,
6551 ClaimFrontierInvalidReason::RecoveryBlock,
6552 ));
6553 }
6554 if block.recovery_attach_seq.checked_add(1) != Some(block.replacement_terminal_seq) {
6555 return Err(sequence_error(
6556 block_ordinal + u128::from(block.terminal.is_some()) + 1,
6557 ClaimFrontierInvalidReason::RecoveryBlock,
6558 ));
6559 }
6560 let Some(participant) = active_participant(active, provenance.participant_index) else {
6561 return Err(sequence_error(
6562 block_ordinal,
6563 ClaimFrontierInvalidReason::LogicalOwner,
6564 ));
6565 };
6566 let expected_binding = match provenance.phase {
6567 RecoveryClaimPhase::PreFate => {
6568 FrontierBinding::Bound(provenance.prior_binding_epoch)
6569 }
6570 RecoveryClaimPhase::PostFate => {
6571 FrontierBinding::Detached(provenance.prior_binding_epoch)
6572 }
6573 RecoveryClaimPhase::RecoveredBound => {
6574 FrontierBinding::Bound(provenance.current_binding_epoch)
6575 }
6576 };
6577 if participant.binding != expected_binding {
6578 return Err(sequence_error(
6579 block_ordinal,
6580 ClaimFrontierInvalidReason::LogicalOwner,
6581 ));
6582 }
6583 let terminal_valid = match (provenance.phase, block.terminal) {
6584 (RecoveryClaimPhase::PreFate, Some(terminal)) => {
6585 terminal.owner.participant_index == provenance.participant_index
6586 && terminal.owner.binding_epoch == provenance.prior_binding_epoch
6587 }
6588 (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
6589 _ => false,
6590 };
6591 if !terminal_valid {
6592 return Err(sequence_error(
6593 block_ordinal,
6594 ClaimFrontierInvalidReason::RecoveryBlock,
6595 ));
6596 }
6597 Ok(())
6598 }
6599 }
6600}
6601
6602fn validate_sequence_candidates(
6603 active: &ActiveIdentityRanks,
6604 candidates: &[ImmutableSequenceCandidate],
6605 retained_floor: u128,
6606 retained_records: &[RetainedCausalRecord],
6607 historical_causal_authorities: &[HistoricalCausalAuthority],
6608 ledger: SequenceLedger,
6609) -> Result<(), ClaimFrontierError> {
6610 let mut seen_keys = Vec::new();
6611 let mut previous_sequence = None;
6612 let mut previous_order = retained_records.last().map(|record| record.admission_order);
6613 for candidate in candidates {
6614 let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6615 let order = candidate.admission_order();
6616 if previous_sequence.is_some_and(|previous| previous >= candidate.delivery_seq())
6617 || previous_order.is_some_and(|previous| previous >= order)
6618 || seen_keys.contains(&order)
6619 {
6620 return Err(sequence_error(
6621 ordinal,
6622 ClaimFrontierInvalidReason::CandidateKey,
6623 ));
6624 }
6625 previous_sequence = Some(candidate.delivery_seq());
6626 previous_order = Some(order);
6627 seen_keys.push(order);
6628 match candidate {
6629 ImmutableSequenceCandidate::BindingTerminal { owner, .. } => {
6630 if order.candidate_phase() != CandidatePhase::BindingTerminal
6631 || order.participant_index() != owner.participant_index
6632 || !terminal_matches_active(active, *owner)
6633 {
6634 return Err(sequence_error(
6635 ordinal,
6636 ClaimFrontierInvalidReason::CandidateKey,
6637 ));
6638 }
6639 }
6640 ImmutableSequenceCandidate::Marker(marker) => {
6641 let Some(participant) = active_participant(active, order.participant_index())
6642 else {
6643 return Err(sequence_error(
6644 ordinal,
6645 ClaimFrontierInvalidReason::LogicalOwner,
6646 ));
6647 };
6648 if order.candidate_phase() != CandidatePhase::CompactionMarker
6649 || marker.current_owner != MarkerSequenceOwner::Marker
6650 || marker.target_binding != participant.binding
6651 || marker.abandoned_after != participant.cursor
6652 || marker.abandoned_after > marker.abandoned_through
6653 || u128::from(marker.physical_floor_at_decision) != retained_floor
6654 || u128::from(marker.physical_floor_at_decision)
6655 > u128::from(marker.abandoned_through) + 1
6656 || marker.abandoned_through >= marker.delivery_seq
6657 || !marker_provenance_targets(marker.provenance, order.participant_index())
6658 || !marker_has_causal_authority(
6659 *marker,
6660 retained_records,
6661 historical_causal_authorities,
6662 )
6663 {
6664 return Err(sequence_error(
6665 ordinal,
6666 ClaimFrontierInvalidReason::CandidateKey,
6667 ));
6668 }
6669 }
6670 }
6671 }
6672 Ok(())
6673}
6674
6675#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6676enum TerminalOccurrenceKind {
6677 Movable,
6678 Candidate,
6679}
6680
6681#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6682struct TerminalOccurrence {
6683 owner: BindingTerminalOwner,
6684 ordinal: u128,
6685 kind: TerminalOccurrenceKind,
6686}
6687
6688pub(in crate::lifecycle) const fn terminal_owner_sort_key(
6693 owner: &BindingTerminalOwner,
6694) -> (ParticipantId, (ConnectionIncarnation, Generation)) {
6695 (
6696 owner.participant_index,
6697 owner.binding_epoch.canonical_sort_key(),
6698 )
6699}
6700
6701fn validate_sequence_direct_owners(
6702 active: &ActiveIdentityRanks,
6703 movable: &[MovableSequenceClaim],
6704 candidates: &[ImmutableSequenceCandidate],
6705 recovery: Option<RecoverySequenceBlockRestore>,
6706 ledger: SequenceLedger,
6707) -> Result<Vec<BindingTerminalOwner>, ClaimFrontierError> {
6708 let (mut exit_owners, terminal_occurrences) =
6709 collect_sequence_direct_owners(active, movable, candidates, recovery, ledger)?;
6710 validate_sequence_exit_owners(active, &mut exit_owners, ledger)?;
6711 validate_sequence_terminal_owners(active, &terminal_occurrences, ledger)?;
6712 let mut owners: Vec<_> = terminal_occurrences
6713 .into_iter()
6714 .map(|occurrence| occurrence.owner)
6715 .collect();
6716 owners.sort_by_key(terminal_owner_sort_key);
6717 Ok(owners)
6718}
6719
6720fn collect_sequence_direct_owners(
6721 active: &ActiveIdentityRanks,
6722 movable: &[MovableSequenceClaim],
6723 candidates: &[ImmutableSequenceCandidate],
6724 recovery: Option<RecoverySequenceBlockRestore>,
6725 ledger: SequenceLedger,
6726) -> Result<(Vec<ParticipantId>, Vec<TerminalOccurrence>), ClaimFrontierError> {
6727 let mut ordered_movable = movable.to_vec();
6728 ordered_movable.sort_by_key(|claim| claim.delivery_seq);
6729 let mut exit_owners = Vec::new();
6730 let mut terminal_occurrences = Vec::new();
6731 for claim in ordered_movable {
6732 let ordinal = sequence_ordinal(ledger, claim.delivery_seq);
6733 match claim.owner {
6734 SequenceDirectOwner::MembershipExit { participant_index } => {
6735 if !active.contains(participant_index) || exit_owners.contains(&participant_index) {
6736 return Err(sequence_error(
6737 ordinal,
6738 ClaimFrontierInvalidReason::LogicalOwner,
6739 ));
6740 }
6741 exit_owners.push(participant_index);
6742 }
6743 SequenceDirectOwner::BindingTerminal(owner) => {
6744 if !terminal_matches_bound(active, owner) {
6745 return Err(sequence_error(
6746 ordinal,
6747 ClaimFrontierInvalidReason::LogicalOwner,
6748 ));
6749 }
6750 push_terminal_occurrence(
6751 &mut terminal_occurrences,
6752 owner,
6753 ordinal,
6754 TerminalOccurrenceKind::Movable,
6755 )?;
6756 }
6757 }
6758 }
6759 for candidate in candidates {
6760 if let ImmutableSequenceCandidate::BindingTerminal { owner, .. } = candidate {
6761 let ordinal = sequence_ordinal(ledger, candidate.delivery_seq());
6762 push_terminal_occurrence(
6763 &mut terminal_occurrences,
6764 *owner,
6765 ordinal,
6766 TerminalOccurrenceKind::Candidate,
6767 )?;
6768 }
6769 }
6770 if let Some(terminal) = recovery.and_then(|block| block.terminal) {
6771 let ordinal = sequence_ordinal(ledger, terminal.delivery_seq);
6772 push_terminal_occurrence(
6773 &mut terminal_occurrences,
6774 terminal.owner,
6775 ordinal,
6776 TerminalOccurrenceKind::Movable,
6777 )?;
6778 }
6779 Ok((exit_owners, terminal_occurrences))
6780}
6781
6782fn push_terminal_occurrence(
6783 occurrences: &mut Vec<TerminalOccurrence>,
6784 owner: BindingTerminalOwner,
6785 ordinal: u128,
6786 kind: TerminalOccurrenceKind,
6787) -> Result<(), ClaimFrontierError> {
6788 if occurrences
6789 .iter()
6790 .any(|occurrence| occurrence.owner == owner)
6791 {
6792 return Err(sequence_error(
6793 ordinal,
6794 ClaimFrontierInvalidReason::LogicalOwner,
6795 ));
6796 }
6797 occurrences.push(TerminalOccurrence {
6798 owner,
6799 ordinal,
6800 kind,
6801 });
6802 Ok(())
6803}
6804
6805fn validate_sequence_exit_owners(
6806 active: &ActiveIdentityRanks,
6807 exit_owners: &mut [ParticipantId],
6808 ledger: SequenceLedger,
6809) -> Result<(), ClaimFrontierError> {
6810 exit_owners.sort_unstable();
6811 if exit_owners.len() != active.participants.len()
6812 || !exit_owners.iter().copied().eq(active
6813 .participants
6814 .iter()
6815 .map(|participant| participant.participant_index))
6816 {
6817 return Err(sequence_error(
6818 ledger.required_reserve(),
6819 ClaimFrontierInvalidReason::LogicalOwner,
6820 ));
6821 }
6822 Ok(())
6823}
6824
6825fn validate_sequence_terminal_owners(
6826 active: &ActiveIdentityRanks,
6827 terminal_occurrences: &[TerminalOccurrence],
6828 ledger: SequenceLedger,
6829) -> Result<(), ClaimFrontierError> {
6830 for participant in &active.participants {
6831 let matching: Vec<_> = terminal_occurrences
6832 .iter()
6833 .filter(|occurrence| {
6834 occurrence.owner.participant_index == participant.participant_index
6835 })
6836 .copied()
6837 .collect();
6838 match participant.binding {
6839 FrontierBinding::Bound(epoch) => {
6840 if !matches!(matching.as_slice(), [occurrence] if occurrence.owner.binding_epoch == epoch)
6841 {
6842 return Err(sequence_error(
6843 matching.first().map_or_else(
6844 || ledger.required_reserve(),
6845 |occurrence| occurrence.ordinal,
6846 ),
6847 ClaimFrontierInvalidReason::LogicalOwner,
6848 ));
6849 }
6850 }
6851 FrontierBinding::Detached(epoch) => {
6852 if matching.len() > 1
6853 || matching.first().is_some_and(|occurrence| {
6854 occurrence.owner.binding_epoch != epoch
6855 || occurrence.kind != TerminalOccurrenceKind::Candidate
6856 })
6857 {
6858 return Err(sequence_error(
6859 matching.first().map_or_else(
6860 || ledger.required_reserve(),
6861 |occurrence| occurrence.ordinal,
6862 ),
6863 ClaimFrontierInvalidReason::LogicalOwner,
6864 ));
6865 }
6866 }
6867 }
6868 }
6869 Ok(())
6870}
6871
6872fn validate_sequence_products(
6873 active: &ActiveIdentityRanks,
6874 restore: SequenceProductRangesRestore,
6875 terminal_owners: &[BindingTerminalOwner],
6876 recovery: Option<RecoverySequenceBlockRestore>,
6877 recovery_provenance: Option<RecoveryClaimProvenance>,
6878 ledger: SequenceLedger,
6879) -> Result<SequenceProductRanges, ClaimFrontierError> {
6880 let live_count = usize_to_u64(active.participants.len());
6881 let other_count = live_count.saturating_sub(1);
6882 let live_times_terminal = validate_terminal_product_ranges(
6883 restore.live_times_terminal,
6884 terminal_owners,
6885 live_count,
6886 ledger,
6887 )?;
6888 let live_times_replacement_terminal = validate_replacement_product_range(
6889 restore.live_times_replacement_terminal,
6890 recovery,
6891 recovery_provenance,
6892 live_count,
6893 ledger,
6894 )?;
6895 let other_live_times_exit =
6896 validate_exit_product_ranges(active, restore.other_live_times_exit, other_count, ledger)?;
6897 Ok(SequenceProductRanges {
6898 live_times_terminal,
6899 live_times_replacement_terminal,
6900 other_live_times_exit,
6901 })
6902}
6903
6904fn validate_terminal_product_ranges(
6905 mut ranges: Vec<TerminalProductRangeRestore>,
6906 terminal_owners: &[BindingTerminalOwner],
6907 live_count: u64,
6908 ledger: SequenceLedger,
6909) -> Result<Vec<TerminalProductRange>, ClaimFrontierError> {
6910 ranges.sort_by_key(|range| range.start);
6911 let mut seen_terminals = Vec::new();
6912 let mut live_times_terminal = Vec::new();
6913 for range in ranges {
6914 let ordinal = sequence_ordinal(ledger, range.start);
6915 if range.length != live_count
6916 || !terminal_owners.contains(&range.terminal)
6917 || seen_terminals.contains(&range.terminal)
6918 {
6919 return Err(sequence_error(
6920 ordinal,
6921 ClaimFrontierInvalidReason::ProductRange,
6922 ));
6923 }
6924 seen_terminals.push(range.terminal);
6925 live_times_terminal.push(TerminalProductRange {
6926 start: range.start,
6927 length: range.length,
6928 terminal: range.terminal,
6929 });
6930 }
6931 if seen_terminals.len() != terminal_owners.len() {
6932 return Err(sequence_error(
6933 ledger.required_reserve(),
6934 ClaimFrontierInvalidReason::ProductRange,
6935 ));
6936 }
6937 Ok(live_times_terminal)
6938}
6939
6940fn validate_replacement_product_range(
6941 range: Option<ReplacementTerminalProductRangeRestore>,
6942 recovery: Option<RecoverySequenceBlockRestore>,
6943 recovery_provenance: Option<RecoveryClaimProvenance>,
6944 live_count: u64,
6945 ledger: SequenceLedger,
6946) -> Result<Option<ReplacementTerminalProductRange>, ClaimFrontierError> {
6947 let validated = match (range, recovery, recovery_provenance) {
6948 (None, None, None) => None,
6949 (Some(range), Some(_), Some(provenance)) if range.length == live_count => {
6950 Some(ReplacementTerminalProductRange {
6951 start: range.start,
6952 length: range.length,
6953 participant_index: provenance.participant_index,
6954 marker_delivery_seq: provenance.marker_delivery_seq,
6955 prior_binding_epoch: provenance.prior_binding_epoch,
6956 })
6957 }
6958 (Some(range), _, _) => {
6959 return Err(sequence_error(
6960 sequence_ordinal(ledger, range.start),
6961 ClaimFrontierInvalidReason::ProductRange,
6962 ));
6963 }
6964 (None, Some(_), _) | (None, None, Some(_)) => {
6965 return Err(sequence_error(
6966 ledger.required_reserve(),
6967 ClaimFrontierInvalidReason::ProductRange,
6968 ));
6969 }
6970 };
6971 Ok(validated)
6972}
6973
6974fn validate_exit_product_ranges(
6975 active: &ActiveIdentityRanks,
6976 mut ranges: Vec<ExitProductRangeRestore>,
6977 other_count: u64,
6978 ledger: SequenceLedger,
6979) -> Result<Vec<ExitProductRange>, ClaimFrontierError> {
6980 ranges.sort_by_key(|range| range.start);
6981 let mut seen_exits = Vec::new();
6982 let mut other_live_times_exit = Vec::new();
6983 if other_count == 0 && !ranges.is_empty() {
6984 return Err(sequence_error(
6985 ledger.required_reserve(),
6986 ClaimFrontierInvalidReason::ProductRange,
6987 ));
6988 }
6989 for range in ranges {
6990 let ordinal = sequence_ordinal(ledger, range.start);
6991 if range.length != other_count
6992 || !active.contains(range.exit_participant)
6993 || seen_exits.contains(&range.exit_participant)
6994 {
6995 return Err(sequence_error(
6996 ordinal,
6997 ClaimFrontierInvalidReason::ProductRange,
6998 ));
6999 }
7000 seen_exits.push(range.exit_participant);
7001 other_live_times_exit.push(ExitProductRange {
7002 start: range.start,
7003 length: range.length,
7004 exit_participant: range.exit_participant,
7005 });
7006 }
7007 seen_exits.sort_unstable();
7008 let expected_exit_ranges = if other_count == 0 {
7009 0
7010 } else {
7011 active.participants.len()
7012 };
7013 if seen_exits.len() != expected_exit_ranges
7014 || !seen_exits.iter().copied().eq(active
7015 .participants
7016 .iter()
7017 .take(expected_exit_ranges)
7018 .map(|participant| participant.participant_index))
7019 {
7020 return Err(sequence_error(
7021 ledger.required_reserve(),
7022 ClaimFrontierInvalidReason::ProductRange,
7023 ));
7024 }
7025 Ok(other_live_times_exit)
7026}
7027
7028fn validate_order_recovery(
7029 active: &ActiveIdentityRanks,
7030 recovery: Option<RecoveryOrderBlockRestore>,
7031 provenance: Option<RecoveryClaimProvenance>,
7032 ledger: OrderLedger,
7033 frontier_length: u128,
7034) -> Result<(), ClaimFrontierError> {
7035 let claims = ledger.claims();
7036 let expected = claims.recovery_operation() && claims.recovery_replacement_terminal();
7037 match (expected, recovery, provenance) {
7038 (false, None, None) => Ok(()),
7039 (true, None, _) | (true, Some(_), None) | (false, None, Some(_)) => Err(order_error(
7040 frontier_length,
7041 ClaimFrontierInvalidReason::RecoveryBlock,
7042 )),
7043 (false, Some(block), _) => Err(order_error(
7044 order_ordinal(ledger, block_start_order(block)),
7045 ClaimFrontierInvalidReason::RecoveryBlock,
7046 )),
7047 (true, Some(block), Some(provenance)) => {
7048 let block_ordinal = order_ordinal(ledger, block_start_order(block));
7049 let expected_recovery_operation = block
7050 .active_binding
7051 .map_or(Some(block.recovery_operation_order), |active_binding| {
7052 active_binding.transaction_order.checked_add(1)
7053 });
7054 if expected_recovery_operation != Some(block.recovery_operation_order) {
7055 return Err(order_error(
7056 block_ordinal + 1,
7057 ClaimFrontierInvalidReason::RecoveryBlock,
7058 ));
7059 }
7060 if block.recovery_operation_order.checked_add(1)
7061 != Some(block.replacement_terminal_order)
7062 {
7063 return Err(order_error(
7064 block_ordinal + u128::from(block.active_binding.is_some()) + 1,
7065 ClaimFrontierInvalidReason::RecoveryBlock,
7066 ));
7067 }
7068 let Some(participant) = active_participant(active, provenance.participant_index) else {
7069 return Err(order_error(
7070 block_ordinal,
7071 ClaimFrontierInvalidReason::LogicalOwner,
7072 ));
7073 };
7074 let expected_binding = match provenance.phase {
7075 RecoveryClaimPhase::PreFate => {
7076 FrontierBinding::Bound(provenance.prior_binding_epoch)
7077 }
7078 RecoveryClaimPhase::PostFate => {
7079 FrontierBinding::Detached(provenance.prior_binding_epoch)
7080 }
7081 RecoveryClaimPhase::RecoveredBound => {
7082 FrontierBinding::Bound(provenance.current_binding_epoch)
7083 }
7084 };
7085 if participant.binding != expected_binding {
7086 return Err(order_error(
7087 block_ordinal,
7088 ClaimFrontierInvalidReason::LogicalOwner,
7089 ));
7090 }
7091 let active_binding_valid = match (provenance.phase, block.active_binding) {
7092 (RecoveryClaimPhase::PreFate, Some(active_binding)) => {
7093 active_binding.owner.participant_index == provenance.participant_index
7094 && active_binding.owner.binding_epoch == provenance.prior_binding_epoch
7095 }
7096 (RecoveryClaimPhase::PostFate | RecoveryClaimPhase::RecoveredBound, None) => true,
7097 _ => false,
7098 };
7099 if !active_binding_valid {
7100 return Err(order_error(
7101 block_ordinal,
7102 ClaimFrontierInvalidReason::RecoveryBlock,
7103 ));
7104 }
7105 Ok(())
7106 }
7107 }
7108}
7109
7110fn validate_order_candidates(
7111 restore: &[ImmutableOrderCandidateMajorRestore],
7112 ledger: OrderLedger,
7113) -> Result<Vec<ImmutableOrderCandidateMajor>, ClaimFrontierError> {
7114 let mut groups = restore.to_vec();
7115 groups.sort_by_key(|group| group.transaction_order);
7116 let mut seen_keys = Vec::new();
7117 let mut previous_major = None;
7118 let mut validated = Vec::new();
7119 for group in groups {
7120 let ordinal = order_ordinal(ledger, group.transaction_order);
7121 let below_allocated_high = matches!(
7122 ledger.high(),
7123 OrderHigh::Allocated(high) if group.transaction_order < high
7124 );
7125 if group.candidate_keys.is_empty()
7126 || previous_major == Some(group.transaction_order)
7127 || below_allocated_high
7128 {
7129 return Err(order_error(
7130 ordinal,
7131 ClaimFrontierInvalidReason::CandidateKey,
7132 ));
7133 }
7134 previous_major = Some(group.transaction_order);
7135 let mut previous = None;
7136 for key in &group.candidate_keys {
7137 if key.transaction_order() != group.transaction_order
7138 || previous.is_some_and(|previous| previous >= *key)
7139 || seen_keys.contains(key)
7140 {
7141 return Err(order_error(
7142 ordinal,
7143 ClaimFrontierInvalidReason::CandidateKey,
7144 ));
7145 }
7146 previous = Some(*key);
7147 seen_keys.push(*key);
7148 }
7149 validated.push(ImmutableOrderCandidateMajor {
7150 transaction_order: group.transaction_order,
7151 candidate_keys: group.candidate_keys,
7152 });
7153 }
7154 Ok(validated)
7155}
7156
7157fn validate_order_direct_owners(
7158 active: &ActiveIdentityRanks,
7159 movable: &[MovableOrderClaim],
7160 recovery: Option<RecoveryOrderBlockRestore>,
7161 ledger: OrderLedger,
7162) -> Result<(), ClaimFrontierError> {
7163 let mut ordered = movable.to_vec();
7164 ordered.sort_by_key(|claim| claim.transaction_order);
7165 let mut exits = Vec::new();
7166 let mut terminals = Vec::new();
7167 for claim in ordered {
7168 let ordinal = order_ordinal(ledger, claim.transaction_order);
7169 match claim.owner {
7170 OrderDirectOwner::MembershipExit { participant_index } => {
7171 if !active.contains(participant_index) || exits.contains(&participant_index) {
7172 return Err(order_error(
7173 ordinal,
7174 ClaimFrontierInvalidReason::LogicalOwner,
7175 ));
7176 }
7177 exits.push(participant_index);
7178 }
7179 OrderDirectOwner::ActiveBindingTerminal(owner) => {
7180 if !terminal_matches_bound(active, owner) || terminals.contains(&owner) {
7181 return Err(order_error(
7182 ordinal,
7183 ClaimFrontierInvalidReason::LogicalOwner,
7184 ));
7185 }
7186 terminals.push(owner);
7187 }
7188 }
7189 }
7190 if let Some(active_binding) = recovery.and_then(|block| block.active_binding) {
7191 let ordinal = order_ordinal(ledger, active_binding.transaction_order);
7192 if !terminal_matches_bound(active, active_binding.owner)
7193 || terminals.contains(&active_binding.owner)
7194 {
7195 return Err(order_error(
7196 ordinal,
7197 ClaimFrontierInvalidReason::LogicalOwner,
7198 ));
7199 }
7200 terminals.push(active_binding.owner);
7201 }
7202 exits.sort_unstable();
7203 if exits.len() != active.participants.len()
7204 || !exits.iter().copied().eq(active
7205 .participants
7206 .iter()
7207 .map(|participant| participant.participant_index))
7208 {
7209 return Err(order_error(
7210 ledger.claims().total(),
7211 ClaimFrontierInvalidReason::LogicalOwner,
7212 ));
7213 }
7214 terminals.sort_by_key(terminal_owner_sort_key);
7215 if usize_to_u128(terminals.len()) != u128::from(ledger.claims().active_binding_terminals()) {
7216 return Err(order_error(
7217 ledger.claims().total(),
7218 ClaimFrontierInvalidReason::LogicalOwner,
7219 ));
7220 }
7221 Ok(())
7222}
7223
7224fn validate_cross_counter(
7225 sequence: &SequenceClaimFrontier,
7226 order: &OrderClaimFrontier,
7227) -> Result<(), ClaimFrontierError> {
7228 match (sequence.recovery, order.recovery) {
7229 (None, None) => {}
7230 (Some(sequence_block), Some(order_block))
7231 if sequence_block.participant_index == order_block.participant_index
7232 && sequence_block.marker_delivery_seq == order_block.marker_delivery_seq
7233 && sequence_block.recovered_binding_epoch
7234 == order_block.recovered_binding_epoch
7235 && sequence_block.terminal.map(|terminal| terminal.owner)
7236 == order_block.active_binding.map(|active| active.owner) => {}
7237 (Some(sequence_block), _) => {
7238 return Err(sequence_error(
7239 sequence_ordinal(
7240 sequence.ledger,
7241 block_start_validated_sequence(sequence_block),
7242 ),
7243 ClaimFrontierInvalidReason::RecoveryBlock,
7244 ));
7245 }
7246 (None, Some(order_block)) => {
7247 return Err(order_error(
7248 order_ordinal(order.ledger, block_start_validated_order(order_block)),
7249 ClaimFrontierInvalidReason::RecoveryBlock,
7250 ));
7251 }
7252 }
7253
7254 let mut order_candidate_keys = Vec::new();
7255 for group in &order.immutable_candidates {
7256 order_candidate_keys.extend(group.candidate_keys.iter().copied());
7257 }
7258 for candidate in &sequence.immutable_candidates {
7259 let key = candidate.admission_order();
7260 if !order_candidate_keys.contains(&key) {
7261 return Err(sequence_error(
7262 sequence_ordinal(sequence.ledger, candidate.delivery_seq()),
7263 ClaimFrontierInvalidReason::CandidateKey,
7264 ));
7265 }
7266 }
7267 for group in &order.immutable_candidates {
7268 for key in &group.candidate_keys {
7269 if !sequence
7270 .immutable_candidates
7271 .iter()
7272 .any(|candidate| candidate.admission_order() == *key)
7273 {
7274 return Err(order_error(
7275 order_ordinal(order.ledger, group.transaction_order),
7276 ClaimFrontierInvalidReason::CandidateKey,
7277 ));
7278 }
7279 }
7280 }
7281
7282 let mut sequence_movable_terminals = Vec::new();
7283 for claim in &sequence.movable_claims {
7284 if let SequenceDirectOwner::BindingTerminal(owner) = claim.owner {
7285 sequence_movable_terminals.push(owner);
7286 }
7287 }
7288 if let Some(terminal) = sequence.recovery.and_then(|block| block.terminal) {
7289 sequence_movable_terminals.push(terminal.owner);
7290 }
7291 let mut order_movable_terminals = Vec::new();
7292 for claim in &order.movable_claims {
7293 if let OrderDirectOwner::ActiveBindingTerminal(owner) = claim.owner {
7294 order_movable_terminals.push(owner);
7295 }
7296 }
7297 if let Some(active_binding) = order.recovery.and_then(|block| block.active_binding) {
7298 order_movable_terminals.push(active_binding.owner);
7299 }
7300 sequence_movable_terminals.sort_by_key(terminal_owner_sort_key);
7301 order_movable_terminals.sort_by_key(terminal_owner_sort_key);
7302 if sequence_movable_terminals != order_movable_terminals {
7303 return Err(sequence_error(
7304 sequence.ledger.required_reserve(),
7305 ClaimFrontierInvalidReason::LogicalOwner,
7306 ));
7307 }
7308 Ok(())
7309}
7310
7311const fn marker_provenance_targets(provenance: MarkerProvenance, target: ParticipantId) -> bool {
7312 match provenance {
7313 MarkerProvenance::NonProductM => true,
7314 MarkerProvenance::TerminalProduct {
7315 affected_participant,
7316 ..
7317 } => affected_participant == target,
7318 MarkerProvenance::ExitProduct {
7319 exit_participant,
7320 remaining_participant,
7321 } => exit_participant != remaining_participant && remaining_participant == target,
7322 }
7323}
7324
7325fn marker_has_causal_authority(
7326 marker: MarkerCandidateAuthority,
7327 records: &[RetainedCausalRecord],
7328 historical: &[HistoricalCausalAuthority],
7329) -> bool {
7330 if marker.provenance == MarkerProvenance::NonProductM {
7331 return true;
7332 }
7333 let retained_match = records.iter().any(|record| {
7334 if record.admission_order.transaction_order() != marker.admission_order.transaction_order()
7335 {
7336 return false;
7337 }
7338 match marker.provenance {
7339 MarkerProvenance::NonProductM => true,
7340 MarkerProvenance::TerminalProduct {
7341 terminal: TerminalProductSource::Binding(owner),
7342 ..
7343 } => matches!(
7344 record.kind,
7345 RetainedCausalRecordKind::BindingTerminal(actual) if actual == owner
7346 ),
7347 MarkerProvenance::TerminalProduct {
7348 terminal:
7349 TerminalProductSource::RecoveryReplacement {
7350 participant_index,
7351 binding_epoch,
7352 },
7353 ..
7354 } => matches!(
7355 record.kind,
7356 RetainedCausalRecordKind::BindingTerminal(owner)
7357 if owner.participant_index == participant_index
7358 && owner.binding_epoch == binding_epoch
7359 ),
7360 MarkerProvenance::ExitProduct {
7361 exit_participant, ..
7362 } => matches!(
7363 record.kind,
7364 RetainedCausalRecordKind::MembershipExit { participant_index }
7365 if participant_index == exit_participant
7366 ),
7367 }
7368 });
7369 retained_match
7370 || historical
7371 .iter()
7372 .any(|authority| match (marker.provenance, authority.kind) {
7373 (
7374 MarkerProvenance::TerminalProduct {
7375 terminal: TerminalProductSource::Binding(expected),
7376 ..
7377 },
7378 HistoricalCausalKind::BindingTerminal(owner),
7379 ) => {
7380 owner == expected
7381 && authority.admission_order.transaction_order()
7382 == marker.admission_order.transaction_order()
7383 }
7384 (
7385 MarkerProvenance::TerminalProduct {
7386 terminal:
7387 TerminalProductSource::RecoveryReplacement {
7388 participant_index,
7389 binding_epoch,
7390 },
7391 ..
7392 },
7393 HistoricalCausalKind::BindingTerminal(owner),
7394 ) => {
7395 owner.participant_index == participant_index
7396 && owner.binding_epoch == binding_epoch
7397 && authority.admission_order.transaction_order()
7398 == marker.admission_order.transaction_order()
7399 }
7400 (
7401 MarkerProvenance::ExitProduct {
7402 exit_participant, ..
7403 },
7404 HistoricalCausalKind::MembershipExit(participant_index),
7405 ) => {
7406 participant_index == exit_participant
7407 && authority.admission_order.transaction_order()
7408 == marker.admission_order.transaction_order()
7409 }
7410 _ => false,
7411 })
7412}
7413
7414fn terminal_matches_active(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7415 active_participant(active, owner.participant_index)
7416 .is_some_and(|participant| binding_epoch(participant.binding) == owner.binding_epoch)
7417}
7418
7419fn terminal_matches_bound(active: &ActiveIdentityRanks, owner: BindingTerminalOwner) -> bool {
7420 active_participant(active, owner.participant_index).is_some_and(|participant| {
7421 participant.binding == FrontierBinding::Bound(owner.binding_epoch)
7422 })
7423}
7424
7425fn active_participant(
7426 active: &ActiveIdentityRanks,
7427 participant_index: ParticipantId,
7428) -> Option<FrontierParticipant> {
7429 active
7430 .participants
7431 .binary_search_by_key(&participant_index, |participant| {
7432 participant.participant_index
7433 })
7434 .ok()
7435 .and_then(|index| active.participants.get(index))
7436 .copied()
7437}
7438
7439const fn binding_epoch(binding: FrontierBinding) -> BindingEpoch {
7440 match binding {
7441 FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
7442 }
7443}
7444
7445fn block_start_sequence(block: RecoverySequenceBlockRestore) -> DeliverySeq {
7446 block
7447 .terminal
7448 .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7449}
7450
7451fn block_start_validated_sequence(block: RecoverySequenceBlock) -> DeliverySeq {
7452 block
7453 .terminal
7454 .map_or(block.recovery_attach_seq, |terminal| terminal.delivery_seq)
7455}
7456
7457fn block_start_order(block: RecoveryOrderBlockRestore) -> TransactionOrder {
7458 block
7459 .active_binding
7460 .map_or(block.recovery_operation_order, |active_binding| {
7461 active_binding.transaction_order
7462 })
7463}
7464
7465fn block_start_validated_order(block: RecoveryOrderBlock) -> TransactionOrder {
7466 block
7467 .active_binding
7468 .map_or(block.recovery_operation_order, |active_binding| {
7469 active_binding.transaction_order
7470 })
7471}
7472
7473fn order_frontier_start(high: OrderHigh) -> u128 {
7474 match high {
7475 OrderHigh::Empty => 0,
7476 OrderHigh::Allocated(high) => u128::from(high) + 1,
7477 }
7478}
7479
7480const fn order_is_above_high(value: TransactionOrder, high: OrderHigh) -> bool {
7481 match high {
7482 OrderHigh::Empty => true,
7483 OrderHigh::Allocated(high) => value > high,
7484 }
7485}
7486
7487fn order_frontier_candidate_count(restore: &OrderClaimFrontierRestore, high: OrderHigh) -> u128 {
7488 usize_to_u128(
7489 restore
7490 .immutable_candidates
7491 .iter()
7492 .filter(|candidate| order_is_above_high(candidate.transaction_order, high))
7493 .count(),
7494 )
7495}
7496
7497fn sequence_ordinal(ledger: SequenceLedger, value: DeliverySeq) -> u128 {
7498 u128::from(value).saturating_sub(u128::from(ledger.high_watermark()) + 1)
7499}
7500
7501fn order_ordinal(ledger: OrderLedger, value: TransactionOrder) -> u128 {
7502 u128::from(value).saturating_sub(order_frontier_start(ledger.high()))
7503}
7504
7505fn checked_rank_value(start: DeliverySeq, active_rank: usize) -> Option<DeliverySeq> {
7506 let rank = u64::try_from(active_rank).ok()?;
7507 start.checked_add(rank)
7508}
7509
7510fn usize_to_u64(value: usize) -> u64 {
7511 u64::try_from(value).unwrap_or(u64::MAX)
7512}
7513
7514fn usize_to_u128(value: usize) -> u128 {
7515 u64::try_from(value).map_or(u128::MAX, u128::from)
7516}
7517
7518fn rank_index(rank: usize) -> u128 {
7519 usize_to_u128(rank)
7520}
7521
7522const fn frontier_error(
7523 counter: ClaimFrontierCounter,
7524 first_bad_position: u128,
7525 reason: ClaimFrontierInvalidReason,
7526) -> ClaimFrontierError {
7527 ClaimFrontierError {
7528 counter,
7529 first_bad_position,
7530 reason,
7531 }
7532}
7533
7534const fn sequence_error(
7535 first_bad_position: u128,
7536 reason: ClaimFrontierInvalidReason,
7537) -> ClaimFrontierError {
7538 frontier_error(
7539 ClaimFrontierCounter::DeliverySequence,
7540 first_bad_position,
7541 reason,
7542 )
7543}
7544
7545const fn order_error(
7546 first_bad_position: u128,
7547 reason: ClaimFrontierInvalidReason,
7548) -> ClaimFrontierError {
7549 frontier_error(
7550 ClaimFrontierCounter::TransactionOrder,
7551 first_bad_position,
7552 reason,
7553 )
7554}