Skip to content

Commit 4a0f37c

Browse files
committed
feature(client-lib, client-cli): introduce a function to download digests and verified them against certificate
1 parent e52b52a commit 4a0f37c

File tree

8 files changed

+201
-12
lines changed

8 files changed

+201
-12
lines changed

examples/client-cardano-database-v2/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async fn main() -> MithrilResult<()> {
135135
);
136136
let message = wait_spinner(
137137
&progress_bar,
138-
MessageBuilder::new().compute_cardano_database_message(&certificate, &merkle_proof),
138+
MessageBuilder::new().compute_cardano_database_message(&certificate, merkle_proof.root()),
139139
)
140140
.await?;
141141

mithril-client-cli/src/commands/cardano_db/shared_steps.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::Path;
55

66
use mithril_client::{
77
CardanoDatabaseSnapshot, Client, MessageBuilder, MithrilCertificate, MithrilResult,
8-
cardano_database_client::ImmutableFileRange,
8+
cardano_database_client::{ImmutableFileRange, VerifiedDigests},
99
common::{ImmutableFileNumber, MKProof, ProtocolMessage},
1010
};
1111

@@ -47,6 +47,22 @@ pub fn immutable_file_range(
4747
}
4848
}
4949

50+
pub async fn download_and_verify_digests(
51+
step_number: u16,
52+
progress_printer: &ProgressPrinter,
53+
client: &Client,
54+
certificate: &MithrilCertificate,
55+
cardano_database_snapshot: &CardanoDatabaseSnapshot,
56+
) -> MithrilResult<VerifiedDigests> {
57+
progress_printer.report_step(step_number, "Downloading and verifying digests…")?;
58+
let verified_digests = client
59+
.cardano_database_v2()
60+
.download_and_verify_digests(certificate, cardano_database_snapshot)
61+
.await?;
62+
63+
Ok(verified_digests)
64+
}
65+
5066
/// Computes and verifies the Merkle proof for the given certificate and database snapshot.
5167
pub async fn compute_verify_merkle_proof(
5268
step_number: u16,
@@ -85,7 +101,7 @@ pub async fn compute_cardano_db_snapshot_message(
85101
progress_printer.report_step(step_number, "Computing the cardano db snapshot message")?;
86102
let message = CardanoDbUtils::wait_spinner(
87103
progress_printer,
88-
MessageBuilder::new().compute_cardano_database_message(certificate, merkle_proof),
104+
MessageBuilder::new().compute_cardano_database_message(certificate, merkle_proof.root()),
89105
)
90106
.await
91107
.with_context(|| "Can not compute the cardano db snapshot message")?;

mithril-client-cli/src/commands/cardano_db/verify.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ impl CardanoDbVerifyCommand {
104104

105105
let immutable_file_range = shared_steps::immutable_file_range(None, None);
106106

107+
let verified_digests = shared_steps::download_and_verify_digests(
108+
2,
109+
&progress_printer,
110+
&client,
111+
&certificate,
112+
&cardano_db_message,
113+
)
114+
.await?;
115+
107116
let merkle_proof = shared_steps::compute_verify_merkle_proof(
108117
2,
109118
&progress_printer,

mithril-client/src/cardano_database_client/api.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use mithril_cardano_node_internal_database::entities::ImmutableFile;
1616

1717
use crate::aggregator_client::AggregatorClient;
1818
#[cfg(feature = "fs")]
19+
use crate::cardano_database_client::VerifiedDigests;
20+
#[cfg(feature = "fs")]
1921
use crate::feedback::FeedbackSender;
2022
#[cfg(feature = "fs")]
2123
use crate::file_downloader::FileDownloader;
@@ -100,6 +102,18 @@ impl CardanoDatabaseClient {
100102
.await
101103
}
102104

105+
/// Download and verify the digests against the certificate.
106+
#[cfg(feature = "fs")]
107+
pub async fn download_and_verify_digests(
108+
&self,
109+
certificate: &CertificateMessage,
110+
cardano_database_snapshot: &CardanoDatabaseSnapshotMessage,
111+
) -> MithrilResult<VerifiedDigests> {
112+
self.artifact_prover
113+
.download_and_verify_digests(certificate, cardano_database_snapshot)
114+
.await
115+
}
116+
103117
/// Compute the Merkle proof of membership for the given immutable file range.
104118
#[cfg(feature = "fs")]
105119
pub async fn compute_merkle_proof(

mithril-client/src/cardano_database_client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,5 @@ cfg_fs! {
136136

137137
pub use download_unpack::DownloadUnpackOptions;
138138
pub use immutable_file_range::ImmutableFileRange;
139+
pub use proving::VerifiedDigests;
139140
}

mithril-client/src/cardano_database_client/proving.rs

Lines changed: 154 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,22 @@ use mithril_common::{
2121
};
2222

2323
use crate::{
24-
MithrilResult,
24+
MessageBuilder, MithrilResult,
2525
feedback::MithrilEvent,
2626
file_downloader::{DownloadEvent, FileDownloader, FileDownloaderUri},
2727
utils::{create_directory_if_not_exists, delete_directory, read_files_in_directory},
2828
};
2929

3030
use super::immutable_file_range::ImmutableFileRange;
3131

32+
/// Represents the verified digests and the Merkle tree built from them.
33+
pub struct VerifiedDigests {
34+
/// A map of immutable file names to their corresponding verified digests.
35+
pub digests: BTreeMap<ImmutableFileName, HexEncodedDigest>,
36+
/// The Merkle tree built from the digests.
37+
pub merkle_tree: MKTree<MKTreeStoreInMemory>,
38+
}
39+
3240
pub struct InternalArtifactProver {
3341
http_file_downloader: Arc<dyn FileDownloader>,
3442
logger: slog::Logger,
@@ -84,6 +92,52 @@ impl InternalArtifactProver {
8492
merkle_tree.compute_proof(&computed_digests)
8593
}
8694

95+
///Download digests and verify its authenticity against the certificate.
96+
pub async fn download_and_verify_digests(
97+
&self,
98+
certificate: &CertificateMessage,
99+
cardano_database_snapshot: &CardanoDatabaseSnapshotMessage,
100+
) -> MithrilResult<VerifiedDigests> {
101+
let digest_target_dir = Self::digest_target_dir();
102+
delete_directory(&digest_target_dir)?;
103+
self.download_unpack_digest_file(&cardano_database_snapshot.digests, &digest_target_dir)
104+
.await?;
105+
let last_immutable_file_number = cardano_database_snapshot.beacon.immutable_file_number;
106+
107+
let downloaded_digests = self.read_digest_file(&digest_target_dir)?;
108+
delete_directory(&digest_target_dir)?;
109+
110+
let filtered_digests = downloaded_digests
111+
.clone()
112+
.into_iter()
113+
.filter(|(immutable_file_name, _)| {
114+
match ImmutableFile::new(Path::new(immutable_file_name).to_path_buf()) {
115+
Ok(immutable_file) => immutable_file.number <= last_immutable_file_number,
116+
Err(_) => false,
117+
}
118+
})
119+
.collect::<BTreeMap<_, _>>();
120+
121+
let filtered_digests_values = filtered_digests.values().collect::<Vec<_>>();
122+
let merkle_tree: MKTree<MKTreeStoreInMemory> = MKTree::new(&filtered_digests_values)?;
123+
124+
let message = MessageBuilder::new()
125+
.compute_cardano_database_message(certificate, &merkle_tree.compute_root()?)
126+
.await?;
127+
128+
if !certificate.match_message(&message) {
129+
return Err(anyhow!(
130+
"Certificate message does not match the computed message for certificate {}",
131+
certificate.hash
132+
));
133+
}
134+
135+
Ok(VerifiedDigests {
136+
digests: filtered_digests,
137+
merkle_tree,
138+
})
139+
}
140+
87141
async fn download_unpack_digest_file(
88142
&self,
89143
digests_locations: &DigestsMessagePart,
@@ -198,7 +252,9 @@ mod tests {
198252
IMMUTABLE_DIR, digesters::ComputedImmutablesDigests,
199253
};
200254
use mithril_common::{
201-
StdResult, entities::ImmutableFileNumber, messages::DigestsMessagePart,
255+
StdResult,
256+
entities::{ImmutableFileNumber, ProtocolMessage, ProtocolMessagePartKey},
257+
messages::DigestsMessagePart,
202258
};
203259

204260
use super::*;
@@ -208,6 +264,7 @@ mod tests {
208264
beacon: &CardanoDbBeacon,
209265
immutable_file_range: &RangeInclusive<ImmutableFileNumber>,
210266
digests_offset: usize,
267+
digests_location: &str,
211268
) -> (
212269
PathBuf,
213270
CardanoDatabaseSnapshotMessage,
@@ -221,7 +278,7 @@ mod tests {
221278
digests: DigestsMessagePart {
222279
size_uncompressed: 1024,
223280
locations: vec![DigestLocation::CloudStorage {
224-
uri: "http://whatever/digests.json".to_string(),
281+
uri: digests_location.to_string(),
225282
compression_algorithm: None,
226283
}],
227284
},
@@ -298,6 +355,96 @@ mod tests {
298355
Ok(())
299356
}
300357

358+
fn build_digests_map(size: usize) -> BTreeMap<ImmutableFile, HexEncodedDigest> {
359+
let mut digests = BTreeMap::new();
360+
for i in 1..=size {
361+
for name in ["chunk", "primary", "secondary"] {
362+
let immutable_file_name = format!("{:05}.{name}", i);
363+
let immutable_file =
364+
ImmutableFile::new(PathBuf::from(immutable_file_name)).unwrap();
365+
let digest = format!("digest-{i}-{name}");
366+
digests.insert(immutable_file, digest);
367+
}
368+
}
369+
370+
digests
371+
}
372+
373+
#[tokio::test]
374+
async fn download_and_verify_digest_should_return_digest_map_acording_to_beacon() {
375+
let beacon = CardanoDbBeacon {
376+
epoch: Epoch(123),
377+
immutable_file_number: 42,
378+
};
379+
let hightest_immutable_number_in_digest_file =
380+
123 + beacon.immutable_file_number as usize;
381+
let digests_in_certificate_map =
382+
build_digests_map(beacon.immutable_file_number as usize);
383+
let protocol_message_merkle_root = {
384+
let digests_in_certificate_values =
385+
digests_in_certificate_map.values().cloned().collect::<Vec<_>>();
386+
let certificate_merkle_tree: MKTree<MKTreeStoreInMemory> =
387+
MKTree::new(&digests_in_certificate_values).unwrap();
388+
389+
certificate_merkle_tree.compute_root().unwrap().to_hex()
390+
};
391+
let mut protocol_message = ProtocolMessage::new();
392+
protocol_message.set_message_part(
393+
ProtocolMessagePartKey::CardanoDatabaseMerkleRoot,
394+
protocol_message_merkle_root,
395+
);
396+
let certificate = CertificateMessage {
397+
protocol_message: protocol_message.clone(),
398+
signed_message: protocol_message.compute_hash(),
399+
..CertificateMessage::dummy()
400+
};
401+
402+
let digests_location = "http://whatever/digests.json";
403+
let cardano_database_snapshot = CardanoDatabaseSnapshotMessage {
404+
beacon,
405+
digests: DigestsMessagePart {
406+
size_uncompressed: 1024,
407+
locations: vec![DigestLocation::CloudStorage {
408+
uri: digests_location.to_string(),
409+
compression_algorithm: None,
410+
}],
411+
},
412+
..CardanoDatabaseSnapshotMessage::dummy()
413+
};
414+
let client = CardanoDatabaseClientDependencyInjector::new()
415+
.with_http_file_downloader(Arc::new(
416+
MockFileDownloaderBuilder::default()
417+
.with_file_uri(digests_location)
418+
.with_target_dir(InternalArtifactProver::digest_target_dir())
419+
.with_compression(None)
420+
.with_returning(Box::new(move |_, _, _, _, _| {
421+
write_digest_file(
422+
&InternalArtifactProver::digest_target_dir(),
423+
&build_digests_map(hightest_immutable_number_in_digest_file),
424+
)?;
425+
426+
Ok(())
427+
}))
428+
.build(),
429+
))
430+
.build_cardano_database_client();
431+
432+
let verified_digests = client
433+
.download_and_verify_digests(&certificate, &cardano_database_snapshot)
434+
.await
435+
.unwrap();
436+
437+
let expected_digests_in_certificate = digests_in_certificate_map
438+
.iter()
439+
.map(|(immutable_file, digest)| {
440+
(immutable_file.filename.clone(), digest.to_string())
441+
})
442+
.collect();
443+
assert_eq!(verified_digests.digests, expected_digests_in_certificate);
444+
445+
assert!(!InternalArtifactProver::digest_target_dir().exists());
446+
}
447+
301448
#[tokio::test]
302449
async fn compute_merkle_proof_succeeds() {
303450
let beacon = CardanoDbBeacon {
@@ -307,19 +454,21 @@ mod tests {
307454
let immutable_file_range = 1..=15;
308455
let immutable_file_range_to_prove = ImmutableFileRange::Range(2, 4);
309456
let digests_offset = 3;
457+
let digests_location = "http://whatever/digests.json";
310458
let (database_dir, cardano_database_snapshot, certificate, merkle_tree, digests) =
311459
prepare_fake_digests(
312460
"compute_merkle_proof_succeeds",
313461
&beacon,
314462
&immutable_file_range,
315463
digests_offset,
464+
digests_location,
316465
)
317466
.await;
318467
let expected_merkle_root = merkle_tree.compute_root().unwrap();
319468
let client = CardanoDatabaseClientDependencyInjector::new()
320469
.with_http_file_downloader(Arc::new(
321470
MockFileDownloaderBuilder::default()
322-
.with_file_uri("http://whatever/digests.json")
471+
.with_file_uri(digests_location)
323472
.with_target_dir(InternalArtifactProver::digest_target_dir())
324473
.with_compression(None)
325474
.with_returning(Box::new(move |_, _, _, _, _| {
@@ -347,7 +496,7 @@ mod tests {
347496
let merkle_proof_root = merkle_proof.root().to_owned();
348497
assert_eq!(expected_merkle_root, merkle_proof_root);
349498

350-
assert!(!database_dir.join("digest").exists());
499+
assert!(!InternalArtifactProver::digest_target_dir().exists());
351500
}
352501
}
353502

mithril-client/src/message.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use mithril_common::logging::LoggerExtensions;
1313
use mithril_common::protocol::SignerBuilder;
1414
use mithril_common::signable_builder::CardanoStakeDistributionSignableBuilder;
1515
#[cfg(feature = "fs")]
16-
use mithril_common::{crypto_helper::MKProof, entities::SignedEntityType};
16+
use mithril_common::{crypto_helper::MKTreeNode, entities::SignedEntityType};
1717

1818
use crate::{
1919
CardanoStakeDistribution, MithrilCertificate, MithrilResult, MithrilSigner,
@@ -105,12 +105,12 @@ impl MessageBuilder {
105105
pub async fn compute_cardano_database_message(
106106
&self,
107107
certificate: &MithrilCertificate,
108-
merkle_proof: &MKProof,
108+
merkle_root: &MKTreeNode,
109109
) -> MithrilResult<ProtocolMessage> {
110110
let mut message = certificate.protocol_message.clone();
111111
message.set_message_part(
112112
ProtocolMessagePartKey::CardanoDatabaseMerkleRoot,
113-
merkle_proof.root().to_hex(),
113+
merkle_root.to_hex(),
114114
);
115115

116116
Ok(message)

mithril-client/tests/cardano_db_snapshot_list_get_download_verify.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ async fn cardano_db_snapshot_list_get_download_verify() {
146146
merkle_proof.verify().expect("Merkle proof should be valid");
147147

148148
let message = MessageBuilder::new()
149-
.compute_cardano_database_message(&certificate, &merkle_proof)
149+
.compute_cardano_database_message(&certificate, merkle_proof.root())
150150
.await
151151
.expect("Computing cardano database snapshot message should not fail");
152152

0 commit comments

Comments
 (0)