Skip to main content

chio_kernel/admission_operation/projection/
factor_assignment.rs

1use chio_credit::factor::{VerifiedAssignmentAcknowledgementV1, VerifiedAssignmentNotAppliedV1};
2
3use super::*;
4
5pub fn verified_factor_assignment_applied_projection(
6    operation: &AdmissionOperationV1,
7    context: AdmissionProjectionContext,
8    acknowledgement: &VerifiedAssignmentAcknowledgementV1,
9) -> Result<AdmissionTerminalProjection, AdmissionOperationError> {
10    let body = acknowledgement.body();
11    validate_source(
12        operation,
13        &context,
14        body.operation_id(),
15        body.normalized_request_digest(),
16        body.assignment_authorization_set_digest(),
17        body.acknowledged_at_unix_ms(),
18    )?;
19    if body.resulting_disposition_version()
20        != body
21            .prior_disposition_version()
22            .checked_add(1)
23            .ok_or(AdmissionOperationError::InvalidEconomicMutationBinding)?
24        || body.resulting_snapshot_version()
25            != body
26                .expected_snapshot_version()
27                .checked_add(1)
28                .ok_or(AdmissionOperationError::InvalidEconomicMutationBinding)?
29        || body.resulting_resource_fence()
30            != body
31                .expected_resource_fence()
32                .checked_add(1)
33                .ok_or(AdmissionOperationError::InvalidEconomicMutationBinding)?
34    {
35        return Err(AdmissionOperationError::InvalidEconomicMutationBinding);
36    }
37    let result = result_binding(
38        operation,
39        &context,
40        AdmissionOperationState::EconomicMutationApplied,
41        body.acknowledgement_id(),
42        acknowledgement.envelope_digest(),
43        acknowledgement.signature_digest(),
44        body.authority_id(),
45        body.authority_key_epoch(),
46        body.obligation_id(),
47        body.expected_snapshot_version(),
48        body.resulting_snapshot_version(),
49        body.expected_resource_fence(),
50        body.resulting_resource_fence(),
51        EconomicMutationTerminalStatus::Applied,
52    )?;
53    let audit_event = audit_event(
54        operation,
55        &context,
56        AdmissionOperationState::EconomicMutationApplied,
57        body.acknowledgement_id(),
58        acknowledgement.envelope_digest(),
59    )?;
60    Ok(AdmissionTerminalProjection::EconomicMutationApplied {
61        context,
62        result: Box::new(VerifiedEconomicMutationApplied(result)),
63        audit_event: Box::new(audit_event),
64    })
65}
66
67pub fn verified_factor_assignment_not_applied_projection(
68    operation: &AdmissionOperationV1,
69    context: AdmissionProjectionContext,
70    result: &VerifiedAssignmentNotAppliedV1,
71) -> Result<AdmissionTerminalProjection, AdmissionOperationError> {
72    let body = result.body();
73    validate_source(
74        operation,
75        &context,
76        body.operation_id(),
77        body.normalized_request_digest(),
78        body.assignment_authorization_set_digest(),
79        body.decided_at_unix_ms(),
80    )?;
81    let result_binding = result_binding(
82        operation,
83        &context,
84        AdmissionOperationState::EconomicMutationNotApplied,
85        body.result_id(),
86        result.envelope_digest(),
87        result.signature_digest(),
88        body.authority_id(),
89        body.authority_key_epoch(),
90        body.obligation_id(),
91        body.expected_snapshot_version(),
92        body.observed_snapshot_version(),
93        body.expected_resource_fence(),
94        body.resource_fence(),
95        EconomicMutationTerminalStatus::PermanentlyNotApplied,
96    )?;
97    let audit_event = audit_event(
98        operation,
99        &context,
100        AdmissionOperationState::EconomicMutationNotApplied,
101        body.result_id(),
102        result.envelope_digest(),
103    )?;
104    Ok(AdmissionTerminalProjection::EconomicMutationNotApplied {
105        context,
106        result: Box::new(VerifiedEconomicMutationNotApplied(result_binding)),
107        audit_event: Box::new(audit_event),
108    })
109}
110
111fn validate_source(
112    operation: &AdmissionOperationV1,
113    context: &AdmissionProjectionContext,
114    operation_id: &str,
115    normalized_request_digest: &str,
116    authorization_set_digest: &str,
117    authority_time_unix_ms: u64,
118) -> Result<(), AdmissionOperationError> {
119    if operation.binding().kind() != AdmissionOperationKind::GovernedEconomicMutation
120        || operation.state() != AdmissionOperationState::MutationSubmitted
121        || operation.binding().operation_id().as_str() != operation_id
122        || operation.binding().immutable_request_hash().as_str() != normalized_request_digest
123        || operation
124            .supplemental_authorization_digest()
125            .is_none_or(|digest| digest.as_str() != authorization_set_digest)
126        || authority_time_unix_ms > context.trusted_time_unix_ms
127    {
128        return Err(AdmissionOperationError::InvalidEconomicMutationBinding);
129    }
130    context.validate()
131}
132
133#[allow(clippy::too_many_arguments)]
134fn result_binding(
135    operation: &AdmissionOperationV1,
136    context: &AdmissionProjectionContext,
137    projected_state: AdmissionOperationState,
138    record_id: &str,
139    envelope_digest: &str,
140    signature_digest: &str,
141    authority_id: &str,
142    authority_key_epoch: u64,
143    obligation_id: &str,
144    expected_version: u64,
145    resulting_version: u64,
146    expected_fence: u64,
147    resulting_fence: u64,
148    status: EconomicMutationTerminalStatus,
149) -> Result<GovernedEconomicMutationResultBinding, AdmissionOperationError> {
150    let invalid = |_| AdmissionOperationError::InvalidEconomicMutationBinding;
151    let binding = GovernedEconomicMutationResultBinding {
152        binding: AdmissionExactProjectionBindingV1::from_verified(
153            operation,
154            context,
155            projected_state,
156        )?,
157        record_id: AdmissionIdentifier::try_new(
158            "factor_assignment_result_id",
159            record_id.to_owned(),
160        )
161        .map_err(invalid)?,
162        record_digest: AdmissionDigest::try_new(
163            "factor_assignment_result_digest",
164            envelope_digest.to_owned(),
165        )
166        .map_err(invalid)?,
167        participant_id: AdmissionIdentifier::try_new(
168            "factor_assignment_authority_id",
169            authority_id.to_owned(),
170        )
171        .map_err(invalid)?,
172        participant_key_epoch: authority_key_epoch,
173        resource_id: AdmissionIdentifier::try_new(
174            "factor_assignment_obligation_id",
175            obligation_id.to_owned(),
176        )
177        .map_err(invalid)?,
178        expected_resource_version: expected_version,
179        resulting_resource_version: resulting_version,
180        expected_resource_fence: AdmissionIdentifier::try_new(
181            "factor_assignment_expected_resource_fence",
182            format!("factor-assignment-resource-fence:{expected_fence}"),
183        )
184        .map_err(invalid)?,
185        resulting_resource_fence: AdmissionIdentifier::try_new(
186            "factor_assignment_resulting_resource_fence",
187            format!("factor-assignment-resource-fence:{resulting_fence}"),
188        )
189        .map_err(invalid)?,
190        immutable_request_digest: operation.binding().request_binding_hash().clone(),
191        signature_digest: AdmissionDigest::try_new(
192            "factor_assignment_signature_digest",
193            signature_digest.to_owned(),
194        )
195        .map_err(invalid)?,
196        status,
197        anchored_effect: false,
198    };
199    binding.validate_against(operation, context)?;
200    Ok(binding)
201}
202
203fn audit_event(
204    operation: &AdmissionOperationV1,
205    context: &AdmissionProjectionContext,
206    projected_state: AdmissionOperationState,
207    result_id: &str,
208    envelope_digest: &str,
209) -> Result<GovernedMutationAuditEvent, AdmissionOperationError> {
210    let invalid = |_| AdmissionOperationError::InvalidEconomicMutationBinding;
211    Ok(GovernedMutationAuditEvent {
212        binding: AdmissionExactProjectionBindingV1::from_verified(
213            operation,
214            context,
215            projected_state,
216        )?,
217        record_id: AdmissionIdentifier::try_new(
218            "factor_assignment_audit_id",
219            format!("{result_id}:audit"),
220        )
221        .map_err(invalid)?,
222        record_digest: AdmissionDigest::try_new(
223            "factor_assignment_audit_digest",
224            envelope_digest.to_owned(),
225        )
226        .map_err(invalid)?,
227    })
228}