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 } ;
@@ -21,6 +22,14 @@ const PRERELEASE_DISTRIBUTION_TAG: &str = "prerelease";
2122
2223const CARDANO_DISTRIBUTION_TEMP_DIR : & str = "cardano-node-distribution-tmp" ;
2324
25+ const SNAPSHOT_CONVERTER_BIN_DIR : & str = "bin" ;
26+ const SNAPSHOT_CONVERTER_BIN_NAME_UNIX : & str = "snapshot-converter" ;
27+ const SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS : & str = "snapshot-converter.exe" ;
28+ const SNAPSHOT_CONVERTER_CONFIG_DIR : & str = "share" ;
29+ const SNAPSHOT_CONVERTER_CONFIG_FILE : & str = "config.json" ;
30+
31+ const LEDGER_DIR : & str = "ledger" ;
32+
2433#[ derive( Debug , Clone , ValueEnum ) ]
2534enum UTxOHDFlavor {
2635 #[ clap( name = "Legacy" ) ]
@@ -38,6 +47,23 @@ impl fmt::Display for UTxOHDFlavor {
3847 }
3948}
4049
50+ #[ derive( Debug , Clone , ValueEnum ) ]
51+ enum CardanoNetwork {
52+ Preview ,
53+ Preprod ,
54+ Mainnet ,
55+ }
56+
57+ impl fmt:: Display for CardanoNetwork {
58+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
59+ match self {
60+ Self :: Preview => write ! ( f, "preview" ) ,
61+ Self :: Preprod => write ! ( f, "preprod" ) ,
62+ Self :: Mainnet => write ! ( f, "mainnet" ) ,
63+ }
64+ }
65+ }
66+
4167/// Clap command to convert a restored `InMemory` Mithril snapshot to another flavor.
4268#[ derive( Parser , Debug , Clone ) ]
4369pub struct SnapshotConverterCommand {
@@ -51,6 +77,10 @@ pub struct SnapshotConverterCommand {
5177 #[ clap( long) ]
5278 cardano_node_version : String ,
5379
80+ /// Cardano network.
81+ #[ clap( long) ]
82+ cardano_network : CardanoNetwork ,
83+
5484 /// UTxO-HD flavor to convert the ledger snapshot to.
5585 #[ clap( long) ]
5686 utxo_hd_flavor : UTxOHDFlavor ,
@@ -86,6 +116,19 @@ impl SnapshotConverterCommand {
86116 )
87117 } ) ?;
88118
119+ Self :: convert_ledger_snapshot (
120+ & self . db_directory ,
121+ & self . cardano_network ,
122+ & distribution_temp_dir,
123+ & self . utxo_hd_flavor ,
124+ )
125+ . with_context ( || {
126+ format ! (
127+ "Failed to convert ledger snapshot to flavor: {}" ,
128+ self . utxo_hd_flavor
129+ )
130+ } ) ?;
131+
89132 Ok ( ( ) )
90133 }
91134
@@ -138,6 +181,120 @@ impl SnapshotConverterCommand {
138181
139182 Ok ( ( ) )
140183 }
184+
185+ // 1. Find the `snapshot-converter` binary
186+ // 2. Find the configuration file
187+ // 3. Find the less recent ledger snapshot in the db directory
188+ // 4. Copy the ledger snapshot to the distribution directory (backup)
189+ // 5. Run the `snapshot-converter` command with the appropriate arguments
190+ // - Legacy: snapshot-converter Mem <PATH-IN> Legacy <PATH-OUT> cardano --config <CONFIG>
191+ // - LMDB: snapshot-converter Mem <PATH-IN> LMDB <PATH-OUT> cardano --config <CONFIG>
192+ fn convert_ledger_snapshot (
193+ db_dir : & Path ,
194+ cardano_network : & CardanoNetwork ,
195+ distribution_dir : & Path ,
196+ utxo_hd_flavor : & UTxOHDFlavor ,
197+ ) -> MithrilResult < ( ) > {
198+ let snapshot_converter_bin_path =
199+ Self :: get_snapshot_converter_binary_path ( distribution_dir, env:: consts:: OS ) ?;
200+ // TODO: check if this configuration file is enough to convert the snapshot.
201+ let config_path =
202+ Self :: get_snapshot_converter_config_path ( distribution_dir, cardano_network) ;
203+
204+ let ( slot_number, ledger_snapshot_path) = Self :: find_less_recent_ledger_snapshot ( db_dir) ?;
205+ let snapshot_backup_path = distribution_dir. join ( ledger_snapshot_path. file_name ( ) . unwrap ( ) ) ;
206+ std:: fs:: copy ( ledger_snapshot_path. clone ( ) , snapshot_backup_path. clone ( ) ) ?;
207+
208+ let snapshot_converted_output_path = distribution_dir
209+ . join ( slot_number. to_string ( ) )
210+ . join ( utxo_hd_flavor. to_string ( ) . to_lowercase ( ) ) ;
211+
212+ // TODO: verify if the paths are correct, the command needs relative path.
213+ Command :: new ( snapshot_converter_bin_path. clone ( ) )
214+ . arg ( "Mem" )
215+ . arg ( snapshot_backup_path)
216+ . arg ( utxo_hd_flavor. to_string ( ) )
217+ . arg ( snapshot_converted_output_path)
218+ . arg ( "cardano" )
219+ . arg ( "--config" )
220+ . arg ( config_path)
221+ . status ( )
222+ . with_context ( || {
223+ format ! (
224+ "Failed to get help of snapshot-converter: {}" ,
225+ snapshot_converter_bin_path. display( )
226+ )
227+ } ) ?;
228+
229+ println ! (
230+ "Snapshot converter executed successfully. The ledger snapshot has been converted to {} flavor in {}." ,
231+ utxo_hd_flavor,
232+ distribution_dir. display( )
233+ ) ;
234+ Ok ( ( ) )
235+ }
236+
237+ fn get_snapshot_converter_binary_path (
238+ distribution_dir : & Path ,
239+ target_os : & str ,
240+ ) -> MithrilResult < PathBuf > {
241+ let base_path = distribution_dir. join ( SNAPSHOT_CONVERTER_BIN_DIR ) ;
242+
243+ let binary_name = match target_os {
244+ "linux" | "macos" => SNAPSHOT_CONVERTER_BIN_NAME_UNIX ,
245+ "windows" => SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS ,
246+ _ => return Err ( anyhow ! ( "Unsupported platform: {}" , target_os) ) ,
247+ } ;
248+
249+ Ok ( base_path. join ( binary_name) )
250+ }
251+
252+ fn get_snapshot_converter_config_path (
253+ distribution_dir : & Path ,
254+ network : & CardanoNetwork ,
255+ ) -> PathBuf {
256+ distribution_dir
257+ . join ( SNAPSHOT_CONVERTER_CONFIG_DIR )
258+ . join ( network. to_string ( ) )
259+ . join ( SNAPSHOT_CONVERTER_CONFIG_FILE )
260+ }
261+
262+ // TODO: quick dirty code to go further in `convert_ledger_snapshot` function, must be enhanced and tested.
263+ fn find_less_recent_ledger_snapshot ( db_dir : & Path ) -> MithrilResult < ( u64 , PathBuf ) > {
264+ let ledger_dir = db_dir. join ( LEDGER_DIR ) ;
265+
266+ let entries = std:: fs:: read_dir ( & ledger_dir) . with_context ( || {
267+ format ! ( "Failed to read ledger directory: {}" , ledger_dir. display( ) )
268+ } ) ?;
269+
270+ let mut min_slot: Option < ( u64 , PathBuf ) > = None ;
271+
272+ for entry in entries {
273+ let entry = entry?;
274+ let file_name = entry. file_name ( ) ;
275+ let file_name_str = file_name. to_str ( ) . unwrap ( ) ;
276+
277+ let slot = match file_name_str. parse :: < u64 > ( ) {
278+ Ok ( n) => n,
279+ Err ( _) => continue ,
280+ } ;
281+
282+ let path = entry. path ( ) ;
283+ if path. is_dir ( ) {
284+ match & min_slot {
285+ Some ( ( current_min, _) ) if * current_min <= slot => { }
286+ _ => min_slot = Some ( ( slot, path) ) ,
287+ }
288+ }
289+ }
290+
291+ min_slot. ok_or_else ( || {
292+ anyhow ! (
293+ "No valid ledger snapshot found in: {}" ,
294+ ledger_dir. display( )
295+ )
296+ } )
297+ }
141298}
142299
143300#[ 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