Skip to main content

ic_backup/model/ic_snapshot_upload_data_observation/
mod.rs

1//! Exact already reserved destination-data observations of unresolved uploads.
2
3use crate::model::attempt_journal::ObservationReservation;
4use crate::model::{
5    artifacts::ArtifactChecksumRecord,
6    attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
7    ic_observation::{
8        IcObservationRequestError, IcObservationResponseError, IcObservationResponseInput,
9        fmt_response_input, validate_response_input,
10    },
11    ic_snapshot_data::{IcSnapshotDataRequest, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES},
12    ic_snapshot_upload::{
13        IcSnapshotUploadAttemptError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
14        original_authority,
15    },
16    operation_plan::OperationPlanRecord,
17};
18use std::fmt;
19use thiserror::Error;
20
21mod settlement;
22pub use settlement::{IcSnapshotUploadDataAttribution, IcSnapshotUploadDataSettlement};
23
24/// Original data-upload intent and its independently spent exact readback request.
25///
26/// Retain destination metadata and original read bytes before reservation. Their
27/// authenticity, allocation attribution, fresh access, chronology and proof of no
28/// prior observation dispatch remain integration-owned. Reconstruction is no retry.
29#[derive(Debug)]
30pub struct IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata> {
31    plan: &'request OperationPlanRecord,
32    mutation: &'request IcSnapshotUploadRequest<'source>,
33    payload: &'request IcSnapshotDataRequest<'metadata>,
34    reservation: ObservationReservation,
35    original_chunk_checksum: &'request ArtifactChecksumRecord,
36}
37
38impl<'request, 'source, 'metadata>
39    IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata>
40{
41    /// Bind one original data upload to a reserved read of the same destination/extent.
42    ///
43    /// Metadata allocation, other IDs, targets, regions, offsets, lengths or chunk
44    /// hashes reject. Destination metadata must declare uploaded source and original
45    /// region sizes. This neither authenticates that metadata nor spends.
46    /// # Errors
47    /// Rejects changed originals, mismatching readback and missing reservations.
48    pub fn new(
49        plan: &'request OperationPlanRecord,
50        operation_sequence: u64,
51        journal: &AttemptJournalRecord,
52        mutation: &'request IcSnapshotUploadRequest<'source>,
53        payload: &'request IcSnapshotDataRequest<'metadata>,
54    ) -> Result<Self, IcSnapshotUploadDataObservationError> {
55        let IcSnapshotUploadKind::Data {
56            snapshot_id,
57            source_kind,
58            chunk_checksum,
59            ..
60        } = mutation.kind()
61        else {
62            return Err(IcSnapshotUploadDataObservationError::DataUploadRequired);
63        };
64        let authority = original_authority(plan, operation_sequence, journal, mutation)?;
65        if payload.target() != mutation.target()
66            || payload.snapshot_id() != snapshot_id
67            || !payload.matches_kind(source_kind)
68        {
69            return Err(IcSnapshotUploadDataObservationError::ReadbackMismatch);
70        }
71        let actual = payload.metadata().metadata();
72        let original = mutation.source().metadata();
73        let actual_sizes = [
74            actual.wasm_module_size,
75            actual.wasm_memory_size,
76            actual.stable_memory_size,
77        ];
78        let original_sizes = [
79            original.wasm_module_size,
80            original.wasm_memory_size,
81            original.stable_memory_size,
82        ];
83        if !matches!(
84            actual.source,
85            Some(ic_platform::management_canister_types::SnapshotSource::MetadataUpload(_))
86        ) || actual_sizes != original_sizes
87        {
88            return Err(IcSnapshotUploadDataObservationError::DestinationMetadataMismatch);
89        }
90        let reservation = ObservationReservation::new(authority, journal, payload.digest())
91            .map_err(crate::model::ic_observation::IcObservationRequestError::from)?;
92        let request = Self {
93            plan,
94            mutation,
95            payload,
96            reservation,
97            original_chunk_checksum: chunk_checksum,
98        };
99        Ok(request)
100    }
101
102    /// Read the retained complete original plan and immutable allowances.
103    #[must_use]
104    pub const fn plan(&self) -> &OperationPlanRecord {
105        self.plan
106    }
107    /// Read exact original source-bound upload bytes, without a dispatch permit.
108    #[must_use]
109    pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
110        self.mutation
111    }
112    /// Read exact original destination metadata/read request, without fresh access.
113    #[must_use]
114    pub const fn payload(&self) -> &'request IcSnapshotDataRequest<'metadata> {
115        self.payload
116    }
117    /// Read original operation authority and assigned limits.
118    #[must_use]
119    pub const fn authority(&self) -> &AttemptAuthorityRecord {
120        &self.reservation.authority
121    }
122    /// Read the original pending data-upload attempt.
123    #[must_use]
124    pub const fn mutation_attempt(&self) -> u32 {
125        self.reservation.mutation_attempt
126    }
127    /// Read the already consumed exact data-read observation attempt.
128    #[must_use]
129    pub const fn observation_attempt(&self) -> u32 {
130        self.reservation.observation_attempt
131    }
132    /// Read original encoded chunk identity; it establishes no write attribution.
133    #[must_use]
134    pub const fn original_chunk_checksum(&self) -> &ArtifactChecksumRecord {
135        self.original_chunk_checksum
136    }
137    /// Recheck original current authority, pending IDs and exact read digest.
138    /// # Errors
139    /// Rejects changed/settled reservations and different original read bytes.
140    pub fn validate_journal(
141        &self,
142        journal: &AttemptJournalRecord,
143    ) -> Result<(), IcObservationRequestError> {
144        self.reservation
145            .validate(journal)
146            .map_err(IcObservationRequestError::from)
147    }
148}
149
150/// Exact original data/readback declaration denial, with no outcome transition.
151#[derive(Debug, Error)]
152pub enum IcSnapshotUploadDataObservationError {
153    /// Metadata allocation is outside this boundary.
154    #[error("data upload observation requires original data intent")]
155    DataUploadRequired,
156    /// Destination target, raw ID or exact original extent/hash differs.
157    #[error("data upload observation readback differs")]
158    ReadbackMismatch,
159    /// Destination source is unknown/not uploaded, or declared region sizes differ.
160    #[error("data upload observation destination metadata differs")]
161    DestinationMetadataMismatch,
162    /// Canonical original upload authority/source admission rejected.
163    #[error(transparent)]
164    Upload(#[from] IcSnapshotUploadAttemptError),
165    /// Canonical current original observation reservation rejected.
166    #[error(transparent)]
167    Observation(#[from] IcObservationRequestError),
168}
169
170/// Passive destination-data claims using the existing 2 MiB data-reply ceiling.
171///
172/// The existing status/list response keeps its 1 MiB ceiling. This owner reuses
173/// canonical attempt/principal admission and redaction without widening that API.
174#[derive(Clone)]
175pub struct IcSnapshotUploadDataObservationResponse {
176    input: IcObservationResponseInput,
177}
178
179impl IcSnapshotUploadDataObservationResponse {
180    /// Admit bounded raw bytes and passive chronological/canonical claims.
181    /// # Errors
182    /// Rejects invalid attempts/principals and replies over the existing data bound.
183    pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
184        validate_response_input(&mut input, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES)?;
185        Ok(Self { input })
186    }
187    /// Read immutable existing claim fields and exact original raw data reply.
188    #[must_use]
189    pub const fn input(&self) -> &IcObservationResponseInput {
190        &self.input
191    }
192}
193impl fmt::Debug for IcSnapshotUploadDataObservationResponse {
194    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
195        fmt_response_input(
196            &self.input,
197            formatter,
198            "IcSnapshotUploadDataObservationResponse",
199        )
200    }
201}
202
203#[cfg(test)]
204mod tests;