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