22// - Add logs.
33// - Remove the temporary directory in all cases (error or success).
44use std:: path:: Path ;
5+ use std:: process:: Command ;
56use std:: { env, fmt, path:: PathBuf } ;
67
78use anyhow:: { anyhow, Context } ;
@@ -23,6 +24,14 @@ const PRERELEASE_DISTRIBUTION_TAG: &str = "prerelease";
2324
2425const CARDANO_DISTRIBUTION_TEMP_DIR : & str = "cardano-node-distribution-tmp" ;
2526
27+ const SNAPSHOT_CONVERTER_BIN_DIR : & str = "bin" ;
28+ const SNAPSHOT_CONVERTER_BIN_NAME_UNIX : & str = "snapshot-converter" ;
29+ const SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS : & str = "snapshot-converter.exe" ;
30+ const SNAPSHOT_CONVERTER_CONFIG_DIR : & str = "share" ;
31+ const SNAPSHOT_CONVERTER_CONFIG_FILE : & str = "config.json" ;
32+
33+ const LEDGER_DIR : & str = "ledger" ;
34+
2635#[ derive( Debug , Clone , ValueEnum ) ]
2736enum UTxOHDFlavor {
2837 #[ clap( name = "Legacy" ) ]
@@ -40,6 +49,23 @@ impl fmt::Display for UTxOHDFlavor {
4049 }
4150}
4251
52+ #[ derive( Debug , Clone , ValueEnum ) ]
53+ enum CardanoNetwork {
54+ Preview ,
55+ Preprod ,
56+ Mainnet ,
57+ }
58+
59+ impl fmt:: Display for CardanoNetwork {
60+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
61+ match self {
62+ Self :: Preview => write ! ( f, "preview" ) ,
63+ Self :: Preprod => write ! ( f, "preprod" ) ,
64+ Self :: Mainnet => write ! ( f, "mainnet" ) ,
65+ }
66+ }
67+ }
68+
4369/// Clap command to convert a restored `InMemory` Mithril snapshot to another flavor.
4470#[ derive( Parser , Debug , Clone ) ]
4571pub struct SnapshotConverterCommand {
@@ -53,6 +79,10 @@ pub struct SnapshotConverterCommand {
5379 #[ clap( long) ]
5480 cardano_node_version : String ,
5581
82+ /// Cardano network.
83+ #[ clap( long) ]
84+ cardano_network : CardanoNetwork ,
85+
5686 /// UTxO-HD flavor to convert the ledger snapshot to.
5787 #[ clap( long) ]
5888 utxo_hd_flavor : UTxOHDFlavor ,
@@ -88,6 +118,19 @@ impl SnapshotConverterCommand {
88118 )
89119 } ) ?;
90120
121+ Self :: convert_ledger_snapshot (
122+ & self . db_directory ,
123+ & self . cardano_network ,
124+ & distribution_temp_dir,
125+ & self . utxo_hd_flavor ,
126+ )
127+ . with_context ( || {
128+ format ! (
129+ "Failed to convert ledger snapshot to flavor: {}" ,
130+ self . utxo_hd_flavor
131+ )
132+ } ) ?;
133+
91134 Ok ( ( ) )
92135 }
93136
@@ -141,6 +184,120 @@ impl SnapshotConverterCommand {
141184
142185 Ok ( ( ) )
143186 }
187+
188+ // 1. Find the `snapshot-converter` binary
189+ // 2. Find the configuration file
190+ // 3. Find the less recent ledger snapshot in the db directory
191+ // 4. Copy the ledger snapshot to the distribution directory (backup)
192+ // 5. Run the `snapshot-converter` command with the appropriate arguments
193+ // - Legacy: snapshot-converter Mem <PATH-IN> Legacy <PATH-OUT> cardano --config <CONFIG>
194+ // - LMDB: snapshot-converter Mem <PATH-IN> LMDB <PATH-OUT> cardano --config <CONFIG>
195+ fn convert_ledger_snapshot (
196+ db_dir : & Path ,
197+ cardano_network : & CardanoNetwork ,
198+ distribution_dir : & Path ,
199+ utxo_hd_flavor : & UTxOHDFlavor ,
200+ ) -> MithrilResult < ( ) > {
201+ let snapshot_converter_bin_path =
202+ Self :: get_snapshot_converter_binary_path ( distribution_dir, env:: consts:: OS ) ?;
203+ // TODO: check if this configuration file is enough to convert the snapshot.
204+ let config_path =
205+ Self :: get_snapshot_converter_config_path ( distribution_dir, cardano_network) ;
206+
207+ let ( slot_number, ledger_snapshot_path) = Self :: find_less_recent_ledger_snapshot ( db_dir) ?;
208+ let snapshot_backup_path = distribution_dir. join ( ledger_snapshot_path. file_name ( ) . unwrap ( ) ) ;
209+ std:: fs:: copy ( ledger_snapshot_path. clone ( ) , snapshot_backup_path. clone ( ) ) ?;
210+
211+ let snapshot_converted_output_path = distribution_dir
212+ . join ( slot_number. to_string ( ) )
213+ . join ( utxo_hd_flavor. to_string ( ) . to_lowercase ( ) ) ;
214+
215+ // TODO: verify if the paths are correct, the command needs relative path.
216+ Command :: new ( snapshot_converter_bin_path. clone ( ) )
217+ . arg ( "Mem" )
218+ . arg ( snapshot_backup_path)
219+ . arg ( utxo_hd_flavor. to_string ( ) )
220+ . arg ( snapshot_converted_output_path)
221+ . arg ( "cardano" )
222+ . arg ( "--config" )
223+ . arg ( config_path)
224+ . status ( )
225+ . with_context ( || {
226+ format ! (
227+ "Failed to get help of snapshot-converter: {}" ,
228+ snapshot_converter_bin_path. display( )
229+ )
230+ } ) ?;
231+
232+ println ! (
233+ "Snapshot converter executed successfully. The ledger snapshot has been converted to {} flavor in {}." ,
234+ utxo_hd_flavor,
235+ distribution_dir. display( )
236+ ) ;
237+ Ok ( ( ) )
238+ }
239+
240+ fn get_snapshot_converter_binary_path (
241+ distribution_dir : & Path ,
242+ target_os : & str ,
243+ ) -> MithrilResult < PathBuf > {
244+ let base_path = distribution_dir. join ( SNAPSHOT_CONVERTER_BIN_DIR ) ;
245+
246+ let binary_name = match target_os {
247+ "linux" | "macos" => SNAPSHOT_CONVERTER_BIN_NAME_UNIX ,
248+ "windows" => SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS ,
249+ _ => return Err ( anyhow ! ( "Unsupported platform: {}" , target_os) ) ,
250+ } ;
251+
252+ Ok ( base_path. join ( binary_name) )
253+ }
254+
255+ fn get_snapshot_converter_config_path (
256+ distribution_dir : & Path ,
257+ network : & CardanoNetwork ,
258+ ) -> PathBuf {
259+ distribution_dir
260+ . join ( SNAPSHOT_CONVERTER_CONFIG_DIR )
261+ . join ( network. to_string ( ) )
262+ . join ( SNAPSHOT_CONVERTER_CONFIG_FILE )
263+ }
264+
265+ // TODO: quick dirty code to go further in `convert_ledger_snapshot` function, must be enhanced and tested.
266+ fn find_less_recent_ledger_snapshot ( db_dir : & Path ) -> MithrilResult < ( u64 , PathBuf ) > {
267+ let ledger_dir = db_dir. join ( LEDGER_DIR ) ;
268+
269+ let entries = std:: fs:: read_dir ( & ledger_dir) . with_context ( || {
270+ format ! ( "Failed to read ledger directory: {}" , ledger_dir. display( ) )
271+ } ) ?;
272+
273+ let mut min_slot: Option < ( u64 , PathBuf ) > = None ;
274+
275+ for entry in entries {
276+ let entry = entry?;
277+ let file_name = entry. file_name ( ) ;
278+ let file_name_str = file_name. to_str ( ) . unwrap ( ) ;
279+
280+ let slot = match file_name_str. parse :: < u64 > ( ) {
281+ Ok ( n) => n,
282+ Err ( _) => continue ,
283+ } ;
284+
285+ let path = entry. path ( ) ;
286+ if path. is_dir ( ) {
287+ match & min_slot {
288+ Some ( ( current_min, _) ) if * current_min <= slot => { }
289+ _ => min_slot = Some ( ( slot, path) ) ,
290+ }
291+ }
292+ }
293+
294+ min_slot. ok_or_else ( || {
295+ anyhow ! (
296+ "No valid ledger snapshot found in: {}" ,
297+ ledger_dir. display( )
298+ )
299+ } )
300+ }
144301}
145302
146303#[ cfg( test) ]
@@ -257,4 +414,115 @@ mod tests {
257414 . await
258415 . unwrap ( ) ;
259416 }
417+
418+ #[ test]
419+ fn get_snapshot_converter_binary_path_linux ( ) {
420+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
421+
422+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
423+ & distribution_dir,
424+ "linux" ,
425+ )
426+ . unwrap ( ) ;
427+
428+ assert_eq ! (
429+ binary_path,
430+ distribution_dir
431+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
432+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
433+ ) ;
434+ }
435+
436+ #[ test]
437+ fn get_snapshot_converter_binary_path_macos ( ) {
438+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
439+
440+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
441+ & distribution_dir,
442+ "macos" ,
443+ )
444+ . unwrap ( ) ;
445+
446+ assert_eq ! (
447+ binary_path,
448+ distribution_dir
449+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
450+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
451+ ) ;
452+ }
453+
454+ #[ test]
455+ fn get_snapshot_converter_binary_path_windows ( ) {
456+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
457+
458+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
459+ & distribution_dir,
460+ "windows" ,
461+ )
462+ . unwrap ( ) ;
463+
464+ assert_eq ! (
465+ binary_path,
466+ distribution_dir
467+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
468+ . join( SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS )
469+ ) ;
470+ }
471+
472+ #[ test]
473+ fn get_snapshot_converter_config_path_mainnet ( ) {
474+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
475+ let network = CardanoNetwork :: Mainnet ;
476+
477+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
478+ & distribution_dir,
479+ & network,
480+ ) ;
481+
482+ assert_eq ! (
483+ config_path,
484+ distribution_dir
485+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
486+ . join( network. to_string( ) )
487+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
488+ ) ;
489+ }
490+
491+ #[ test]
492+ fn get_snapshot_converter_config_path_preprod ( ) {
493+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
494+ let network = CardanoNetwork :: Preprod ;
495+
496+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
497+ & distribution_dir,
498+ & network,
499+ ) ;
500+
501+ assert_eq ! (
502+ config_path,
503+ distribution_dir
504+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
505+ . join( network. to_string( ) )
506+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
507+ ) ;
508+ }
509+
510+ #[ test]
511+ fn get_snapshot_converter_config_path_preview ( ) {
512+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
513+ let network = CardanoNetwork :: Preview ;
514+
515+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
516+ & distribution_dir,
517+ & network,
518+ ) ;
519+
520+ assert_eq ! (
521+ config_path,
522+ distribution_dir
523+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
524+ . join( network. to_string( ) )
525+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
526+ ) ;
527+ }
260528}
0 commit comments