1use super::{MemoryRuntime, RuntimeDiagnosticError, RuntimeLifecycle};
2use crate::{
3 AllocationHistory, AllocationLedger, AllocationPolicy, AllocationSlotDescriptor,
4 DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
5 DiagnosticMemorySize, DiagnosticMemorySizeOutcome, DiagnosticRangeAuthority,
6 DiagnosticRuntimeBinding, DiagnosticStableCell, DiagnosticStableCellStatus, LedgerCommitError,
7 LedgerPayloadEnvelopeError, MemoryRuntimeDoctorReport, PolicyIdentity, RuntimeBootstrapPolicy,
8 StableCellLedgerRecord,
9 physical::CommitStoreDiagnostic,
10 registry::{SealedDeclarationFingerprint, SealedDeclarationSnapshot},
11 slot::MEMORY_MANAGER_LEDGER_ID,
12 stable_cell::decode_stable_cell_ledger_record_from_memory,
13};
14use ic_stable_structures::Memory;
15use std::fmt::Display;
16
17impl<M: Memory> MemoryRuntime<M> {
18 pub fn diagnostic_export(&self) -> Result<DiagnosticExport, RuntimeDiagnosticError> {
20 if !self.is_bootstrapped() {
21 return Err(RuntimeDiagnosticError::NotBootstrapped);
22 }
23 let record = self.ledger_record_from_memory()?;
24 let recovered = record.store().recover()?;
25 let ledger = recovered.ledger();
26 Ok(
27 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
28 ledger,
29 ledger_anchor_descriptor(),
30 Some(record.store().physical().diagnostic()),
31 self.memory_sizes(ledger)?,
32 ),
33 )
34 }
35
36 pub fn commit_recovery_diagnostic(
41 &self,
42 ) -> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
43 let record = self.ledger_record_from_memory()?;
44 Ok(record.store().physical().diagnostic())
45 }
46
47 #[must_use]
49 pub fn doctor_report<P>(
50 &self,
51 declarations: &SealedDeclarationSnapshot,
52 policy: &P,
53 ) -> MemoryRuntimeDoctorReport
54 where
55 P: RuntimeBootstrapPolicy,
56 P::Error: Display,
57 {
58 let stable_cell = self.stable_cell_diagnostic();
59 let commit_recovery = stable_cell
60 .record
61 .as_ref()
62 .map(|record| record.store().physical().diagnostic());
63 let recovered = stable_cell
64 .record
65 .as_ref()
66 .map(|record| record.store().recover());
67 let recovered_for_export = recovered.as_ref().and_then(|result| result.as_ref().ok());
68 let ledger = recovered_for_export.map(|recovered| {
69 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_size_outcomes(
70 recovered.ledger(),
71 ledger_anchor_descriptor(),
72 commit_recovery,
73 self.memory_size_outcomes(recovered.ledger()),
74 )
75 });
76 let diagnostic_declarations = declarations
77 .registered_declarations()
78 .iter()
79 .map(|registration| {
80 DiagnosticDeclaration::new(
81 registration.authority(),
82 registration.declaration().clone(),
83 )
84 })
85 .collect();
86 let registered_records = declarations
87 .registered_ranges()
88 .iter()
89 .map(|registration| registration.record().clone())
90 .collect();
91 let range_authority = DiagnosticRangeAuthority::new(
92 registered_records,
93 Ok(declarations.range_authority().clone()),
94 );
95 let tested_policy_identity = policy
96 .runtime_bootstrap_identity()
97 .map_err(|err| DiagnosticFailure::new(DiagnosticCode::PolicyIdentity, err.to_string()));
98 let tested_declaration_fingerprint = declarations.fingerprint();
99 let established_bootstrap_binding = self.established_bootstrap_binding();
100 let bootstrap_binding = diagnostic_bootstrap_binding(
101 &tested_policy_identity,
102 tested_declaration_fingerprint,
103 established_bootstrap_binding.as_ref(),
104 );
105 let validation = match &tested_policy_identity {
106 Ok(_) => diagnostic_validation(
107 declarations,
108 policy,
109 stable_cell.record.as_ref(),
110 recovered.as_ref(),
111 ),
112 Err(failure) => DiagnosticCheck::not_run(failure.code, failure.message.clone()),
113 };
114
115 MemoryRuntimeDoctorReport {
116 bootstrapped: self.is_bootstrapped(),
117 tested_policy_identity,
118 tested_declaration_fingerprint,
119 established_bootstrap_binding,
120 bootstrap_binding,
121 ledger_anchor: ledger_anchor_descriptor(),
122 stable_cell: stable_cell.diagnostic,
123 commit_recovery,
124 ledger,
125 registered_declarations: diagnostic_declarations,
126 range_authority,
127 validation,
128 }
129 }
130
131 fn memory_sizes(
132 &self,
133 ledger: &AllocationLedger,
134 ) -> Result<Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)>, RuntimeDiagnosticError> {
135 ledger
136 .allocation_history()
137 .records()
138 .iter()
139 .map(|record| {
140 let id = record.slot().memory_manager_id()?;
141 Ok((
142 record.slot().clone(),
143 DiagnosticMemorySize::from_wasm_pages(self.memory(id).size()),
144 ))
145 })
146 .collect()
147 }
148
149 pub(super) fn memory_size_outcomes(
150 &self,
151 ledger: &AllocationLedger,
152 ) -> Vec<(AllocationSlotDescriptor, DiagnosticMemorySizeOutcome)> {
153 ledger
154 .allocation_history()
155 .records()
156 .iter()
157 .map(|record| {
158 let outcome = match record.slot().memory_manager_id() {
159 Ok(id) => DiagnosticMemorySizeOutcome::Measured(
160 DiagnosticMemorySize::from_wasm_pages(self.memory(id).size()),
161 ),
162 Err(err) => DiagnosticMemorySizeOutcome::Failed(DiagnosticFailure::new(
163 DiagnosticCode::MemorySize,
164 err.to_string(),
165 )),
166 };
167 (record.slot().clone(), outcome)
168 })
169 .collect()
170 }
171
172 fn established_bootstrap_binding(&self) -> Option<DiagnosticRuntimeBinding> {
173 match &self.lifecycle {
174 RuntimeLifecycle::Unbootstrapped => None,
175 RuntimeLifecycle::Bootstrapped { binding, .. } => Some(DiagnosticRuntimeBinding::new(
176 binding.policy_identity.clone(),
177 binding.declarations.fingerprint(),
178 )),
179 }
180 }
181
182 fn stable_cell_diagnostic(&self) -> StableCellDiagnostic {
183 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
184 let memory_size = DiagnosticMemorySize::from_wasm_pages(memory.size());
185 if memory.size() == 0 {
186 return StableCellDiagnostic {
187 diagnostic: DiagnosticStableCell::new(
188 DiagnosticStableCellStatus::Empty,
189 memory_size,
190 ),
191 record: Some(StableCellLedgerRecord::default()),
192 };
193 }
194
195 match decode_stable_cell_ledger_record_from_memory(&memory) {
196 Ok(record) => StableCellDiagnostic {
197 diagnostic: DiagnosticStableCell::new(
198 DiagnosticStableCellStatus::Readable,
199 memory_size,
200 ),
201 record: Some(record),
202 },
203 Err(err) => StableCellDiagnostic {
204 diagnostic: DiagnosticStableCell::new(
205 DiagnosticStableCellStatus::Corrupt {
206 failure: DiagnosticFailure::new(
207 DiagnosticCode::StableCell,
208 err.to_string(),
209 ),
210 },
211 memory_size,
212 ),
213 record: None,
214 },
215 }
216 }
217}
218
219struct StableCellDiagnostic {
220 diagnostic: DiagnosticStableCell,
221 record: Option<StableCellLedgerRecord>,
222}
223
224const fn ledger_anchor_descriptor() -> AllocationSlotDescriptor {
225 AllocationSlotDescriptor::memory_manager_unchecked(MEMORY_MANAGER_LEDGER_ID)
226}
227
228fn diagnostic_validation<P: AllocationPolicy>(
229 declarations: &SealedDeclarationSnapshot,
230 custom_policy: &P,
231 stable_cell_record: Option<&StableCellLedgerRecord>,
232 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
233) -> DiagnosticCheck
234where
235 P::Error: Display,
236{
237 let recovered = match diagnostic_validation_ledger(stable_cell_record, recovered) {
238 Ok(recovered) => recovered,
239 Err(failure) => return DiagnosticCheck::not_run(failure.code, failure.message),
240 };
241 let policy = super::policy::RuntimeMemoryManagerPolicy {
242 declarations,
243 custom_policy,
244 };
245 match crate::validate_allocations(
246 &recovered,
247 declarations.allocation_snapshot().clone(),
248 &policy,
249 ) {
250 Ok(_) => DiagnosticCheck::passed(),
251 Err(err) => DiagnosticCheck::failed(DiagnosticCode::AllocationValidation, err.to_string()),
252 }
253}
254
255fn diagnostic_bootstrap_binding(
256 tested_policy_identity: &Result<PolicyIdentity, DiagnosticFailure>,
257 tested_declaration_fingerprint: SealedDeclarationFingerprint,
258 established: Option<&DiagnosticRuntimeBinding>,
259) -> DiagnosticCheck {
260 let tested_policy_identity = match tested_policy_identity {
261 Ok(identity) => identity,
262 Err(failure) => {
263 return DiagnosticCheck::not_run(failure.code, failure.message.clone());
264 }
265 };
266 let Some(established) = established else {
267 return DiagnosticCheck::not_run(
268 DiagnosticCode::RuntimeBinding,
269 "runtime has not completed bootstrap",
270 );
271 };
272 if &established.policy_identity == tested_policy_identity
273 && established.declaration_fingerprint == tested_declaration_fingerprint
274 {
275 return DiagnosticCheck::passed();
276 }
277 DiagnosticCheck::failed(
278 DiagnosticCode::RuntimeBinding,
279 format!(
280 "tested policy/declaration binding differs from established runtime binding: \
281 tested_policy={tested_policy_identity:?}, \
282 tested_declarations={tested_declaration_fingerprint:?}, \
283 established={established:?}"
284 ),
285 )
286}
287
288pub(super) fn diagnostic_validation_ledger(
289 stable_cell_record: Option<&StableCellLedgerRecord>,
290 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
291) -> Result<crate::RecoveredLedger, DiagnosticFailure> {
292 if let Some(Ok(recovered)) = recovered {
293 return Ok(recovered.clone());
294 }
295 if let Some(Err(err)) = recovered {
296 if stable_cell_record.is_some_and(|record| record.store().physical().is_uninitialized()) {
297 return diagnostic_genesis_recovered_ledger();
298 }
299 let code = if matches!(
300 err,
301 LedgerCommitError::PayloadEnvelope(
302 LedgerPayloadEnvelopeError::UnsupportedFormat { .. }
303 )
304 ) {
305 DiagnosticCode::UnsupportedFormat
306 } else {
307 DiagnosticCode::LedgerRecovery
308 };
309 return Err(DiagnosticFailure::new(
310 code,
311 format!("protected ledger recovery: {err}"),
312 ));
313 }
314 if stable_cell_record.is_some() {
315 return diagnostic_genesis_recovered_ledger();
316 }
317 Err(DiagnosticFailure::new(
318 DiagnosticCode::StableCell,
319 "stable-cell ledger record is not readable",
320 ))
321}
322
323fn diagnostic_genesis_recovered_ledger() -> Result<crate::RecoveredLedger, DiagnosticFailure> {
324 AllocationLedger::new(0, AllocationHistory::default())
325 .map(|ledger| crate::RecoveredLedger::from_trusted_parts(ledger, 0))
326 .map_err(|err| {
327 DiagnosticFailure::new(
328 DiagnosticCode::GenesisLedger,
329 format!("genesis ledger: {err}"),
330 )
331 })
332}