@@ -21,14 +21,22 @@ use mithril_common::{
21
21
} ;
22
22
23
23
use crate :: {
24
- MithrilResult ,
24
+ MessageBuilder , MithrilResult ,
25
25
feedback:: MithrilEvent ,
26
26
file_downloader:: { DownloadEvent , FileDownloader , FileDownloaderUri } ,
27
27
utils:: { create_directory_if_not_exists, delete_directory, read_files_in_directory} ,
28
28
} ;
29
29
30
30
use super :: immutable_file_range:: ImmutableFileRange ;
31
31
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
+
32
40
pub struct InternalArtifactProver {
33
41
http_file_downloader : Arc < dyn FileDownloader > ,
34
42
logger : slog:: Logger ,
@@ -84,6 +92,52 @@ impl InternalArtifactProver {
84
92
merkle_tree. compute_proof ( & computed_digests)
85
93
}
86
94
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
+
87
141
async fn download_unpack_digest_file (
88
142
& self ,
89
143
digests_locations : & DigestsMessagePart ,
@@ -198,7 +252,9 @@ mod tests {
198
252
IMMUTABLE_DIR , digesters:: ComputedImmutablesDigests ,
199
253
} ;
200
254
use mithril_common:: {
201
- StdResult , entities:: ImmutableFileNumber , messages:: DigestsMessagePart ,
255
+ StdResult ,
256
+ entities:: { ImmutableFileNumber , ProtocolMessage , ProtocolMessagePartKey } ,
257
+ messages:: DigestsMessagePart ,
202
258
} ;
203
259
204
260
use super :: * ;
@@ -208,6 +264,7 @@ mod tests {
208
264
beacon : & CardanoDbBeacon ,
209
265
immutable_file_range : & RangeInclusive < ImmutableFileNumber > ,
210
266
digests_offset : usize ,
267
+ digests_location : & str ,
211
268
) -> (
212
269
PathBuf ,
213
270
CardanoDatabaseSnapshotMessage ,
@@ -221,7 +278,7 @@ mod tests {
221
278
digests : DigestsMessagePart {
222
279
size_uncompressed : 1024 ,
223
280
locations : vec ! [ DigestLocation :: CloudStorage {
224
- uri: "http://whatever/digests.json" . to_string( ) ,
281
+ uri: digests_location . to_string( ) ,
225
282
compression_algorithm: None ,
226
283
} ] ,
227
284
} ,
@@ -298,6 +355,96 @@ mod tests {
298
355
Ok ( ( ) )
299
356
}
300
357
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
+
301
448
#[ tokio:: test]
302
449
async fn compute_merkle_proof_succeeds ( ) {
303
450
let beacon = CardanoDbBeacon {
@@ -307,19 +454,21 @@ mod tests {
307
454
let immutable_file_range = 1 ..=15 ;
308
455
let immutable_file_range_to_prove = ImmutableFileRange :: Range ( 2 , 4 ) ;
309
456
let digests_offset = 3 ;
457
+ let digests_location = "http://whatever/digests.json" ;
310
458
let ( database_dir, cardano_database_snapshot, certificate, merkle_tree, digests) =
311
459
prepare_fake_digests (
312
460
"compute_merkle_proof_succeeds" ,
313
461
& beacon,
314
462
& immutable_file_range,
315
463
digests_offset,
464
+ digests_location,
316
465
)
317
466
. await ;
318
467
let expected_merkle_root = merkle_tree. compute_root ( ) . unwrap ( ) ;
319
468
let client = CardanoDatabaseClientDependencyInjector :: new ( )
320
469
. with_http_file_downloader ( Arc :: new (
321
470
MockFileDownloaderBuilder :: default ( )
322
- . with_file_uri ( "http://whatever/digests.json" )
471
+ . with_file_uri ( digests_location )
323
472
. with_target_dir ( InternalArtifactProver :: digest_target_dir ( ) )
324
473
. with_compression ( None )
325
474
. with_returning ( Box :: new ( move |_, _, _, _, _| {
@@ -347,7 +496,7 @@ mod tests {
347
496
let merkle_proof_root = merkle_proof. root ( ) . to_owned ( ) ;
348
497
assert_eq ! ( expected_merkle_root, merkle_proof_root) ;
349
498
350
- assert ! ( !database_dir . join ( "digest" ) . exists( ) ) ;
499
+ assert ! ( !InternalArtifactProver :: digest_target_dir ( ) . exists( ) ) ;
351
500
}
352
501
}
353
502
0 commit comments