ic_memory/ledger/
integrity.rs1use super::{AllocationLedger, AllocationRecord, AllocationState, LedgerIntegrityError};
2use crate::declaration::validate_runtime_fingerprint;
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 crate::constants::MAX_ALLOCATIONS,
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; crate::constants::MAX_ALLOCATIONS];
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 occupied = &mut slots[usize::from(record.slot.id())];
60 if *occupied {
61 return Err(LedgerIntegrityError::DuplicateSlot {
62 slot: record.slot.clone(),
63 });
64 }
65 *occupied = true;
66 validate_record_integrity(self.current_generation, record)?;
67 }
68
69 let mut generations = BTreeSet::new();
70 for generation in self.allocation_history.generations() {
71 if !generations.insert(generation.generation) {
72 return Err(LedgerIntegrityError::DuplicateGeneration {
73 generation: generation.generation,
74 });
75 }
76 if generation.generation > self.current_generation {
77 return Err(LedgerIntegrityError::FutureGeneration {
78 generation: generation.generation,
79 current_generation: self.current_generation,
80 });
81 }
82 if generation.parent_generation >= generation.generation {
83 return Err(LedgerIntegrityError::InvalidParentGeneration {
84 generation: generation.generation,
85 parent_generation: generation.parent_generation,
86 });
87 }
88 }
89
90 Ok(())
91 }
92
93 pub fn validate_committed_integrity(&self) -> Result<(), LedgerIntegrityError> {
98 self.validate_integrity()?;
99
100 if self.current_generation != 0
101 && !self
102 .allocation_history
103 .generations()
104 .iter()
105 .any(|record| record.generation == self.current_generation)
106 {
107 return Err(LedgerIntegrityError::MissingCurrentGenerationRecord {
108 current_generation: self.current_generation,
109 });
110 }
111
112 let mut expected_parent = 0;
113 for generation in self.allocation_history.generations() {
114 validate_runtime_fingerprint(generation.runtime_fingerprint.as_deref())
115 .map_err(LedgerIntegrityError::DiagnosticMetadata)?;
116
117 if generation.generation != expected_parent + 1 {
118 return Err(LedgerIntegrityError::NonIncreasingGenerationRecords {
119 generation: generation.generation,
120 });
121 }
122
123 if generation.parent_generation != expected_parent {
124 return Err(LedgerIntegrityError::BrokenGenerationChain {
125 generation: generation.generation,
126 expected_parent,
127 actual_parent: generation.parent_generation,
128 });
129 }
130
131 expected_parent = generation.generation;
132 }
133
134 for record in self.allocation_history.records() {
138 if record.first_generation == 0 {
139 return Err(LedgerIntegrityError::UnknownRecordGeneration {
140 stable_key: record.stable_key.clone(),
141 generation: 0,
142 });
143 }
144 }
145
146 Ok(())
147 }
148}
149
150fn validate_record_integrity(
151 current_generation: u64,
152 record: &AllocationRecord,
153) -> Result<(), LedgerIntegrityError> {
154 record
155 .stable_key
156 .validate()
157 .map_err(LedgerIntegrityError::InvalidStableKey)?;
158
159 if record.first_generation > record.last_seen_generation {
160 return Err(LedgerIntegrityError::InvalidRecordGenerationOrder {
161 stable_key: record.stable_key.clone(),
162 first_generation: record.first_generation,
163 last_seen_generation: record.last_seen_generation,
164 });
165 }
166 if record.last_seen_generation > current_generation {
167 return Err(LedgerIntegrityError::FutureRecordGeneration {
168 stable_key: record.stable_key.clone(),
169 generation: record.last_seen_generation,
170 current_generation,
171 });
172 }
173
174 match record.state {
175 AllocationState::Retired {
176 generation: retired_generation,
177 } => {
178 if retired_generation < record.first_generation {
179 return Err(LedgerIntegrityError::RetiredBeforeFirstGeneration {
180 stable_key: record.stable_key.clone(),
181 first_generation: record.first_generation,
182 retired_generation,
183 });
184 }
185 if retired_generation > current_generation {
186 return Err(LedgerIntegrityError::FutureRecordGeneration {
187 stable_key: record.stable_key.clone(),
188 generation: retired_generation,
189 current_generation,
190 });
191 }
192 if retired_generation <= record.last_seen_generation {
193 return Err(LedgerIntegrityError::RetirementNotAfterLastSeen {
194 stable_key: record.stable_key.clone(),
195 last_seen_generation: record.last_seen_generation,
196 retired_generation,
197 });
198 }
199 }
200 AllocationState::Reserved | AllocationState::Active => {}
201 }
202
203 validate_schema_history_integrity(current_generation, record)
204}
205
206fn validate_schema_history_integrity(
207 current_generation: u64,
208 record: &AllocationRecord,
209) -> Result<(), LedgerIntegrityError> {
210 if record.schema_history.is_empty() {
211 return Err(LedgerIntegrityError::EmptySchemaHistory {
212 stable_key: record.stable_key.clone(),
213 });
214 }
215
216 let first_schema_generation = record.schema_history[0].generation;
217 if first_schema_generation != record.first_generation {
218 return Err(LedgerIntegrityError::SchemaHistoryStartMismatch {
219 stable_key: record.stable_key.clone(),
220 first_generation: record.first_generation,
221 schema_generation: first_schema_generation,
222 });
223 }
224
225 let mut previous = None;
226 for schema in &record.schema_history {
227 schema
228 .schema
229 .validate()
230 .map_err(|error| LedgerIntegrityError::InvalidSchemaMetadata {
231 stable_key: record.stable_key.clone(),
232 generation: schema.generation,
233 error,
234 })?;
235 if previous.is_some_and(|generation| schema.generation <= generation) {
236 return Err(LedgerIntegrityError::NonIncreasingSchemaHistory {
237 stable_key: record.stable_key.clone(),
238 });
239 }
240 if schema.generation > current_generation {
242 return Err(LedgerIntegrityError::SchemaHistoryOutOfBounds {
243 stable_key: record.stable_key.clone(),
244 generation: schema.generation,
245 });
246 }
247 if schema.generation > record.last_seen_generation {
248 return Err(LedgerIntegrityError::SchemaHistoryAfterLastSeen {
249 stable_key: record.stable_key.clone(),
250 generation: schema.generation,
251 last_seen_generation: record.last_seen_generation,
252 });
253 }
254 previous = Some(schema.generation);
255 }
256
257 Ok(())
258}