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