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, 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 current = journal.view();
92 let request = Self {
93 plan,
94 mutation,
95 payload,
96 authority,
97 mutation_attempt: current
98 .pending_mutation
99 .ok_or(IcObservationRequestError::NoPendingMutation)?,
100 observation_attempt: current
101 .pending_observation
102 .ok_or(IcObservationRequestError::NoPendingObservation)?,
103 original_chunk_checksum: chunk_checksum,
104 };
105 request.validate_journal(journal)?;
106 Ok(request)
107 }
108
109 #[must_use]
111 pub const fn plan(&self) -> &OperationPlanRecord {
112 self.plan
113 }
114 #[must_use]
116 pub const fn mutation(&self) -> &'request IcSnapshotUploadRequest<'source> {
117 self.mutation
118 }
119 #[must_use]
121 pub const fn payload(&self) -> &'request IcSnapshotDataRequest<'metadata> {
122 self.payload
123 }
124 #[must_use]
126 pub const fn authority(&self) -> &AttemptAuthorityRecord {
127 &self.authority
128 }
129 #[must_use]
131 pub const fn mutation_attempt(&self) -> u32 {
132 self.mutation_attempt
133 }
134 #[must_use]
136 pub const fn observation_attempt(&self) -> u32 {
137 self.observation_attempt
138 }
139 #[must_use]
141 pub const fn original_chunk_checksum(&self) -> &ArtifactChecksumRecord {
142 self.original_chunk_checksum
143 }
144 pub fn validate_journal(
148 &self,
149 journal: &AttemptJournalRecord,
150 ) -> Result<(), IcObservationRequestError> {
151 ObservationReservation {
152 authority: &self.authority,
153 mutation_attempt: self.mutation_attempt,
154 observation_attempt: self.observation_attempt,
155 request: self.payload.digest(),
156 }
157 .validate(journal)
158 }
159}
160
161#[derive(Debug, Error)]
163pub enum IcSnapshotUploadDataObservationError {
164 #[error("data upload observation requires original data intent")]
166 DataUploadRequired,
167 #[error("data upload observation readback differs")]
169 ReadbackMismatch,
170 #[error("data upload observation destination metadata differs")]
172 DestinationMetadataMismatch,
173 #[error(transparent)]
175 Upload(#[from] IcSnapshotUploadAttemptError),
176 #[error(transparent)]
178 Observation(#[from] IcObservationRequestError),
179}
180
181#[derive(Clone)]
186pub struct IcSnapshotUploadDataObservationResponse {
187 input: IcObservationResponseInput,
188}
189
190impl IcSnapshotUploadDataObservationResponse {
191 pub fn new(mut input: IcObservationResponseInput) -> Result<Self, IcObservationResponseError> {
195 validate_response_input(&mut input, MAX_IC_SNAPSHOT_DATA_REPLY_BYTES)?;
196 Ok(Self { input })
197 }
198 #[must_use]
200 pub const fn input(&self) -> &IcObservationResponseInput {
201 &self.input
202 }
203}
204impl fmt::Debug for IcSnapshotUploadDataObservationResponse {
205 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206 fmt_response_input(
207 &self.input,
208 formatter,
209 "IcSnapshotUploadDataObservationResponse",
210 )
211 }
212}
213
214#[cfg(test)]
215mod tests;