Skip to content

Commit 0b08c6f

Browse files
committed
feat: implement compute_merkle_tree on CardanoImmutableDigester
1 parent 07fba55 commit 0b08c6f

File tree

4 files changed

+194
-9
lines changed

4 files changed

+194
-9
lines changed

mithril-common/src/digesters/cardano_immutable_digester.rs

Lines changed: 164 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{
2+
crypto_helper::{MKTree, MKTreeStoreInMemory},
23
digesters::{
34
cache::ImmutableFileDigestCacheProvider, ImmutableDigester, ImmutableDigesterError,
45
ImmutableFile,
@@ -16,7 +17,7 @@ use std::{collections::BTreeMap, io, path::Path, sync::Arc};
1617
type ComputedImmutablesDigestsResult = Result<ComputedImmutablesDigests, io::Error>;
1718

1819
struct ComputedImmutablesDigests {
19-
digests: Vec<HexEncodedDigest>,
20+
entries: BTreeMap<ImmutableFile, HexEncodedDigest>,
2021
new_cached_entries: Vec<(ImmutableFileName, HexEncodedDigest)>,
2122
}
2223

@@ -91,7 +92,7 @@ impl ImmutableDigester for CardanoImmutableDigester {
9192
let digest = {
9293
let mut hasher = Sha256::new();
9394
hasher.update(compute_beacon_hash(&self.cardano_network, beacon).as_bytes());
94-
for digest in computed_immutables_digests.digests {
95+
for (_, digest) in computed_immutables_digests.entries {
9596
hasher.update(digest);
9697
}
9798
let hash: [u8; 32] = hasher.finalize().into();
@@ -115,6 +116,40 @@ impl ImmutableDigester for CardanoImmutableDigester {
115116

116117
Ok(digest)
117118
}
119+
120+
async fn compute_merkle_tree(
121+
&self,
122+
dirpath: &Path,
123+
beacon: &CardanoDbBeacon,
124+
) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError> {
125+
let immutables_to_process =
126+
list_immutable_files_to_process(dirpath, beacon.immutable_file_number)?;
127+
info!(self.logger, ">> compute_merkle_tree"; "beacon" => #?beacon, "nb_of_immutables" => immutables_to_process.len());
128+
let computed_immutables_digests = self.process_immutables(immutables_to_process).await?;
129+
130+
let digests: Vec<HexEncodedDigest> =
131+
computed_immutables_digests.entries.into_values().collect();
132+
let mktree =
133+
MKTree::new(&digests).map_err(ImmutableDigesterError::MerkleTreeComputationError)?;
134+
135+
debug!(
136+
self.logger,
137+
"Successfully computed Merkle tree for Cardano database"; "beacon" => #?beacon);
138+
139+
if let Some(cache_provider) = self.cache_provider.as_ref() {
140+
if let Err(error) = cache_provider
141+
.store(computed_immutables_digests.new_cached_entries)
142+
.await
143+
{
144+
warn!(
145+
self.logger, "Error while storing new immutable files digests to cache";
146+
"error" => ?error
147+
);
148+
}
149+
}
150+
151+
Ok(mktree)
152+
}
118153
}
119154

120155
fn list_immutable_files_to_process(
@@ -153,17 +188,17 @@ fn compute_immutables_digests(
153188
total: entries.len(),
154189
};
155190

156-
let mut digests = Vec::with_capacity(entries.len());
191+
let mut digests = BTreeMap::new();
157192

158193
for (ix, (entry, cache)) in entries.iter().enumerate() {
159194
match cache {
160195
None => {
161196
let data = hex::encode(entry.compute_raw_hash::<Sha256>()?);
162-
digests.push(data.clone());
197+
digests.insert(entry.clone(), data.clone());
163198
new_cached_entries.push((entry.filename.clone(), data));
164199
}
165200
Some(digest) => {
166-
digests.push(digest.to_string());
201+
digests.insert(entry.clone(), digest.to_string());
167202
}
168203
};
169204

@@ -173,7 +208,7 @@ fn compute_immutables_digests(
173208
}
174209

175210
Ok(ComputedImmutablesDigests {
176-
digests,
211+
entries: digests,
177212
new_cached_entries,
178213
})
179214
}
@@ -359,6 +394,33 @@ mod tests {
359394
);
360395
}
361396

397+
#[tokio::test]
398+
async fn can_compute_merkle_tree_of_a_hundred_immutable_file_trio() {
399+
let immutable_db = db_builder("can_compute_merkle_tree_of_a_hundred_immutable_file_trio")
400+
.with_immutables(&(1..=100).collect::<Vec<ImmutableFileNumber>>())
401+
.append_immutable_trio()
402+
.build();
403+
let logger = TestLogger::stdout();
404+
let digester = CardanoImmutableDigester::new(
405+
"devnet".to_string(),
406+
Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
407+
logger.clone(),
408+
);
409+
let beacon = CardanoDbBeacon::new(1, 100);
410+
411+
let result = digester
412+
.compute_merkle_tree(&immutable_db.dir, &beacon)
413+
.await
414+
.expect("compute_merkle_tree must not fail");
415+
416+
let expected_merkle_root = result.compute_root().unwrap().to_hex();
417+
418+
assert_eq!(
419+
"8552f75838176c967a33eb6da1fe5f3c9940b706d75a9c2352c0acd8439f3d84".to_string(),
420+
expected_merkle_root
421+
)
422+
}
423+
362424
#[tokio::test]
363425
async fn can_compute_hash_of_a_hundred_immutable_file_trio() {
364426
let immutable_db = db_builder("can_compute_hash_of_a_hundred_immutable_file_trio")
@@ -385,8 +447,8 @@ mod tests {
385447
}
386448

387449
#[tokio::test]
388-
async fn digests_are_stored_into_cache_provider() {
389-
let immutable_db = db_builder("digests_are_stored_into_cache_provider")
450+
async fn compute_digest_store_digests_into_cache_provider() {
451+
let immutable_db = db_builder("compute_digest_store_digests_into_cache_provider")
390452
.with_immutables(&[1, 2])
391453
.append_immutable_trio()
392454
.build();
@@ -420,6 +482,42 @@ mod tests {
420482
assert_eq!(expected, cached_entries);
421483
}
422484

485+
#[tokio::test]
486+
async fn compute_merkle_tree_store_digests_into_cache_provider() {
487+
let immutable_db = db_builder("compute_merkle_tree_store_digests_into_cache_provider")
488+
.with_immutables(&[1, 2])
489+
.append_immutable_trio()
490+
.build();
491+
let immutables = immutable_db.immutables_files;
492+
let cache = Arc::new(MemoryImmutableFileDigestCacheProvider::default());
493+
let logger = TestLogger::stdout();
494+
let digester = CardanoImmutableDigester::new(
495+
"devnet".to_string(),
496+
Some(cache.clone()),
497+
logger.clone(),
498+
);
499+
let beacon = CardanoDbBeacon::new(1, 2);
500+
501+
digester
502+
.compute_merkle_tree(&immutable_db.dir, &beacon)
503+
.await
504+
.expect("compute_digest must not fail");
505+
506+
let cached_entries = cache
507+
.get(immutables.clone())
508+
.await
509+
.expect("Cache read should not fail");
510+
let expected: BTreeMap<_, _> = immutables
511+
.into_iter()
512+
.map(|i| {
513+
let digest = hex::encode(i.compute_raw_hash::<Sha256>().unwrap());
514+
(i, Some(digest))
515+
})
516+
.collect();
517+
518+
assert_eq!(expected, cached_entries);
519+
}
520+
423521
#[tokio::test]
424522
async fn computed_digest_with_cold_or_hot_or_without_any_cache_are_equals() {
425523
let immutable_db = DummyImmutablesDbBuilder::new(
@@ -464,6 +562,53 @@ mod tests {
464562
);
465563
}
466564

565+
#[tokio::test]
566+
async fn computed_merkle_tree_with_cold_or_hot_or_without_any_cache_are_equals() {
567+
let immutable_db = DummyImmutablesDbBuilder::new(
568+
"computed_merkle_tree_with_cold_or_hot_or_without_any_cache_are_equals",
569+
)
570+
.with_immutables(&[1, 2, 3])
571+
.append_immutable_trio()
572+
.build();
573+
let logger = TestLogger::stdout();
574+
let no_cache_digester =
575+
CardanoImmutableDigester::new("devnet".to_string(), None, logger.clone());
576+
let cache_digester = CardanoImmutableDigester::new(
577+
"devnet".to_string(),
578+
Some(Arc::new(MemoryImmutableFileDigestCacheProvider::default())),
579+
logger.clone(),
580+
);
581+
let beacon = CardanoDbBeacon::new(1, 3);
582+
583+
let without_cache_digest = no_cache_digester
584+
.compute_merkle_tree(&immutable_db.dir, &beacon)
585+
.await
586+
.expect("compute_merkle_tree must not fail");
587+
588+
let cold_cache_digest = cache_digester
589+
.compute_merkle_tree(&immutable_db.dir, &beacon)
590+
.await
591+
.expect("compute_merkle_tree must not fail");
592+
593+
let full_cache_digest = cache_digester
594+
.compute_merkle_tree(&immutable_db.dir, &beacon)
595+
.await
596+
.expect("compute_merkle_tree must not fail");
597+
598+
let without_cache_merkle_root = without_cache_digest.compute_root().unwrap();
599+
let cold_cache_merkle_root = cold_cache_digest.compute_root().unwrap();
600+
let full_cache_merkle_root = full_cache_digest.compute_root().unwrap();
601+
assert_eq!(
602+
without_cache_merkle_root, full_cache_merkle_root,
603+
"Merkle roots with or without cache should be the same"
604+
);
605+
606+
assert_eq!(
607+
cold_cache_merkle_root, full_cache_merkle_root,
608+
"Merkle roots with cold or with hot cache should be the same"
609+
);
610+
}
611+
467612
#[tokio::test]
468613
async fn hash_computation_is_quicker_with_a_full_cache() {
469614
let immutable_db = db_builder("hash_computation_is_quicker_with_a_full_cache")
@@ -506,7 +651,7 @@ mod tests {
506651
}
507652

508653
#[tokio::test]
509-
async fn cache_read_failure_dont_block_computation() {
654+
async fn cache_read_failure_dont_block_computations() {
510655
let immutable_db = db_builder("cache_read_failure_dont_block_computation")
511656
.with_immutables(&[1, 2, 3])
512657
.append_immutable_trio()
@@ -530,6 +675,11 @@ mod tests {
530675
.compute_digest(&immutable_db.dir, &beacon)
531676
.await
532677
.expect("compute_digest must not fail even with cache write failure");
678+
679+
digester
680+
.compute_merkle_tree(&immutable_db.dir, &beacon)
681+
.await
682+
.expect("compute_merkle_tree must not fail even with cache write failure");
533683
}
534684

535685
#[tokio::test]
@@ -557,5 +707,10 @@ mod tests {
557707
.compute_digest(&immutable_db.dir, &beacon)
558708
.await
559709
.expect("compute_digest must not fail even with cache read failure");
710+
711+
digester
712+
.compute_merkle_tree(&immutable_db.dir, &beacon)
713+
.await
714+
.expect("compute_merkle_tree must not fail even with cache read failure");
560715
}
561716
}

mithril-common/src/digesters/dumb_immutable_observer.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::Path;
22

33
use crate::{
4+
crypto_helper::{MKTree, MKTreeStoreInMemory},
45
digesters::{ImmutableDigester, ImmutableDigesterError},
56
entities::CardanoDbBeacon,
67
};
@@ -51,4 +52,12 @@ impl ImmutableDigester for DumbImmutableDigester {
5152
})
5253
}
5354
}
55+
56+
async fn compute_merkle_tree(
57+
&self,
58+
_dirpath: &Path,
59+
_beacon: &CardanoDbBeacon,
60+
) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError> {
61+
unimplemented!()
62+
}
5463
}

mithril-common/src/digesters/immutable_digester.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{
2+
crypto_helper::{MKTree, MKTreeStoreInMemory},
23
digesters::ImmutableFileListingError,
34
entities::{CardanoDbBeacon, ImmutableFileNumber},
45
};
@@ -54,6 +55,13 @@ pub trait ImmutableDigester: Sync + Send {
5455
dirpath: &Path,
5556
beacon: &CardanoDbBeacon,
5657
) -> Result<String, ImmutableDigesterError>;
58+
59+
/// Compute the digests merkle tree
60+
async fn compute_merkle_tree(
61+
&self,
62+
dirpath: &Path,
63+
beacon: &CardanoDbBeacon,
64+
) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError>;
5765
}
5866

5967
/// [ImmutableDigester] related Errors.
@@ -78,4 +86,8 @@ pub enum ImmutableDigesterError {
7886
/// Error raised when the digest computation failed.
7987
#[error("Digest computation failed")]
8088
DigestComputationError(#[from] io::Error),
89+
90+
/// Error raised when the Merkle tree computation failed.
91+
#[error("Merkle tree computation failed")]
92+
MerkleTreeComputationError(anyhow::Error),
8193
}

mithril-common/src/signable_builder/cardano_immutable_full_signable_builder.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod tests {
6565
use async_trait::async_trait;
6666
use std::path::Path;
6767

68+
use crate::crypto_helper::{MKTree, MKTreeStoreInMemory};
6869
use crate::digesters::{ImmutableDigester, ImmutableDigesterError};
6970
use crate::entities::CardanoDbBeacon;
7071
use crate::test_utils::TestLogger;
@@ -83,6 +84,14 @@ mod tests {
8384
) -> Result<String, ImmutableDigesterError> {
8485
Ok(format!("immutable {}", beacon.immutable_file_number))
8586
}
87+
88+
async fn compute_merkle_tree(
89+
&self,
90+
_dirpath: &Path,
91+
_beacon: &CardanoDbBeacon,
92+
) -> Result<MKTree<MKTreeStoreInMemory>, ImmutableDigesterError> {
93+
unimplemented!()
94+
}
8695
}
8796

8897
#[tokio::test]

0 commit comments

Comments
 (0)