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