1
1
use std:: {
2
2
fmt:: Write ,
3
- fs:: { remove_file, File } ,
4
3
path:: { Path , PathBuf } ,
5
4
sync:: Arc ,
6
5
time:: Duration ,
7
6
} ;
8
7
9
8
use async_trait:: async_trait;
10
- use flate2:: read:: GzDecoder ;
11
- use human_bytes:: human_bytes;
9
+ use futures:: Future ;
12
10
use indicatif:: { MultiProgress , ProgressBar , ProgressDrawTarget , ProgressState , ProgressStyle } ;
13
11
use mithril_common:: {
14
12
certificate_chain:: CertificateVerifier ,
15
13
crypto_helper:: { key_decode_hex, ProtocolGenesisVerifier } ,
16
14
digesters:: ImmutableDigester ,
17
- entities:: { ProtocolMessagePartKey , Snapshot } ,
15
+ entities:: { Certificate , ProtocolMessagePartKey , Snapshot } ,
18
16
StdError , StdResult ,
19
17
} ;
20
- use slog_scope:: { debug, info, warn} ;
21
- use tar:: Archive ;
18
+ use slog_scope:: { debug, warn} ;
22
19
use thiserror:: Error ;
23
20
use tokio:: { select, time:: sleep} ;
24
21
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
+ } ;
32
26
33
27
/// [SnapshotService] related errors.
34
28
#[ derive( Error , Debug ) ]
@@ -61,12 +55,6 @@ pub enum SnapshotServiceError {
61
55
/// Eventual nested error
62
56
error : StdError ,
63
57
} ,
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 ) ,
70
58
}
71
59
72
60
/// ## SnapshotService
@@ -122,14 +110,63 @@ impl MithrilClientSnapshotService {
122
110
}
123
111
}
124
112
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 ?;
130
148
131
149
Ok ( ( ) )
132
150
}
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
+ }
133
170
}
134
171
135
172
#[ async_trait]
@@ -167,44 +204,17 @@ impl SnapshotService for MithrilClientSnapshotService {
167
204
progress_target : ProgressDrawTarget ,
168
205
) -> StdResult < PathBuf > {
169
206
debug ! ( "Snapshot service: download." ) ;
170
- // 0 - Check prerequisistes are met
207
+
171
208
let unpack_dir = pathdir. join ( "db" ) ;
172
209
let progress_bar = MultiProgress :: with_draw_target ( progress_target) ;
173
210
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 ( ) ;
179
212
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) ?;
195
215
}
196
216
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…" ) ?;
208
218
let certificate = self
209
219
. certificate_client
210
220
. get ( & snapshot. certificate_hash )
@@ -213,27 +223,10 @@ impl SnapshotService for MithrilClientSnapshotService {
213
223
SnapshotServiceError :: CouldNotFindCertificate ( snapshot. certificate_hash . clone ( ) )
214
224
} ) ?;
215
225
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 ?;
235
229
236
- // 4 - Launch download and unpack the file on disk
237
230
progress_bar. println ( "4/7 - Downloading the snapshot…" ) ?;
238
231
let pb = progress_bar. add ( ProgressBar :: new ( snapshot. size ) ) ;
239
232
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 {
247
240
. map_err ( |e| format ! ( "Could not download file in '{}': {e}" , pathdir. display( ) ) ) ?;
248
241
249
242
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…" ) ?;
272
247
let unpacked_snapshot_digest = self
273
248
. immutable_digester
274
249
. compute_digest ( & unpack_dir, & certificate. beacon )
@@ -280,8 +255,7 @@ impl SnapshotService for MithrilClientSnapshotService {
280
255
)
281
256
} ) ?;
282
257
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…" ) ?;
285
259
let mut protocol_message = certificate. protocol_message . clone ( ) ;
286
260
protocol_message. set_message_part (
287
261
ProtocolMessagePartKey :: SnapshotDigest ,
@@ -318,7 +292,10 @@ mod tests {
318
292
} ,
319
293
test_utils:: fake_data,
320
294
} ;
321
- use std:: { fs:: create_dir_all, io:: Write } ;
295
+ use std:: {
296
+ fs:: { create_dir_all, File } ,
297
+ io:: Write ,
298
+ } ;
322
299
323
300
use crate :: {
324
301
aggregator_client:: { AggregatorClient , MockAggregatorHTTPClient } ,
@@ -628,9 +605,9 @@ mod tests {
628
605
. await
629
606
. expect_err ( "Snapshot download should fail." ) ;
630
607
631
- if let Some ( e) = err. downcast_ref :: < SnapshotServiceError > ( ) {
608
+ if let Some ( e) = err. downcast_ref :: < SnapshotUnpackerError > ( ) {
632
609
match e {
633
- SnapshotServiceError :: UnpackDirectoryAlreadyExists ( path) => {
610
+ SnapshotUnpackerError :: UnpackDirectoryAlreadyExists ( path) => {
634
611
assert_eq ! ( & test_path. join( "db" ) , path) ;
635
612
}
636
613
_ => panic ! ( "Wrong error type when unpack dir already exists." ) ,
0 commit comments