mithril_common/digesters/
ledger_state_snapshot.rs1use std::{
2 cmp::Ordering,
3 ffi::OsString,
4 path::{Path, PathBuf},
5};
6use thiserror::Error;
7use walkdir::WalkDir;
8
9use crate::digesters::LEDGER_DIR;
10use crate::entities::SlotNumber;
11
12fn find_ledger_dir(path_to_walk: &Path) -> Option<PathBuf> {
14 WalkDir::new(path_to_walk)
15 .into_iter()
16 .filter_entry(|e| e.file_type().is_dir())
17 .filter_map(|e| e.ok())
18 .find(|f| f.file_name() == LEDGER_DIR)
19 .map(|e| e.into_path())
20}
21
22fn is_ledger_state_snapshot(path: &Path) -> bool {
23 if path.is_dir() {
24 path.join(LedgerStateSnapshot::IN_MEMORY_META).exists()
25 && path.join(LedgerStateSnapshot::IN_MEMORY_STATE).exists()
26 && path.join(LedgerStateSnapshot::IN_MEMORY_TABLES).exists()
27 && path
28 .join(LedgerStateSnapshot::IN_MEMORY_TABLES)
29 .join(LedgerStateSnapshot::IN_MEMORY_TVAR)
30 .exists()
31 } else {
32 path.is_file()
33 }
34}
35
36#[derive(Debug, PartialEq, Eq, Clone)]
38pub enum LedgerStateSnapshot {
39 Legacy {
41 path: PathBuf,
43 slot_number: SlotNumber,
45 filename: OsString,
47 },
48 InMemory {
50 path: PathBuf,
52 slot_number: SlotNumber,
54 folder_name: OsString,
56 },
57}
58
59#[derive(Error, Debug)]
61pub enum LedgerStateSnapshotListingError {
62 #[error("Couldn't find the 'ledger' folder in '{0:?}'")]
64 MissingLedgerFolder(PathBuf),
65}
66
67impl LedgerStateSnapshot {
68 pub const IN_MEMORY_META: &'static str = "meta";
70 pub const IN_MEMORY_STATE: &'static str = "state";
72 pub const IN_MEMORY_TABLES: &'static str = "tables";
74 pub const IN_MEMORY_TVAR: &'static str = "tvar";
76
77 pub fn legacy(path: PathBuf, slot_number: SlotNumber, filename: OsString) -> Self {
79 Self::Legacy {
80 path,
81 slot_number,
82 filename,
83 }
84 }
85
86 pub fn in_memory(path: PathBuf, slot_number: SlotNumber, folder_name: OsString) -> Self {
88 Self::InMemory {
89 path,
90 slot_number,
91 folder_name,
92 }
93 }
94
95 pub fn from_path(path: &Path) -> Option<LedgerStateSnapshot> {
101 path.file_name().and_then(|filename| {
102 filename
103 .to_string_lossy()
104 .parse::<u64>()
105 .map(|number| {
106 if path.is_dir() {
107 Self::in_memory(
108 path.to_path_buf(),
109 SlotNumber(number),
110 filename.to_os_string(),
111 )
112 } else {
113 Self::legacy(
114 path.to_path_buf(),
115 SlotNumber(number),
116 filename.to_os_string(),
117 )
118 }
119 })
120 .ok()
121 })
122 }
123
124 pub fn list_all_in_dir(
126 dir: &Path,
127 ) -> Result<Vec<LedgerStateSnapshot>, LedgerStateSnapshotListingError> {
128 let ledger_dir = find_ledger_dir(dir).ok_or(
129 LedgerStateSnapshotListingError::MissingLedgerFolder(dir.to_path_buf()),
130 )?;
131 let mut files: Vec<LedgerStateSnapshot> = vec![];
132
133 for path in WalkDir::new(ledger_dir)
134 .min_depth(1)
135 .max_depth(1)
136 .into_iter()
137 .filter_entry(|e| is_ledger_state_snapshot(e.path()))
138 .filter_map(|file| file.ok())
139 {
140 if let Some(ledger_file) = LedgerStateSnapshot::from_path(path.path()) {
141 files.push(ledger_file);
142 }
143 }
144 files.sort();
145
146 Ok(files)
147 }
148
149 pub fn get_files_relative_path(&self) -> Vec<PathBuf> {
153 match self {
154 LedgerStateSnapshot::Legacy { filename, .. } => vec![PathBuf::from(filename)],
155 LedgerStateSnapshot::InMemory { folder_name, .. } => {
156 vec![
157 PathBuf::from(folder_name).join(Self::IN_MEMORY_META),
158 PathBuf::from(folder_name).join(Self::IN_MEMORY_STATE),
159 PathBuf::from(folder_name)
160 .join(Self::IN_MEMORY_TABLES)
161 .join(Self::IN_MEMORY_TVAR),
162 ]
163 }
164 }
165 }
166
167 pub fn slot_number(&self) -> SlotNumber {
169 match self {
170 LedgerStateSnapshot::Legacy { slot_number, .. }
171 | LedgerStateSnapshot::InMemory { slot_number, .. } => *slot_number,
172 }
173 }
174}
175
176impl PartialOrd for LedgerStateSnapshot {
177 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
178 Some(self.cmp(other))
179 }
180}
181
182impl Ord for LedgerStateSnapshot {
183 fn cmp(&self, other: &Self) -> Ordering {
184 self.slot_number().cmp(&other.slot_number())
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use std::fs::{create_dir, File};
191 use std::io::prelude::*;
192
193 use crate::test_utils::temp_dir_create;
194
195 use super::*;
196
197 fn create_ledger_dir(parent_dir: &Path) -> PathBuf {
198 let ledger_dir = parent_dir.join(LEDGER_DIR);
199 create_dir(&ledger_dir).unwrap();
200 ledger_dir
201 }
202
203 fn create_fake_files(parent_dir: &Path, child_filenames: &[&str]) {
204 for filename in child_filenames {
205 let file = parent_dir.join(Path::new(filename));
206 let mut source_file = File::create(file).unwrap();
207 write!(source_file, "This is a test file named '{filename}'").unwrap();
208 }
209 }
210
211 fn extract_filenames(ledger_files: &[LedgerStateSnapshot]) -> Vec<String> {
212 ledger_files
213 .iter()
214 .flat_map(|i| i.get_files_relative_path())
215 .map(|p| p.file_name().unwrap().to_string_lossy().to_string())
216 .collect()
217 }
218
219 #[test]
220 fn list_all_ledger_file_fail_if_not_in_ledger_dir() {
221 let target_dir = temp_dir_create!();
222
223 LedgerStateSnapshot::list_all_in_dir(&target_dir)
224 .expect_err("LedgerStateSnapshot::list_all_in_dir should have Failed");
225 }
226
227 #[test]
228 fn list_all_ledger_file_should_works_in_a_empty_folder() {
229 let target_dir = temp_dir_create!();
230 create_ledger_dir(&target_dir);
231 let result = LedgerStateSnapshot::list_all_in_dir(&target_dir)
232 .expect("LedgerStateSnapshot::list_all_in_dir should work in a empty folder");
233
234 assert_eq!(Vec::<LedgerStateSnapshot>::new(), result);
235 }
236
237 mod legacy_ledger_state {
238 use super::*;
239
240 #[test]
241 fn list_all_ledger_file_order_should_be_deterministic() {
242 let target_dir = temp_dir_create!();
243 let ledger_dir = create_ledger_dir(&target_dir);
244 let entries = vec!["424", "123", "124", "00125", "21", "223", "0423"];
245 create_fake_files(&ledger_dir, &entries);
246 let ledger_files = LedgerStateSnapshot::list_all_in_dir(&target_dir)
247 .expect("LedgerStateSnapshot::list_all_in_dir Failed");
248
249 assert_eq!(
250 vec!["21", "123", "124", "00125", "223", "0423", "424"],
251 extract_filenames(&ledger_files)
252 );
253 }
254
255 #[test]
256 fn list_all_ledger_file_should_work_with_non_ledger_files() {
257 let target_dir = temp_dir_create!();
258 let ledger_dir = create_ledger_dir(&target_dir);
259 let entries = vec!["123", "124", "README.md", "124.back"];
260 create_fake_files(&ledger_dir, &entries);
261 let ledger_files = LedgerStateSnapshot::list_all_in_dir(&target_dir)
262 .expect("LedgerStateSnapshot::list_all_in_dir Failed");
263
264 assert_eq!(vec!["123", "124"], extract_filenames(&ledger_files));
265 }
266 }
267
268 mod utxo_hd_in_memory_ledger_state {
276 use std::fs::create_dir_all;
277
278 use super::*;
279
280 #[test]
281 fn list_all_ledger_state_should_not_include_utxo_hd_folder_that_does_not_contains_meta_state_or_tvar_files(
282 ) {
283 let target_dir = temp_dir_create!();
284 let ledger_dir = create_ledger_dir(&target_dir);
285
286 let ledger_empty_dir = ledger_dir.join("000");
287 create_dir(&ledger_empty_dir).unwrap();
288
289 let ledger_with_missing_meta_files = ledger_dir.join("100");
290 create_dir_all(
291 ledger_with_missing_meta_files.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
292 )
293 .unwrap();
294 create_fake_files(
295 &ledger_with_missing_meta_files,
296 &[LedgerStateSnapshot::IN_MEMORY_STATE],
297 );
298 create_fake_files(
299 &ledger_with_missing_meta_files.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
300 &[LedgerStateSnapshot::IN_MEMORY_TVAR],
301 );
302
303 let ledger_with_missing_state_files = ledger_dir.join("200");
304 create_dir_all(
305 ledger_with_missing_state_files.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
306 )
307 .unwrap();
308 create_fake_files(
309 &ledger_with_missing_state_files,
310 &[LedgerStateSnapshot::IN_MEMORY_META],
311 );
312 create_fake_files(
313 &ledger_with_missing_meta_files.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
314 &[LedgerStateSnapshot::IN_MEMORY_TVAR],
315 );
316
317 let ledger_with_missing_tvar_files = ledger_dir.join("300");
318 create_dir_all(
319 ledger_with_missing_tvar_files.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
320 )
321 .unwrap();
322 create_fake_files(
323 &ledger_with_missing_tvar_files,
324 &[
325 LedgerStateSnapshot::IN_MEMORY_STATE,
326 LedgerStateSnapshot::IN_MEMORY_META,
327 ],
328 );
329
330 let ledger_with_missing_table_folder = ledger_dir.join("400");
331 create_dir(&ledger_with_missing_table_folder).unwrap();
332 create_fake_files(
333 &ledger_with_missing_table_folder,
334 &[
335 LedgerStateSnapshot::IN_MEMORY_STATE,
336 LedgerStateSnapshot::IN_MEMORY_META,
337 ],
338 );
339
340 let result = LedgerStateSnapshot::list_all_in_dir(&target_dir).unwrap();
341
342 assert_eq!(Vec::<LedgerStateSnapshot>::new(), result);
343 }
344
345 #[test]
346 fn list_all_ledger_state_with_valid_utxo_hd_folder_structure() {
347 let target_dir = temp_dir_create!();
348 let ledger_dir = create_ledger_dir(&target_dir);
349
350 let ledger_state = ledger_dir.join("200");
351 create_dir_all(ledger_state.join(LedgerStateSnapshot::IN_MEMORY_TABLES)).unwrap();
352 create_fake_files(
353 &ledger_state,
354 &[
355 LedgerStateSnapshot::IN_MEMORY_META,
356 LedgerStateSnapshot::IN_MEMORY_STATE,
357 ],
358 );
359 create_fake_files(
360 &ledger_state.join(LedgerStateSnapshot::IN_MEMORY_TABLES),
361 &[LedgerStateSnapshot::IN_MEMORY_TVAR],
362 );
363
364 let result = LedgerStateSnapshot::list_all_in_dir(&target_dir).unwrap();
365
366 assert_eq!(
367 vec![LedgerStateSnapshot::in_memory(
368 ledger_state,
369 SlotNumber(200),
370 "200".into()
371 )],
372 result
373 );
374 }
375
376 #[test]
377 fn get_relative_path_only_list_meta_state_and_tvar_files_even_if_there_are_other_files_in_the_folder(
378 ) {
379 let target_dir = temp_dir_create!();
380 create_dir_all(
381 target_dir
382 .join("050")
383 .join(LedgerStateSnapshot::IN_MEMORY_TABLES),
384 )
385 .unwrap();
386 let ledger_state = LedgerStateSnapshot::in_memory(
387 target_dir.join("050"),
388 SlotNumber(50),
389 "050".into(),
390 );
391
392 assert_eq!(
393 vec![
394 PathBuf::from("050").join(LedgerStateSnapshot::IN_MEMORY_META),
395 PathBuf::from("050").join(LedgerStateSnapshot::IN_MEMORY_STATE),
396 PathBuf::from("050")
397 .join(LedgerStateSnapshot::IN_MEMORY_TABLES)
398 .join(LedgerStateSnapshot::IN_MEMORY_TVAR)
399 ],
400 ledger_state.get_files_relative_path(),
401 )
402 }
403 }
404}