liminal_protocol/lifecycle/admission/
order.rs1use alloc::boxed::Box;
2
3use crate::wire::{ConversationOrderExhausted, OrderAllocatingEnvelope, TransactionOrder};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub struct OrderClaims {
8 active_binding_terminals: u64,
9 membership_exits: u64,
10 recovery_operation: bool,
11 recovery_replacement_terminal: bool,
12}
13
14impl OrderClaims {
15 pub const fn new(
23 active_binding_terminals: u64,
24 membership_exits: u64,
25 recovery_operation: bool,
26 recovery_replacement_terminal: bool,
27 ) -> Result<Self, OrderClaimsInvariantError> {
28 if recovery_operation != recovery_replacement_terminal {
29 return Err(OrderClaimsInvariantError::RecoveryPairMismatch {
30 recovery_operation,
31 recovery_replacement_terminal,
32 });
33 }
34 Ok(Self {
35 active_binding_terminals,
36 membership_exits,
37 recovery_operation,
38 recovery_replacement_terminal,
39 })
40 }
41
42 #[must_use]
44 pub const fn active_binding_terminals(self) -> u64 {
45 self.active_binding_terminals
46 }
47
48 #[must_use]
50 pub const fn membership_exits(self) -> u64 {
51 self.membership_exits
52 }
53
54 #[must_use]
56 pub const fn recovery_operation(self) -> bool {
57 self.recovery_operation
58 }
59
60 #[must_use]
62 pub const fn recovery_replacement_terminal(self) -> bool {
63 self.recovery_replacement_terminal
64 }
65
66 #[must_use]
68 pub fn total(self) -> u128 {
69 u128::from(self.active_binding_terminals)
70 + u128::from(self.membership_exits)
71 + u128::from(self.recovery_operation)
72 + u128::from(self.recovery_replacement_terminal)
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum OrderClaimsInvariantError {
79 RecoveryPairMismatch {
81 recovery_operation: bool,
83 recovery_replacement_terminal: bool,
85 },
86}
87
88#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
90pub enum OrderHigh {
91 #[default]
93 Empty,
94 Allocated(TransactionOrder),
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum OrderLedgerInvariantError {
101 ClaimsExceedRemaining {
103 remaining: u128,
105 claims: u128,
107 },
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub struct OrderLedger {
125 high: OrderHigh,
126 claims: OrderClaims,
127}
128
129impl OrderLedger {
130 pub fn try_new(
136 high: OrderHigh,
137 claims: OrderClaims,
138 ) -> Result<Self, OrderLedgerInvariantError> {
139 let remaining = remaining_after(high);
140 let claim_count = claims.total();
141 if claim_count > remaining {
142 return Err(OrderLedgerInvariantError::ClaimsExceedRemaining {
143 remaining,
144 claims: claim_count,
145 });
146 }
147 Ok(Self { high, claims })
148 }
149
150 #[must_use]
152 pub const fn high(self) -> OrderHigh {
153 self.high
154 }
155
156 #[must_use]
158 pub const fn claims(self) -> OrderClaims {
159 self.claims
160 }
161
162 #[must_use]
164 pub fn remaining(self) -> u128 {
165 remaining_after(self.high)
166 }
167
168 #[cfg(test)]
179 pub(crate) fn plan_enrollment(self) -> Result<ResultingOrderClaims, OrderAdmissionError> {
180 self.plan_enrollment_with_recovery_quartet(false)
181 }
182
183 pub(crate) fn plan_enrollment_with_recovery_quartet(
190 self,
191 endow_recovery_quartet: bool,
192 ) -> Result<ResultingOrderClaims, OrderAdmissionError> {
193 self.ensure_quartet_can_be_endowed(endow_recovery_quartet)?;
194 let active_binding_terminals = self
195 .claims
196 .active_binding_terminals
197 .checked_add(1)
198 .ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
199 let membership_exits = self
200 .claims
201 .membership_exits
202 .checked_add(1)
203 .ok_or(OrderAdmissionError::MembershipExitClaimOverflow)?;
204 Ok(ResultingOrderClaims(OrderClaims {
205 active_binding_terminals,
206 membership_exits,
207 recovery_operation: self.claims.recovery_operation || endow_recovery_quartet,
208 recovery_replacement_terminal: self.claims.recovery_replacement_terminal
209 || endow_recovery_quartet,
210 }))
211 }
212
213 #[cfg(test)]
222 pub(crate) fn plan_detached_attach(self) -> Result<ResultingOrderClaims, OrderAdmissionError> {
223 let active_binding_terminals = self
224 .claims
225 .active_binding_terminals
226 .checked_add(1)
227 .ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
228 Ok(ResultingOrderClaims(OrderClaims {
229 active_binding_terminals,
230 ..self.claims
231 }))
232 }
233
234 #[cfg(test)]
239 #[must_use]
240 pub(crate) const fn plan_supersession(self) -> ResultingOrderClaims {
241 ResultingOrderClaims(self.claims)
242 }
243
244 #[must_use]
246 pub(crate) const fn plan_ordinary_record(self) -> ResultingOrderClaims {
247 ResultingOrderClaims(self.claims)
248 }
249
250 const fn ensure_quartet_can_be_endowed(
251 self,
252 endow_recovery_quartet: bool,
253 ) -> Result<(), OrderAdmissionError> {
254 if endow_recovery_quartet
255 && (self.claims.recovery_operation || self.claims.recovery_replacement_terminal)
256 {
257 return Err(OrderAdmissionError::RecoveryOrderReserveAlreadyPresent);
258 }
259 Ok(())
260 }
261
262 pub(in crate::lifecycle) fn apply_fenced_recovery(self) -> Result<Self, OrderAdmissionError> {
275 if !self.claims.recovery_operation || !self.claims.recovery_replacement_terminal {
276 return Err(OrderAdmissionError::RecoveryOrderReserveMissing {
277 recovery_operation: self.claims.recovery_operation,
278 recovery_replacement_terminal: self.claims.recovery_replacement_terminal,
279 });
280 }
281 let active_binding_terminals = self
282 .claims
283 .active_binding_terminals
284 .checked_add(1)
285 .ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
286 Ok(Self {
287 high: self.high,
288 claims: OrderClaims {
289 active_binding_terminals,
290 membership_exits: self.claims.membership_exits,
291 recovery_operation: false,
292 recovery_replacement_terminal: false,
293 },
294 })
295 }
296}
297
298#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub struct ResultingOrderClaims(OrderClaims);
304
305impl ResultingOrderClaims {
306 const fn claims(self) -> OrderClaims {
307 self.0
308 }
309}
310
311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
313pub struct OrderAllocation {
314 major: TransactionOrder,
315 resulting: OrderLedger,
316}
317
318impl OrderAllocation {
319 #[must_use]
321 pub const fn major(self) -> TransactionOrder {
322 self.major
323 }
324
325 #[must_use]
327 pub const fn resulting(self) -> OrderLedger {
328 self.resulting
329 }
330}
331
332#[derive(Clone, Debug, PartialEq, Eq)]
334pub enum OrderAdmissionError {
335 Exhausted(Box<ConversationOrderExhausted>),
337 InitialClaimsExceedRemaining {
339 remaining: u128,
341 resulting_claims: u128,
343 },
344 ActiveBindingClaimOverflow,
346 MembershipExitClaimOverflow,
348 RecoveryOrderReserveAlreadyPresent,
350 RecoveryOrderReserveMissing {
352 recovery_operation: bool,
354 recovery_replacement_terminal: bool,
356 },
357}
358
359pub fn allocate_order(
370 request: OrderAllocatingEnvelope,
371 current: OrderLedger,
372 resulting_claims: ResultingOrderClaims,
373) -> Result<OrderAllocation, OrderAdmissionError> {
374 let resulting_claims = resulting_claims.claims();
375 let resulting_claim_count = resulting_claims.total();
376 match current.high {
377 OrderHigh::Empty => {
378 let major = 0;
379 let resulting_remaining = u128::from(u64::MAX);
380 if resulting_claim_count > resulting_remaining {
381 return Err(OrderAdmissionError::InitialClaimsExceedRemaining {
382 remaining: resulting_remaining,
383 resulting_claims: resulting_claim_count,
384 });
385 }
386 Ok(OrderAllocation {
387 major,
388 resulting: OrderLedger {
389 high: OrderHigh::Allocated(major),
390 claims: resulting_claims,
391 },
392 })
393 }
394 OrderHigh::Allocated(high) => {
395 let order_remaining = current.remaining();
396 let reserved_claims = current.claims.total();
397 let next = high.checked_add(1);
398 let resulting_order_remaining = next.map_or(0, |_| order_remaining - 1);
399 let Some(major) = next else {
400 return Err(OrderAdmissionError::Exhausted(Box::new(
401 ConversationOrderExhausted::new(
402 request,
403 high,
404 order_remaining,
405 reserved_claims,
406 resulting_order_remaining,
407 resulting_claim_count,
408 ),
409 )));
410 };
411 if resulting_claim_count > resulting_order_remaining {
412 return Err(OrderAdmissionError::Exhausted(Box::new(
413 ConversationOrderExhausted::new(
414 request,
415 high,
416 order_remaining,
417 reserved_claims,
418 resulting_order_remaining,
419 resulting_claim_count,
420 ),
421 )));
422 }
423 Ok(OrderAllocation {
424 major,
425 resulting: OrderLedger {
426 high: OrderHigh::Allocated(major),
427 claims: resulting_claims,
428 },
429 })
430 }
431 }
432}
433
434fn remaining_after(high: OrderHigh) -> u128 {
435 match high {
436 OrderHigh::Empty => u128::from(u64::MAX) + 1,
437 OrderHigh::Allocated(high) => u128::from(u64::MAX - high),
438 }
439}