liminal_protocol/lifecycle/operations/
binding_terminal.rs1use alloc::boxed::Box;
2
3use crate::{
4 algebra::ResourceVector,
5 lifecycle::{ActiveBinding, AdmissionOrder},
6 wire::{BindingEpoch, ConversationId, DeliverySeq, ParticipantId, TransactionOrder},
7};
8
9use super::{LiveFrontierError, LiveFrontierOwner, RetainedRecordCharge};
10use crate::lifecycle::{CommittedBindingTerminalPosition, PendingBindingTerminalPosition};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum BindingTerminalEncoding {
15 ParticipantLifecycleV3CanonicalJson,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum BindingTerminalCauseClass {
22 Detached,
24 Died,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct CandidateTerminalKey {
31 active_binding: ActiveBinding,
32 cause_class: BindingTerminalCauseClass,
33 admission_order: AdmissionOrder,
34 delivery_seq: DeliverySeq,
35}
36
37impl CandidateTerminalKey {
38 #[must_use]
40 pub const fn conversation_id(self) -> ConversationId {
41 self.active_binding.conversation_id
42 }
43
44 #[must_use]
46 pub const fn participant_id(self) -> ParticipantId {
47 self.active_binding.participant_id
48 }
49
50 #[must_use]
52 pub const fn binding_epoch(self) -> BindingEpoch {
53 self.active_binding.binding_epoch
54 }
55
56 #[must_use]
58 pub const fn cause_class(self) -> BindingTerminalCauseClass {
59 self.cause_class
60 }
61
62 #[must_use]
64 pub const fn admission_order(self) -> AdmissionOrder {
65 self.admission_order
66 }
67
68 #[must_use]
70 pub const fn delivery_seq(self) -> DeliverySeq {
71 self.delivery_seq
72 }
73
74 #[must_use]
76 pub const fn bind_v3_charge(
77 self,
78 encoded_charge: ResourceVector,
79 ) -> BindingTerminalCandidateCharge {
80 BindingTerminalCandidateCharge {
81 conversation_id: self.conversation_id(),
82 participant_id: self.participant_id(),
83 binding_epoch: self.binding_epoch(),
84 admission_order: self.admission_order,
85 delivery_seq: self.delivery_seq,
86 encoding: BindingTerminalEncoding::ParticipantLifecycleV3CanonicalJson,
87 charge: RetainedRecordCharge::new(
88 self.delivery_seq,
89 self.admission_order,
90 encoded_charge,
91 ),
92 }
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct BindingTerminalCandidateCharge {
99 conversation_id: ConversationId,
100 participant_id: ParticipantId,
101 binding_epoch: BindingEpoch,
102 admission_order: AdmissionOrder,
103 delivery_seq: DeliverySeq,
104 encoding: BindingTerminalEncoding,
105 charge: RetainedRecordCharge,
106}
107
108impl BindingTerminalCandidateCharge {
109 #[must_use]
111 pub const fn retained_charge(self) -> RetainedRecordCharge {
112 self.charge
113 }
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub enum BindingTerminalPrepareError {
119 Authority,
121 TransactionOrder,
123 DeliverySequence,
125 ObserverProgress,
127}
128
129#[derive(Debug, PartialEq, Eq)]
131pub struct BindingTerminalPrepareRefused {
132 owner: LiveFrontierOwner,
133 error: BindingTerminalPrepareError,
134}
135
136impl BindingTerminalPrepareRefused {
137 #[must_use]
139 pub const fn error(&self) -> BindingTerminalPrepareError {
140 self.error
141 }
142
143 #[must_use]
145 pub fn into_owner(self) -> LiveFrontierOwner {
146 self.owner
147 }
148}
149
150#[derive(Debug, PartialEq, Eq)]
152pub struct PreparedBindingTerminal {
153 owner: LiveFrontierOwner,
154 key: CandidateTerminalKey,
155 hard_observer_progress: DeliverySeq,
156}
157
158impl PreparedBindingTerminal {
159 #[must_use]
161 pub const fn candidate_key(&self) -> CandidateTerminalKey {
162 self.key
163 }
164
165 #[must_use]
167 pub const fn hard_observer_progress(&self) -> DeliverySeq {
168 self.hard_observer_progress
169 }
170
171 #[must_use]
173 pub fn admit(self, candidate: BindingTerminalCandidateCharge) -> BindingTerminalAdmission {
174 if candidate.conversation_id != self.key.conversation_id()
175 || candidate.participant_id != self.key.participant_id()
176 || candidate.binding_epoch != self.key.binding_epoch()
177 || candidate.admission_order != self.key.admission_order()
178 || candidate.delivery_seq != self.key.delivery_seq()
179 || candidate.encoding != BindingTerminalEncoding::ParticipantLifecycleV3CanonicalJson
180 || candidate.charge.delivery_seq() != self.key.delivery_seq()
181 || candidate.charge.admission_order() != self.key.admission_order()
182 || candidate.charge.encoded_charge().entries != 1
183 {
184 return admit_refusal(self.owner, BindingTerminalAdmitError::CandidateCharge);
185 }
186
187 let retained_len = u64::try_from(self.owner.retained_charges().len());
188 let has_capacity = retained_len
189 .ok()
190 .and_then(|len| len.checked_add(1))
191 .is_some_and(|len| len <= self.owner.retained_record_limit());
192 if has_capacity {
193 return match self.owner.commit_binding_terminal_candidate(
194 self.key.active_binding,
195 self.key.admission_order,
196 self.key.delivery_seq,
197 candidate.charge,
198 ) {
199 Ok(owner) => BindingTerminalAdmission::Commit(BindingTerminalCommit {
200 owner,
201 position: CommittedBindingTerminalPosition::new(
202 self.key.admission_order.transaction_order(),
203 self.key.delivery_seq,
204 ),
205 }),
206 Err(failure) => {
207 let (owner, error) = *failure;
208 admit_refusal(owner, map_live_frontier_error(error))
209 }
210 };
211 }
212 if self.hard_observer_progress < self.key.delivery_seq {
213 return match self.owner.pend_binding_terminal_candidate(
214 self.key.active_binding,
215 self.key.admission_order,
216 self.key.delivery_seq,
217 ) {
218 Ok(owner) => BindingTerminalAdmission::Pending(BindingTerminalPending {
219 owner,
220 position: PendingBindingTerminalPosition::new(
221 self.key.admission_order.transaction_order(),
222 ),
223 blocked_at_observer: self.hard_observer_progress,
224 }),
225 Err(failure) => {
226 let (owner, error) = *failure;
227 admit_refusal(owner, map_live_frontier_error(error))
228 }
229 };
230 }
231 admit_refusal(self.owner, BindingTerminalAdmitError::RetainedRecordLimit)
232 }
233
234 #[must_use]
236 pub fn into_owner(self) -> LiveFrontierOwner {
237 self.owner
238 }
239}
240
241#[derive(Debug, PartialEq, Eq)]
243pub struct BindingTerminalCommit {
244 owner: LiveFrontierOwner,
245 position: CommittedBindingTerminalPosition,
246}
247
248impl BindingTerminalCommit {
249 #[must_use]
251 pub fn into_parts(self) -> (LiveFrontierOwner, CommittedBindingTerminalPosition) {
252 (self.owner, self.position)
253 }
254}
255
256#[derive(Debug, PartialEq, Eq)]
258pub struct BindingTerminalPending {
259 owner: LiveFrontierOwner,
260 position: PendingBindingTerminalPosition,
261 blocked_at_observer: DeliverySeq,
262}
263
264impl BindingTerminalPending {
265 #[must_use]
267 pub const fn blocked_at_observer(&self) -> DeliverySeq {
268 self.blocked_at_observer
269 }
270
271 #[must_use]
273 pub fn into_parts(self) -> (LiveFrontierOwner, PendingBindingTerminalPosition) {
274 (self.owner, self.position)
275 }
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq)]
280pub enum BindingTerminalAdmitError {
281 CandidateCharge,
283 RetainedRecordLimit,
285 Authority,
287 Precedence,
289 Frontier,
291 ClosureAccounting,
293}
294
295#[derive(Debug, PartialEq, Eq)]
297pub struct BindingTerminalAdmitRefused {
298 owner: LiveFrontierOwner,
299 error: BindingTerminalAdmitError,
300}
301
302impl BindingTerminalAdmitRefused {
303 #[must_use]
305 pub const fn error(&self) -> BindingTerminalAdmitError {
306 self.error
307 }
308
309 #[must_use]
311 pub fn into_owner(self) -> LiveFrontierOwner {
312 self.owner
313 }
314}
315
316#[derive(Debug, PartialEq, Eq)]
318pub enum BindingTerminalAdmission {
319 Commit(BindingTerminalCommit),
321 Pending(BindingTerminalPending),
323 Refused(Box<BindingTerminalAdmitRefused>),
325}
326
327fn admit_refusal(
328 owner: LiveFrontierOwner,
329 error: BindingTerminalAdmitError,
330) -> BindingTerminalAdmission {
331 BindingTerminalAdmission::Refused(Box::new(BindingTerminalAdmitRefused { owner, error }))
332}
333
334const fn map_live_frontier_error(error: LiveFrontierError) -> BindingTerminalAdmitError {
335 match error {
336 LiveFrontierError::Authority => BindingTerminalAdmitError::Authority,
337 LiveFrontierError::Precedence => BindingTerminalAdmitError::Precedence,
338 LiveFrontierError::RetainedCharge => BindingTerminalAdmitError::CandidateCharge,
339 LiveFrontierError::RetainedRecordLimit => BindingTerminalAdmitError::RetainedRecordLimit,
340 LiveFrontierError::Frontier => BindingTerminalAdmitError::Frontier,
341 LiveFrontierError::ClosureAccounting => BindingTerminalAdmitError::ClosureAccounting,
342 }
343}
344
345impl LiveFrontierOwner {
346 pub fn prepare_binding_terminal(
353 self,
354 active_binding: ActiveBinding,
355 cause_class: BindingTerminalCauseClass,
356 next_transaction_order: TransactionOrder,
357 next_delivery_sequence: DeliverySeq,
358 hard_observer_progress: DeliverySeq,
359 ) -> Result<PreparedBindingTerminal, Box<BindingTerminalPrepareRefused>> {
360 let authority_matches = active_binding.conversation_id
361 == self.frontiers().conversation_id()
362 && self
363 .frontiers()
364 .active_identities()
365 .participants()
366 .iter()
367 .any(|participant| {
368 participant.participant_index() == active_binding.participant_id
369 && participant.binding()
370 == crate::lifecycle::FrontierBinding::Bound(
371 active_binding.binding_epoch,
372 )
373 });
374 if !authority_matches {
375 return prepare_refusal(self, BindingTerminalPrepareError::Authority);
376 }
377 let expected_order = match self.frontiers().order().ledger().high() {
378 crate::lifecycle::OrderHigh::Empty => Some(0),
379 crate::lifecycle::OrderHigh::Allocated(high) => high.checked_add(1),
380 };
381 if expected_order != Some(next_transaction_order) {
382 return prepare_refusal(self, BindingTerminalPrepareError::TransactionOrder);
383 }
384 if self
385 .frontiers()
386 .sequence()
387 .ledger()
388 .high_watermark()
389 .checked_add(1)
390 != Some(next_delivery_sequence)
391 {
392 return prepare_refusal(self, BindingTerminalPrepareError::DeliverySequence);
393 }
394 if hard_observer_progress > self.frontiers().sequence().ledger().high_watermark() {
395 return prepare_refusal(self, BindingTerminalPrepareError::ObserverProgress);
396 }
397 Ok(PreparedBindingTerminal {
398 owner: self,
399 key: CandidateTerminalKey {
400 active_binding,
401 cause_class,
402 admission_order: AdmissionOrder::binding_terminal(
403 next_transaction_order,
404 active_binding.participant_id,
405 ),
406 delivery_seq: next_delivery_sequence,
407 },
408 hard_observer_progress,
409 })
410 }
411}
412
413fn prepare_refusal(
414 owner: LiveFrontierOwner,
415 error: BindingTerminalPrepareError,
416) -> Result<PreparedBindingTerminal, Box<BindingTerminalPrepareRefused>> {
417 Err(Box::new(BindingTerminalPrepareRefused { owner, error }))
418}