chio_kernel/admission_operation/projection/
channel_terminal_authority.rs1use super::*;
2use crate::tool_outcome::sign_channel_terminal_outcome_commitment;
3use crate::Keypair;
4use chio_settle::channel::{
5 SignedChannelTerminalOutcomeCommitmentV1, VerifiedAdmittedChannelReservationV1,
6};
7
8#[derive(Debug, thiserror::Error)]
9pub enum ChannelTerminalAuthorityError {
10 #[error("channel terminal authority is unavailable: {0}")]
11 Unavailable(String),
12 #[error("channel terminal reservation was not found")]
13 NotFound,
14 #[error("channel terminal authority was fenced")]
15 Fenced,
16 #[error("channel terminal authority conflict: {0}")]
17 Conflict(String),
18 #[error("channel terminal authority outcome is unknown: {0}")]
19 OutcomeUnknown(String),
20 #[error("channel terminal authority returned mismatched evidence")]
21 BindingMismatch,
22}
23
24pub struct ChannelTerminalAdvanceRequest<'a> {
25 operation: &'a AdmissionOperationV1,
26 context: &'a AdmissionProjectionContext,
27 reservation: &'a VerifiedAdmittedChannelReservationV1,
28 receipt: &'a VerifiedAdmissionReceipt,
29 terminal_outcome: &'a SignedChannelTerminalOutcomeCommitmentV1,
30}
31
32impl<'a> ChannelTerminalAdvanceRequest<'a> {
33 #[must_use]
34 pub const fn operation(&self) -> &'a AdmissionOperationV1 {
35 self.operation
36 }
37
38 #[must_use]
39 pub const fn context(&self) -> &'a AdmissionProjectionContext {
40 self.context
41 }
42
43 #[must_use]
44 pub const fn reservation(&self) -> &'a VerifiedAdmittedChannelReservationV1 {
45 self.reservation
46 }
47
48 #[must_use]
49 pub const fn receipt(&self) -> &'a VerifiedAdmissionReceipt {
50 self.receipt
51 }
52
53 #[must_use]
54 pub const fn terminal_outcome(&self) -> &'a SignedChannelTerminalOutcomeCommitmentV1 {
55 self.terminal_outcome
56 }
57}
58
59#[derive(Debug, Clone)]
60pub struct PreparedChannelTerminalProjectionV1 {
61 reservation: VerifiedAdmittedChannelReservationV1,
62 terminal_outcome: SignedChannelTerminalOutcomeCommitmentV1,
63 advance: VerifiedChannelTerminalAdvanceV1,
64 channel: VerifiedChannelTerminalProjectionV1,
65 obligation: Option<ObligationProjection>,
66}
67
68impl PreparedChannelTerminalProjectionV1 {
69 #[must_use]
70 pub const fn reservation(&self) -> &VerifiedAdmittedChannelReservationV1 {
71 &self.reservation
72 }
73
74 #[must_use]
75 pub const fn terminal_outcome(&self) -> &SignedChannelTerminalOutcomeCommitmentV1 {
76 &self.terminal_outcome
77 }
78
79 #[must_use]
80 pub const fn advance(&self) -> &VerifiedChannelTerminalAdvanceV1 {
81 &self.advance
82 }
83
84 #[must_use]
85 pub const fn channel(&self) -> &VerifiedChannelTerminalProjectionV1 {
86 &self.channel
87 }
88
89 #[must_use]
90 pub const fn obligation(&self) -> Option<&ObligationProjection> {
91 self.obligation.as_ref()
92 }
93}
94
95pub struct ChannelTerminalCommitRequest<'a> {
96 prepared: &'a PreparedChannelTerminalProjectionV1,
97 recovery_lease: &'a AdmissionRecoveryLease,
98 envelope: &'a SignedAdmissionTerminalProjectionV1,
99 active_fence: &'a StoreMutationFence,
100 trusted_now_unix_ms: u64,
101}
102
103impl<'a> ChannelTerminalCommitRequest<'a> {
104 #[must_use]
105 pub const fn prepared(&self) -> &'a PreparedChannelTerminalProjectionV1 {
106 self.prepared
107 }
108
109 #[must_use]
110 pub const fn recovery_lease(&self) -> &'a AdmissionRecoveryLease {
111 self.recovery_lease
112 }
113
114 #[must_use]
115 pub const fn envelope(&self) -> &'a SignedAdmissionTerminalProjectionV1 {
116 self.envelope
117 }
118
119 #[must_use]
120 pub const fn active_fence(&self) -> &'a StoreMutationFence {
121 self.active_fence
122 }
123
124 #[must_use]
125 pub const fn trusted_now_unix_ms(&self) -> u64 {
126 self.trusted_now_unix_ms
127 }
128}
129
130pub trait QualifiedChannelTerminalAuthority: Send + Sync {
131 fn load_admitted_reservation(
132 &self,
133 operation: &AdmissionOperationV1,
134 context: &AdmissionProjectionContext,
135 ) -> Result<VerifiedAdmittedChannelReservationV1, ChannelTerminalAuthorityError>;
136
137 fn prepare_terminal_advance(
138 &self,
139 request: ChannelTerminalAdvanceRequest<'_>,
140 ) -> Result<VerifiedChannelTerminalAdvanceV1, ChannelTerminalAuthorityError>;
141
142 fn commit_anchored_terminal_projection(
143 &self,
144 request: ChannelTerminalCommitRequest<'_>,
145 ) -> Result<AdmissionTerminal, ChannelTerminalAuthorityError>;
146}
147
148fn same_admitted_reservation(
149 expected: &VerifiedAdmittedChannelReservationV1,
150 actual: &VerifiedAdmittedChannelReservationV1,
151) -> bool {
152 let expected_snapshot = expected.snapshot();
153 let actual_snapshot = actual.snapshot();
154 expected.proposal() == actual.proposal()
155 && expected_snapshot.lifecycle() == actual_snapshot.lifecycle()
156 && expected_snapshot.escrow() == actual_snapshot.escrow()
157 && expected_snapshot.settlement_authority_scope_id()
158 == actual_snapshot.settlement_authority_scope_id()
159 && expected_snapshot.checkpoint_sequence() == actual_snapshot.checkpoint_sequence()
160 && expected_snapshot.checkpoint_digest() == actual_snapshot.checkpoint_digest()
161 && expected_snapshot.channel_head_digest() == actual_snapshot.channel_head_digest()
162 && expected_snapshot.escrow_head_digest() == actual_snapshot.escrow_head_digest()
163 && expected_snapshot.channel_predecessor_digest()
164 == actual_snapshot.channel_predecessor_digest()
165 && expected_snapshot.escrow_predecessor_digest()
166 == actual_snapshot.escrow_predecessor_digest()
167 && expected_snapshot.observed_at_unix_ms() == actual_snapshot.observed_at_unix_ms()
168 && expected.ready_effect() == actual.ready_effect()
169 && expected.ready_effect_head_digest() == actual.ready_effect_head_digest()
170}
171
172pub(crate) fn prepare_channel_terminal_projection(
173 authority: Option<&dyn QualifiedChannelTerminalAuthority>,
174 operation: &AdmissionOperationV1,
175 context: &AdmissionProjectionContext,
176 receipt: &VerifiedAdmissionReceipt,
177 tool_outcome: &ToolOutcomeTerminalEvidenceV1,
178 kernel_keypair: &Keypair,
179) -> Result<PreparedChannelTerminalProjectionV1, ChannelTerminalAuthorityError> {
180 if !operation.binding().participant_requirements().channel {
181 return Err(ChannelTerminalAuthorityError::BindingMismatch);
182 }
183 let authority = authority.ok_or_else(|| {
184 ChannelTerminalAuthorityError::Unavailable(
185 "no qualified channel terminal authority is configured".to_owned(),
186 )
187 })?;
188 let reservation = authority.load_admitted_reservation(operation, context)?;
189 let terminal_outcome = sign_channel_terminal_outcome_commitment(
190 operation,
191 &reservation,
192 receipt,
193 tool_outcome,
194 context,
195 kernel_keypair,
196 )
197 .map_err(|_| ChannelTerminalAuthorityError::BindingMismatch)?;
198 let advance = authority.prepare_terminal_advance(ChannelTerminalAdvanceRequest {
199 operation,
200 context,
201 reservation: &reservation,
202 receipt,
203 terminal_outcome: &terminal_outcome,
204 })?;
205 let result = &terminal_outcome.body.terminal_result;
206 if !same_admitted_reservation(&reservation, advance.reservation())
207 || advance.effect_result_id() != result.result_id
208 || advance.effect_result_digest() != result.result_digest
209 || advance.effect_result() != &result.result
210 {
211 return Err(ChannelTerminalAuthorityError::BindingMismatch);
212 }
213 let (channel, obligation) = VerifiedChannelTerminalProjectionV1::from_verified(
214 operation,
215 context,
216 receipt,
217 tool_outcome,
218 &advance,
219 )
220 .map_err(|_| ChannelTerminalAuthorityError::BindingMismatch)?;
221 Ok(PreparedChannelTerminalProjectionV1 {
222 reservation,
223 terminal_outcome,
224 advance,
225 channel,
226 obligation,
227 })
228}
229
230#[allow(clippy::too_many_arguments)]
231pub(crate) fn commit_prepared_channel_terminal_projection(
232 authority: &dyn QualifiedChannelTerminalAuthority,
233 operation: &AdmissionOperationV1,
234 recovery_lease: &AdmissionRecoveryLease,
235 projection: &AdmissionTerminalProjection,
236 capabilities: &AdmissionProjectionCapabilities,
237 prepared: &PreparedChannelTerminalProjectionV1,
238 kernel_keypair: &Keypair,
239 active_fence: &StoreMutationFence,
240 trusted_now_unix_ms: u64,
241) -> Result<AdmissionTerminal, ChannelTerminalAuthorityError> {
242 let envelope = SignedAdmissionTerminalProjectionV1::from_verified(
243 operation,
244 projection,
245 capabilities,
246 kernel_keypair,
247 )
248 .map_err(|_| ChannelTerminalAuthorityError::BindingMismatch)?;
249 let verified = envelope
250 .verify()
251 .map_err(|_| ChannelTerminalAuthorityError::BindingMismatch)?;
252 if verified.channel_terminal() != Some(prepared.channel()) {
253 return Err(ChannelTerminalAuthorityError::BindingMismatch);
254 }
255 let terminal = authority.commit_anchored_terminal_projection(ChannelTerminalCommitRequest {
256 prepared,
257 recovery_lease,
258 envelope: &envelope,
259 active_fence,
260 trusted_now_unix_ms,
261 })?;
262 let expected = verified.terminal_operation();
263 if terminal.operation_id != *expected.binding().operation_id()
264 || terminal.state != expected.state()
265 || Some(&terminal.replay) != expected.terminal_replay()
266 {
267 return Err(ChannelTerminalAuthorityError::BindingMismatch);
268 }
269 Ok(terminal)
270}