1
1
use std:: {
2
2
fmt:: Write ,
3
+ fs:: File ,
3
4
path:: { Path , PathBuf } ,
4
5
sync:: Arc ,
5
6
time:: Duration ,
@@ -201,18 +202,18 @@ impl SnapshotService for MithrilClientSnapshotService {
201
202
async fn download (
202
203
& self ,
203
204
snapshot_entity : & SignedEntity < Snapshot > ,
204
- pathdir : & Path ,
205
+ download_dir : & Path ,
205
206
genesis_verification_key : & str ,
206
207
progress_target : ProgressDrawTarget ,
207
208
) -> StdResult < PathBuf > {
208
209
debug ! ( "Snapshot service: download." ) ;
209
210
210
- let unpack_dir = pathdir . join ( "db" ) ;
211
+ let db_dir = download_dir . join ( "db" ) ;
211
212
let progress_bar = MultiProgress :: with_draw_target ( progress_target) ;
212
213
progress_bar. println ( "1/7 - Checking local disk info…" ) ?;
213
214
let unpacker = SnapshotUnpacker ;
214
215
215
- if let Err ( e) = unpacker. check_prerequisites ( & unpack_dir , snapshot_entity. artifact . size ) {
216
+ if let Err ( e) = unpacker. check_prerequisites ( & db_dir , snapshot_entity. artifact . size ) {
216
217
self . check_disk_space_error ( e) ?;
217
218
}
218
219
@@ -237,49 +238,61 @@ impl SnapshotService for MithrilClientSnapshotService {
237
238
. unwrap ( )
238
239
. with_key ( "eta" , |state : & ProgressState , w : & mut dyn Write | write ! ( w, "{:.1}s" , state. eta( ) . as_secs_f64( ) ) . unwrap ( ) )
239
240
. progress_chars ( "#>-" ) ) ;
240
- let filepath = self
241
+ let snapshot_path = self
241
242
. snapshot_client
242
- . download ( & snapshot_entity. artifact , pathdir , pb)
243
+ . download ( & snapshot_entity. artifact , download_dir , pb)
243
244
. await
244
- . map_err ( |e| format ! ( "Could not download file in '{}': {e}" , pathdir. display( ) ) ) ?;
245
+ . map_err ( |e| {
246
+ format ! (
247
+ "Could not download file in '{}': {e}" ,
248
+ download_dir. display( )
249
+ )
250
+ } ) ?;
245
251
246
252
progress_bar. println ( "5/7 - Unpacking the snapshot…" ) ?;
247
- let unpacker = unpacker. unpack_snapshot ( & filepath , & unpack_dir ) ;
253
+ let unpacker = unpacker. unpack_snapshot ( & snapshot_path , & db_dir ) ;
248
254
self . wait_spinner ( & progress_bar, unpacker) . await ?;
249
255
256
+ // Append 'clean' file to speedup node bootstrap
257
+ if let Err ( error) = File :: create ( db_dir. join ( "clean" ) ) {
258
+ warn ! (
259
+ "Could not create clean shutdown marker file in directory {}: {error}" ,
260
+ db_dir. display( )
261
+ ) ;
262
+ } ;
263
+
250
264
progress_bar. println ( "6/7 - Computing the snapshot digest…" ) ?;
251
265
let unpacked_snapshot_digest = self
252
266
. immutable_digester
253
- . compute_digest ( & unpack_dir , & certificate. beacon )
267
+ . compute_digest ( & db_dir , & certificate. beacon )
254
268
. await
255
- . map_err ( |e| {
256
- format ! (
257
- "Could not compute digest in '{}': {e}" ,
258
- unpack_dir. display( )
259
- )
260
- } ) ?;
269
+ . map_err ( |e| format ! ( "Could not compute digest in '{}': {e}" , db_dir. display( ) ) ) ?;
261
270
262
271
progress_bar. println ( "7/7 - Verifying the snapshot signature…" ) ?;
263
- let mut protocol_message = certificate. protocol_message . clone ( ) ;
264
- protocol_message. set_message_part (
265
- ProtocolMessagePartKey :: SnapshotDigest ,
266
- unpacked_snapshot_digest,
267
- ) ;
268
- if protocol_message. compute_hash ( ) != certificate. signed_message {
272
+ let expected_message = {
273
+ let mut protocol_message = certificate. protocol_message . clone ( ) ;
274
+ protocol_message. set_message_part (
275
+ ProtocolMessagePartKey :: SnapshotDigest ,
276
+ unpacked_snapshot_digest,
277
+ ) ;
278
+ protocol_message. compute_hash ( )
279
+ } ;
280
+
281
+ if expected_message != certificate. signed_message {
269
282
debug ! ( "Digest verification failed, removing unpacked files & directory." ) ;
270
283
271
- if let Err ( e ) = std:: fs:: remove_dir_all ( & unpack_dir ) {
272
- warn ! ( "Error while removing unpacked files & directory: {e }." ) ;
284
+ if let Err ( error ) = std:: fs:: remove_dir_all ( & db_dir ) {
285
+ warn ! ( "Error while removing unpacked files & directory: {error }." ) ;
273
286
}
274
287
275
288
return Err ( SnapshotServiceError :: CouldNotVerifySnapshot {
276
289
digest : snapshot_entity. artifact . digest . clone ( ) ,
277
- path : filepath . canonicalize ( ) . unwrap ( ) ,
290
+ path : snapshot_path . canonicalize ( ) . unwrap ( ) ,
278
291
}
279
292
. into ( ) ) ;
280
293
}
281
294
282
- Ok ( unpack_dir )
295
+ Ok ( db_dir )
283
296
}
284
297
}
285
298
@@ -298,34 +311,44 @@ mod tests {
298
311
test_utils:: fake_data,
299
312
} ;
300
313
use std:: {
314
+ ffi:: OsStr ,
301
315
fs:: { create_dir_all, File } ,
302
316
io:: Write ,
303
317
} ;
304
318
305
319
use crate :: {
306
320
aggregator_client:: { AggregatorClient , MockAggregatorHTTPClient } ,
307
321
dependencies:: DependenciesBuilder ,
322
+ services:: mock:: * ,
308
323
FromSnapshotMessageAdapter ,
309
324
} ;
310
325
311
- use super :: super :: mock:: * ;
312
-
313
326
use super :: * ;
314
327
315
328
/// see [`archive_file_path`] to see where the dummy will be created
316
329
fn build_dummy_snapshot ( digest : & str , data_expected : & str , test_dir : & Path ) {
330
+ // create a fake file to archive
331
+ let data_file_path = {
332
+ let data_file_path = test_dir. join ( "db" ) . join ( "test_data.txt" ) ;
333
+ create_dir_all ( data_file_path. parent ( ) . unwrap ( ) ) . unwrap ( ) ;
334
+
335
+ let mut source_file = File :: create ( data_file_path. as_path ( ) ) . unwrap ( ) ;
336
+ write ! ( source_file, "{data_expected}" ) . unwrap ( ) ;
337
+
338
+ data_file_path
339
+ } ;
340
+
341
+ // create the archive
317
342
let archive_file_path = test_dir. join ( format ! ( "snapshot-{digest}" ) ) ;
318
- let data_file_path = test_dir. join ( Path :: new ( "db/test_data.txt" ) ) ;
319
- create_dir_all ( data_file_path. parent ( ) . unwrap ( ) ) . unwrap ( ) ;
320
- let mut source_file = File :: create ( data_file_path. as_path ( ) ) . unwrap ( ) ;
321
- write ! ( source_file, "{data_expected}" ) . unwrap ( ) ;
322
343
let archive_file = File :: create ( archive_file_path) . unwrap ( ) ;
323
344
let archive_encoder = GzEncoder :: new ( & archive_file, Compression :: default ( ) ) ;
324
345
let mut archive_builder = tar:: Builder :: new ( archive_encoder) ;
325
346
archive_builder
326
347
. append_dir_all ( "." , data_file_path. parent ( ) . unwrap ( ) )
327
348
. unwrap ( ) ;
328
349
archive_builder. into_inner ( ) . unwrap ( ) . finish ( ) . unwrap ( ) ;
350
+
351
+ // remove the fake file
329
352
let _ = std:: fs:: remove_dir_all ( data_file_path. parent ( ) . unwrap ( ) ) ;
330
353
}
331
354
@@ -361,6 +384,36 @@ mod tests {
361
384
}
362
385
}
363
386
387
+ fn get_mocks_for_snapshot_service_configured_to_make_download_succeed ( ) -> (
388
+ MockAggregatorHTTPClient ,
389
+ MockCertificateVerifierImpl ,
390
+ DumbImmutableDigester ,
391
+ ) {
392
+ let mut http_client = MockAggregatorHTTPClient :: new ( ) ;
393
+ http_client. expect_probe ( ) . returning ( |_| Ok ( ( ) ) ) ;
394
+ http_client
395
+ . expect_download ( )
396
+ . returning ( move |_, _, _| Ok ( ( ) ) )
397
+ . times ( 1 ) ;
398
+ http_client. expect_get_content ( ) . returning ( |_| {
399
+ let mut message = CertificateMessage :: dummy ( ) ;
400
+ message. signed_message = message. protocol_message . compute_hash ( ) ;
401
+ let message = serde_json:: to_string ( & message) . unwrap ( ) ;
402
+
403
+ Ok ( message)
404
+ } ) ;
405
+
406
+ let mut certificate_verifier = MockCertificateVerifierImpl :: new ( ) ;
407
+ certificate_verifier
408
+ . expect_verify_certificate_chain ( )
409
+ . returning ( |_, _, _| Ok ( ( ) ) )
410
+ . times ( 1 ) ;
411
+
412
+ let dumb_digester = DumbImmutableDigester :: new ( "snapshot-digest-123" , true ) ;
413
+
414
+ ( http_client, certificate_verifier, dumb_digester)
415
+ }
416
+
364
417
fn get_dep_builder ( http_client : Arc < dyn AggregatorClient > ) -> DependenciesBuilder {
365
418
let config_builder: ConfigBuilder < DefaultState > = ConfigBuilder :: default ( ) ;
366
419
let config = config_builder
@@ -472,40 +525,65 @@ mod tests {
472
525
async fn test_download_snapshot_ok ( ) {
473
526
let test_path = std:: env:: temp_dir ( ) . join ( "test_download_snapshot_ok" ) ;
474
527
let _ = std:: fs:: remove_dir_all ( & test_path) ;
475
- let mut http_client = MockAggregatorHTTPClient :: new ( ) ;
476
- http_client. expect_probe ( ) . returning ( |_| Ok ( ( ) ) ) ;
477
- http_client
478
- . expect_download ( )
479
- . returning ( move |_, _, _| Ok ( ( ) ) )
480
- . times ( 1 ) ;
481
- http_client. expect_get_content ( ) . returning ( |_| {
482
- let mut message = CertificateMessage :: dummy ( ) ;
483
- message. signed_message = message. protocol_message . compute_hash ( ) ;
484
- let message = serde_json:: to_string ( & message) . unwrap ( ) ;
485
528
486
- Ok ( message)
487
- } ) ;
529
+ let ( http_client, certificate_verifier, digester) =
530
+ get_mocks_for_snapshot_service_configured_to_make_download_succeed ( ) ;
531
+
488
532
let mut builder = get_dep_builder ( Arc :: new ( http_client) ) ;
489
- let mut certificate_verifier = MockCertificateVerifierImpl :: new ( ) ;
490
- certificate_verifier
491
- . expect_verify_certificate_chain ( )
492
- . returning ( |_, _, _| Ok ( ( ) ) )
493
- . times ( 1 ) ;
494
533
builder. certificate_verifier = Some ( Arc :: new ( certificate_verifier) ) ;
495
- builder. immutable_digester = Some ( Arc :: new ( DumbImmutableDigester :: new (
496
- "snapshot-digest-123" ,
497
- true ,
498
- ) ) ) ;
499
- let snapshot = FromSnapshotMessageAdapter :: adapt ( get_snapshot_message ( ) ) ;
534
+ builder. immutable_digester = Some ( Arc :: new ( digester) ) ;
500
535
let snapshot_service = builder. get_snapshot_service ( ) . await . unwrap ( ) ;
501
536
537
+ let snapshot = FromSnapshotMessageAdapter :: adapt ( get_snapshot_message ( ) ) ;
538
+ build_dummy_snapshot (
539
+ "digest-10.tar.gz" ,
540
+ "1234567890" . repeat ( 124 ) . as_str ( ) ,
541
+ & test_path,
542
+ ) ;
543
+
502
544
let ( _, verifier) = setup_genesis ( ) ;
503
545
let genesis_verification_key = verifier. to_verification_key ( ) ;
546
+
547
+ let filepath = snapshot_service
548
+ . download (
549
+ & snapshot,
550
+ & test_path,
551
+ & key_encode_hex ( genesis_verification_key) . unwrap ( ) ,
552
+ ProgressDrawTarget :: hidden ( ) ,
553
+ )
554
+ . await
555
+ . expect ( "Snapshot download should succeed." ) ;
556
+ assert ! (
557
+ filepath. is_dir( ) ,
558
+ "Unpacked location must be in a directory."
559
+ ) ;
560
+ assert_eq ! ( Some ( OsStr :: new( "db" ) ) , filepath. file_name( ) ) ;
561
+ }
562
+
563
+ #[ tokio:: test]
564
+ async fn test_download_snapshot_ok_add_clean_file_allowing_node_bootstrap_speedup ( ) {
565
+ let test_path = std:: env:: temp_dir ( )
566
+ . join ( "test_download_snapshot_ok_add_clean_file_allowing_node_bootstrap_speedup" ) ;
567
+ let _ = std:: fs:: remove_dir_all ( & test_path) ;
568
+
569
+ let ( http_client, certificate_verifier, digester) =
570
+ get_mocks_for_snapshot_service_configured_to_make_download_succeed ( ) ;
571
+
572
+ let mut builder = get_dep_builder ( Arc :: new ( http_client) ) ;
573
+ builder. certificate_verifier = Some ( Arc :: new ( certificate_verifier) ) ;
574
+ builder. immutable_digester = Some ( Arc :: new ( digester) ) ;
575
+ let snapshot_service = builder. get_snapshot_service ( ) . await . unwrap ( ) ;
576
+
577
+ let snapshot = FromSnapshotMessageAdapter :: adapt ( get_snapshot_message ( ) ) ;
504
578
build_dummy_snapshot (
505
579
"digest-10.tar.gz" ,
506
580
"1234567890" . repeat ( 124 ) . as_str ( ) ,
507
581
& test_path,
508
582
) ;
583
+
584
+ let ( _, verifier) = setup_genesis ( ) ;
585
+ let genesis_verification_key = verifier. to_verification_key ( ) ;
586
+
509
587
let filepath = snapshot_service
510
588
. download (
511
589
& snapshot,
@@ -515,42 +593,32 @@ mod tests {
515
593
)
516
594
. await
517
595
. expect ( "Snapshot download should succeed." ) ;
518
- assert ! ( filepath. exists( ) ) ;
519
- let unpack_dir = filepath
520
- . parent ( )
521
- . expect ( "Test downloaded file must be in a directory." )
522
- . join ( "db" ) ;
523
- assert ! ( unpack_dir. is_dir( ) ) ;
596
+
597
+ let clean_file = filepath. join ( "clean" ) ;
598
+
599
+ assert ! (
600
+ clean_file. is_file( ) ,
601
+ "'clean' file should exist and be a file"
602
+ ) ;
603
+
604
+ let clean_file_metadata = clean_file. metadata ( ) . unwrap ( ) ;
605
+ assert_eq ! ( clean_file_metadata. len( ) , 0 , "'clean' file should be empty" )
524
606
}
525
607
526
608
#[ tokio:: test]
527
609
async fn test_download_snapshot_invalid_digest ( ) {
528
610
let test_path = std:: env:: temp_dir ( ) . join ( "test_download_snapshot_invalid_digest" ) ;
529
611
let _ = std:: fs:: remove_dir_all ( & test_path) ;
530
- let mut http_client = MockAggregatorHTTPClient :: new ( ) ;
531
- http_client. expect_probe ( ) . returning ( |_| Ok ( ( ) ) ) ;
532
- http_client
533
- . expect_download ( )
534
- . returning ( move |_, _, _| Ok ( ( ) ) )
535
- . times ( 1 ) ;
536
- http_client. expect_get_content ( ) . returning ( |_| {
537
- let mut message = CertificateMessage :: dummy ( ) ;
538
- message. signed_message = message. protocol_message . compute_hash ( ) ;
539
- let message = serde_json:: to_string ( & message) . unwrap ( ) ;
540
612
541
- Ok ( message)
542
- } ) ;
543
- let http_client = Arc :: new ( http_client) ;
544
- let mut dep_builder = get_dep_builder ( http_client) ;
545
- let mut certificate_verifier = MockCertificateVerifierImpl :: new ( ) ;
546
- certificate_verifier
547
- . expect_verify_certificate_chain ( )
548
- . returning ( |_, _, _| Ok ( ( ) ) )
549
- . times ( 1 ) ;
613
+ let ( http_client, certificate_verifier, _) =
614
+ get_mocks_for_snapshot_service_configured_to_make_download_succeed ( ) ;
550
615
let immutable_digester = DumbImmutableDigester :: new ( "snapshot-digest-KO" , true ) ;
616
+
617
+ let mut dep_builder = get_dep_builder ( Arc :: new ( http_client) ) ;
551
618
dep_builder. certificate_verifier = Some ( Arc :: new ( certificate_verifier) ) ;
552
619
dep_builder. immutable_digester = Some ( Arc :: new ( immutable_digester) ) ;
553
620
let snapshot_service = dep_builder. get_snapshot_service ( ) . await . unwrap ( ) ;
621
+
554
622
let mut signed_entity = FromSnapshotMessageAdapter :: adapt ( get_snapshot_message ( ) ) ;
555
623
signed_entity. artifact . digest = "digest-10" . to_string ( ) ;
556
624
@@ -561,6 +629,7 @@ mod tests {
561
629
"1234567890" . repeat ( 124 ) . as_str ( ) ,
562
630
& test_path,
563
631
) ;
632
+
564
633
let err = snapshot_service
565
634
. download (
566
635
& signed_entity,
@@ -600,14 +669,15 @@ mod tests {
600
669
let test_path = std:: env:: temp_dir ( ) . join ( "test_download_snapshot_dir_already_exists" ) ;
601
670
let _ = std:: fs:: remove_dir_all ( & test_path) ;
602
671
create_dir_all ( test_path. join ( "db" ) ) . unwrap ( ) ;
672
+
603
673
let http_client = MockAggregatorHTTPClient :: new ( ) ;
604
- let http_client = Arc :: new ( http_client) ;
605
- let mut dep_builder = get_dep_builder ( http_client) ;
674
+ let mut dep_builder = get_dep_builder ( Arc :: new ( http_client) ) ;
606
675
let snapshot_service = dep_builder. get_snapshot_service ( ) . await . unwrap ( ) ;
607
676
608
677
let ( _, verifier) = setup_genesis ( ) ;
609
678
let genesis_verification_key = verifier. to_verification_key ( ) ;
610
679
let snapshot = FromSnapshotMessageAdapter :: adapt ( get_snapshot_message ( ) ) ;
680
+
611
681
let err = snapshot_service
612
682
. download (
613
683
& snapshot,
0 commit comments