Skip to content

Commit 30375d1

Browse files
committed
feat(client-lib): use unexpected downloaded file verifier in cdb v1/v2 downloads
1 parent 759aaeb commit 30375d1

File tree

2 files changed

+182
-22
lines changed

2 files changed

+182
-22
lines changed

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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +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, ANCILLARIES_NOT_SIGNED_BY_MITHRIL};
123+
use crate::utils::{
124+
AncillaryVerifier, UnexpectedDownloadedFileVerifier, ANCILLARIES_NOT_SIGNED_BY_MITHRIL,
125+
};
124126
use crate::{MithrilResult, Snapshot, SnapshotListItem};
125127

126128
/// Error for the Snapshot client
@@ -224,23 +226,29 @@ impl SnapshotClient {
224226
snapshot: &Snapshot,
225227
target_dir: &Path,
226228
) -> MithrilResult<()> {
227-
use crate::feedback::MithrilEvent;
228229
if self.ancillary_verifier.is_none() {
229230
return Err(SnapshotClientError::MissingAncillaryVerifier.into());
230231
}
231232

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

243-
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
244252
}
245253

246254
/// Download and unpack the given immutable files of the snapshot to the given directory
@@ -259,16 +267,48 @@ impl SnapshotClient {
259267
self.logger,
260268
"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."
261269
);
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<()> {
262298
use crate::feedback::MithrilEvent;
299+
263300
let download_id = MithrilEvent::new_snapshot_download_id();
264301
self.download_unpack_immutables_files(snapshot, target_dir, &download_id)
265302
.await?;
303+
if include_ancillary {
304+
self.download_unpack_ancillary(snapshot, target_dir, &download_id)
305+
.await?;
306+
}
266307
create_bootstrap_node_files(
267308
&self.logger,
268309
target_dir,
269310
&snapshot.network,
270311
)?;
271-
272312
Ok(())
273313
}
274314

@@ -438,9 +478,12 @@ mod tests {
438478
test_utils::TestLogger,
439479
};
440480

441-
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+
};
442485

443-
use mithril_common::{assert_dir_eq, temp_dir_create};
486+
use super::*;
444487

445488
fn dummy_download_event() -> DownloadEvent {
446489
DownloadEvent::Full {
@@ -621,6 +664,38 @@ mod tests {
621664
"Expected SnapshotClientError::MissingAncillaryVerifier, but got: {error:#?}"
622665
);
623666
}
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+
}
624699
}
625700

626701
mod download_unpack {
@@ -650,12 +725,34 @@ mod tests {
650725
"Expected log message not found, logs: {log_inspector}"
651726
);
652727
}
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+
}
653753
}
654754

655755
mod download_unpack_ancillary {
656-
use mithril_common::crypto_helper::ManifestSigner;
657-
use mithril_common::test_utils::fake_keys;
658-
659756
use crate::file_downloader::FakeAncillaryFileBuilder;
660757

661758
use super::*;

0 commit comments

Comments
 (0)