Skip to main content

chio_kernel/admission_operation/projection/
channel_terminal_authority.rs

1use 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}