1mod checkpoint;
2
3use alloc::{collections::BTreeMap, vec::Vec};
4
5use crate::algebra::{FloorComputation, floor_transition};
6use crate::wire::{
7 AckCommitted, AckGap, AckNoOp, AckRegression, BindingEpoch, ConversationId, DeliverySeq,
8 ParticipantAck, ParticipantAckEnvelope, ParticipantId, ParticipantIndex,
9};
10
11use super::{ClaimFrontiers, FrontierBinding, edge::ClosureDebt};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub struct CursorProgressKey {
16 pub participant_index: ParticipantIndex,
18 pub boundary: DeliverySeq,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum CursorProgressFact {
25 Pending,
27 Consumed,
29}
30
31#[derive(Clone, Debug, Default, PartialEq, Eq)]
33pub struct CursorProgressFacts {
34 facts: BTreeMap<CursorProgressKey, CursorProgressFact>,
35}
36
37#[derive(Debug, PartialEq, Eq)]
55pub struct RecipientAckObligations {
56 participant_id: ParticipantId,
57 acknowledged_through: DeliverySeq,
58 delivery_sequences: Vec<DeliverySeq>,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum RecipientAckObligationsError {
64 NotLive {
66 acknowledged_through: DeliverySeq,
68 delivery_seq: DeliverySeq,
70 },
71 NotStrictlyIncreasing {
73 previous: DeliverySeq,
75 current: DeliverySeq,
77 },
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum RecipientAckObligationsContextError {
83 Participant {
85 expected: ParticipantId,
87 actual: ParticipantId,
89 },
90 AcknowledgedThrough {
92 expected: DeliverySeq,
94 actual: DeliverySeq,
96 },
97}
98
99impl RecipientAckObligations {
100 pub fn try_new(
107 participant_id: ParticipantId,
108 acknowledged_through: DeliverySeq,
109 delivery_sequences: Vec<DeliverySeq>,
110 ) -> Result<Self, RecipientAckObligationsError> {
111 let mut previous = None;
112 for delivery_seq in &delivery_sequences {
113 if *delivery_seq <= acknowledged_through {
114 return Err(RecipientAckObligationsError::NotLive {
115 acknowledged_through,
116 delivery_seq: *delivery_seq,
117 });
118 }
119 if let Some(previous) = previous
120 && *delivery_seq <= previous
121 {
122 return Err(RecipientAckObligationsError::NotStrictlyIncreasing {
123 previous,
124 current: *delivery_seq,
125 });
126 }
127 previous = Some(*delivery_seq);
128 }
129 Ok(Self {
130 participant_id,
131 acknowledged_through,
132 delivery_sequences,
133 })
134 }
135
136 pub(in crate::lifecycle) fn contains_endpoint(
137 &self,
138 participant_id: ParticipantId,
139 acknowledged_through: DeliverySeq,
140 endpoint: DeliverySeq,
141 ) -> Result<bool, RecipientAckObligationsContextError> {
142 if self.participant_id != participant_id {
143 return Err(RecipientAckObligationsContextError::Participant {
144 expected: participant_id,
145 actual: self.participant_id,
146 });
147 }
148 if self.acknowledged_through != acknowledged_through {
149 return Err(RecipientAckObligationsContextError::AcknowledgedThrough {
150 expected: acknowledged_through,
151 actual: self.acknowledged_through,
152 });
153 }
154 Ok(self.delivery_sequences.binary_search(&endpoint).is_ok())
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub struct BoundParticipantCursor {
164 participant_id: ParticipantId,
165 active_binding_epoch: BindingEpoch,
166 cursor: DeliverySeq,
167}
168
169impl BoundParticipantCursor {
170 #[must_use]
172 pub const fn new(
173 participant_id: ParticipantId,
174 active_binding_epoch: BindingEpoch,
175 cursor: DeliverySeq,
176 ) -> Self {
177 Self {
178 participant_id,
179 active_binding_epoch,
180 cursor,
181 }
182 }
183
184 #[must_use]
186 pub const fn participant_index(self) -> ParticipantIndex {
187 self.participant_id
188 }
189
190 #[must_use]
192 pub const fn participant_id(self) -> ParticipantId {
193 self.participant_id
194 }
195
196 #[must_use]
198 pub const fn active_binding_epoch(self) -> BindingEpoch {
199 self.active_binding_epoch
200 }
201
202 #[must_use]
204 pub const fn cursor(self) -> DeliverySeq {
205 self.cursor
206 }
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211struct DebtParticipantCursor {
212 participant_id: ParticipantId,
213 binding: FrontierBinding,
214 cursor: DeliverySeq,
215}
216
217impl From<BoundParticipantCursor> for DebtParticipantCursor {
218 fn from(participant: BoundParticipantCursor) -> Self {
219 Self {
220 participant_id: participant.participant_id,
221 binding: FrontierBinding::Bound(participant.active_binding_epoch),
222 cursor: participant.cursor,
223 }
224 }
225}
226
227impl DebtParticipantCursor {
228 const fn advance_to(&mut self, boundary: DeliverySeq) {
229 if boundary > self.cursor {
230 self.cursor = boundary;
231 }
232 }
233
234 const fn binding_epoch(self) -> BindingEpoch {
235 match self.binding {
236 FrontierBinding::Bound(epoch) | FrontierBinding::Detached(epoch) => epoch,
237 }
238 }
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
243pub enum CursorEpisodeBuildError {
244 DuplicateParticipant {
246 participant_id: ParticipantId,
248 },
249 ObserverBeyondHighWatermark {
251 observer_progress: DeliverySeq,
253 candidate_high_watermark: DeliverySeq,
255 },
256 CursorBeyondHighWatermark {
258 participant_id: ParticipantId,
260 cursor: DeliverySeq,
262 candidate_high_watermark: DeliverySeq,
264 },
265 FloorBeyondRetainedEnd {
267 current_floor: u128,
269 retained_end: u128,
271 },
272 CapacityFloorBelowBase {
274 cap_floor: u128,
276 base_floor: u128,
278 },
279 CapacityFloorBeyondObserver {
281 cap_floor: u128,
283 observer_limit: u128,
285 },
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Eq)]
290pub enum CumulativeAckAuthorizationError {
291 ParticipantIndexUnknown,
293 ConversationMismatch,
295 ParticipantMismatch,
297 GenerationMismatch,
299 BindingEpochMismatch,
301 ObligationContext {
303 error: RecipientAckObligationsContextError,
305 },
306 CursorRelationInvariant,
308}
309
310#[derive(Clone, Debug, PartialEq, Eq)]
312pub enum CumulativeAckOutcome {
313 Committed(AckCommitted),
315 NoOp(AckNoOp),
317 Gap(AckGap),
319 Regression(AckRegression),
321}
322
323#[derive(Clone, Debug, PartialEq, Eq)]
333pub struct NonzeroDebtCursorEpisode {
334 conversation_id: ConversationId,
335 debt: ClosureDebt,
336 observer_progress: DeliverySeq,
337 candidate_high_watermark: DeliverySeq,
338 cap_floor: u128,
339 floor: FloorComputation,
340 participants: BTreeMap<ParticipantIndex, DebtParticipantCursor>,
341 facts: CursorProgressFacts,
342}
343
344#[derive(Clone, Copy, Debug, PartialEq, Eq)]
346pub enum CursorFactEncodeError {
347 TooManyParticipants,
349 TooManyFacts,
351 LengthOverflow,
353}
354
355impl CursorProgressFacts {
356 #[must_use]
358 pub const fn new() -> Self {
359 Self {
360 facts: BTreeMap::new(),
361 }
362 }
363
364 pub fn record(&mut self, key: CursorProgressKey) -> bool {
368 if self.facts.contains_key(&key) {
369 return false;
370 }
371 self.facts.insert(key, CursorProgressFact::Pending);
372 true
373 }
374
375 pub fn consume_through(
378 &mut self,
379 participant_index: ParticipantIndex,
380 through: DeliverySeq,
381 ) -> Vec<CursorProgressKey> {
382 let mut consumed = Vec::new();
383 for (key, fact) in &mut self.facts {
384 if key.participant_index == participant_index
385 && key.boundary <= through
386 && *fact == CursorProgressFact::Pending
387 {
388 *fact = CursorProgressFact::Consumed;
389 consumed.push(*key);
390 }
391 }
392 consumed
393 }
394
395 #[must_use]
397 pub fn get(&self, key: CursorProgressKey) -> Option<CursorProgressFact> {
398 self.facts.get(&key).copied()
399 }
400
401 #[must_use]
403 pub fn len(&self) -> usize {
404 self.facts.len()
405 }
406
407 #[must_use]
409 pub fn is_empty(&self) -> bool {
410 self.facts.is_empty()
411 }
412
413 pub fn encode(&self) -> Result<Vec<u8>, CursorFactEncodeError> {
424 let count =
425 u32::try_from(self.facts.len()).map_err(|_| CursorFactEncodeError::TooManyFacts)?;
426 let body_len = self
427 .facts
428 .len()
429 .checked_mul(17)
430 .ok_or(CursorFactEncodeError::LengthOverflow)?;
431 let capacity = 4_usize
432 .checked_add(body_len)
433 .ok_or(CursorFactEncodeError::LengthOverflow)?;
434 let mut bytes = Vec::with_capacity(capacity);
435 bytes.extend_from_slice(&count.to_be_bytes());
436 for (key, fact) in &self.facts {
437 bytes.extend_from_slice(&key.participant_index.to_be_bytes());
438 bytes.extend_from_slice(&key.boundary.to_be_bytes());
439 bytes.push(match fact {
440 CursorProgressFact::Pending => 0,
441 CursorProgressFact::Consumed => 1,
442 });
443 }
444 Ok(bytes)
445 }
446}
447
448impl NonzeroDebtCursorEpisode {
449 #[allow(clippy::too_many_arguments)]
462 pub fn new(
463 conversation_id: ConversationId,
464 debt: ClosureDebt,
465 observer_progress: DeliverySeq,
466 candidate_high_watermark: DeliverySeq,
467 current_floor: u128,
468 cap_floor: u128,
469 participants: Vec<BoundParticipantCursor>,
470 ) -> Result<Self, CursorEpisodeBuildError> {
471 if observer_progress > candidate_high_watermark {
472 return Err(CursorEpisodeBuildError::ObserverBeyondHighWatermark {
473 observer_progress,
474 candidate_high_watermark,
475 });
476 }
477 let retained_end = u128::from(candidate_high_watermark) + 1;
478 if current_floor > retained_end {
479 return Err(CursorEpisodeBuildError::FloorBeyondRetainedEnd {
480 current_floor,
481 retained_end,
482 });
483 }
484
485 let mut indexed = BTreeMap::new();
486 for participant in participants {
487 if participant.cursor > candidate_high_watermark {
488 return Err(CursorEpisodeBuildError::CursorBeyondHighWatermark {
489 participant_id: participant.participant_id,
490 cursor: participant.cursor,
491 candidate_high_watermark,
492 });
493 }
494 if indexed.contains_key(&participant.participant_id) {
495 return Err(CursorEpisodeBuildError::DuplicateParticipant {
496 participant_id: participant.participant_id,
497 });
498 }
499 indexed.insert(
500 participant.participant_id,
501 DebtParticipantCursor::from(participant),
502 );
503 }
504
505 let minimum_member_cursor = indexed.values().map(|participant| participant.cursor).min();
506 let floor = floor_transition(
507 current_floor,
508 minimum_member_cursor,
509 candidate_high_watermark,
510 observer_progress,
511 cap_floor,
512 );
513 let base_floor = current_floor.max(floor.preferred_floor);
514 if cap_floor < base_floor {
515 return Err(CursorEpisodeBuildError::CapacityFloorBelowBase {
516 cap_floor,
517 base_floor,
518 });
519 }
520 let observer_limit = u128::from(observer_progress) + 1;
521 if cap_floor > observer_limit {
522 return Err(CursorEpisodeBuildError::CapacityFloorBeyondObserver {
523 cap_floor,
524 observer_limit,
525 });
526 }
527
528 Ok(Self {
529 conversation_id,
530 debt,
531 observer_progress,
532 candidate_high_watermark,
533 cap_floor,
534 floor,
535 participants: indexed,
536 facts: CursorProgressFacts::new(),
537 })
538 }
539
540 #[allow(clippy::too_many_arguments)]
541 pub(super) fn restore(
542 conversation_id: ConversationId,
543 debt: ClosureDebt,
544 observer_progress: DeliverySeq,
545 candidate_high_watermark: DeliverySeq,
546 current_floor: u128,
547 cap_floor: u128,
548 participants: Vec<BoundParticipantCursor>,
549 facts: Vec<(CursorProgressKey, CursorProgressFact)>,
550 ) -> Option<Self> {
551 let mut episode = Self::new(
552 conversation_id,
553 debt,
554 observer_progress,
555 candidate_high_watermark,
556 current_floor,
557 cap_floor,
558 participants,
559 )
560 .ok()?;
561 if episode.floor.resulting_floor != current_floor {
562 return None;
563 }
564 for (key, fact) in facts {
565 let participant = episode.participants.get(&key.participant_index)?;
566 if key.boundary > candidate_high_watermark
567 || (fact == CursorProgressFact::Consumed && key.boundary > participant.cursor)
568 || (fact == CursorProgressFact::Pending && key.boundary <= participant.cursor)
569 || episode.facts.facts.insert(key, fact).is_some()
570 {
571 return None;
572 }
573 }
574 Some(episode)
575 }
576
577 pub(super) fn from_claim_frontiers(
578 frontiers: &ClaimFrontiers,
579 debt: ClosureDebt,
580 observer_progress: DeliverySeq,
581 ) -> Result<Self, CursorEpisodeBuildError> {
582 let candidate_high_watermark = frontiers.sequence().ledger().high_watermark();
583 if observer_progress > candidate_high_watermark {
584 return Err(CursorEpisodeBuildError::ObserverBeyondHighWatermark {
585 observer_progress,
586 candidate_high_watermark,
587 });
588 }
589 let current_floor = frontiers.retained_floor();
590 let retained_end = u128::from(candidate_high_watermark) + 1;
591 if current_floor > retained_end {
592 return Err(CursorEpisodeBuildError::FloorBeyondRetainedEnd {
593 current_floor,
594 retained_end,
595 });
596 }
597 let participants = frontiers
598 .active_identities()
599 .participants()
600 .iter()
601 .map(|participant| {
602 (
603 participant.participant_index(),
604 DebtParticipantCursor {
605 participant_id: participant.participant_index(),
606 binding: participant.binding(),
607 cursor: participant.cursor(),
608 },
609 )
610 })
611 .collect::<BTreeMap<_, _>>();
612 let minimum_member_cursor = participants
613 .values()
614 .map(|participant| participant.cursor)
615 .min();
616 let preferred = floor_transition(
617 current_floor,
618 minimum_member_cursor,
619 candidate_high_watermark,
620 observer_progress,
621 current_floor,
622 );
623 let cap_floor = current_floor.max(preferred.preferred_floor);
624 let floor = floor_transition(
625 current_floor,
626 minimum_member_cursor,
627 candidate_high_watermark,
628 observer_progress,
629 cap_floor,
630 );
631 Ok(Self {
632 conversation_id: frontiers.conversation_id(),
633 debt,
634 observer_progress,
635 candidate_high_watermark,
636 cap_floor,
637 floor,
638 participants,
639 facts: CursorProgressFacts::new(),
640 })
641 }
642
643 pub(super) fn reconcile_claim_frontiers(
644 mut self,
645 frontiers: &ClaimFrontiers,
646 debt: ClosureDebt,
647 observer_progress: DeliverySeq,
648 ) -> Result<Self, CursorEpisodeBuildError> {
649 let mut next = Self::from_claim_frontiers(frontiers, debt, observer_progress)?;
650 for (key, fact) in self.facts.facts {
651 let Some(participant) = next.participants.get(&key.participant_index) else {
652 continue;
653 };
654 if key.boundary <= next.candidate_high_watermark {
655 let reconciled = if key.boundary <= participant.cursor {
656 CursorProgressFact::Consumed
657 } else {
658 fact
659 };
660 next.facts.facts.insert(key, reconciled);
661 }
662 }
663 for participant in next.participants.values() {
664 let _ = next
665 .facts
666 .consume_through(participant.participant_id, participant.cursor);
667 }
668 self.participants.clear();
669 Ok(next)
670 }
671
672 pub(super) fn participant_binding(
673 &self,
674 participant_index: ParticipantIndex,
675 ) -> Option<(FrontierBinding, DeliverySeq)> {
676 self.participants
677 .get(&participant_index)
678 .map(|participant| (participant.binding, participant.cursor))
679 }
680
681 pub(super) const fn conversation_id(&self) -> ConversationId {
684 self.conversation_id
685 }
686
687 #[must_use]
689 pub const fn debt(&self) -> ClosureDebt {
690 self.debt
691 }
692
693 #[must_use]
695 pub const fn observer_progress(&self) -> DeliverySeq {
696 self.observer_progress
697 }
698
699 #[must_use]
701 pub const fn candidate_high_watermark(&self) -> DeliverySeq {
702 self.candidate_high_watermark
703 }
704
705 #[must_use]
707 pub const fn cap_floor(&self) -> u128 {
708 self.cap_floor
709 }
710
711 #[must_use]
713 pub const fn floor_computation(&self) -> FloorComputation {
714 self.floor
715 }
716
717 #[must_use]
719 pub fn retained_suffix_start(&self) -> Option<DeliverySeq> {
720 if self.floor.resulting_floor > u128::from(self.candidate_high_watermark) {
721 return None;
722 }
723 DeliverySeq::try_from(self.floor.resulting_floor).ok()
724 }
725
726 #[must_use]
728 pub fn retains(&self, delivery_seq: DeliverySeq) -> bool {
729 u128::from(delivery_seq) >= self.floor.resulting_floor
730 && delivery_seq <= self.candidate_high_watermark
731 }
732
733 #[must_use]
735 pub fn participant(
736 &self,
737 participant_index: ParticipantIndex,
738 ) -> Option<BoundParticipantCursor> {
739 let participant = self.participants.get(&participant_index).copied()?;
740 let FrontierBinding::Bound(epoch) = participant.binding else {
741 return None;
742 };
743 Some(BoundParticipantCursor::new(
744 participant.participant_id,
745 epoch,
746 participant.cursor,
747 ))
748 }
749
750 #[must_use]
752 pub const fn facts(&self) -> &CursorProgressFacts {
753 &self.facts
754 }
755
756 pub fn acknowledge(
775 &mut self,
776 participant_index: ParticipantIndex,
777 receiving_binding_epoch: BindingEpoch,
778 request: &ParticipantAck,
779 contiguously_available_through: DeliverySeq,
780 ) -> Result<CumulativeAckOutcome, CumulativeAckAuthorizationError> {
781 let available_through = contiguously_available_through.min(self.candidate_high_watermark);
782 self.acknowledge_by_endpoint(
783 participant_index,
784 receiving_binding_epoch,
785 request,
786 move |_, _, endpoint| Ok(endpoint <= available_through),
787 )
788 }
789
790 pub fn acknowledge_with_obligations(
803 &mut self,
804 participant_index: ParticipantIndex,
805 receiving_binding_epoch: BindingEpoch,
806 request: &ParticipantAck,
807 obligations: &RecipientAckObligations,
808 ) -> Result<CumulativeAckOutcome, CumulativeAckAuthorizationError> {
809 self.acknowledge_by_endpoint(
810 participant_index,
811 receiving_binding_epoch,
812 request,
813 |participant_id, acknowledged_through, endpoint| {
814 obligations
815 .contains_endpoint(participant_id, acknowledged_through, endpoint)
816 .map_err(|error| CumulativeAckAuthorizationError::ObligationContext { error })
817 },
818 )
819 }
820
821 fn acknowledge_by_endpoint<F>(
822 &mut self,
823 participant_index: ParticipantIndex,
824 receiving_binding_epoch: BindingEpoch,
825 request: &ParticipantAck,
826 endpoint_is_available: F,
827 ) -> Result<CumulativeAckOutcome, CumulativeAckAuthorizationError>
828 where
829 F: FnOnce(
830 ParticipantId,
831 DeliverySeq,
832 DeliverySeq,
833 ) -> Result<bool, CumulativeAckAuthorizationError>,
834 {
835 let Some(participant) = self.participants.get(&participant_index).copied() else {
836 return Err(CumulativeAckAuthorizationError::ParticipantIndexUnknown);
837 };
838 if request.conversation_id != self.conversation_id {
839 return Err(CumulativeAckAuthorizationError::ConversationMismatch);
840 }
841 if request.participant_id != participant.participant_id {
842 return Err(CumulativeAckAuthorizationError::ParticipantMismatch);
843 }
844 let FrontierBinding::Bound(active_binding_epoch) = participant.binding else {
845 return Err(CumulativeAckAuthorizationError::BindingEpochMismatch);
846 };
847 if request.capability_generation != active_binding_epoch.capability_generation {
848 return Err(CumulativeAckAuthorizationError::GenerationMismatch);
849 }
850 if receiving_binding_epoch != active_binding_epoch {
851 return Err(CumulativeAckAuthorizationError::BindingEpochMismatch);
852 }
853
854 let current_cursor = participant.cursor;
855 let through_seq = request.through_seq;
856 let envelope = ParticipantAckEnvelope {
857 conversation_id: request.conversation_id,
858 participant_id: request.participant_id,
859 capability_generation: request.capability_generation,
860 through_seq,
861 };
862 let endpoint_available =
863 endpoint_is_available(participant.participant_id, current_cursor, through_seq)?;
864
865 if through_seq < current_cursor {
866 return AckRegression::new(envelope, current_cursor)
867 .map(CumulativeAckOutcome::Regression)
868 .ok_or(CumulativeAckAuthorizationError::CursorRelationInvariant);
869 }
870 if through_seq == current_cursor {
871 return Ok(CumulativeAckOutcome::NoOp(AckNoOp::participant_ack(
872 envelope,
873 )));
874 }
875 if through_seq > self.candidate_high_watermark || !endpoint_available {
876 return AckGap::new(envelope, current_cursor)
877 .map(CumulativeAckOutcome::Gap)
878 .ok_or(CumulativeAckAuthorizationError::CursorRelationInvariant);
879 }
880
881 let Some(stored) = self.participants.get_mut(&participant_index) else {
882 return Err(CumulativeAckAuthorizationError::ParticipantIndexUnknown);
883 };
884 stored.advance_to(through_seq);
885 let key = CursorProgressKey {
886 participant_index,
887 boundary: through_seq,
888 };
889 self.facts.record(key);
890 let _ = self.facts.consume_through(participant_index, through_seq);
891
892 let minimum_member_cursor = self
893 .participants
894 .values()
895 .map(|participant| participant.cursor)
896 .min();
897 self.floor = floor_transition(
898 self.floor.resulting_floor,
899 minimum_member_cursor,
900 self.candidate_high_watermark,
901 self.observer_progress,
902 self.cap_floor,
903 );
904 self.cap_floor = self.floor.resulting_floor;
907
908 Ok(CumulativeAckOutcome::Committed(AckCommitted::new(envelope)))
909 }
910
911 pub fn encode(&self) -> Result<Vec<u8>, CursorFactEncodeError> {
928 let participant_count = u32::try_from(self.participants.len())
929 .map_err(|_| CursorFactEncodeError::TooManyParticipants)?;
930 let fact_count =
931 u32::try_from(self.facts.len()).map_err(|_| CursorFactEncodeError::TooManyFacts)?;
932 let participant_bytes = self
933 .participants
934 .len()
935 .checked_mul(41)
936 .ok_or(CursorFactEncodeError::LengthOverflow)?;
937 let fact_bytes = self
938 .facts
939 .len()
940 .checked_mul(17)
941 .ok_or(CursorFactEncodeError::LengthOverflow)?;
942 let capacity = 96_usize
943 .checked_add(participant_bytes)
944 .and_then(|length| length.checked_add(fact_bytes))
945 .ok_or(CursorFactEncodeError::LengthOverflow)?;
946
947 let debt = self.debt.value();
948 let mut bytes = Vec::with_capacity(capacity);
949 bytes.extend_from_slice(&self.conversation_id.to_be_bytes());
950 bytes.extend_from_slice(&debt.entries.to_be_bytes());
951 bytes.extend_from_slice(&debt.bytes.to_be_bytes());
952 bytes.extend_from_slice(&self.observer_progress.to_be_bytes());
953 bytes.extend_from_slice(&self.candidate_high_watermark.to_be_bytes());
954 bytes.extend_from_slice(&self.floor.resulting_floor.to_be_bytes());
955 bytes.extend_from_slice(&self.cap_floor.to_be_bytes());
956 bytes.extend_from_slice(&participant_count.to_be_bytes());
957 for participant in self.participants.values() {
958 bytes.push(match participant.binding {
959 FrontierBinding::Bound(_) => 0,
960 FrontierBinding::Detached(_) => 1,
961 });
962 bytes.extend_from_slice(&participant.participant_id.to_be_bytes());
963 let binding_epoch = participant.binding_epoch();
964 bytes.extend_from_slice(
965 &binding_epoch
966 .connection_incarnation
967 .server_incarnation
968 .to_be_bytes(),
969 );
970 bytes.extend_from_slice(
971 &binding_epoch
972 .connection_incarnation
973 .connection_ordinal
974 .to_be_bytes(),
975 );
976 bytes.extend_from_slice(&binding_epoch.capability_generation.get().to_be_bytes());
977 bytes.extend_from_slice(&participant.cursor.to_be_bytes());
978 }
979 bytes.extend_from_slice(&fact_count.to_be_bytes());
980 for (key, fact) in &self.facts.facts {
981 bytes.extend_from_slice(&key.participant_index.to_be_bytes());
982 bytes.extend_from_slice(&key.boundary.to_be_bytes());
983 bytes.push(match fact {
984 CursorProgressFact::Pending => 0,
985 CursorProgressFact::Consumed => 1,
986 });
987 }
988 Ok(bytes)
989 }
990}