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