Repository: input-output-hk/mithril
Pull request #652 (2022-12-13 16:39:01)

Optimize snapshot digest computation
## Content

This PR includes add a cache system to the snapshot digest computation, each individual immutable files digest is stored in this cache so we don't have to recompute them twice.

In order to do this the snapshot digest computation has changed, **this is a breaking change**, older snapshot digest are not compatible. Before the digest was the `sha256` hash of the beacon plus all immutable now it's the hash of the beacon plus the hash of the immutable (a "hash of hash" instead of a simple "hash").

## Pre-submit checklist

- Branch
  - [x] Tests are provided (if possible)
  - [x] Commit sequence broadly makes sense
  - [x] Key commits have useful messages
- PR
  - [x] No clippy warnings in the CI
  - [x] Self-reviewed the diff
  - [x] Useful pull request description
  - [x] Reviewer requested
- Documentation
  - [ ] Update README file (if relevant)
  - [ ] Update documentation website (if relevant)
  - [ ] Add dev blog post (if relevant)

## Issue(s)
Relates to #510

github-actions[bot]: ## Test Results
    3 files  ±  0    28 suites  ±0   2m 48s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests") -2s
359 tests +18  359 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +18  0 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 
412 runs  +18  412 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +18  0 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 

Results for commit ef1cf6af. ± Comparison against base commit bc19ee5e.

[test-results]:data:application/gzip;base64,H4sIAErBmGMC/13MTQ7CIBCG4as0rF3wJ7ZextBhSCa2xVBYGe8uJa2iy+edyfdkniZc2bVTp46tmVKF7ItcjjZRWIqF2UK5pfp6Hg7d1gzwn+70KIl/grc0/QSMMcS9xLxsm1rIHcdkW76L1c1gdbsHYZ4pFTD0Aryx3nrFNaABrh1a40YNctAwql4hiotlrzdWwl2dBgEAAA==


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2e164d6cc..f61e7c8df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -688,6 +688,7 @@ dependencies = [
  "ciborium",
  "clap 3.2.23",
  "criterion-plot",
+ "futures",
  "itertools",
  "lazy_static",
  "num-traits",
@@ -699,6 +700,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "tinytemplate",
+ "tokio",
  "walkdir",
 ]
 
@@ -968,6 +970,15 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "directories"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+dependencies = [
+ "dirs-sys",
+]
+
 [[package]]
 name = "dirs-next"
 version = "2.0.0"
@@ -978,6 +989,17 @@ dependencies = [
  "dirs-sys-next",
 ]
 
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "dirs-sys-next"
 version = "0.1.2"
@@ -2016,6 +2038,7 @@ dependencies = [
  "clap 4.0.29",
  "cli-table",
  "config",
+ "directories",
  "flate2",
  "futures",
  "hex",
@@ -2042,6 +2065,8 @@ dependencies = [
  "bech32",
  "blake2 0.10.5",
  "chrono",
+ "criterion",
+ "digest 0.10.6",
  "ed25519-dalek",
  "fixed",
  "glob",
@@ -2063,7 +2088,9 @@ dependencies = [
  "serde_yaml",
  "sha2 0.10.6",
  "slog",
+ "slog-async",
  "slog-scope",
+ "slog-term",
  "sqlite",
  "thiserror",
  "tokio",
diff --git a/mithril-aggregator/src/command_args.rs b/mithril-aggregator/src/command_args.rs
index a1053ccc9..ba1139b73 100644
--- a/mithril-aggregator/src/command_args.rs
+++ b/mithril-aggregator/src/command_args.rs
@@ -2,19 +2,18 @@ use clap::{Parser, Subcommand};
 use config::{builder::DefaultState, ConfigBuilder, Map, Source, Value, ValueKind};
 use slog::Level;
 use slog_scope::debug;
-use std::error::Error;
-use std::fs;
-use std::path::PathBuf;
-use std::sync::Arc;
-use tokio::sync::RwLock;
-use tokio::time::Duration;
+use std::{error::Error, fs, path::PathBuf, sync::Arc};
+use tokio::{sync::RwLock, time::Duration};
 
 use mithril_common::{
     certificate_chain::MithrilCertificateVerifier,
     chain_observer::{CardanoCliRunner, ChainObserver},
     crypto_helper::{key_decode_hex, ProtocolGenesisSigner, ProtocolGenesisVerifier},
     database::{ApplicationNodeType, DatabaseVersionChecker},
-    digesters::{CardanoImmutableDigester, ImmutableFileSystemObserver},
+    digesters::{
+        cache::{ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProviderBuilder},
+        CardanoImmutableDigester, ImmutableFileSystemObserver,
+    },
     entities::{Epoch, HexEncodedGenesisSecretKey},
     store::{adapter::SQLiteAdapter, StakeStore},
     BeaconProviderImpl,
@@ -254,6 +253,16 @@ pub struct ServeCommand {
     /// Defaults to work folder
     #[clap(long)]
     pub snapshot_directory: Option<PathBuf>,
+
+    /// Disable immutables digests cache.
+    #[clap(long)]
+    disable_digests_cache: bool,
+
+    /// If set the existing immutables digests cache will be reset.
+    ///
+    /// Will be ignored if set in conjunction with `--disable-digests-cache`.
+    #[clap(long)]
+    reset_digests_cache: bool,
 }
 
 impl Source for ServeCommand {
@@ -357,6 +366,7 @@ impl ServeCommand {
         ));
         let digester = Arc::new(CardanoImmutableDigester::new(
             config.db_directory.clone(),
+            self.build_digester_cache_provider(&config).await?,
             slog_scope::logger(),
         ));
         let multi_signer = Arc::new(RwLock::new(MultiSignerImpl::new(
@@ -453,6 +463,24 @@ impl ServeCommand {
         println!("Exiting...");
         Ok(())
     }
+
+    async fn build_digester_cache_provider(
+        &self,
+        config: &Configuration,
+    ) -> Result<Option<Arc<dyn ImmutableFileDigestCacheProvider>>, Box<dyn Error>> {
+        if self.disable_digests_cache {
+            return Ok(None);
+        }
+        let cache_provider = JsonImmutableFileDigestCacheProviderBuilder::new(
+            &config.data_stores_directory,
+            &format!("immutables_digests_{}.json", config.network),
+        )
+        .should_reset_digests_cache(self.reset_digests_cache)
+        .build()
+        .await?;
+
+        Ok(Some(Arc::new(cache_provider)))
+    }
 }
 
 /// Genesis tools
diff --git a/mithril-client/Cargo.toml b/mithril-client/Cargo.toml
index dd648e873..34c537ecc 100644
--- a/mithril-client/Cargo.toml
+++ b/mithril-client/Cargo.toml
@@ -14,6 +14,7 @@ async-trait = "0.1.52"
 clap = { version = "4.0", features = ["derive", "env"] }
 cli-table = "0.4"
 config = "0.13.1"
+directories = "4.0.1"
 flate2 = "1.0.23"
 futures = "0.3"
 hex = "0.4.3"
diff --git a/mithril-client/src/commands/restore.rs b/mithril-client/src/commands/restore.rs
index 5aa6759f8..a142ff6fc 100644
--- a/mithril-client/src/commands/restore.rs
+++ b/mithril-client/src/commands/restore.rs
@@ -1,13 +1,16 @@
-use std::{error::Error, path::Path, sync::Arc};
-
 use clap::Parser;
 use config::{builder::DefaultState, ConfigBuilder};
+use directories::ProjectDirs;
 use mithril_common::{
     certificate_chain::MithrilCertificateVerifier,
     crypto_helper::{key_decode_hex, ProtocolGenesisVerifier},
-    digesters::CardanoImmutableDigester,
+    digesters::{
+        cache::ImmutableFileDigestCacheProvider,
+        cache::JsonImmutableFileDigestCacheProviderBuilder, CardanoImmutableDigester,
+    },
 };
-use slog_scope::debug;
+use slog_scope::{debug, warn};
+use std::{error::Error, path::Path, sync::Arc};
 
 use crate::{AggregatorHTTPClient, AggregatorHandler, Config, Runtime};
 
@@ -18,6 +21,16 @@ pub struct RestoreCommand {
     #[clap(long)]
     json: bool,
 
+    /// Disable immutables digests cache.
+    #[clap(long)]
+    disable_digests_cache: bool,
+
+    /// If set the existing immutables digests cache will be reset.
+    ///
+    /// Will be ignored if set in conjunction with `--disable-digests-cache`.
+    #[clap(long)]
+    reset_digests_cache: bool,
+
     /// Digest of the snapshot to download. Use the `list` command to get that information.
     digest: String,
 }
@@ -37,14 +50,21 @@ impl RestoreCommand {
         debug!("{:?}", config);
         let mut runtime = Runtime::new(config.network.clone());
         let aggregator_handler =
-            AggregatorHTTPClient::new(config.network.clone(), config.aggregator_endpoint);
+            AggregatorHTTPClient::new(config.network.clone(), config.aggregator_endpoint.clone());
         let certificate_verifier = Box::new(MithrilCertificateVerifier::new(slog_scope::logger()));
         let genesis_verification_key = key_decode_hex(&config.genesis_verification_key)?;
         let genesis_verifier =
             ProtocolGenesisVerifier::from_verification_key(genesis_verification_key);
         let unpacked_path = aggregator_handler.unpack_snapshot(&self.digest).await?;
+
         let digester = Box::new(CardanoImmutableDigester::new(
             Path::new(&unpacked_path).into(),
+            build_digester_cache_provider(
+                self.disable_digests_cache,
+                self.reset_digests_cache,
+                &config,
+            )
+            .await?,
             slog_scope::logger(),
         ));
         let output = runtime
@@ -73,3 +93,32 @@ docker run -v cardano-node-ipc:/ipc -v cardano-node-data:/data --mount type=bind
         Ok(())
     }
 }
+
+async fn build_digester_cache_provider(
+    disable_digests_cache: bool,
+    reset_digests_cache: bool,
+    config: &Config,
+) -> Result<Option<Arc<dyn ImmutableFileDigestCacheProvider>>, Box<dyn Error>> {
+    if disable_digests_cache {
+        return Ok(None);
+    }
+
+    match ProjectDirs::from("io", "iohk", "mithril") {
+        None => {
+            warn!("Could not get cache directory, disabling immutables digests cache");
+            Ok(None)
+        }
+        Some(project_dirs) => {
+            let cache_provider = JsonImmutableFileDigestCacheProviderBuilder::new(
+                project_dirs.cache_dir(),
+                &format!("immutables_digests_{}.json", config.network),
+            )
+            .ensure_dir_exist()
+            .should_reset_digests_cache(reset_digests_cache)
+            .build()
+            .await?;
+
+            Ok(Some(Arc::new(cache_provider)))
+        }
+    }
+}
diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml
index bf0f825c2..af708a752 100644
--- a/mithril-common/Cargo.toml
+++ b/mithril-common/Cargo.toml
@@ -11,11 +11,16 @@ repository = { workspace = true }
 [lib]
 crate-type = ["lib", "cdylib", "staticlib"]
 
+[[bench]]
+name = "digester"
+harness = false
+
 [dependencies]
 async-trait = "0.1.52"
 bech32 = "0.9.1"
 blake2 = "0.10.4"
 chrono = "0.4"
+digest = "0.10.6"
 ed25519-dalek = { version = "1.0.1", features = ["serde"] }
 fixed = "1.15.0"
 glob = "0.3"
@@ -36,6 +41,7 @@ serde_json = "1.0"
 serde_yaml = "0.9.10"
 sha2 = "0.10.2"
 slog = { version = "2.7.0", features = ["max_level_trace", "release_max_level_debug"] }
+slog-scope = "4.4.0"
 sqlite = "0.28"
 thiserror = "1.0.31"
 tokio = { version = "1.17.0", features = ["full"] }
@@ -51,7 +57,10 @@ mithril-stm = { path = "../mithril-stm" }
 mithril-stm = { path = "../mithril-stm", default-features = false, features = ["num-integer-backend"] }
 
 [dev-dependencies]
+criterion   = { version = "0.4.0", features = ["html_reports", "async_tokio"] }
+slog-async = "2.7.0"
 slog-scope = "4.4.0"
+slog-term = "2.9.0"
 
 [features]
 default = ["allow_skip_signer_certification"]
diff --git a/mithril-common/benches/digester.rs b/mithril-common/benches/digester.rs
new file mode 100644
index 000000000..da1dea32b
--- /dev/null
+++ b/mithril-common/benches/digester.rs
@@ -0,0 +1,98 @@
+use criterion::{criterion_group, criterion_main, Criterion};
+use mithril_common::{
+    digesters::cache::{ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProvider},
+    digesters::{
+        cache::MemoryImmutableFileDigestCacheProvider, CardanoImmutableDigester, ImmutableDigester,
+    },
+    entities::{Beacon, ImmutableFileNumber},
+};
+use slog::Drain;
+use std::{
+    fs,
+    fs::File,
+    io::prelude::Write,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+
+fn temp_dir() -> PathBuf {
+    std::env::temp_dir()
+        .join("mithril_benches")
+        .join("digester")
+}
+
+fn db_dir() -> PathBuf {
+    temp_dir().join("db").join("immutable")
+}
+
+fn create_db(dir: &Path, number_of_immutables: ImmutableFileNumber, file_size: u64) {
+    if dir.exists() {
+        fs::remove_dir_all(dir).unwrap_or_else(|e| panic!("Could not remove dir {:?}: {}", dir, e));
+    }
+    fs::create_dir_all(dir).unwrap_or_else(|e| panic!("Could not create dir {:?}: {}", dir, e));
+
+    // + 1 to simulate "in-progress" immutable trio.
+    for filename in (1..=(number_of_immutables + 1)).flat_map(|i| {
+        [
+            format!("{:05}.chunk", i),
+            format!("{:05}.primary", i),
+            format!("{:05}.secondary", i),
+        ]
+    }) {
+        let file = dir.join(Path::new(&filename));
+        let mut source_file = File::create(file).unwrap();
+
+        write!(source_file, "This is a test file named '{}'", filename).unwrap();
+        writeln!(source_file).unwrap();
+        source_file.set_len(file_size).unwrap();
+    }
+}
+
+#[inline]
+fn create_logger() -> slog::Logger {
+    let drain = slog_async::Async::new(slog::Discard).build().fuse();
+    slog::Logger::root(Arc::new(drain), slog::o!())
+}
+
+#[inline]
+async fn compute_digest(
+    cache_provider: Option<Arc<dyn ImmutableFileDigestCacheProvider>>,
+    number_of_immutables: ImmutableFileNumber,
+) {
+    let digester = CardanoImmutableDigester::new(db_dir(), cache_provider, create_logger());
+    digester
+        .compute_digest(&Beacon::new("devnet".to_string(), 1, number_of_immutables))
+        .await
+        .expect("digest computation should not fail");
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+    let runtime = tokio::runtime::Runtime::new().unwrap();
+    let number_of_immutable = 25;
+    create_db(&db_dir(), number_of_immutable, 65536 * 4);
+
+    c.bench_function("digester no cache", |bencher| {
+        bencher
+            .to_async(&runtime)
+            .iter(|| async { compute_digest(None, number_of_immutable).await })
+    });
+    c.bench_function("digester memory cache", |bencher| {
+        let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
+
+        bencher
+            .to_async(&runtime)
+            .iter(|| async { compute_digest(Some(cache.clone()), number_of_immutable).await })
+    });
+    c.bench_function("digester json cache", |bencher| {
+        let cache = Arc::new(JsonImmutableFileDigestCacheProvider::new(
+            &temp_dir().join("immutable-cache-store.json"),
+        ));
+
+        bencher
+            .to_async(&runtime)
+            .iter(|| async { compute_digest(Some(cache.clone()), number_of_immutable).await })
+    });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/mithril-common/src/digesters/cache/json_provider.rs b/mithril-common/src/digesters/cache/json_provider.rs
new file mode 100644
index 000000000..ae3df8611
--- /dev/null
+++ b/mithril-common/src/digesters/cache/json_provider.rs
@@ -0,0 +1,295 @@
+use crate::{
+    digesters::{
+        cache::provider::{ImmutableDigesterCacheGetError, ImmutableDigesterCacheStoreError},
+        cache::CacheProviderResult,
+        cache::ImmutableFileDigestCacheProvider,
+        ImmutableFile,
+    },
+    entities::{HexEncodedDigest, ImmutableFileName},
+};
+
+use async_trait::async_trait;
+use std::{
+    collections::BTreeMap,
+    path::{Path, PathBuf},
+};
+use tokio::{
+    fs,
+    fs::File,
+    io::{AsyncReadExt, AsyncWriteExt},
+};
+
+type InnerStructure = BTreeMap<ImmutableFileName, HexEncodedDigest>;
+
+/// A in memory [ImmutableFileDigestCacheProvider].
+pub struct JsonImmutableFileDigestCacheProvider {
+    filepath: PathBuf,
+}
+
+impl JsonImmutableFileDigestCacheProvider {
+    /// [JsonImmutableFileDigestCacheProvider] factory
+    pub fn new(filepath: &Path) -> Self {
+        Self {
+            filepath: filepath.to_path_buf(),
+        }
+    }
+
+    #[cfg(test)]
+    /// [Test Only] Build a new [JsonImmutableFileDigestCacheProvider] that contains the given values.
+    pub async fn from(filepath: &Path, values: InnerStructure) -> Self {
+        let provider = Self::new(filepath);
+        provider.write_data(values).await.unwrap();
+        provider
+    }
+
+    async fn write_data(
+        &self,
+        values: InnerStructure,
+    ) -> Result<(), ImmutableDigesterCacheStoreError> {
+        let mut file = File::create(&self.filepath).await?;
+        file.write_all(serde_json::to_string_pretty(&values)?.as_bytes())
+            .await?;
+
+        Ok(())
+    }
+
+    async fn read_data(&self) -> Result<InnerStructure, ImmutableDigesterCacheGetError> {
+        match self.filepath.exists() {
+            true => {
+                let mut file = File::open(&self.filepath).await?;
+                let mut json_string = String::new();
+                file.read_to_string(&mut json_string).await?;
+                let values: InnerStructure = serde_json::from_str(&json_string)?;
+                Ok(values)
+            }
+            false => Ok(BTreeMap::new()),
+        }
+    }
+}
+
+#[async_trait]
+impl ImmutableFileDigestCacheProvider for JsonImmutableFileDigestCacheProvider {
+    async fn store(
+        &self,
+        digest_per_filenames: Vec<(ImmutableFileName, HexEncodedDigest)>,
+    ) -> CacheProviderResult<()> {
+        let mut data = self.read_data().await?;
+        for (filename, digest) in digest_per_filenames {
+            data.insert(filename, digest);
+        }
+        self.write_data(data).await?;
+
+        Ok(())
+    }
+
+    async fn get(
+        &self,
+        immutables: Vec<ImmutableFile>,
+    ) -> CacheProviderResult<BTreeMap<ImmutableFile, Option<HexEncodedDigest>>> {
+        let values = self.read_data().await?;
+        let mut result = BTreeMap::new();
+
+        for immutable in immutables {
+            let value = values.get(&immutable.filename).map(|f| f.to_owned());
+            result.insert(immutable, value);
+        }
+
+        Ok(result)
+    }
+
+    async fn reset(&self) -> CacheProviderResult<()> {
+        fs::remove_file(&self.filepath)
+            .await
+            .map_err(ImmutableDigesterCacheStoreError::from)?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::digesters::cache::{
+        ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProvider,
+    };
+    use crate::digesters::ImmutableFile;
+    use std::{collections::BTreeMap, fs, path::PathBuf};
+
+    fn get_test_dir(subdir_name: &str) -> PathBuf {
+        let parent_dir = std::env::temp_dir()
+            .join("mithril_test")
+            .join("json_digester_cache_provider")
+            .join(subdir_name);
+
+        if parent_dir.exists() {
+            fs::remove_dir_all(&parent_dir)
+                .unwrap_or_else(|e| panic!("Could not remove dir {:?}: {}", parent_dir, e));
+        }
+        fs::create_dir_all(&parent_dir)
+            .unwrap_or_else(|e| panic!("Could not create dir {:?}: {}", parent_dir, e));
+
+        parent_dir
+    }
+
+    #[tokio::test]
+    async fn can_store_values() {
+        let file = get_test_dir("can_store_values").join("immutable-cache-store.json");
+        let provider = JsonImmutableFileDigestCacheProvider::new(&file);
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "digest 0".to_string()),
+            ("1.chunk".to_string(), "digest 1".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("digest 0".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("digest 1".to_string()),
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn returns_only_asked_immutables_cache() {
+        let file =
+            get_test_dir("returns_only_asked_immutables_cache").join("immutable-cache-store.json");
+        let provider = JsonImmutableFileDigestCacheProvider::from(
+            &file,
+            BTreeMap::from([
+                ("0.chunk".to_string(), "digest 0".to_string()),
+                ("1.chunk".to_string(), "digest 1".to_string()),
+            ]),
+        )
+        .await;
+        let expected: BTreeMap<_, _> = BTreeMap::from([(
+            ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+            Some("digest 0".to_string()),
+        )]);
+        let immutables = expected.keys().cloned().collect();
+
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn returns_none_for_uncached_asked_immutables() {
+        let file = get_test_dir("returns_none_for_uncached_asked_immutables")
+            .join("immutable-cache-store.json");
+        let provider = JsonImmutableFileDigestCacheProvider::from(
+            &file,
+            BTreeMap::from([("0.chunk".to_string(), "digest 0".to_string())]),
+        )
+        .await;
+        let expected: BTreeMap<_, _> = BTreeMap::from([(
+            ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk".to_string()),
+            None,
+        )]);
+        let immutables = expected.keys().cloned().collect();
+
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn store_erase_existing_values() {
+        let file = get_test_dir("store_erase_existing_values").join("immutable-cache-store.json");
+        let provider = JsonImmutableFileDigestCacheProvider::from(
+            &file,
+            BTreeMap::from([
+                ("0.chunk".to_string(), "to erase".to_string()),
+                ("1.chunk".to_string(), "keep me".to_string()),
+                ("2.chunk".to_string(), "keep me too".to_string()),
+            ]),
+        )
+        .await;
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "updated".to_string()),
+            ("1.chunk".to_string(), "keep me".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("updated".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("keep me".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk".to_string()),
+                Some("keep me too".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 3, "3.chunk".to_string()),
+                None,
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn reset_clear_existing_values() {
+        let file = get_test_dir("store_erase_existing_values").join("immutable-cache-store.json");
+        let provider = JsonImmutableFileDigestCacheProvider::new(&file);
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "digest 0".to_string()),
+            ("1.chunk".to_string(), "digest 1".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("digest 0".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("digest 1".to_string()),
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        provider.reset().await.expect("reset should not fails");
+
+        let result: BTreeMap<_, _> = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert!(result.into_iter().all(|(_, cache)| cache.is_none()));
+    }
+}
diff --git a/mithril-common/src/digesters/cache/json_provider_builder.rs b/mithril-common/src/digesters/cache/json_provider_builder.rs
new file mode 100644
index 000000000..7f830ce4a
--- /dev/null
+++ b/mithril-common/src/digesters/cache/json_provider_builder.rs
@@ -0,0 +1,107 @@
+use crate::digesters::cache::{
+    ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProvider,
+};
+use slog_scope::info;
+use std::{error::Error, path::Path};
+use tokio::fs;
+
+/// A [JsonImmutableFileDigestCacheProvider] builder.
+pub struct JsonImmutableFileDigestCacheProviderBuilder<'a> {
+    cache_dir: &'a Path,
+    filename: &'a str,
+    ensure_dir_exist: bool,
+    reset_digests_cache: bool,
+}
+
+impl<'a> JsonImmutableFileDigestCacheProviderBuilder<'a> {
+    /// [JsonImmutableFileDigestCacheProviderBuilder] factory.
+    pub fn new(cache_dir: &'a Path, filename: &'a str) -> Self {
+        Self {
+            cache_dir,
+            filename,
+            ensure_dir_exist: false,
+            reset_digests_cache: false,
+        }
+    }
+
+    /// If set will create the cache directory if it doesn't already exist.
+    pub fn ensure_dir_exist(&mut self) -> &mut Self {
+        self.ensure_dir_exist = true;
+        self
+    }
+
+    /// Set if existing cached values in the provider must be reset.
+    pub fn should_reset_digests_cache(&mut self, should_reset: bool) -> &mut Self {
+        self.reset_digests_cache = should_reset;
+        self
+    }
+
+    /// Build a [JsonImmutableFileDigestCacheProvider] based on the parameters previously set.
+    pub async fn build(&self) -> Result<JsonImmutableFileDigestCacheProvider, Box<dyn Error>> {
+        let cache_file = self.cache_dir.join(self.filename);
+        let cache_provider = JsonImmutableFileDigestCacheProvider::new(&cache_file);
+
+        if self.ensure_dir_exist {
+            fs::create_dir_all(&self.cache_dir).await.map_err(|e| {
+                format!(
+                    "Failure when creating cache directory `{}`: {}",
+                    self.cache_dir.display(),
+                    e
+                )
+            })?;
+        }
+
+        if self.reset_digests_cache {
+            cache_provider.reset().await.map_err(|e| {
+                format!(
+                    "Failure when resetting digests cache file `{}`: {}",
+                    cache_file.display(),
+                    e
+                )
+            })?;
+        }
+
+        info!(
+            "Storing/Getting immutables digests cache from: {}",
+            cache_file.display()
+        );
+
+        Ok(cache_provider)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::digesters::cache::JsonImmutableFileDigestCacheProviderBuilder;
+
+    #[tokio::test]
+    async fn create_dir_if_ensure_dir_exist_is_set() {
+        let dir = std::env::temp_dir()
+            .join("mithril_test")
+            .join("json_provider_builder")
+            .join("create_dir_if_ensure_dir_exist_is_set");
+
+        JsonImmutableFileDigestCacheProviderBuilder::new(&dir, "test.json")
+            .ensure_dir_exist()
+            .build()
+            .await
+            .expect("Build should not fail");
+
+        assert!(dir.exists());
+    }
+
+    #[tokio::test]
+    async fn dont_create_dir_if_ensure_dir_exist_is_not_set() {
+        let dir = std::env::temp_dir()
+            .join("mithril_test")
+            .join("json_provider_builder")
+            .join("dont_create_dir_if_ensure_dir_exist_is_not_set");
+
+        JsonImmutableFileDigestCacheProviderBuilder::new(&dir, "test.json")
+            .build()
+            .await
+            .expect("Build should not fail");
+
+        assert!(!dir.exists());
+    }
+}
diff --git a/mithril-common/src/digesters/cache/memory_provider.rs b/mithril-common/src/digesters/cache/memory_provider.rs
new file mode 100644
index 000000000..7f46ffdf2
--- /dev/null
+++ b/mithril-common/src/digesters/cache/memory_provider.rs
@@ -0,0 +1,227 @@
+use crate::{
+    digesters::cache::CacheProviderResult,
+    digesters::cache::ImmutableFileDigestCacheProvider,
+    digesters::ImmutableFile,
+    entities::{HexEncodedDigest, ImmutableFileName},
+};
+
+use async_trait::async_trait;
+use std::collections::{BTreeMap, HashMap};
+use tokio::sync::RwLock;
+
+/// A in memory [ImmutableFileDigestCacheProvider].
+pub struct MemoryImmutableFileDigestCacheProvider {
+    store: RwLock<HashMap<ImmutableFileName, HexEncodedDigest>>,
+}
+
+impl MemoryImmutableFileDigestCacheProvider {
+    /// Build a new [MemoryImmutableFileDigestCacheProvider] that contains the given values.
+    pub fn from(values: HashMap<ImmutableFileName, HexEncodedDigest>) -> Self {
+        Self {
+            store: RwLock::new(values),
+        }
+    }
+}
+
+impl Default for MemoryImmutableFileDigestCacheProvider {
+    fn default() -> Self {
+        Self {
+            store: RwLock::new(HashMap::new()),
+        }
+    }
+}
+
+#[async_trait]
+impl ImmutableFileDigestCacheProvider for MemoryImmutableFileDigestCacheProvider {
+    async fn store(
+        &self,
+        digest_per_filenames: Vec<(ImmutableFileName, HexEncodedDigest)>,
+    ) -> CacheProviderResult<()> {
+        let mut store = self.store.write().await;
+        for (filename, digest) in digest_per_filenames {
+            store.insert(filename, digest);
+        }
+
+        Ok(())
+    }
+
+    async fn get(
+        &self,
+        immutables: Vec<ImmutableFile>,
+    ) -> CacheProviderResult<BTreeMap<ImmutableFile, Option<HexEncodedDigest>>> {
+        let store = self.store.read().await;
+        let mut result = BTreeMap::new();
+
+        for immutable in immutables {
+            let value = store.get(&immutable.filename).map(|f| f.to_owned());
+            result.insert(immutable, value);
+        }
+
+        Ok(result)
+    }
+
+    async fn reset(&self) -> CacheProviderResult<()> {
+        let mut store = self.store.write().await;
+        store.clear();
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        digesters::cache::{
+            ImmutableFileDigestCacheProvider, MemoryImmutableFileDigestCacheProvider,
+        },
+        digesters::ImmutableFile,
+    };
+    use std::collections::{BTreeMap, HashMap};
+    use std::path::PathBuf;
+
+    #[tokio::test]
+    async fn can_store_values() {
+        let provider = MemoryImmutableFileDigestCacheProvider::default();
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "digest 0".to_string()),
+            ("1.chunk".to_string(), "digest 1".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("digest 0".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("digest 1".to_string()),
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn returns_only_asked_immutables_cache() {
+        let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([
+            ("0.chunk".to_string(), "digest 0".to_string()),
+            ("1.chunk".to_string(), "digest 1".to_string()),
+        ]));
+        let expected: BTreeMap<_, _> = BTreeMap::from([(
+            ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+            Some("digest 0".to_string()),
+        )]);
+        let immutables = expected.keys().cloned().collect();
+
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn returns_none_for_uncached_asked_immutables() {
+        let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([(
+            "0.chunk".to_string(),
+            "digest 0".to_string(),
+        )]));
+        let expected: BTreeMap<_, _> = BTreeMap::from([(
+            ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk".to_string()),
+            None,
+        )]);
+        let immutables = expected.keys().cloned().collect();
+
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn store_erase_existing_values() {
+        let provider = MemoryImmutableFileDigestCacheProvider::from(HashMap::from([
+            ("0.chunk".to_string(), "to erase".to_string()),
+            ("1.chunk".to_string(), "keep me".to_string()),
+            ("2.chunk".to_string(), "keep me too".to_string()),
+        ]));
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "updated".to_string()),
+            ("1.chunk".to_string(), "keep me".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("updated".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("keep me".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 2, "2.chunk".to_string()),
+                Some("keep me too".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 3, "3.chunk".to_string()),
+                None,
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        let result = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert_eq!(expected, result);
+    }
+
+    #[tokio::test]
+    async fn reset_clear_existing_values() {
+        let provider = MemoryImmutableFileDigestCacheProvider::default();
+        let values_to_store = vec![
+            ("0.chunk".to_string(), "digest 0".to_string()),
+            ("1.chunk".to_string(), "digest 1".to_string()),
+        ];
+        let expected: BTreeMap<_, _> = BTreeMap::from([
+            (
+                ImmutableFile::dummy(PathBuf::default(), 0, "0.chunk".to_string()),
+                Some("digest 0".to_string()),
+            ),
+            (
+                ImmutableFile::dummy(PathBuf::default(), 1, "1.chunk".to_string()),
+                Some("digest 1".to_string()),
+            ),
+        ]);
+        let immutables = expected.keys().cloned().collect();
+
+        provider
+            .store(values_to_store)
+            .await
+            .expect("Cache write should not fail");
+        provider.reset().await.expect("reset should not fails");
+
+        let result: BTreeMap<_, _> = provider
+            .get(immutables)
+            .await
+            .expect("Cache read should not fail");
+
+        assert!(result.into_iter().all(|(_, cache)| cache.is_none()));
+    }
+}
diff --git a/mithril-common/src/digesters/cache/mod.rs b/mithril-common/src/digesters/cache/mod.rs
new file mode 100644
index 000000000..c40e4d7de
--- /dev/null
+++ b/mithril-common/src/digesters/cache/mod.rs
@@ -0,0 +1,17 @@
+//! Cache provider to accelerate [super::CardanoImmutableDigester] computation by storing and
+//! reusing [super::ImmutableFile] digests.
+
+mod json_provider;
+mod json_provider_builder;
+mod memory_provider;
+mod provider;
+
+pub use json_provider::JsonImmutableFileDigestCacheProvider;
+pub use json_provider_builder::JsonImmutableFileDigestCacheProviderBuilder;
+pub use memory_provider::MemoryImmutableFileDigestCacheProvider;
+#[cfg(test)]
+pub use provider::MockImmutableFileDigestCacheProvider;
+pub use provider::{
+    CacheProviderResult, ImmutableDigesterCacheGetError, ImmutableDigesterCacheProviderError,
+    ImmutableDigesterCacheStoreError, ImmutableFileDigestCacheProvider,
+};
diff --git a/mithril-common/src/digesters/cache/provider.rs b/mithril-common/src/digesters/cache/provider.rs
new file mode 100644
index 000000000..eb5c85792
--- /dev/null
+++ b/mithril-common/src/digesters/cache/provider.rs
@@ -0,0 +1,69 @@
+use crate::digesters::ImmutableFile;
+use crate::entities::{HexEncodedDigest, ImmutableFileName};
+
+use async_trait::async_trait;
+use std::collections::BTreeMap;
+use std::io;
+use thiserror::Error;
+
+#[cfg(test)]
+pub use mockall::automock;
+
+/// A specialized result type for [ImmutableFileDigestCacheProvider].
+pub type CacheProviderResult<T> = Result<T, ImmutableDigesterCacheProviderError>;
+
+/// [ImmutableFileDigestCacheProvider] related errors.
+#[derive(Error, Debug)]
+pub enum ImmutableDigesterCacheProviderError {
+    /// Error raised by [ImmutableFileDigestCacheProvider::store].
+    #[error("Could not store immutable file digests cache: {0}")]
+    Store(#[from] ImmutableDigesterCacheStoreError),
+
+    /// Error raised by [ImmutableFileDigestCacheProvider::get].
+    #[error("Could not read immutable file digests cache: {0}")]
+    Get(#[from] ImmutableDigesterCacheGetError),
+}
+
+/// [ImmutableFileDigestCacheProvider::store] related errors.
+#[derive(Error, Debug)]
+pub enum ImmutableDigesterCacheStoreError {
+    /// Raised when an IO error is raised when storing a cache.
+    #[error("IO error when storing cache: {0}")]
+    Io(#[from] io::Error),
+
+    /// Raised when json cache serialization fails.
+    #[error("IO error when serializing json cache: {0}")]
+    JsonSerialization(#[from] serde_json::Error),
+}
+
+/// [ImmutableFileDigestCacheProvider::get] related errors.
+#[derive(Error, Debug)]
+pub enum ImmutableDigesterCacheGetError {
+    /// Raised when an IO error is raised when getting a cache.
+    #[error("IO error when getting cache: {0}")]
+    Io(#[from] io::Error),
+
+    /// Raised when json cache deserialization fails.
+    #[error("IO error when deserializing json cache: {0}")]
+    JsonDeserialization(#[from] serde_json::Error),
+}
+
+/// A cache provider that store individual [ImmutableFile] digests.
+#[cfg_attr(test, automock)]
+#[async_trait]
+pub trait ImmutableFileDigestCacheProvider: Sync + Send {
+    /// Store the given digests
+    async fn store(
+        &self,
+        digest_per_filenames: Vec<(ImmutableFileName, HexEncodedDigest)>,
+    ) -> CacheProviderResult<()>;
+
+    /// Associate each given [immutable files][ImmutableFile] with a cached value if one exist.
+    async fn get(
+        &self,
+        immutables: Vec<ImmutableFile>,
+    ) -> CacheProviderResult<BTreeMap<ImmutableFile, Option<HexEncodedDigest>>>;
+
+    /// Reset the stored values
+    async fn reset(&self) -> CacheProviderResult<()>;
+}
diff --git a/mithril-common/src/digesters/cardano_immutable_digester.rs b/mithril-common/src/digesters/cardano_immutable_digester.rs
index 8fd7b775e..2332eb937 100644
--- a/mithril-common/src/digesters/cardano_immutable_digester.rs
+++ b/mithril-common/src/digesters/cardano_immutable_digester.rs
@@ -1,27 +1,42 @@
-use crate::digesters::{ImmutableDigester, ImmutableDigesterError, ImmutableFile};
-use crate::entities::Beacon;
-
+use crate::{
+    digesters::{
+        cache::ImmutableFileDigestCacheProvider, ImmutableDigester, ImmutableDigesterError,
+        ImmutableFile,
+    },
+    entities::{Beacon, HexEncodedDigest, ImmutableFileName},
+};
 use async_trait::async_trait;
 use sha2::{Digest, Sha256};
-use slog::{debug, info, Logger};
-use std::fs::File;
-use std::io;
-use std::path::PathBuf;
+use slog::{debug, info, warn, Logger};
+use std::{collections::BTreeMap, io, path::PathBuf, sync::Arc};
+
+/// Result of a cache computation, contains the digest and the list of new entries to add
+/// to the [ImmutableFileDigestCacheProvider].
+type CacheComputationResult =
+    Result<([u8; 32], Vec<(ImmutableFileName, HexEncodedDigest)>), io::Error>;
 
 /// A digester working directly on a Cardano DB immutables files
 pub struct CardanoImmutableDigester {
     /// A cardano node DB directory
     db_directory: PathBuf,
 
+    /// A [ImmutableFileDigestCacheProvider] instance
+    cache_provider: Option<Arc<dyn ImmutableFileDigestCacheProvider>>,
+
     /// The logger where the logs should be written
     logger: Logger,
 }
 
 impl CardanoImmutableDigester {
     /// ImmutableDigester factory
-    pub fn new(db_directory: PathBuf, logger: Logger) -> Self {
+    pub fn new(
+        db_directory: PathBuf,
+        cache_provider: Option<Arc<dyn ImmutableFileDigestCacheProvider>>,
+        logger: Logger,
+    ) -> Self {
         Self {
             db_directory,
+            cache_provider,
             logger,
         }
     }
@@ -50,18 +65,42 @@ impl ImmutableDigester for CardanoImmutableDigester {
             Some(_) => {
                 info!(self.logger, "#compute_digest"; "beacon" => #?beacon, "nb_of_immutables" => immutables.len());
 
+                let cached_values = match self.cache_provider.as_ref() {
+                    None => BTreeMap::from_iter(immutables.into_iter().map(|i| (i, None))),
+                    Some(cache_provider) => match cache_provider.get(immutables.clone()).await {
+                        Ok(values) => values,
+                        Err(error) => {
+                            warn!(
+                                self.logger,
+                                "Error while getting cached immutable files digests: {}", error
+                            );
+                            BTreeMap::from_iter(immutables.into_iter().map(|i| (i, None)))
+                        }
+                    },
+                };
+
                 // digest is done in a separate thread because it is blocking the whole task
                 let logger = self.logger.clone();
                 let thread_beacon = beacon.clone();
-                let hash = tokio::task::spawn_blocking(move || -> Result<[u8; 32], io::Error> {
-                    compute_hash(logger, &thread_beacon, &immutables)
-                })
-                .await
-                .map_err(|e| ImmutableDigesterError::DigestComputationError(e.into()))??;
+                let (hash, new_cache_entries) =
+                    tokio::task::spawn_blocking(move || -> CacheComputationResult {
+                        compute_hash(logger, &thread_beacon, cached_values)
+                    })
+                    .await
+                    .map_err(|e| ImmutableDigesterError::DigestComputationError(e.into()))??;
                 let digest = hex::encode(hash);
 
                 debug!(self.logger, "#computed digest: {:?}", digest);
 
+                if let Some(cache_provider) = self.cache_provider.as_ref() {
+                    if let Err(error) = cache_provider.store(new_cache_entries).await {
+                        warn!(
+                            self.logger,
+                            "Error while storing new immutable files digests to cache: {}", error
+                        );
+                    }
+                }
+
                 Ok(digest)
             }
         }
@@ -71,9 +110,10 @@ impl ImmutableDigester for CardanoImmutableDigester {
 fn compute_hash(
     logger: Logger,
     beacon: &Beacon,
-    entries: &Vec<ImmutableFile>,
-) -> Result<[u8; 32], io::Error> {
+    entries: BTreeMap<ImmutableFile, Option<HexEncodedDigest>>,
+) -> CacheComputationResult {
     let mut hasher = Sha256::new();
+    let mut new_cached_entries = Vec::new();
     let mut progress = Progress {
         index: 0,
         total: entries.len(),
@@ -81,17 +121,24 @@ fn compute_hash(
 
     hasher.update(beacon.compute_hash().as_bytes());
 
-    for (ix, entry) in entries.iter().enumerate() {
-        let mut file = File::open(&entry.path)?;
-
-        io::copy(&mut file, &mut hasher)?;
+    for (ix, (entry, cache)) in entries.iter().enumerate() {
+        match cache {
+            None => {
+                let data = hex::encode(entry.compute_raw_hash::<Sha256>()?);
+                hasher.update(&data);
+                new_cached_entries.push((entry.filename.clone(), data));
+            }
+            Some(digest) => {
+                hasher.update(digest);
+            }
+        };
 
         if progress.report(ix) {
             info!(logger, "hashing: {}", &progress);
         }
     }
 
-    Ok(hasher.finalize().into())
+    Ok((hasher.finalize().into(), new_cached_entries))
 }
 
 struct Progress {
@@ -119,6 +166,33 @@ impl std::fmt::Display for Progress {
 #[cfg(test)]
 mod tests {
     use super::Progress;
+    use crate::{
+        digesters::{
+            cache::{
+                ImmutableDigesterCacheGetError, ImmutableDigesterCacheProviderError,
+                ImmutableDigesterCacheStoreError, ImmutableFileDigestCacheProvider,
+                MemoryImmutableFileDigestCacheProvider, MockImmutableFileDigestCacheProvider,
+            },
+            CardanoImmutableDigester, DummyImmutablesDbBuilder, ImmutableDigester,
+            ImmutableDigesterError,
+        },
+        entities::{Beacon, ImmutableFileNumber},
+    };
+    use sha2::Sha256;
+    use slog::Drain;
+    use std::{collections::BTreeMap, io, sync::Arc};
+    use tokio::time::Instant;
+
+    fn create_logger() -> slog::Logger {
+        let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
+        let drain = slog_term::CompactFormat::new(decorator).build().fuse();
+        let drain = slog_async::Async::new(drain).build().fuse();
+        slog::Logger::root(Arc::new(drain), slog::o!())
+    }
+
+    fn db_builder(dir_name: &str) -> DummyImmutablesDbBuilder {
+        DummyImmutablesDbBuilder::new(&format!("cardano_immutable_digester/{}", dir_name))
+    }
 
     #[test]
     fn reports_progress_every_5_percent() {
@@ -145,4 +219,279 @@ mod tests {
         assert!(!progress.report(3));
         assert!(!progress.report(15));
     }
+
+    #[tokio::test]
+    async fn fail_if_no_file_in_folder() {
+        let immutable_db = db_builder("fail_if_no_file_in_folder").build();
+        let digester = CardanoImmutableDigester::new(immutable_db.dir, None, create_logger());
+        let beacon = Beacon::new("devnet".to_string(), 1, 1);
+
+        let result = digester
+            .compute_digest(&beacon)
+            .await
+            .expect_err("compute_digest should've failed");
+
+        assert_eq!(
+            format!(
+                "{:?}",
+                ImmutableDigesterError::NotEnoughImmutable {
+                    expected_number: beacon.immutable_file_number,
+                    found_number: None,
+                }
+            ),
+            format!("{:?}", result)
+        );
+    }
+
+    #[tokio::test]
+    async fn fail_if_a_invalid_file_is_in_immutable_folder() {
+        let immutable_db = db_builder("fail_if_no_immutable_exist")
+            .with_non_immutables(&["not_immutable"])
+            .build();
+        let digester = CardanoImmutableDigester::new(immutable_db.dir, None, create_logger());
+        let beacon = Beacon::new("devnet".to_string(), 1, 1);
+
+        assert!(digester.compute_digest(&beacon).await.is_err());
+    }
+
+    #[tokio::test]
+    async fn fail_if_theres_only_the_uncompleted_immutable_trio() {
+        let immutable_db = db_builder("fail_if_theres_only_the_uncompleted_immutable_trio")
+            .append_immutable_trio()
+            .build();
+        let digester = CardanoImmutableDigester::new(immutable_db.dir, None, create_logger());
+        let beacon = Beacon::new("devnet".to_string(), 1, 1);
+
+        let result = digester
+            .compute_digest(&beacon)
+            .await
+            .expect_err("compute_digest should've failed");
+
+        assert_eq!(
+            format!(
+                "{:?}",
+                ImmutableDigesterError::NotEnoughImmutable {
+                    expected_number: beacon.immutable_file_number,
+                    found_number: None,
+                }
+            ),
+            format!("{:?}", result)
+        );
+    }
+
+    #[tokio::test]
+    async fn fail_if_less_immutable_than_what_required_in_beacon() {
+        let immutable_db = db_builder("fail_if_less_immutable_than_what_required_in_beacon")
+            .with_immutables(&[1, 2, 3, 4, 5])
+            .append_immutable_trio()
+            .build();
+        let digester = CardanoImmutableDigester::new(immutable_db.dir, None, create_logger());
+        let beacon = Beacon::new("devnet".to_string(), 1, 10);
+
+        let result = digester
+            .compute_digest(&beacon)
+            .await
+            .expect_err("compute_digest should've failed");
+
+        assert_eq!(
+            format!(
+                "{:?}",
+                ImmutableDigesterError::NotEnoughImmutable {
+                    expected_number: beacon.immutable_file_number,
+                    found_number: Some(5),
+                }
+            ),
+            format!("{:?}", result)
+        );
+    }
+
+    #[tokio::test]
+    async fn can_compute_hash_of_a_hundred_immutable_file_trio() {
+        let immutable_db = db_builder("can_compute_hash_of_a_hundred_immutable_file_trio")
+            .with_immutables(&(1..=100).collect::<Vec<ImmutableFileNumber>>())
+            .append_immutable_trio()
+            .build();
+        let logger = create_logger();
+        let digester = CardanoImmutableDigester::new(
+            immutable_db.dir,
+            Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
+            logger.clone(),
+        );
+        let beacon = Beacon::new("devnet".to_string(), 1, 100);
+
+        let result = digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+
+        assert_eq!(
+            "a27fd67e495c2c77e4b6b0af9925b2b0bc39656c56adfad4aaab9f20fae49122".to_string(),
+            result
+        )
+    }
+
+    #[tokio::test]
+    async fn digests_are_stored_into_cache_provider() {
+        let immutable_db = db_builder("digests_are_stored_into_cache_provider")
+            .with_immutables(&[1, 2])
+            .append_immutable_trio()
+            .build();
+        let immutables = immutable_db.immutables_files;
+        let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
+        let logger = create_logger();
+        let digester =
+            CardanoImmutableDigester::new(immutable_db.dir, Some(cache.clone()), logger.clone());
+        let beacon = Beacon::new("devnet".to_string(), 1, 2);
+
+        digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+
+        let cached_entries = cache
+            .get(immutables.clone())
+            .await
+            .expect("Cache read should not fail");
+        let expected: BTreeMap<_, _> = immutables
+            .into_iter()
+            .map(|i| {
+                let digest = hex::encode(i.compute_raw_hash::<Sha256>().unwrap());
+                (i, Some(digest))
+            })
+            .collect();
+
+        assert_eq!(expected, cached_entries);
+    }
+
+    #[tokio::test]
+    async fn computed_digest_with_cold_or_hot_or_without_any_cache_are_equals() {
+        let immutable_db = DummyImmutablesDbBuilder::new(
+            "computed_digest_with_cold_or_hot_or_without_any_cache_are_equals",
+        )
+        .with_immutables(&[1, 2, 3])
+        .append_immutable_trio()
+        .build();
+        let logger = create_logger();
+        let no_cache_digester =
+            CardanoImmutableDigester::new(immutable_db.dir.clone(), None, logger.clone());
+        let cache_digester = CardanoImmutableDigester::new(
+            immutable_db.dir,
+            Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
+            logger.clone(),
+        );
+        let beacon = Beacon::new("devnet".to_string(), 1, 3);
+
+        let without_cache_digest = no_cache_digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+
+        let cold_cache_digest = cache_digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+
+        let full_cache_digest = cache_digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+
+        assert_eq!(
+            without_cache_digest, full_cache_digest,
+            "Digests with or without cache should be the same"
+        );
+
+        assert_eq!(
+            cold_cache_digest, full_cache_digest,
+            "Digests with cold or with hot cache should be the same"
+        );
+    }
+
+    #[tokio::test]
+    async fn hash_computation_is_quicker_with_a_full_cache() {
+        let immutable_db = db_builder("hash_computation_is_quicker_with_a_full_cache")
+            .with_immutables(&(1..=50).collect::<Vec<ImmutableFileNumber>>())
+            .append_immutable_trio()
+            .set_file_size(65536)
+            .build();
+        let cache = MemoryImmutableFileDigestCacheProvider::default();
+        let logger = create_logger();
+        let digester =
+            CardanoImmutableDigester::new(immutable_db.dir, Some(Arc::new(cache)), logger.clone());
+        let beacon = Beacon::new("devnet".to_string(), 1, 50);
+
+        let now = Instant::now();
+        digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+        let elapsed_without_cache = now.elapsed();
+
+        let now = Instant::now();
+        digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail");
+        let elapsed_with_cache = now.elapsed();
+
+        // Note real performance doesn't matter here, the purpose is only to check that the computation
+        // time is faster with cache.
+        // We set the limit to 90% to avoid flakiness and ensure that the cache is useful (Note: Real
+        // performance is around ~100 times faster in debug).
+        assert!(
+            elapsed_with_cache < (elapsed_without_cache * 9 / 10),
+            "digest computation with full cache should be faster than without cache,\
+            time elapsed: with cache {:?}, without cache {:?}",
+            elapsed_with_cache,
+            elapsed_without_cache
+        );
+    }
+
+    #[tokio::test]
+    async fn cache_read_failure_dont_block_computation() {
+        let immutable_db = db_builder("cache_read_failure_dont_block_computation")
+            .with_immutables(&[1, 2, 3])
+            .append_immutable_trio()
+            .build();
+        let mut cache = MockImmutableFileDigestCacheProvider::new();
+        cache.expect_get().returning(|_| Ok(BTreeMap::new()));
+        cache.expect_store().returning(|_| {
+            Err(ImmutableDigesterCacheProviderError::Store(
+                ImmutableDigesterCacheStoreError::Io(io::Error::new(io::ErrorKind::Other, "error")),
+            ))
+        });
+        let logger = create_logger();
+        let digester =
+            CardanoImmutableDigester::new(immutable_db.dir, Some(Arc::new(cache)), logger.clone());
+        let beacon = Beacon::new("devnet".to_string(), 1, 3);
+
+        digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail even with cache write failure");
+    }
+
+    #[tokio::test]
+    async fn cache_write_failure_dont_block_computation() {
+        let immutable_db = db_builder("cache_write_failure_dont_block_computation")
+            .with_immutables(&[1, 2, 3])
+            .append_immutable_trio()
+            .build();
+        let mut cache = MockImmutableFileDigestCacheProvider::new();
+        cache.expect_get().returning(|_| {
+            Err(ImmutableDigesterCacheProviderError::Get(
+                ImmutableDigesterCacheGetError::Io(io::Error::new(io::ErrorKind::Other, "error")),
+            ))
+        });
+        cache.expect_store().returning(|_| Ok(()));
+        let logger = create_logger();
+        let digester =
+            CardanoImmutableDigester::new(immutable_db.dir, Some(Arc::new(cache)), logger.clone());
+        let beacon = Beacon::new("devnet".to_string(), 1, 3);
+
+        digester
+            .compute_digest(&beacon)
+            .await
+            .expect("compute_digest must not fail even with cache read failure");
+    }
 }
diff --git a/mithril-common/src/digesters/dummy_immutable_db_builder.rs b/mithril-common/src/digesters/dummy_immutable_db_builder.rs
new file mode 100644
index 000000000..97cca9719
--- /dev/null
+++ b/mithril-common/src/digesters/dummy_immutable_db_builder.rs
@@ -0,0 +1,136 @@
+#![cfg(test)]
+use crate::{digesters::ImmutableFile, entities::ImmutableFileNumber};
+use std::{
+    fs::File,
+    io::prelude::Write,
+    path::{Path, PathBuf},
+};
+
+pub struct DummyImmutablesDbBuilder {
+    dir: PathBuf,
+    immutables_to_write: Vec<ImmutableFileNumber>,
+    non_immutables_to_write: Vec<String>,
+    append_uncompleted_trio: bool,
+    file_size: Option<u64>,
+}
+
+pub struct DummyImmutableDb {
+    pub dir: PathBuf,
+    pub immutables_files: Vec<ImmutableFile>,
+    pub non_immutables_files: Vec<PathBuf>,
+}
+
+impl DummyImmutablesDbBuilder {
+    /// [DummyImmutablesDbBuilder] factory, will create a folder with the given `dirname` in the
+    /// system temp directory, if it exists already it will be cleaned.
+    pub fn new(dir_name: &str) -> Self {
+        Self {
+            dir: Self::get_test_dir(dir_name),
+            immutables_to_write: vec![],
+            non_immutables_to_write: vec![],
+            append_uncompleted_trio: false,
+            file_size: None,
+        }
+    }
+
+    pub fn with_immutables(&mut self, immutables: &[ImmutableFileNumber]) -> &mut Self {
+        self.immutables_to_write = immutables.to_vec();
+        self
+    }
+
+    pub fn with_non_immutables(&mut self, non_immutables: &[&str]) -> &mut Self {
+        self.non_immutables_to_write = non_immutables.iter().map(|f| f.to_string()).collect();
+        self
+    }
+
+    /// Makes [build][Self::build] add another trio of immutables file, that won't be included
+    /// in its returned vec, to simulate the last 3 'uncompleted / wip' files that can be found in
+    /// a cardano immutable db.
+    pub fn append_immutable_trio(&mut self) -> &mut Self {
+        self.append_uncompleted_trio = true;
+        self
+    }
+
+    /// Set the size of all files written by [build][Self::build] to the given `file_size` in bytes.
+    ///
+    /// Note: by default the size of the produced files is less than a 1kb.
+    pub fn set_file_size(&mut self, file_size: u64) -> &mut Self {
+        self.file_size = Some(file_size);
+        self
+    }
+
+    pub fn build(&self) -> DummyImmutableDb {
+        let mut non_immutables_files = vec![];
+        let mut immutable_numbers = self.immutables_to_write.clone();
+        immutable_numbers.sort();
+
+        if self.append_uncompleted_trio {
+            self.write_immutable_trio(match immutable_numbers.last() {
+                None => 0,
+                Some(last) => last + 1,
+            });
+        }
+
+        for non_immutable in &self.non_immutables_to_write {
+            non_immutables_files.push(self.write_dummy_file(non_immutable));
+        }
+
+        DummyImmutableDb {
+            dir: self.dir.clone(),
+            immutables_files: immutable_numbers
+                .into_iter()
+                .flat_map(|ifn| self.write_immutable_trio(ifn))
+                .collect::<Vec<_>>(),
+            non_immutables_files,
+        }
+    }
+
+    fn get_test_dir(subdir_name: &str) -> PathBuf {
+        let parent_dir = std::env::temp_dir()
+            .join("mithril_test")
+            .join(subdir_name)
+            .join("immutable");
+
+        if parent_dir.exists() {
+            std::fs::remove_dir_all(&parent_dir)
+                .unwrap_or_else(|e| panic!("Could not remove dir {:?}: {}", parent_dir, e));
+        }
+        std::fs::create_dir_all(&parent_dir)
+            .unwrap_or_else(|e| panic!("Could not create dir {:?}: {}", parent_dir, e));
+
+        parent_dir
+    }
+
+    fn write_immutable_trio(&self, immutable: ImmutableFileNumber) -> Vec<ImmutableFile> {
+        let mut result = vec![];
+        for filename in [
+            format!("{:05}.chunk", immutable),
+            format!("{:05}.primary", immutable),
+            format!("{:05}.secondary", immutable),
+        ] {
+            let file = self.write_dummy_file(&filename);
+            result.push(ImmutableFile {
+                number: immutable.to_owned(),
+                path: file,
+                filename: filename.to_string(),
+            });
+        }
+        result
+    }
+
+    /// Create a file with the given name in the given dir, write some text to it, and then
+    /// return its path.
+    fn write_dummy_file(&self, filename: &str) -> PathBuf {
+        let file = self.dir.join(Path::new(filename));
+        let mut source_file = File::create(&file).unwrap();
+
+        write!(source_file, "This is a test file named '{}'", filename).unwrap();
+
+        if let Some(file_size) = self.file_size {
+            writeln!(source_file).unwrap();
+            source_file.set_len(file_size).unwrap();
+        }
+
+        file
+    }
+}
diff --git a/mithril-common/src/digesters/immutable_digester.rs b/mithril-common/src/digesters/immutable_digester.rs
index 8c92a18e3..135d9facc 100644
--- a/mithril-common/src/digesters/immutable_digester.rs
+++ b/mithril-common/src/digesters/immutable_digester.rs
@@ -1,5 +1,7 @@
-use crate::digesters::ImmutableFileListingError;
-use crate::entities::{Beacon, ImmutableFileNumber};
+use crate::{
+    digesters::ImmutableFileListingError,
+    entities::{Beacon, ImmutableFileNumber},
+};
 use async_trait::async_trait;
 use std::io;
 use thiserror::Error;
diff --git a/mithril-common/src/digesters/immutable_file.rs b/mithril-common/src/digesters/immutable_file.rs
index 02020fff7..c34a1d488 100644
--- a/mithril-common/src/digesters/immutable_file.rs
+++ b/mithril-common/src/digesters/immutable_file.rs
@@ -1,10 +1,14 @@
-use crate::entities::ImmutableFileNumber;
-use std::cmp::Ordering;
+use crate::entities::{ImmutableFileName, ImmutableFileNumber};
 
-use std::ffi::OsStr;
-use std::io;
-use std::num::ParseIntError;
-use std::path::{Path, PathBuf};
+use digest::{Digest, Output};
+use std::{
+    cmp::Ordering,
+    ffi::OsStr,
+    fs::File,
+    io,
+    num::ParseIntError,
+    path::{Path, PathBuf},
+};
 use thiserror::Error;
 use walkdir::WalkDir;
 
@@ -14,13 +18,16 @@ fn is_immutable(path: &Path) -> bool {
 }
 
 /// Represent an immutable file in a Cardano node database directory
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 pub struct ImmutableFile {
     /// The path to the immutable file
     pub path: PathBuf,
 
     /// The immutable file number
     pub number: ImmutableFileNumber,
+
+    /// The filename
+    pub filename: ImmutableFileName,
 }
 
 /// [ImmutableFile::new] related errors.
@@ -59,21 +66,49 @@ pub enum ImmutableFileListingError {
 
 impl ImmutableFile {
     /// ImmutableFile factory
-    pub fn new(path: PathBuf) -> Result<Self, ImmutableFileCreationError> {
+    pub fn new(path: PathBuf) -> Result<ImmutableFile, ImmutableFileCreationError> {
         let filename = path
+            .file_name()
+            .ok_or(ImmutableFileCreationError::FileNameExtraction { path: path.clone() })?
+            .to_str()
+            .ok_or(ImmutableFileCreationError::FileNameExtraction { path: path.clone() })?
+            .to_string();
+
+        let filestem = path
             .file_stem()
-            .ok_or(ImmutableFileCreationError::FileStemExtraction { path: path.clone() })?;
-        let filename = filename
+            .ok_or(ImmutableFileCreationError::FileStemExtraction { path: path.clone() })?
             .to_str()
             .ok_or(ImmutableFileCreationError::FileNameExtraction { path: path.clone() })?;
-        let immutable_file_number = filename.parse::<ImmutableFileNumber>()?;
+        let immutable_file_number = filestem.parse::<ImmutableFileNumber>()?;
 
         Ok(Self {
             path,
             number: immutable_file_number,
+            filename,
         })
     }
 
+    /// ImmutableFile factory, TEST ONLY as it bypass the checks done by [ImmutableFile::new].
+    #[cfg(test)]
+    pub(crate) fn dummy(path: PathBuf, number: ImmutableFileNumber, filename: String) -> Self {
+        Self {
+            path,
+            number,
+            filename,
+        }
+    }
+
+    /// Compute the hash of this immutable file.
+    pub fn compute_raw_hash<D: Digest>(&self) -> Result<Output<D>, io::Error>
+    where
+        D: io::Write,
+    {
+        let mut hasher = D::new();
+        let mut file = File::open(&self.path)?;
+        io::copy(&mut file, &mut hasher)?;
+        Ok(hasher.finalize())
+    }
+
     /// List all [`ImmutableFile`] in a given directory.
     ///
     /// Important Note: It will skip the last chunk / primary / secondary trio since they're not yet
@@ -96,6 +131,7 @@ impl ImmutableFile {
         }
         files.sort();
 
+        // @todo: make the skip of the last 'trio' more robust
         Ok(files.into_iter().rev().skip(3).rev().collect())
     }
 }
diff --git a/mithril-common/src/digesters/mod.rs b/mithril-common/src/digesters/mod.rs
index 6477fe93f..37ea4e0ea 100644
--- a/mithril-common/src/digesters/mod.rs
+++ b/mithril-common/src/digesters/mod.rs
@@ -1,6 +1,8 @@
 //! Tools to compute mithril digest from a Cardano node database.
 
+pub mod cache;
 mod cardano_immutable_digester;
+mod dummy_immutable_db_builder;
 mod immutable_digester;
 mod immutable_file;
 mod immutable_file_observer;
@@ -12,3 +14,6 @@ pub use immutable_file_observer::{
     DumbImmutableFileObserver, ImmutableFileObserver, ImmutableFileObserverError,
     ImmutableFileSystemObserver,
 };
+
+#[cfg(test)]
+pub use dummy_immutable_db_builder::DummyImmutablesDbBuilder;
diff --git a/mithril-common/src/entities/mod.rs b/mithril-common/src/entities/mod.rs
index bb1876ec4..d68187af1 100644
--- a/mithril-common/src/entities/mod.rs
+++ b/mithril-common/src/entities/mod.rs
@@ -29,8 +29,9 @@ pub use signer::{Signer, SignerWithStake};
 pub use single_signatures::SingleSignatures;
 pub use snapshot::Snapshot;
 pub use type_alias::{
-    HexEncodedAgregateVerificationKey, HexEncodedGenesisSecretKey, HexEncodedGenesisSignature,
-    HexEncodedGenesisVerificationKey, HexEncodedKey, HexEncodedMultiSignature, HexEncodedOpCert,
-    HexEncodedSingleSignature, HexEncodedVerificationKey, HexEncodedVerificationKeySignature,
+    HexEncodedAgregateVerificationKey, HexEncodedDigest, HexEncodedGenesisSecretKey,
+    HexEncodedGenesisSignature, HexEncodedGenesisVerificationKey, HexEncodedKey,
+    HexEncodedMultiSignature, HexEncodedOpCert, HexEncodedSingleSignature,
+    HexEncodedVerificationKey, HexEncodedVerificationKeySignature, ImmutableFileName,
     ImmutableFileNumber, LotteryIndex, MagicId, PartyId, ProtocolVersion, Stake, StakeDistribution,
 };
diff --git a/mithril-common/src/entities/type_alias.rs b/mithril-common/src/entities/type_alias.rs
index ce0d8f34e..ba766c422 100644
--- a/mithril-common/src/entities/type_alias.rs
+++ b/mithril-common/src/entities/type_alias.rs
@@ -3,6 +3,9 @@ use std::collections::HashMap;
 /// ImmutableFileNumber represents the id of immutable files in the Cardano node database
 pub type ImmutableFileNumber = u64;
 
+/// ImmutableFileName represents the filename, with extension, of a immutable file in the Cardano node database
+pub type ImmutableFileName = String;
+
 /// PartyId represents a signing party in Mithril protocol
 pub type PartyId = String;
 
@@ -50,3 +53,6 @@ pub type HexEncodedGenesisVerificationKey = HexEncodedKey;
 
 /// Hex encoded Genesis Signature
 pub type HexEncodedGenesisSignature = HexEncodedKey;
+
+/// Hex encoded Sha256 Digest
+pub type HexEncodedDigest = HexEncodedKey;
diff --git a/mithril-signer/src/certificate_handler.rs b/mithril-signer/src/certificate_handler.rs
index 2d0e5056d..192a40a5f 100644
--- a/mithril-signer/src/certificate_handler.rs
+++ b/mithril-signer/src/certificate_handler.rs
@@ -321,7 +321,7 @@ mod tests {
 
     use mithril_common::fake_data;
 
-    use crate::entities::Config;
+    use crate::configuration::Config;
 
     fn setup_test() -> (MockServer, Config) {
         let server = MockServer::start();
@@ -338,6 +338,8 @@ mod tests {
             store_retention_limit: None,
             kes_secret_key_path: None,
             operational_certificate_path: None,
+            disable_digests_cache: false,
+            reset_digests_cache: false,
         };
         (server, config)
     }
diff --git a/mithril-signer/src/entities.rs b/mithril-signer/src/configuration.rs
similarity index 88%
rename from mithril-signer/src/entities.rs
rename to mithril-signer/src/configuration.rs
index b1eeb3a89..5525a87ae 100644
--- a/mithril-signer/src/entities.rs
+++ b/mithril-signer/src/configuration.rs
@@ -47,6 +47,14 @@ pub struct Config {
 
     /// File path to the operational certificate of the pool
     pub operational_certificate_path: Option<PathBuf>,
+
+    /// Disable immutables digests cache.
+    pub disable_digests_cache: bool,
+
+    /// If set the existing immutables digests cache will be reset.
+    ///
+    /// Will be ignored if set in conjunction with `disable_digests_cache`.
+    pub reset_digests_cache: bool,
 }
 
 impl Config {
diff --git a/mithril-signer/src/lib.rs b/mithril-signer/src/lib.rs
index d578d28b1..e89879583 100644
--- a/mithril-signer/src/lib.rs
+++ b/mithril-signer/src/lib.rs
@@ -7,13 +7,13 @@
 //! for more information on how it works.
 
 mod certificate_handler;
-mod entities;
+mod configuration;
 mod protocol_initializer_store;
 mod runtime;
 mod single_signer;
 
 pub use certificate_handler::*;
-pub use entities::Config;
+pub use configuration::Config;
 pub use protocol_initializer_store::{ProtocolInitializerStore, ProtocolInitializerStorer};
 pub use runtime::*;
 pub use single_signer::*;
diff --git a/mithril-signer/src/main.rs b/mithril-signer/src/main.rs
index f5cba9b72..33f7ed2f1 100644
--- a/mithril-signer/src/main.rs
+++ b/mithril-signer/src/main.rs
@@ -37,6 +37,16 @@ pub struct Args {
         help = "Directory where the configuration file is located"
     )]
     configuration_dir: PathBuf,
+
+    /// Disable immutables digests cache.
+    #[clap(long)]
+    disable_digests_cache: bool,
+
+    /// If set the existing immutables digests cache will be reset.
+    ///
+    /// Will be ignored if set in conjunction with `--disable-digests-cache`.
+    #[clap(long)]
+    reset_digests_cache: bool,
 }
 
 impl Args {
@@ -69,6 +79,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
 
     // Load config
     let config: Config = config::Config::builder()
+        .set_default("disable_digests_cache", args.disable_digests_cache)?
+        .set_default("reset_digests_cache", args.reset_digests_cache)?
         .add_source(
             config::File::with_name(&format!(
                 "{}/{}.json",
@@ -82,7 +94,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
         .map_err(|e| format!("configuration build error: {}", e))?
         .try_deserialize()
         .map_err(|e| format!("configuration deserialize error: {}", e))?;
-    let services = ProductionServiceBuilder::new(&config).build()?;
+    let services = ProductionServiceBuilder::new(&config).build().await?;
     DatabaseVersionChecker::new(
         slog_scope::logger(),
         ApplicationNodeType::Signer,
diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs
index 6c83c6153..f920c0184 100644
--- a/mithril-signer/src/runtime/runner.rs
+++ b/mithril-signer/src/runtime/runner.rs
@@ -507,7 +507,9 @@ mod tests {
             data_stores_directory: PathBuf::new(),
             kes_secret_key_path: None,
             operational_certificate_path: None,
+            disable_digests_cache: false,
             store_retention_limit: None,
+            reset_digests_cache: false,
         };
 
         SignerRunner::new(
diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs
index a46a00ab4..48ad4b097 100644
--- a/mithril-signer/src/runtime/signer_services.rs
+++ b/mithril-signer/src/runtime/signer_services.rs
@@ -1,7 +1,13 @@
-use std::error::Error as StdError;
-use std::fs;
-use std::sync::Arc;
+use async_trait::async_trait;
+use std::{
+    error::{Error as StdError, Error},
+    fs,
+    sync::Arc,
+};
 
+use mithril_common::digesters::cache::{
+    ImmutableFileDigestCacheProvider, JsonImmutableFileDigestCacheProviderBuilder,
+};
 use mithril_common::{
     chain_observer::{CardanoCliChainObserver, CardanoCliRunner, ChainObserver},
     crypto_helper::{OpCert, ProtocolPartyId, SerDeShelleyFileFormat},
@@ -26,9 +32,10 @@ type ProtocolInitializerStoreService = Arc<dyn ProtocolInitializerStorer>;
 
 /// The ServiceBuilder is intended to manage Services instance creation.
 /// The goal of this is to put all this code out of the way of business code.
+#[async_trait]
 pub trait ServiceBuilder {
     /// Create a SignerService instance.
-    fn build(&self) -> Result<SignerServices, Box<dyn StdError>>;
+    async fn build(&self) -> Result<SignerServices, Box<dyn StdError>>;
 }
 
 /// Create a SignerService instance for Production environment.
@@ -62,11 +69,30 @@ impl<'a> ProductionServiceBuilder<'a> {
                 .ok_or("A party_id should at least be provided")?),
         }
     }
+
+    async fn build_digester_cache_provider(
+        &self,
+    ) -> Result<Option<Arc<dyn ImmutableFileDigestCacheProvider>>, Box<dyn Error>> {
+        if self.config.disable_digests_cache {
+            return Ok(None);
+        }
+
+        let cache_provider = JsonImmutableFileDigestCacheProviderBuilder::new(
+            &self.config.data_stores_directory,
+            &format!("immutables_digests_{}.json", self.config.network),
+        )
+        .should_reset_digests_cache(self.config.reset_digests_cache)
+        .build()
+        .await?;
+
+        Ok(Some(Arc::new(cache_provider)))
+    }
 }
 
+#[async_trait]
 impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> {
     /// Build a Services for the Production environment.
-    fn build(&self) -> Result<SignerServices, Box<dyn StdError>> {
+    async fn build(&self) -> Result<SignerServices, Box<dyn StdError>> {
         if !self.config.data_stores_directory.exists() {
             fs::create_dir_all(self.config.data_stores_directory.clone())
                 .map_err(|e| format!("Could not create data stores directory: {:?}", e))?;
@@ -86,6 +112,7 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> {
         ));
         let digester = Arc::new(CardanoImmutableDigester::new(
             self.config.db_directory.clone(),
+            self.build_digester_cache_provider().await?,
             slog_scope::logger(),
         ));
         let stake_store = Arc::new(StakeStore::new(
@@ -162,8 +189,8 @@ mod tests {
         test_dir
     }
 
-    #[test]
-    fn test_auto_create_stores_directory() {
+    #[tokio::test]
+    async fn test_auto_create_stores_directory() {
         let stores_dir = get_test_dir().join("stores");
         let config = Config {
             cardano_cli_path: PathBuf::new(),
@@ -178,12 +205,15 @@ mod tests {
             store_retention_limit: None,
             kes_secret_key_path: None,
             operational_certificate_path: None,
+            disable_digests_cache: false,
+            reset_digests_cache: false,
         };
 
         assert!(!stores_dir.exists());
         let service_builder = ProductionServiceBuilder::new(&config);
         service_builder
             .build()
+            .await
             .expect("service builder build should not fail");
         assert!(stores_dir.exists());
     }


Test patch:
diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs
index bdad0410b..4026c74fb 100644
--- a/mithril-signer/tests/test_extensions/state_machine_tester.rs
+++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs
@@ -69,6 +69,8 @@ impl StateMachineTester {
             operational_certificate_path: selected_signer_temp_dir
                 .as_ref()
                 .map(|dir| dir.join("opcert.cert")),
+            disable_digests_cache: false,
+            reset_digests_cache: false,
         };
 
         let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/client.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/client.rs
index 291f4c409..ed5c35dc1 100644
--- a/mithril-test-lab/mithril-end-to-end/src/mithril/client.rs
+++ b/mithril-test-lab/mithril-end-to-end/src/mithril/client.rs
@@ -42,7 +42,12 @@ impl Client {
                 vec!["download".to_string(), digest]
             }
             ClientCommand::Restore { digest } => {
-                vec!["restore".to_string(), digest]
+                vec![
+                    "restore".to_string(),
+                    // Disable immutable digests cache as they will changes between two e2e executions
+                    "--disable-digests-cache".to_string(),
+                    digest,
+                ]
             }
         };