ic_backup/model/ic_snapshot_upload_data_observation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord},
6 ic_observation::{
7 IcObservationRequestError, IcObservationResponseError, IcObservationResponseInput,
8 ObservationReservation, fmt_response_input, pending_attempts, validate_response_input,
9 },
10 ic_snapshot_data::{IcSnapshotDataRequest, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES},
11 ic_snapshot_upload::{
12 IcSnapshotUploadAttemptError, IcSnapshotUploadKind, IcSnapshotUploadRequest,
13 original_authority,
14 },
15 operation_plan::OperationPlanRecord,
16};
17use std::fmt;
18use thiserror::Error;
19
20mod settlement;
21pub use settlement::{IcSnapshotUploadDataAttribution, IcSnapshotUploadDataSettlement};
22
23#[derive(Debug)]
29pub struct IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata> {
30 plan: &'request OperationPlanRecord,
31 mutation: &'request IcSnapshotUploadRequest<'source>,
32 payload: &'request IcSnapshotDataRequest<'metadata>,
33 authority: AttemptAuthorityRecord,
34 mutation_attempt: u32,
35 observation_attempt: u32,
36 original_chunk_checksum: &'request ArtifactChecksumRecord,
37}
38
39impl<'request, 'source, 'metadata>
40 IcSnapshotUploadDataObservationRequest<'request, 'source, 'metadata>
41{
42 pub fn new(
50 plan: &'request OperationPlanRecord,
51 operation_sequence: u64,
52 journal: &AttemptJournalRecord,
53 mutation: &'request IcSnapshotUploadRequest<'source>,
54 payload: &'request IcSnapshotDataRequest<'metadata>,
55 ) -> Result<Self, IcSnapshotUploadDataObservationError> {
56 let IcSnapshotUploadKind::Data {
57 snapshot_id,
58 source_kind,
59 chunk_checksum,
60 ..
61 } = mutation.kind()
62 else {
63 return Err(IcSnapshotUploadDataObservationError::DataUploadRequired);
64 };
65 let authority = original_authority(plan, operation_sequence, journal, mutation)?;
66 if payload.target() != mutation.target()
67 || payload.snapshot_id() != snapshot_id
68 || !payload.matches_kind(source_kind)
69 {
70 return Err(IcSnapshotUploadDataObservationError::ReadbackMismatch);
71 }
72 let actual = payload.metadata().metadata();
73 let original = mutation.source().metadata();
74 let actual_sizes = [
75 actual.wasm_module_size,
76 actual.wasm_memory_size,
77 actual.stable_memory_size,
78 ];
79 let original_sizes = [
80 original.wasm_module_size,
81 original.wasm_memory_size,
82 original.stable_memory_size,
83 ];
84 if !matches!(
85 actual.source,
86 Some(ic_management_canister_types::SnapshotSource::MetadataUpload(_))
87 ) || actual_sizes != original_sizes
88 {
89 return Err(IcSnapshotUploadDataObservationError::DestinationMetadataMismatch);
90 }
91 let (mutation_attempt, observation_attempt) = pending_attempts(journal)?;
92 let request = Self {
93 plan,
94 mutation,
95 payload,
96 authority,
97 mutation_attempt,
98 observation_attempt,
99 original_chunk_checksum: chunk_checksum,
100 };
101 request.validate_journal(journal)?;
102 Ok(request)
103 }
104
105 #[must_use]
107 pub const fn plan(&self) -> &OperationPlanRecord {
108 self.plan
109 }
110 #[must_use]
112 pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
113 self.mutation
114 }
115 #[must_use]
117 pub const fn payload(&self) -> &'request IcSnapshotDataRequest<'metadata> {
118 self.payload
119 }
120 #[must_use]
122 pub const fn authority(&self) -> &AttemptAuthorityRecord {
123 &self.authority
124 }
125 #[must_use]
127 pub const fn mutation_attempt(&self) -> u32 {
128 self.mutation_attempt
129 }
130 #[must_use]
132 pub const fn observation_attempt(&self) -> u32 {
133 self.observation_attempt
134 }
135 #[must_use]
137 pub const fn original_chunk_checksum(&self) -> &ArtifactChecksumRecord {
138 self.original_chunk_checksum
139 }
140 pub fn validate_journal(
144 &self,
145 journal: &AttemptJournalRecord,
146 ) -> Result<(), IcObservationRequestError> {
147 ObservationReservation {
148 authority: &self.authority,
149 mutation_attempt: self.mutation_attempt,
150 observation_attempt: self.observation_attempt,
151 request: self.payload.digest(),
152 }
153 .validate(journal)
154 }
155}
156
157#[derive(Debug, Error)]
159pub enum IcSnapshotUploadDataObservationError {
160 #[error("data upload observation requires original data intent")]
162 DataUploadRequired,
163 #[error("data upload observation readback differs")]
165 ReadbackMismatch,
166 #[error("data upload observation destination metadata differs")]
168 DestinationMetadataMismatch,
169 #[error(transparent)]
171 Upload(#[from] IcSnapshotUploadAttemptError),
172 #[error(transparent)]
174 Observation(#[from] IcObservationRequestError),
175}
176
177#[derive(Clone)]
182pub struct IcSnapshotUploadDataObservationResponse {
183 input: IcObservationResponseInput,
184}
185
186impl IcSnapshotUploadDataObservationResponse {
187 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
191 validate_response_input(&mut input, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES)?;
192 Ok(Self { input })
193 }
194 #[must_use]
196 pub const fn input(&self) -> &IcObservationResponseInput {
197 &self.input
198 }
199}
200impl fmt::Debug for IcSnapshotUploadDataObservationResponse {
201 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202 fmt_response_input(
203 &self.input,
204 formatter,
205 "IcSnapshotUploadDataObservationResponse",
206 )
207 }
208}
209
210#[cfg(test)]
211mod tests;