1use crate::{
2 crypto_helper::{MKTree, MKTreeStoreInMemory},
3 digesters::{
4 cache::ImmutableFileDigestCacheProvider, ImmutableDigester, ImmutableDigesterError,
5 ImmutableFile,
6 },
7 entities::{CardanoDbBeacon, HexEncodedDigest, ImmutableFileNumber},
8 logging::LoggerExtensions,
9};
10use async_trait::async_trait;
11use sha2::{Digest, Sha256};
12use slog::{debug, info, warn, Logger};
13use std::{collections::BTreeMap, io, ops::RangeInclusive, path::Path, sync::Arc};
14
15use super::immutable_digester::ComputedImmutablesDigests;
16
17pub struct CardanoImmutableDigester {
19 cardano_network: String,
20
21 cache_provider: Option<Arc<dyn ImmutableFileDigestCacheProvider>>,
23
24 logger: Logger,
26}
27
28impl CardanoImmutableDigester {
29 pub fn new(
31 cardano_network: String,
32 cache_provider: Option<Arc<dyn ImmutableFileDigestCacheProvider>>,
33 logger: Logger,
34 ) -> Self {
35 Self {
36 cardano_network,
37 cache_provider,
38 logger: logger.new_with_component_name::<Self>(),
39 }
40 }
41
42 async fn process_immutables(
43 &self,
44 immutables: Vec<ImmutableFile>,
45 ) -> Result<ComputedImmutablesDigests, ImmutableDigesterError> {
46 let cached_values = self.fetch_immutables_cached(immutables).await;
47
48 let logger = self.logger.clone();
50 let computed_digests =
51 tokio::task::spawn_blocking(move || -> Result<ComputedImmutablesDigests, io::Error> {
52 ComputedImmutablesDigests::compute_immutables_digests(cached_values, logger)
53 })
54 .await
55 .map_err(|e| ImmutableDigesterError::DigestComputationError(e.into()))??;
56
57 Ok(computed_digests)
58 }
59
60 async fn fetch_immutables_cached(
61 &self,
62 immutables: Vec<ImmutableFile>,
63 ) -> BTreeMap<ImmutableFile, Option<String>> {
64 match self.cache_provider.as_ref() {
65 None => BTreeMap::from_iter(immutables.into_iter().map(|i| (i, None))),
66 Some(cache_provider) => match cache_provider.get(immutables.clone()).await {
67 Ok(values) => values,
68 Err(error) => {
69 warn!(
70 self.logger, "Error while getting cached immutable files digests";
71 "error" => ?error
72 );
73 BTreeMap::from_iter(immutables.into_iter().map(|i| (i, None)))
74 }
75 },
76 }
77 }
78
79 async fn update_cache(&self, computed_immutables_digests: &ComputedImmutablesDigests) {
80 if let Some(cache_provider) = self.cache_provider.as_ref() {
81 let new_cached_entries = computed_immutables_digests
82 .entries
83 .iter()
84 .filter(|(file, _hash)| {
85 computed_immutables_digests
86 .new_cached_entries
87 .contains(&file.filename)
88 })
89 .map(|(file, hash)| (file.filename.clone(), hash.clone()))
90 .collect();
91
92 if let Err(error) = cache_provider.store(new_cached_entries).await {
93 warn!(
94 self.logger, "Error while storing new immutable files digests to cache";
95 "error" => ?error
96 );
97 }
98 }
99 }
100}
101
102#[async_trait]
103impl ImmutableDigester for CardanoImmutableDigester {
104 async fn compute_digest(
105 &self,
106 dirpath: &Path,
107 beacon: &CardanoDbBeacon,
108 ) -> Result<String, ImmutableDigesterError> {
109 let immutables_to_process =
110 list_immutable_files_to_process(dirpath, beacon.immutable_file_number)?;
111 info!(self.logger, ">> compute_digest"; "beacon" => #?beacon, "nb_of_immutables" => immutables_to_process.len());
112 let computed_immutables_digests = self.process_immutables(immutables_to_process).await?;
113
114 self.update_cache(&computed_immutables_digests).await;
115
116 let digest = {
117 let mut hasher = Sha256::new();
118 hasher.update(compute_beacon_hash(&self.cardano_network, beacon).as_bytes());
119 for (_, digest) in computed_immutables_digests.entries {
120 hasher.update(digest);
121 }
122 let hash: [u8; 32] = hasher.finalize().into();
123
124 hex::encode(hash)
125 };
126
127 debug!(self.logger, "Computed digest: {digest:?}");
128
129 Ok(digest)
130 }
131
132 async fn compute_digests_for_range(
133 &self,
134 dirpath: &Path,
135 range: &RangeInclusive<ImmutableFileNumber>,
136 ) -> Result<ComputedImmutablesDigests, ImmutableDigesterError> {
137 let immutables_to_process = list_immutable_files_to_process_for_range(dirpath, range)?;
138 info!(self.logger, ">> compute_digests_for_range"; "nb_of_immutables" => immutables_to_process.len());
139 let computed_immutables_digests = self.process_immutables(immutables_to_process).await?;
140
141 self.update_cache(&computed_immutables_digests).await;
142
143 debug!(
144 self.logger,
145 "Successfully computed Digests for Cardano database"; "range" => #?range);
146
147 Ok(computed_immutables_digests)
148 }
149
150 async fn compute_merkle_tree(
151 &self,
152 dirpath: &Path,
153 beacon: &CardanoDbBeacon,
154 ) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError> {
155 let immutables_to_process =
156 list_immutable_files_to_process(dirpath, beacon.immutable_file_number)?;
157 info!(self.logger, ">> compute_merkle_tree"; "beacon" => #?beacon, "nb_of_immutables" => immutables_to_process.len());
158 let computed_immutables_digests = self.process_immutables(immutables_to_process).await?;
159
160 self.update_cache(&computed_immutables_digests).await;
161
162 let digests: Vec<HexEncodedDigest> =
163 computed_immutables_digests.entries.into_values().collect();
164 let mktree =
165 MKTree::new(&digests).map_err(ImmutableDigesterError::MerkleTreeComputationError)?;
166
167 debug!(
168 self.logger,
169 "Successfully computed Merkle tree for Cardano database"; "beacon" => #?beacon);
170
171 Ok(mktree)
172 }
173}
174
175fn list_immutable_files_to_process(
176 dirpath: &Path,
177 up_to_file_number: ImmutableFileNumber,
178) -> Result<Vec<ImmutableFile>, ImmutableDigesterError> {
179 let immutables: Vec<ImmutableFile> = ImmutableFile::list_all_in_dir(dirpath)?
180 .into_iter()
181 .filter(|f| f.number <= up_to_file_number)
182 .collect();
183
184 match immutables.last() {
185 None => Err(ImmutableDigesterError::NotEnoughImmutable {
186 expected_number: up_to_file_number,
187 found_number: None,
188 db_dir: dirpath.to_owned(),
189 }),
190 Some(last_immutable_file) if last_immutable_file.number < up_to_file_number => {
191 Err(ImmutableDigesterError::NotEnoughImmutable {
192 expected_number: up_to_file_number,
193 found_number: Some(last_immutable_file.number),
194 db_dir: dirpath.to_owned(),
195 })
196 }
197 Some(_) => Ok(immutables),
198 }
199}
200
201fn list_immutable_files_to_process_for_range(
202 dirpath: &Path,
203 range: &RangeInclusive<ImmutableFileNumber>,
204) -> Result<Vec<ImmutableFile>, ImmutableDigesterError> {
205 let immutables: Vec<ImmutableFile> = ImmutableFile::list_all_in_dir(dirpath)?
206 .into_iter()
207 .filter(|f| range.contains(&f.number))
208 .collect();
209
210 Ok(immutables)
211}
212
213fn compute_beacon_hash(network: &str, cardano_db_beacon: &CardanoDbBeacon) -> String {
214 let mut hasher = Sha256::new();
215 hasher.update(network.as_bytes());
216 hasher.update(cardano_db_beacon.epoch.to_be_bytes());
217 hasher.update(cardano_db_beacon.immutable_file_number.to_be_bytes());
218 hex::encode(hasher.finalize())
219}
220
221#[cfg(test)]
222mod tests {
223 use sha2::Sha256;
224 use std::{collections::BTreeMap, io, sync::Arc};
225 use tokio::time::Instant;
226
227 use crate::{
228 digesters::{
229 cache::{
230 ImmutableDigesterCacheGetError, ImmutableDigesterCacheProviderError,
231 ImmutableDigesterCacheStoreError, MemoryImmutableFileDigestCacheProvider,
232 MockImmutableFileDigestCacheProvider,
233 },
234 DummyCardanoDbBuilder,
235 },
236 entities::ImmutableFileNumber,
237 test_utils::TestLogger,
238 };
239
240 use super::*;
241
242 fn db_builder(dir_name: &str) -> DummyCardanoDbBuilder {
243 DummyCardanoDbBuilder::new(&format!("cardano_immutable_digester/{dir_name}"))
244 }
245
246 #[test]
247 fn test_compute_beacon_hash() {
248 let hash_expected = "48cbf709b56204d8315aefd3a416b45398094f6fd51785c5b7dcaf7f35aacbfb";
249 let (network, epoch, immutable_file_number) = ("testnet", 10, 100);
250
251 assert_eq!(
252 hash_expected,
253 compute_beacon_hash(network, &CardanoDbBeacon::new(epoch, immutable_file_number))
254 );
255 assert_ne!(
256 hash_expected,
257 compute_beacon_hash(
258 "mainnet",
259 &CardanoDbBeacon::new(epoch, immutable_file_number)
260 )
261 );
262 assert_ne!(
263 hash_expected,
264 compute_beacon_hash(network, &CardanoDbBeacon::new(20, immutable_file_number))
265 );
266 assert_ne!(
267 hash_expected,
268 compute_beacon_hash(network, &CardanoDbBeacon::new(epoch, 200))
269 );
270 }
271
272 #[tokio::test]
273 async fn fail_if_no_file_in_folder() {
274 let cardano_db = db_builder("fail_if_no_file_in_folder").build();
275
276 let result = list_immutable_files_to_process(cardano_db.get_immutable_dir(), 1)
277 .expect_err("list_immutable_files_to_process should have failed");
278
279 assert_eq!(
280 format!(
281 "{:?}",
282 ImmutableDigesterError::NotEnoughImmutable {
283 expected_number: 1,
284 found_number: None,
285 db_dir: cardano_db.get_immutable_dir().to_path_buf(),
286 }
287 ),
288 format!("{result:?}")
289 );
290 }
291
292 #[tokio::test]
293 async fn fail_if_a_invalid_file_is_in_immutable_folder() {
294 let cardano_db = db_builder("fail_if_no_immutable_exist")
295 .with_non_immutables(&["not_immutable"])
296 .build();
297
298 assert!(list_immutable_files_to_process(cardano_db.get_immutable_dir(), 1).is_err());
299 }
300
301 #[tokio::test]
302 async fn can_list_files_to_process_even_if_theres_only_the_uncompleted_immutable_trio() {
303 let cardano_db = db_builder(
304 "can_list_files_to_process_even_if_theres_only_the_uncompleted_immutable_trio",
305 )
306 .with_immutables(&[1])
307 .build();
308
309 let processable_files =
310 list_immutable_files_to_process(cardano_db.get_immutable_dir(), 1).unwrap();
311
312 assert_eq!(
313 vec![
314 "00001.chunk".to_string(),
315 "00001.primary".to_string(),
316 "00001.secondary".to_string()
317 ],
318 processable_files
319 .into_iter()
320 .map(|f| f.filename)
321 .collect::<Vec<_>>()
322 );
323 }
324
325 #[tokio::test]
326 async fn fail_if_less_immutable_than_what_required_in_beacon() {
327 let cardano_db = db_builder("fail_if_less_immutable_than_what_required_in_beacon")
328 .with_immutables(&[1, 2, 3, 4, 5])
329 .append_immutable_trio()
330 .build();
331
332 let result = list_immutable_files_to_process(cardano_db.get_immutable_dir(), 10)
333 .expect_err("list_immutable_files_to_process should've failed");
334
335 assert_eq!(
336 format!(
337 "{:?}",
338 ImmutableDigesterError::NotEnoughImmutable {
339 expected_number: 10,
340 found_number: Some(6),
341 db_dir: cardano_db.get_immutable_dir().to_path_buf(),
342 }
343 ),
344 format!("{result:?}")
345 );
346 }
347
348 #[tokio::test]
349 async fn can_compute_hash_of_a_hundred_immutable_file_trio() {
350 let cardano_db = db_builder("can_compute_hash_of_a_hundred_immutable_file_trio")
351 .with_immutables(&(1..=100).collect::<Vec<ImmutableFileNumber>>())
352 .append_immutable_trio()
353 .build();
354 let logger = TestLogger::stdout();
355 let digester = CardanoImmutableDigester::new(
356 "devnet".to_string(),
357 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
358 logger.clone(),
359 );
360 let beacon = CardanoDbBeacon::new(1, 100);
361
362 let result = digester
363 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
364 .await
365 .expect("compute_digest must not fail");
366
367 assert_eq!(
368 "a27fd67e495c2c77e4b6b0af9925b2b0bc39656c56adfad4aaab9f20fae49122".to_string(),
369 result
370 )
371 }
372
373 #[tokio::test]
374 async fn can_compute_merkle_tree_of_a_hundred_immutable_file_trio() {
375 let cardano_db = db_builder("can_compute_merkle_tree_of_a_hundred_immutable_file_trio")
376 .with_immutables(&(1..=100).collect::<Vec<ImmutableFileNumber>>())
377 .append_immutable_trio()
378 .build();
379 let logger = TestLogger::stdout();
380 let digester = CardanoImmutableDigester::new(
381 "devnet".to_string(),
382 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
383 logger.clone(),
384 );
385 let beacon = CardanoDbBeacon::new(1, 100);
386
387 let result = digester
388 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
389 .await
390 .expect("compute_merkle_tree must not fail");
391
392 let expected_merkle_root = result.compute_root().unwrap().to_hex();
393
394 assert_eq!(
395 "8552f75838176c967a33eb6da1fe5f3c9940b706d75a9c2352c0acd8439f3d84".to_string(),
396 expected_merkle_root
397 )
398 }
399
400 #[tokio::test]
401 async fn can_compute_digests_for_range_of_a_hundred_immutable_file_trio() {
402 let immutable_range = 1..=100;
403 let cardano_db =
404 db_builder("can_compute_digests_for_range_of_a_hundred_immutable_file_trio")
405 .with_immutables(
406 &immutable_range
407 .clone()
408 .collect::<Vec<ImmutableFileNumber>>(),
409 )
410 .append_immutable_trio()
411 .build();
412 let logger = TestLogger::stdout();
413 let digester = CardanoImmutableDigester::new(
414 "devnet".to_string(),
415 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
416 logger.clone(),
417 );
418
419 let result = digester
420 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
421 .await
422 .expect("compute_digests_for_range must not fail");
423
424 assert_eq!(cardano_db.get_immutable_files().len(), result.entries.len())
425 }
426
427 #[tokio::test]
428 async fn can_compute_consistent_digests_for_range() {
429 let immutable_range = 1..=1;
430 let cardano_db = db_builder("can_compute_digests_for_range_consistently")
431 .with_immutables(
432 &immutable_range
433 .clone()
434 .collect::<Vec<ImmutableFileNumber>>(),
435 )
436 .append_immutable_trio()
437 .build();
438 let logger = TestLogger::stdout();
439 let digester = CardanoImmutableDigester::new(
440 "devnet".to_string(),
441 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
442 logger.clone(),
443 );
444
445 let result = digester
446 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
447 .await
448 .expect("compute_digests_for_range must not fail");
449
450 assert_eq!(
451 BTreeMap::from([
452 (
453 ImmutableFile {
454 path: cardano_db.get_immutable_dir().join("00001.chunk"),
455 number: 1,
456 filename: "00001.chunk".to_string()
457 },
458 "faebbf47077f68ef57219396ff69edc738978a3eca946ac7df1983dbf11364ec".to_string()
459 ),
460 (
461 ImmutableFile {
462 path: cardano_db.get_immutable_dir().join("00001.primary"),
463 number: 1,
464 filename: "00001.primary".to_string()
465 },
466 "f11bdb991fc7e72970be7d7f666e10333f92c14326d796fed8c2c041675fa826".to_string()
467 ),
468 (
469 ImmutableFile {
470 path: cardano_db.get_immutable_dir().join("00001.secondary"),
471 number: 1,
472 filename: "00001.secondary".to_string()
473 },
474 "b139684b968fa12ce324cce464d000de0e2c2ded0fd3e473a666410821d3fde3".to_string()
475 )
476 ]),
477 result.entries
478 );
479 }
480
481 #[tokio::test]
482 async fn compute_digest_store_digests_into_cache_provider() {
483 let cardano_db = db_builder("compute_digest_store_digests_into_cache_provider")
484 .with_immutables(&[1, 2])
485 .append_immutable_trio()
486 .build();
487 let immutables = cardano_db.get_immutable_files().clone();
488 let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
489 let logger = TestLogger::stdout();
490 let digester = CardanoImmutableDigester::new(
491 "devnet".to_string(),
492 Some(cache.clone()),
493 logger.clone(),
494 );
495 let beacon = CardanoDbBeacon::new(1, 2);
496
497 digester
498 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
499 .await
500 .expect("compute_digest must not fail");
501
502 let cached_entries = cache
503 .get(immutables.clone())
504 .await
505 .expect("Cache read should not fail");
506 let expected: BTreeMap<_, _> = immutables
507 .into_iter()
508 .map(|i| {
509 let digest = hex::encode(i.compute_raw_hash::<Sha256>().unwrap());
510 (i, Some(digest))
511 })
512 .collect();
513
514 assert_eq!(expected, cached_entries);
515 }
516
517 #[tokio::test]
518 async fn compute_merkle_tree_store_digests_into_cache_provider() {
519 let cardano_db = db_builder("compute_merkle_tree_store_digests_into_cache_provider")
520 .with_immutables(&[1, 2])
521 .append_immutable_trio()
522 .build();
523 let immutables = cardano_db.get_immutable_files().clone();
524 let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
525 let logger = TestLogger::stdout();
526 let digester = CardanoImmutableDigester::new(
527 "devnet".to_string(),
528 Some(cache.clone()),
529 logger.clone(),
530 );
531 let beacon = CardanoDbBeacon::new(1, 2);
532
533 digester
534 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
535 .await
536 .expect("compute_digest must not fail");
537
538 let cached_entries = cache
539 .get(immutables.clone())
540 .await
541 .expect("Cache read should not fail");
542 let expected: BTreeMap<_, _> = immutables
543 .into_iter()
544 .map(|i| {
545 let digest = hex::encode(i.compute_raw_hash::<Sha256>().unwrap());
546 (i, Some(digest))
547 })
548 .collect();
549
550 assert_eq!(expected, cached_entries);
551 }
552
553 #[tokio::test]
554 async fn compute_digests_for_range_stores_digests_into_cache_provider() {
555 let cardano_db = db_builder("compute_digests_for_range_stores_digests_into_cache_provider")
556 .with_immutables(&[1, 2])
557 .append_immutable_trio()
558 .build();
559 let immutables = cardano_db.get_immutable_files().clone();
560 let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
561 let logger = TestLogger::stdout();
562 let digester = CardanoImmutableDigester::new(
563 "devnet".to_string(),
564 Some(cache.clone()),
565 logger.clone(),
566 );
567 let immutable_range = 1..=2;
568
569 digester
570 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
571 .await
572 .expect("compute_digests_for_range must not fail");
573
574 let cached_entries = cache
575 .get(immutables.clone())
576 .await
577 .expect("Cache read should not fail");
578 let expected: BTreeMap<_, _> = immutables
579 .into_iter()
580 .filter(|i| immutable_range.contains(&i.number))
581 .map(|i| {
582 let digest = hex::encode(i.compute_raw_hash::<Sha256>().unwrap());
583 (i.to_owned(), Some(digest))
584 })
585 .collect();
586
587 assert_eq!(expected, cached_entries);
588 }
589
590 #[tokio::test]
591 async fn computed_digest_with_cold_or_hot_or_without_any_cache_are_equals() {
592 let cardano_db = DummyCardanoDbBuilder::new(
593 "computed_digest_with_cold_or_hot_or_without_any_cache_are_equals",
594 )
595 .with_immutables(&[1, 2, 3])
596 .append_immutable_trio()
597 .build();
598 let logger = TestLogger::stdout();
599 let no_cache_digester =
600 CardanoImmutableDigester::new("devnet".to_string(), None, logger.clone());
601 let cache_digester = CardanoImmutableDigester::new(
602 "devnet".to_string(),
603 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
604 logger.clone(),
605 );
606 let beacon = CardanoDbBeacon::new(1, 3);
607
608 let without_cache_digest = no_cache_digester
609 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
610 .await
611 .expect("compute_digest must not fail");
612
613 let cold_cache_digest = cache_digester
614 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
615 .await
616 .expect("compute_digest must not fail");
617
618 let full_cache_digest = cache_digester
619 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
620 .await
621 .expect("compute_digest must not fail");
622
623 assert_eq!(
624 without_cache_digest, full_cache_digest,
625 "Digests with or without cache should be the same"
626 );
627
628 assert_eq!(
629 cold_cache_digest, full_cache_digest,
630 "Digests with cold or with hot cache should be the same"
631 );
632 }
633
634 #[tokio::test]
635 async fn computed_merkle_tree_with_cold_or_hot_or_without_any_cache_are_equals() {
636 let cardano_db = DummyCardanoDbBuilder::new(
637 "computed_merkle_tree_with_cold_or_hot_or_without_any_cache_are_equals",
638 )
639 .with_immutables(&[1, 2, 3])
640 .append_immutable_trio()
641 .build();
642 let logger = TestLogger::stdout();
643 let no_cache_digester =
644 CardanoImmutableDigester::new("devnet".to_string(), None, logger.clone());
645 let cache_digester = CardanoImmutableDigester::new(
646 "devnet".to_string(),
647 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
648 logger.clone(),
649 );
650 let beacon = CardanoDbBeacon::new(1, 3);
651
652 let without_cache_digest = no_cache_digester
653 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
654 .await
655 .expect("compute_merkle_tree must not fail");
656
657 let cold_cache_digest = cache_digester
658 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
659 .await
660 .expect("compute_merkle_tree must not fail");
661
662 let full_cache_digest = cache_digester
663 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
664 .await
665 .expect("compute_merkle_tree must not fail");
666
667 let without_cache_merkle_root = without_cache_digest.compute_root().unwrap();
668 let cold_cache_merkle_root = cold_cache_digest.compute_root().unwrap();
669 let full_cache_merkle_root = full_cache_digest.compute_root().unwrap();
670 assert_eq!(
671 without_cache_merkle_root, full_cache_merkle_root,
672 "Merkle roots with or without cache should be the same"
673 );
674
675 assert_eq!(
676 cold_cache_merkle_root, full_cache_merkle_root,
677 "Merkle roots with cold or with hot cache should be the same"
678 );
679 }
680
681 #[tokio::test]
682 async fn computed_digests_for_range_with_cold_or_hot_or_without_any_cache_are_equals() {
683 let cardano_db = DummyCardanoDbBuilder::new(
684 "computed_digests_for_range_with_cold_or_hot_or_without_any_cache_are_equals",
685 )
686 .with_immutables(&[1, 2, 3])
687 .append_immutable_trio()
688 .build();
689 let logger = TestLogger::stdout();
690 let no_cache_digester =
691 CardanoImmutableDigester::new("devnet".to_string(), None, logger.clone());
692 let cache_digester = CardanoImmutableDigester::new(
693 "devnet".to_string(),
694 Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
695 logger.clone(),
696 );
697 let immutable_range = 1..=3;
698
699 let without_cache_digests = no_cache_digester
700 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
701 .await
702 .expect("compute_digests_for_range must not fail");
703
704 let cold_cache_digests = cache_digester
705 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
706 .await
707 .expect("compute_digests_for_range must not fail");
708
709 let full_cache_digests = cache_digester
710 .compute_digests_for_range(cardano_db.get_immutable_dir(), &immutable_range)
711 .await
712 .expect("compute_digests_for_range must not fail");
713
714 let without_cache_entries = without_cache_digests.entries;
715 let cold_cache_entries = cold_cache_digests.entries;
716 let full_cache_entries = full_cache_digests.entries;
717 assert_eq!(
718 without_cache_entries, full_cache_entries,
719 "Digests for range with or without cache should be the same"
720 );
721
722 assert_eq!(
723 cold_cache_entries, full_cache_entries,
724 "Digests for range with cold or with hot cache should be the same"
725 );
726 }
727
728 #[tokio::test]
729 async fn hash_computation_is_quicker_with_a_full_cache() {
730 let cardano_db = db_builder("hash_computation_is_quicker_with_a_full_cache")
731 .with_immutables(&(1..=50).collect::<Vec<ImmutableFileNumber>>())
732 .append_immutable_trio()
733 .set_immutable_trio_file_size(65538)
734 .build();
735 let cache = MemoryImmutableFileDigestCacheProvider::default();
736 let logger = TestLogger::stdout();
737 let digester = CardanoImmutableDigester::new(
738 "devnet".to_string(),
739 Some(Arc::new(cache)),
740 logger.clone(),
741 );
742 let beacon = CardanoDbBeacon::new(1, 50);
743
744 let now = Instant::now();
745 digester
746 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
747 .await
748 .expect("compute_digest must not fail");
749 let elapsed_without_cache = now.elapsed();
750
751 let now = Instant::now();
752 digester
753 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
754 .await
755 .expect("compute_digest must not fail");
756 let elapsed_with_cache = now.elapsed();
757
758 assert!(
763 elapsed_with_cache < (elapsed_without_cache * 9 / 10),
764 "digest computation with full cache should be faster than without cache,\
765 time elapsed: with cache {elapsed_with_cache:?}, without cache {elapsed_without_cache:?}"
766 );
767 }
768
769 #[tokio::test]
770 async fn cache_read_failure_dont_block_computations() {
771 let cardano_db = db_builder("cache_read_failure_dont_block_computation")
772 .with_immutables(&[1, 2, 3])
773 .append_immutable_trio()
774 .build();
775 let mut cache = MockImmutableFileDigestCacheProvider::new();
776 cache.expect_get().returning(|_| Ok(BTreeMap::new()));
777 cache.expect_store().returning(|_| {
778 Err(ImmutableDigesterCacheProviderError::Store(
779 ImmutableDigesterCacheStoreError::Io(io::Error::new(io::ErrorKind::Other, "error")),
780 ))
781 });
782 let logger = TestLogger::stdout();
783 let digester = CardanoImmutableDigester::new(
784 "devnet".to_string(),
785 Some(Arc::new(cache)),
786 logger.clone(),
787 );
788 let beacon = CardanoDbBeacon::new(1, 3);
789
790 digester
791 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
792 .await
793 .expect("compute_digest must not fail even with cache write failure");
794
795 digester
796 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
797 .await
798 .expect("compute_merkle_tree must not fail even with cache write failure");
799 }
800
801 #[tokio::test]
802 async fn cache_write_failure_dont_block_computation() {
803 let cardano_db = db_builder("cache_write_failure_dont_block_computation")
804 .with_immutables(&[1, 2, 3])
805 .append_immutable_trio()
806 .build();
807 let mut cache = MockImmutableFileDigestCacheProvider::new();
808 cache.expect_get().returning(|_| {
809 Err(ImmutableDigesterCacheProviderError::Get(
810 ImmutableDigesterCacheGetError::Io(io::Error::new(io::ErrorKind::Other, "error")),
811 ))
812 });
813 cache.expect_store().returning(|_| Ok(()));
814 let logger = TestLogger::stdout();
815 let digester = CardanoImmutableDigester::new(
816 "devnet".to_string(),
817 Some(Arc::new(cache)),
818 logger.clone(),
819 );
820 let beacon = CardanoDbBeacon::new(1, 3);
821
822 digester
823 .compute_digest(cardano_db.get_immutable_dir(), &beacon)
824 .await
825 .expect("compute_digest must not fail even with cache read failure");
826
827 digester
828 .compute_merkle_tree(cardano_db.get_immutable_dir(), &beacon)
829 .await
830 .expect("compute_merkle_tree must not fail even with cache read failure");
831 }
832}