ic_memory/ledger/
integrity.rs1use super::{AllocationLedger, AllocationRecord, AllocationState, LedgerIntegrityError};
2use crate::{declaration::validate_runtime_fingerprint, slot::AllocationSlot};
3use std::collections::BTreeSet;
4
5impl AllocationLedger {
6 pub(crate) fn validate_bounds(&self) -> Result<(), LedgerIntegrityError> {
7 for (resource, count, limit) in [
8 (
9 "allocation records",
10 self.allocation_history.records().len(),
11 255,
12 ),
13 (
14 "generation history",
15 self.allocation_history.generations().len(),
16 crate::constants::MAX_LEDGER_GENERATIONS,
17 ),
18 (
19 "schema history",
20 self.allocation_history
21 .records()
22 .iter()
23 .map(|r| r.schema_history.len())
24 .sum(),
25 crate::constants::MAX_LEDGER_GENERATIONS,
26 ),
27 ] {
28 if count > limit {
29 return Err(LedgerIntegrityError::LimitExceeded { resource, limit });
30 }
31 }
32 Ok(())
33 }
34
35 pub(crate) fn validate_staging_bounds(&self) -> Result<(), LedgerIntegrityError> {
36 self.validate_bounds()?;
37 if self.allocation_history.generations().len() >= crate::constants::MAX_LEDGER_GENERATIONS {
38 return Err(LedgerIntegrityError::LimitExceeded {
39 resource: "generation history",
40 limit: crate::constants::MAX_LEDGER_GENERATIONS,
41 });
42 }
43 Ok(())
44 }
45
46 pub fn validate_integrity(&self) -> Result<(), LedgerIntegrityError> {
48 self.validate_bounds()?;
49 let mut stable_keys = BTreeSet::new();
50 let mut slots = [false; 256];
52
53 for record in self.allocation_history.records() {
54 if !stable_keys.insert(&record.stable_key) {
55 return Err(LedgerIntegrityError::DuplicateStableKey {
56 stable_key: record.stable_key.clone(),
57 });
58 }
59 let AllocationSlot::MemoryManagerId(id) = record.slot.slot();
60 let occupied = &mut slots[usize::from(*id)];
61 if *occupied {
62 return Err(LedgerIntegrityError::DuplicateSlot {
63 slot: record.slot.clone(),
64 });
65 }
66 *occupied = true;
67 validate_record_integrity(self.current_generation, record)?;
68 }
69
70 let mut generations = BTreeSet::new();
71 for generation in self.allocation_history.generations() {
72 if !generations.insert(generation.generation) {
73 return Err(LedgerIntegrityError::DuplicateGeneration {
74 generation: generation.generation,
75 });
76 }
77 if generation.generation > self.current_generation {
78 return Err(LedgerIntegrityError::FutureGeneration {
79 generation: generation.generation,
80 current_generation: self.current_generation,
81 });
82 }
83 if generation.parent_generation >= generation.generation {
84 return Err(LedgerIntegrityError::InvalidParentGeneration {
85 generation: generation.generation,
86 parent_generation: generation.parent_generation,
87 });
88 }
89 }
90
91 Ok(())
92 }
93
94 pub fn validate_committed_integrity(&self) -> Result<(), LedgerIntegrityError> {
99 self.validate_integrity()?;
100
101 if self.current_generation != 0
102 && !self
103 .allocation_history
104 .generations()
105 .iter()
106 .any(|record| record.generation == self.current_generation)
107 {
108 return Err(LedgerIntegrityError::MissingCurrentGenerationRecord {
109 current_generation: self.current_generation,
110 });
111 }
112
113 let mut previous = None;
114 for generation in self.allocation_history.generations() {
115 validate_runtime_fingerprint(generation.runtime_fingerprint.as_deref())
116 .map_err(LedgerIntegrityError::DiagnosticMetadata)?;
117
118 let expected_generation = previous.map_or(1, |previous| previous + 1);
119 if generation.generation != expected_generation {
120 return Err(LedgerIntegrityError::NonIncreasingGenerationRecords {
121 generation: generation.generation,
122 });
123 }
124
125 let expected_parent = previous.unwrap_or(0);
126 if generation.parent_generation != expected_parent {
127 return Err(LedgerIntegrityError::BrokenGenerationChain {
128 generation: generation.generation,
129 expected_parent,
130 actual_parent: generation.parent_generation,
131 });
132 }
133
134 previous = Some(generation.generation);
135 }
136
137 for record in self.allocation_history.records() {
141 if record.first_generation == 0 {
142 return Err(LedgerIntegrityError::UnknownRecordGeneration {
143 stable_key: record.stable_key.clone(),
144 generation: 0,
145 });
146 }
147 }
148
149 Ok(())
150 }
151}
152
153fn validate_record_integrity(
154 current_generation: u64,
155 record: &AllocationRecord,
156) -> Result<(), LedgerIntegrityError> {
157 record
158 .stable_key
159 .validate()
160 .map_err(LedgerIntegrityError::InvalidStableKey)?;
161 record
162 .slot
163 .validate()
164 .map_err(LedgerIntegrityError::InvalidSlotDescriptor)?;
165
166 if record.first_generation > record.last_seen_generation {
167 return Err(LedgerIntegrityError::InvalidRecordGenerationOrder {
168 stable_key: record.stable_key.clone(),
169 first_generation: record.first_generation,
170 last_seen_generation: record.last_seen_generation,
171 });
172 }
173 if record.last_seen_generation > current_generation {
174 return Err(LedgerIntegrityError::FutureRecordGeneration {
175 stable_key: record.stable_key.clone(),
176 generation: record.last_seen_generation,
177 current_generation,
178 });
179 }
180
181 match record.state {
182 AllocationState::Retired {
183 generation: retired_generation,
184 } => {
185 if retired_generation < record.first_generation {
186 return Err(LedgerIntegrityError::RetiredBeforeFirstGeneration {
187 stable_key: record.stable_key.clone(),
188 first_generation: record.first_generation,
189 retired_generation,
190 });
191 }
192 if retired_generation > current_generation {
193 return Err(LedgerIntegrityError::FutureRecordGeneration {
194 stable_key: record.stable_key.clone(),
195 generation: retired_generation,
196 current_generation,
197 });
198 }
199 if retired_generation <= record.last_seen_generation {
200 return Err(LedgerIntegrityError::RetirementNotAfterLastSeen {
201 stable_key: record.stable_key.clone(),
202 last_seen_generation: record.last_seen_generation,
203 retired_generation,
204 });
205 }
206 }
207 AllocationState::Reserved | AllocationState::Active => {}
208 }
209
210 validate_schema_history_integrity(current_generation, record)
211}
212
213fn validate_schema_history_integrity(
214 current_generation: u64,
215 record: &AllocationRecord,
216) -> Result<(), LedgerIntegrityError> {
217 if record.schema_history.is_empty() {
218 return Err(LedgerIntegrityError::EmptySchemaHistory {
219 stable_key: record.stable_key.clone(),
220 });
221 }
222
223 let first_schema_generation = record.schema_history[0].generation;
224 if first_schema_generation != record.first_generation {
225 return Err(LedgerIntegrityError::SchemaHistoryStartMismatch {
226 stable_key: record.stable_key.clone(),
227 first_generation: record.first_generation,
228 schema_generation: first_schema_generation,
229 });
230 }
231
232 let mut previous = None;
233 for schema in &record.schema_history {
234 schema
235 .schema
236 .validate()
237 .map_err(|error| LedgerIntegrityError::InvalidSchemaMetadata {
238 stable_key: record.stable_key.clone(),
239 generation: schema.generation,
240 error,
241 })?;
242 if previous.is_some_and(|generation| schema.generation <= generation) {
243 return Err(LedgerIntegrityError::NonIncreasingSchemaHistory {
244 stable_key: record.stable_key.clone(),
245 });
246 }
247 if schema.generation < record.first_generation || schema.generation > current_generation {
248 return Err(LedgerIntegrityError::SchemaHistoryOutOfBounds {
249 stable_key: record.stable_key.clone(),
250 generation: schema.generation,
251 });
252 }
253 if schema.generation > record.last_seen_generation {
254 return Err(LedgerIntegrityError::SchemaHistoryAfterLastSeen {
255 stable_key: record.stable_key.clone(),
256 generation: schema.generation,
257 last_seen_generation: record.last_seen_generation,
258 });
259 }
260 previous = Some(schema.generation);
261 }
262
263 Ok(())
264}