Skip to content

Commit 0e853c6

Browse files
authored
Merge pull request #1012 from input-output-hk/greg/1010/refactor_client_download
refactor client snapshot download
2 parents 52f4df8 + ead06bc commit 0e853c6

File tree

9 files changed

+203
-112
lines changed

9 files changed

+203
-112
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
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.13"
3+
version = "0.3.14"
44
description = "A Mithril Client"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-client/src/commands/snapshot/download.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl SnapshotDownloadCommand {
5454
);
5555
} else {
5656
println!(
57-
r###"Snapshot '{}' has been unpacked and checked against Mithril multi-signature contained in the certificate.
57+
r###"Snapshot '{}' has been unpacked and successfully checked against Mithril multi-signature contained in the certificate.
5858
5959
Files in the directory '{}' can be used to run a Cardano node.
6060

mithril-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod dependencies;
1212
mod entities;
1313
mod message_adapters;
1414
pub mod services;
15+
pub mod utils;
1516

1617
pub use entities::*;
1718
pub use message_adapters::{

mithril-client/src/services/snapshot.rs

Lines changed: 80 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,28 @@
11
use std::{
22
fmt::Write,
3-
fs::{remove_file, File},
43
path::{Path, PathBuf},
54
sync::Arc,
65
time::Duration,
76
};
87

98
use async_trait::async_trait;
10-
use flate2::read::GzDecoder;
11-
use human_bytes::human_bytes;
9+
use futures::Future;
1210
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
1311
use mithril_common::{
1412
certificate_chain::CertificateVerifier,
1513
crypto_helper::{key_decode_hex, ProtocolGenesisVerifier},
1614
digesters::ImmutableDigester,
17-
entities::{ProtocolMessagePartKey, Snapshot},
15+
entities::{Certificate, ProtocolMessagePartKey, Snapshot},
1816
StdError, StdResult,
1917
};
20-
use slog_scope::{debug, info, warn};
21-
use tar::Archive;
18+
use slog_scope::{debug, warn};
2219
use thiserror::Error;
2320
use tokio::{select, time::sleep};
2421

25-
use crate::aggregator_client::{AggregatorHTTPClientError, CertificateClient, SnapshotClient};
26-
27-
/// This ratio will be multiplied by the snapshot size to check if the available
28-
/// disk space is sufficient to store the archive plus the extracted files. If
29-
/// the available space is lower than that, a warning is raised. This ratio has
30-
/// been experimentally established.
31-
const FREE_SPACE_SNAPSHOT_SIZE_RATIO: f64 = 3.5;
22+
use crate::{
23+
aggregator_client::{AggregatorHTTPClientError, CertificateClient, SnapshotClient},
24+
utils::{SnapshotUnpacker, SnapshotUnpackerError},
25+
};
3226

3327
/// [SnapshotService] related errors.
3428
#[derive(Error, Debug)]
@@ -61,12 +55,6 @@ pub enum SnapshotServiceError {
6155
/// Eventual nested error
6256
error: StdError,
6357
},
64-
65-
/// The directory where the files from snapshot are expanded already exists.
66-
/// An error is raised because it lets the user a chance to preserve a
67-
/// previous work.
68-
#[error("Unpack directory '{0}' already exists, please move or delete it.")]
69-
UnpackDirectoryAlreadyExists(PathBuf),
7058
}
7159

7260
/// ## SnapshotService
@@ -122,14 +110,63 @@ impl MithrilClientSnapshotService {
122110
}
123111
}
124112

125-
async fn unpack_snapshot(&self, filepath: &Path, unpack_dir: &Path) -> StdResult<()> {
126-
let snapshot_file_tar_gz = File::open(filepath)?;
127-
let snapshot_file_tar = GzDecoder::new(snapshot_file_tar_gz);
128-
let mut snapshot_archive = Archive::new(snapshot_file_tar);
129-
snapshot_archive.unpack(unpack_dir)?;
113+
fn check_disk_space_error(&self, error: StdError) -> StdResult<()> {
114+
if let Some(SnapshotUnpackerError::NotEnoughSpace {
115+
left_space: _,
116+
pathdir: _,
117+
archive_size: _,
118+
}) = error.downcast_ref::<SnapshotUnpackerError>()
119+
{
120+
warn!("{error}");
121+
122+
Ok(())
123+
} else {
124+
Err(error)
125+
}
126+
}
127+
128+
async fn verify_certificate_chain(
129+
&self,
130+
genesis_verification_key: &str,
131+
certificate: &Certificate,
132+
) -> StdResult<()> {
133+
let genesis_verification_key = key_decode_hex(&genesis_verification_key.to_string())
134+
.map_err(|e| SnapshotServiceError::InvalidParameters {
135+
context: format!("Invalid genesis verification key '{genesis_verification_key}'"),
136+
error: e.into(),
137+
})?;
138+
let genesis_verifier =
139+
ProtocolGenesisVerifier::from_verification_key(genesis_verification_key);
140+
141+
self.certificate_verifier
142+
.verify_certificate_chain(
143+
certificate.clone(),
144+
self.certificate_client.clone(),
145+
&genesis_verifier,
146+
)
147+
.await?;
130148

131149
Ok(())
132150
}
151+
152+
async fn wait_spinner(
153+
&self,
154+
progress_bar: &MultiProgress,
155+
future: impl Future<Output = StdResult<()>>,
156+
) -> StdResult<()> {
157+
let pb = progress_bar.add(ProgressBar::new_spinner());
158+
let spinner = async move {
159+
loop {
160+
pb.tick();
161+
sleep(Duration::from_millis(50)).await;
162+
}
163+
};
164+
165+
select! {
166+
_ = spinner => Ok(()),
167+
res = future => res,
168+
}
169+
}
133170
}
134171

135172
#[async_trait]
@@ -167,44 +204,17 @@ impl SnapshotService for MithrilClientSnapshotService {
167204
progress_target: ProgressDrawTarget,
168205
) -> StdResult<PathBuf> {
169206
debug!("Snapshot service: download.");
170-
// 0 - Check prerequisistes are met
207+
171208
let unpack_dir = pathdir.join("db");
172209
let progress_bar = MultiProgress::with_draw_target(progress_target);
173210
progress_bar.println("1/7 - Checking local disk info…")?;
174-
if unpack_dir.exists() {
175-
return Err(SnapshotServiceError::UnpackDirectoryAlreadyExists(unpack_dir).into());
176-
}
177-
{
178-
let free_space = fs2::available_space(pathdir)? as f64;
211+
let unpacker = SnapshotUnpacker::default();
179212

180-
if free_space < FREE_SPACE_SNAPSHOT_SIZE_RATIO * snapshot.size as f64 {
181-
warn!("There might not be enough space on the disk ({} free) to store and unpack a {} size snapshot.", human_bytes(free_space), human_bytes(snapshot.size as f64));
182-
} else {
183-
info!("It seems there is enough disk space ({} free) to download and unpack the {} large snapshot.", human_bytes(free_space), human_bytes(snapshot.size as f64));
184-
}
185-
186-
let _file =
187-
File::create(&unpack_dir).map_err(|e| SnapshotServiceError::InvalidParameters {
188-
context: format!(
189-
"Can not write in the given directory '{}'.",
190-
pathdir.display()
191-
),
192-
error: e.into(),
193-
})?;
194-
remove_file(&unpack_dir)?;
213+
if let Err(e) = unpacker.check_prerequisites(&unpack_dir, snapshot.size) {
214+
self.check_disk_space_error(e)?;
195215
}
196216

197-
// 1 - Instanciate a genesis key verifier
198-
let genesis_verification_key = key_decode_hex(&genesis_verification_key.to_string())
199-
.map_err(|e| SnapshotServiceError::InvalidParameters {
200-
context: format!("Invalid genesis verification key '{genesis_verification_key}'"),
201-
error: e.into(),
202-
})?;
203-
let genesis_verifier =
204-
ProtocolGenesisVerifier::from_verification_key(genesis_verification_key);
205-
206-
// 2 - Get certificate information
207-
progress_bar.println("2/7 - Fetching certificate information…")?;
217+
progress_bar.println("2/7 - Fetching the certificate's information…")?;
208218
let certificate = self
209219
.certificate_client
210220
.get(&snapshot.certificate_hash)
@@ -213,27 +223,10 @@ impl SnapshotService for MithrilClientSnapshotService {
213223
SnapshotServiceError::CouldNotFindCertificate(snapshot.certificate_hash.clone())
214224
})?;
215225

216-
// 3 - Check the certificate chain
217-
progress_bar.println("3/7 - Check certificate chain…")?;
218-
let pb = progress_bar.add(ProgressBar::new_spinner());
219-
let spinner = async move {
220-
loop {
221-
pb.tick();
222-
sleep(Duration::from_millis(50)).await;
223-
}
224-
};
225-
let verifier = self.certificate_verifier.verify_certificate_chain(
226-
certificate.clone(),
227-
self.certificate_client.clone(),
228-
&genesis_verifier,
229-
);
230-
let res = select! {
231-
_ = spinner => Ok(()),
232-
res = verifier => res,
233-
};
234-
res?;
226+
progress_bar.println("3/7 - Verifying the certificate chain…")?;
227+
let verifier = self.verify_certificate_chain(genesis_verification_key, &certificate);
228+
self.wait_spinner(&progress_bar, verifier).await?;
235229

236-
// 4 - Launch download and unpack the file on disk
237230
progress_bar.println("4/7 - Downloading the snapshot…")?;
238231
let pb = progress_bar.add(ProgressBar::new(snapshot.size));
239232
pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
@@ -247,28 +240,10 @@ impl SnapshotService for MithrilClientSnapshotService {
247240
.map_err(|e| format!("Could not download file in '{}': {e}", pathdir.display()))?;
248241

249242
progress_bar.println("5/7 - Unpacking the snapshot…")?;
250-
{
251-
let pb = progress_bar.add(ProgressBar::new_spinner());
252-
let spinner = async move {
253-
loop {
254-
pb.tick();
255-
sleep(Duration::from_millis(50)).await;
256-
}
257-
};
258-
let unpacker = self.unpack_snapshot(&filepath, &unpack_dir);
259-
let res = select! {
260-
_ = spinner => Ok(()),
261-
res = unpacker => res,
262-
};
263-
res.map_err(|e| {
264-
format!(
265-
"Could not unpack file '{}' in '{}': {e}",
266-
filepath.display(),
267-
unpack_dir.display()
268-
)
269-
})?;
270-
}
271-
progress_bar.println("6/7 - Compute snapshot digest…")?;
243+
let unpacker = unpacker.unpack_snapshot(&filepath, &unpack_dir);
244+
self.wait_spinner(&progress_bar, unpacker).await?;
245+
246+
progress_bar.println("6/7 - Computing the snapshot digest…")?;
272247
let unpacked_snapshot_digest = self
273248
.immutable_digester
274249
.compute_digest(&unpack_dir, &certificate.beacon)
@@ -280,8 +255,7 @@ impl SnapshotService for MithrilClientSnapshotService {
280255
)
281256
})?;
282257

283-
// 5 - Compute protocol message and compare hash sums
284-
progress_bar.println("7/7 - Verifying snapshot signature…")?;
258+
progress_bar.println("7/7 - Verifying the snapshot signature…")?;
285259
let mut protocol_message = certificate.protocol_message.clone();
286260
protocol_message.set_message_part(
287261
ProtocolMessagePartKey::SnapshotDigest,
@@ -318,7 +292,10 @@ mod tests {
318292
},
319293
test_utils::fake_data,
320294
};
321-
use std::{fs::create_dir_all, io::Write};
295+
use std::{
296+
fs::{create_dir_all, File},
297+
io::Write,
298+
};
322299

323300
use crate::{
324301
aggregator_client::{AggregatorClient, MockAggregatorHTTPClient},
@@ -628,9 +605,9 @@ mod tests {
628605
.await
629606
.expect_err("Snapshot download should fail.");
630607

631-
if let Some(e) = err.downcast_ref::<SnapshotServiceError>() {
608+
if let Some(e) = err.downcast_ref::<SnapshotUnpackerError>() {
632609
match e {
633-
SnapshotServiceError::UnpackDirectoryAlreadyExists(path) => {
610+
SnapshotUnpackerError::UnpackDirectoryAlreadyExists(path) => {
634611
assert_eq!(&test_path.join("db"), path);
635612
}
636613
_ => panic!("Wrong error type when unpack dir already exists."),

mithril-client/src/utils/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! Utilities module
2+
//! This module contains tools needed mostly in services layers.
3+
4+
mod unpacker;
5+
6+
pub use unpacker::*;

0 commit comments

Comments
 (0)