Skip to content

Commit 3e925f5

Browse files
authored
Merge pull request #2502 from input-output-hk/djo/2429/cleanup-unexpected-immutables-after-download
feat: cleanup unexpected files in immutable folder after download
2 parents 0f96330 + 1f4b4d7 commit 3e925f5

File tree

6 files changed

+704
-27
lines changed

6 files changed

+704
-27
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.12.5"
3+
version = "0.12.6"
44
description = "Mithril client library"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-client/src/cardano_database_client/download_unpack/internal_downloader.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use crate::cardano_database_client::ImmutableFileRange;
1515
use crate::feedback::{FeedbackSender, MithrilEvent, MithrilEventCardanoDatabase};
1616
use crate::file_downloader::{DownloadEvent, FileDownloader, FileDownloaderUri};
1717
use crate::utils::{
18-
create_bootstrap_node_files, AncillaryVerifier, VecDequeExtensions,
19-
ANCILLARIES_NOT_SIGNED_BY_MITHRIL,
18+
create_bootstrap_node_files, AncillaryVerifier, UnexpectedDownloadedFileVerifier,
19+
VecDequeExtensions, ANCILLARIES_NOT_SIGNED_BY_MITHRIL,
2020
};
2121
use crate::MithrilResult;
2222

@@ -73,6 +73,16 @@ impl InternalArtifactDownloader {
7373
.verify_compatibility(&immutable_file_number_range, last_immutable_file_number)?;
7474
download_unpack_options.verify_can_write_to_target_directory(target_dir)?;
7575

76+
let expected_files_after_download = UnexpectedDownloadedFileVerifier::new(
77+
target_dir,
78+
&cardano_database_snapshot.network,
79+
download_unpack_options.include_ancillary,
80+
last_immutable_file_number,
81+
&self.logger,
82+
)
83+
.compute_expected_state_after_download()
84+
.await?;
85+
7686
let mut tasks = VecDeque::from(self.build_download_tasks_for_immutables(
7787
&cardano_database_snapshot.immutables,
7888
immutable_file_number_range,
@@ -89,8 +99,15 @@ impl InternalArtifactDownloader {
8999
} else {
90100
slog::warn!(self.logger, "The fast bootstrap of the Cardano node is not available with the current parameters used in this command: the ledger state will be recomputed from genesis at startup of the Cardano node. Set the include_ancillary entry to true in the DownloadUnpackOptions.");
91101
}
92-
self.batch_download_unpack(tasks, download_unpack_options.max_parallel_downloads)
102+
103+
// Return the result later so unexpected file removal is always run
104+
let download_result = self
105+
.batch_download_unpack(tasks, download_unpack_options.max_parallel_downloads)
106+
.await;
107+
expected_files_after_download
108+
.remove_unexpected_files()
93109
.await?;
110+
download_result?;
94111

95112
create_bootstrap_node_files(&self.logger, target_dir, &cardano_database_snapshot.network)?;
96113

@@ -268,11 +285,13 @@ mod tests {
268285
use super::*;
269286

270287
mod download_unpack {
288+
use mithril_common::assert_dir_eq;
271289
use mithril_common::crypto_helper::ManifestSigner;
290+
use mithril_common::digesters::IMMUTABLE_DIR;
272291
use mithril_common::entities::CompressionAlgorithm;
273292
use mithril_common::messages::DigestsMessagePart;
274293

275-
use crate::file_downloader::FakeAncillaryFileBuilder;
294+
use crate::file_downloader::{FakeAncillaryFileBuilder, MockFileDownloader};
276295

277296
use super::*;
278297

@@ -349,7 +368,7 @@ mod tests {
349368
..CardanoDatabaseSnapshot::dummy()
350369
};
351370
let target_dir = temp_dir_create!();
352-
fs::create_dir_all(target_dir.join("immutable")).unwrap();
371+
fs::create_dir_all(target_dir.join(IMMUTABLE_DIR)).unwrap();
353372
let client =
354373
CardanoDatabaseClientDependencyInjector::new().build_cardano_database_client();
355374

@@ -436,6 +455,50 @@ mod tests {
436455
.unwrap();
437456
}
438457

458+
#[tokio::test]
459+
async fn download_unpack_remove_unexpected_downloaded_files() {
460+
let target_dir = temp_dir_create!();
461+
let immutable_dir = target_dir.join(IMMUTABLE_DIR);
462+
463+
let immutable_file_range = ImmutableFileRange::UpTo(1);
464+
let download_unpack_options = DownloadUnpackOptions {
465+
include_ancillary: false,
466+
..DownloadUnpackOptions::default()
467+
};
468+
let cardano_db_snapshot = CardanoDatabaseSnapshot::dummy();
469+
let client = CardanoDatabaseClientDependencyInjector::new()
470+
.with_http_file_downloader(Arc::new({
471+
let mut mock_downloader = MockFileDownloader::new();
472+
mock_downloader
473+
.expect_download_unpack()
474+
.returning(move |_, _, _, _, _| {
475+
// Simulate an additional file written mid-download
476+
if !immutable_dir.exists() {
477+
fs::create_dir(&immutable_dir).unwrap();
478+
}
479+
fs::File::create(immutable_dir.join("unexpected.md")).unwrap();
480+
Ok(())
481+
});
482+
mock_downloader
483+
}))
484+
.build_cardano_database_client();
485+
486+
client
487+
.download_unpack(
488+
&cardano_db_snapshot,
489+
&immutable_file_range,
490+
&target_dir,
491+
download_unpack_options,
492+
)
493+
.await
494+
.unwrap();
495+
496+
assert_dir_eq!(
497+
&target_dir,
498+
format!("* {IMMUTABLE_DIR}/\n* clean\n * protocolMagicId")
499+
);
500+
}
501+
439502
#[tokio::test]
440503
async fn fail_if_include_ancillary_is_true_and_ancillary_verifier_is_not_set() {
441504
let download_unpack_options = DownloadUnpackOptions {

mithril-client/src/snapshot_client.rs

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ use crate::file_downloader::{DownloadEvent, FileDownloader};
120120
#[cfg(feature = "fs")]
121121
use crate::utils::create_bootstrap_node_files;
122122
#[cfg(feature = "fs")]
123-
use crate::utils::AncillaryVerifier;
124-
use crate::utils::ANCILLARIES_NOT_SIGNED_BY_MITHRIL;
123+
use crate::utils::{
124+
AncillaryVerifier, UnexpectedDownloadedFileVerifier, ANCILLARIES_NOT_SIGNED_BY_MITHRIL,
125+
};
125126
use crate::{MithrilResult, Snapshot, SnapshotListItem};
126127

127128
/// Error for the Snapshot client
@@ -225,23 +226,29 @@ impl SnapshotClient {
225226
snapshot: &Snapshot,
226227
target_dir: &Path,
227228
) -> MithrilResult<()> {
228-
use crate::feedback::MithrilEvent;
229229
if self.ancillary_verifier.is_none() {
230230
return Err(SnapshotClientError::MissingAncillaryVerifier.into());
231231
}
232232

233-
let download_id = MithrilEvent::new_snapshot_download_id();
234-
self.download_unpack_immutables_files(snapshot, target_dir, &download_id)
235-
.await?;
236-
self.download_unpack_ancillary(snapshot, target_dir, &download_id)
237-
.await?;
238-
create_bootstrap_node_files(
239-
&self.logger,
233+
let include_ancillary = true;
234+
let expected_files_after_download = UnexpectedDownloadedFileVerifier::new(
240235
target_dir,
241236
&snapshot.network,
242-
)?;
237+
include_ancillary,
238+
snapshot.beacon.immutable_file_number,
239+
&self.logger
240+
)
241+
.compute_expected_state_after_download()
242+
.await?;
243243

244-
Ok(())
244+
// Return the result later so unexpected file removal is always run
245+
let result = self.run_download_unpack(snapshot, target_dir, include_ancillary).await;
246+
247+
expected_files_after_download
248+
.remove_unexpected_files()
249+
.await?;
250+
251+
result
245252
}
246253

247254
/// Download and unpack the given immutable files of the snapshot to the given directory
@@ -260,16 +267,48 @@ impl SnapshotClient {
260267
self.logger,
261268
"The fast bootstrap of the Cardano node is not available with the current parameters used in this command: the ledger state will be recomputed from genesis at startup of the Cardano node. Use the extra function download_unpack_full to allow it."
262269
);
270+
271+
let include_ancillary = false;
272+
let expected_files_after_download = UnexpectedDownloadedFileVerifier::new(
273+
target_dir,
274+
&snapshot.network,
275+
include_ancillary,
276+
snapshot.beacon.immutable_file_number,
277+
&self.logger
278+
)
279+
.compute_expected_state_after_download()
280+
.await?;
281+
282+
// Return the result later so unexpected file removal is always run
283+
let result = self.run_download_unpack(snapshot, target_dir, include_ancillary).await;
284+
285+
expected_files_after_download
286+
.remove_unexpected_files()
287+
.await?;
288+
289+
result
290+
}
291+
292+
async fn run_download_unpack(
293+
&self,
294+
snapshot: &Snapshot,
295+
target_dir: &Path,
296+
include_ancillary: bool,
297+
) -> MithrilResult<()> {
263298
use crate::feedback::MithrilEvent;
299+
264300
let download_id = MithrilEvent::new_snapshot_download_id();
265301
self.download_unpack_immutables_files(snapshot, target_dir, &download_id)
266302
.await?;
303+
if include_ancillary {
304+
self.download_unpack_ancillary(snapshot, target_dir, &download_id)
305+
.await?;
306+
}
267307
create_bootstrap_node_files(
268308
&self.logger,
269309
target_dir,
270310
&snapshot.network,
271311
)?;
272-
273312
Ok(())
274313
}
275314

@@ -439,9 +478,12 @@ mod tests {
439478
test_utils::TestLogger,
440479
};
441480

442-
use super::*;
481+
use mithril_common::{
482+
assert_dir_eq, crypto_helper::ManifestSigner, digesters::IMMUTABLE_DIR, temp_dir_create,
483+
test_utils::fake_keys,
484+
};
443485

444-
use mithril_common::{assert_dir_eq, temp_dir_create};
486+
use super::*;
445487

446488
fn dummy_download_event() -> DownloadEvent {
447489
DownloadEvent::Full {
@@ -622,6 +664,38 @@ mod tests {
622664
"Expected SnapshotClientError::MissingAncillaryVerifier, but got: {error:#?}"
623665
);
624666
}
667+
668+
#[tokio::test]
669+
async fn remove_unexpected_downloaded_files_even_if_failing_to_verify_ancillary() {
670+
let test_dir = temp_dir_create!();
671+
let immutable_dir = test_dir.join(IMMUTABLE_DIR);
672+
std::fs::create_dir(&immutable_dir).unwrap();
673+
let snapshot = Snapshot::dummy();
674+
675+
let mut mock_downloader = MockFileDownloader::new();
676+
mock_downloader
677+
.expect_download_unpack()
678+
.returning(move |_, _, _, _, _| {
679+
// Simulate an additional file written mid-download
680+
std::fs::File::create(immutable_dir.join("unexpected.md")).unwrap();
681+
Ok(())
682+
});
683+
684+
let client = setup_snapshot_client(
685+
Arc::new(mock_downloader),
686+
Some(Arc::new(AncillaryVerifier::new(
687+
fake_keys::manifest_verification_key()[0]
688+
.try_into()
689+
.unwrap(),
690+
))),
691+
);
692+
693+
client
694+
.download_unpack_full(&snapshot, &test_dir)
695+
.await
696+
.unwrap_err();
697+
assert_dir_eq!(&test_dir, format!("* {IMMUTABLE_DIR}/"));
698+
}
625699
}
626700

627701
mod download_unpack {
@@ -651,12 +725,34 @@ mod tests {
651725
"Expected log message not found, logs: {log_inspector}"
652726
);
653727
}
728+
729+
#[tokio::test]
730+
async fn remove_unexpected_downloaded_files() {
731+
let test_dir = temp_dir_create!();
732+
let immutable_dir = test_dir.join(IMMUTABLE_DIR);
733+
std::fs::create_dir(&immutable_dir).unwrap();
734+
let snapshot = Snapshot::dummy();
735+
736+
let mut mock_downloader = MockFileDownloader::new();
737+
mock_downloader
738+
.expect_download_unpack()
739+
.returning(move |_, _, _, _, _| {
740+
// Simulate an additional file written mid-download
741+
std::fs::File::create(immutable_dir.join("unexpected.md")).unwrap();
742+
Ok(())
743+
});
744+
745+
let client = setup_snapshot_client(Arc::new(mock_downloader), None);
746+
747+
client.download_unpack(&snapshot, &test_dir).await.unwrap();
748+
assert_dir_eq!(
749+
&test_dir,
750+
format!("* {IMMUTABLE_DIR}/\n* clean\n * protocolMagicId")
751+
);
752+
}
654753
}
655754

656755
mod download_unpack_ancillary {
657-
use mithril_common::crypto_helper::ManifestSigner;
658-
use mithril_common::test_utils::fake_keys;
659-
660756
use crate::file_downloader::FakeAncillaryFileBuilder;
661757

662758
use super::*;

mithril-client/src/utils/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
//! Utilities module
22
//! This module contains tools needed mostly for the snapshot download and unpack.
33
4-
pub const ANCILLARIES_NOT_SIGNED_BY_MITHRIL:&str = "Ancillary verification does not use the Mithril certification: as a mitigation, IOG owned keys are used to sign these files.";
5-
64
cfg_fs! {
5+
pub const ANCILLARIES_NOT_SIGNED_BY_MITHRIL:&str = "Ancillary verification does not use the Mithril certification: as a mitigation, IOG owned keys are used to sign these files.";
6+
77
mod ancillary_verifier;
88
mod stream_reader;
99
mod bootstrap_files;
10+
mod unexpected_downloaded_file_verifier;
1011

1112
pub use ancillary_verifier::AncillaryVerifier;
13+
pub(crate) use unexpected_downloaded_file_verifier::*;
1214
pub use stream_reader::*;
1315
pub use bootstrap_files::*;
1416
}

0 commit comments

Comments
 (0)