Skip to content

Commit 9a1e80d

Browse files
authored
Merge pull request #1132 from input-output-hk/djo/1131/speedup-node-bootstrap-after-client-download
Speedup node bootstrap after client download
2 parents cd56f53 + a6b0073 commit 9a1e80d

File tree

3 files changed

+151
-81
lines changed

3 files changed

+151
-81
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-client"
3-
version = "0.3.26"
3+
version = "0.3.27"
44
description = "A Mithril Client"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-client/src/services/snapshot.rs

Lines changed: 149 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
fmt::Write,
3+
fs::File,
34
path::{Path, PathBuf},
45
sync::Arc,
56
time::Duration,
@@ -201,18 +202,18 @@ impl SnapshotService for MithrilClientSnapshotService {
201202
async fn download(
202203
&self,
203204
snapshot_entity: &SignedEntity<Snapshot>,
204-
pathdir: &Path,
205+
download_dir: &Path,
205206
genesis_verification_key: &str,
206207
progress_target: ProgressDrawTarget,
207208
) -> StdResult<PathBuf> {
208209
debug!("Snapshot service: download.");
209210

210-
let unpack_dir = pathdir.join("db");
211+
let db_dir = download_dir.join("db");
211212
let progress_bar = MultiProgress::with_draw_target(progress_target);
212213
progress_bar.println("1/7 - Checking local disk info…")?;
213214
let unpacker = SnapshotUnpacker;
214215

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) {
216217
self.check_disk_space_error(e)?;
217218
}
218219

@@ -237,49 +238,61 @@ impl SnapshotService for MithrilClientSnapshotService {
237238
.unwrap()
238239
.with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap())
239240
.progress_chars("#>-"));
240-
let filepath = self
241+
let snapshot_path = self
241242
.snapshot_client
242-
.download(&snapshot_entity.artifact, pathdir, pb)
243+
.download(&snapshot_entity.artifact, download_dir, pb)
243244
.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+
})?;
245251

246252
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);
248254
self.wait_spinner(&progress_bar, unpacker).await?;
249255

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+
250264
progress_bar.println("6/7 - Computing the snapshot digest…")?;
251265
let unpacked_snapshot_digest = self
252266
.immutable_digester
253-
.compute_digest(&unpack_dir, &certificate.beacon)
267+
.compute_digest(&db_dir, &certificate.beacon)
254268
.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()))?;
261270

262271
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 {
269282
debug!("Digest verification failed, removing unpacked files & directory.");
270283

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}.");
273286
}
274287

275288
return Err(SnapshotServiceError::CouldNotVerifySnapshot {
276289
digest: snapshot_entity.artifact.digest.clone(),
277-
path: filepath.canonicalize().unwrap(),
290+
path: snapshot_path.canonicalize().unwrap(),
278291
}
279292
.into());
280293
}
281294

282-
Ok(unpack_dir)
295+
Ok(db_dir)
283296
}
284297
}
285298

@@ -298,34 +311,44 @@ mod tests {
298311
test_utils::fake_data,
299312
};
300313
use std::{
314+
ffi::OsStr,
301315
fs::{create_dir_all, File},
302316
io::Write,
303317
};
304318

305319
use crate::{
306320
aggregator_client::{AggregatorClient, MockAggregatorHTTPClient},
307321
dependencies::DependenciesBuilder,
322+
services::mock::*,
308323
FromSnapshotMessageAdapter,
309324
};
310325

311-
use super::super::mock::*;
312-
313326
use super::*;
314327

315328
/// see [`archive_file_path`] to see where the dummy will be created
316329
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
317342
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();
322343
let archive_file = File::create(archive_file_path).unwrap();
323344
let archive_encoder = GzEncoder::new(&archive_file, Compression::default());
324345
let mut archive_builder = tar::Builder::new(archive_encoder);
325346
archive_builder
326347
.append_dir_all(".", data_file_path.parent().unwrap())
327348
.unwrap();
328349
archive_builder.into_inner().unwrap().finish().unwrap();
350+
351+
// remove the fake file
329352
let _ = std::fs::remove_dir_all(data_file_path.parent().unwrap());
330353
}
331354

@@ -361,6 +384,36 @@ mod tests {
361384
}
362385
}
363386

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+
364417
fn get_dep_builder(http_client: Arc<dyn AggregatorClient>) -> DependenciesBuilder {
365418
let config_builder: ConfigBuilder<DefaultState> = ConfigBuilder::default();
366419
let config = config_builder
@@ -472,40 +525,65 @@ mod tests {
472525
async fn test_download_snapshot_ok() {
473526
let test_path = std::env::temp_dir().join("test_download_snapshot_ok");
474527
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();
485528

486-
Ok(message)
487-
});
529+
let (http_client, certificate_verifier, digester) =
530+
get_mocks_for_snapshot_service_configured_to_make_download_succeed();
531+
488532
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);
494533
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));
500535
let snapshot_service = builder.get_snapshot_service().await.unwrap();
501536

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+
502544
let (_, verifier) = setup_genesis();
503545
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());
504578
build_dummy_snapshot(
505579
"digest-10.tar.gz",
506580
"1234567890".repeat(124).as_str(),
507581
&test_path,
508582
);
583+
584+
let (_, verifier) = setup_genesis();
585+
let genesis_verification_key = verifier.to_verification_key();
586+
509587
let filepath = snapshot_service
510588
.download(
511589
&snapshot,
@@ -515,42 +593,32 @@ mod tests {
515593
)
516594
.await
517595
.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")
524606
}
525607

526608
#[tokio::test]
527609
async fn test_download_snapshot_invalid_digest() {
528610
let test_path = std::env::temp_dir().join("test_download_snapshot_invalid_digest");
529611
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();
540612

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();
550615
let immutable_digester = DumbImmutableDigester::new("snapshot-digest-KO", true);
616+
617+
let mut dep_builder = get_dep_builder(Arc::new(http_client));
551618
dep_builder.certificate_verifier = Some(Arc::new(certificate_verifier));
552619
dep_builder.immutable_digester = Some(Arc::new(immutable_digester));
553620
let snapshot_service = dep_builder.get_snapshot_service().await.unwrap();
621+
554622
let mut signed_entity = FromSnapshotMessageAdapter::adapt(get_snapshot_message());
555623
signed_entity.artifact.digest = "digest-10".to_string();
556624

@@ -561,6 +629,7 @@ mod tests {
561629
"1234567890".repeat(124).as_str(),
562630
&test_path,
563631
);
632+
564633
let err = snapshot_service
565634
.download(
566635
&signed_entity,
@@ -600,14 +669,15 @@ mod tests {
600669
let test_path = std::env::temp_dir().join("test_download_snapshot_dir_already_exists");
601670
let _ = std::fs::remove_dir_all(&test_path);
602671
create_dir_all(test_path.join("db")).unwrap();
672+
603673
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));
606675
let snapshot_service = dep_builder.get_snapshot_service().await.unwrap();
607676

608677
let (_, verifier) = setup_genesis();
609678
let genesis_verification_key = verifier.to_verification_key();
610679
let snapshot = FromSnapshotMessageAdapter::adapt(get_snapshot_message());
680+
611681
let err = snapshot_service
612682
.download(
613683
&snapshot,

0 commit comments

Comments
 (0)