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 } ;
@@ -22,6 +23,14 @@ const PRERELEASE_DISTRIBUTION_TAG: &str = "prerelease";
2223
2324const CARDANO_DISTRIBUTION_TEMP_DIR : & str = "cardano-node-distribution-tmp" ;
2425
26+ const SNAPSHOT_CONVERTER_BIN_DIR : & str = "bin" ;
27+ const SNAPSHOT_CONVERTER_BIN_NAME_UNIX : & str = "snapshot-converter" ;
28+ const SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS : & str = "snapshot-converter.exe" ;
29+ const SNAPSHOT_CONVERTER_CONFIG_DIR : & str = "share" ;
30+ const SNAPSHOT_CONVERTER_CONFIG_FILE : & str = "config.json" ;
31+
32+ const LEDGER_DIR : & str = "ledger" ;
33+
2534#[ derive( Debug , Clone , ValueEnum ) ]
2635enum UTxOHDFlavor {
2736 #[ clap( name = "Legacy" ) ]
@@ -39,6 +48,23 @@ impl fmt::Display for UTxOHDFlavor {
3948 }
4049}
4150
51+ #[ derive( Debug , Clone , ValueEnum ) ]
52+ enum CardanoNetwork {
53+ Preview ,
54+ Preprod ,
55+ Mainnet ,
56+ }
57+
58+ impl fmt:: Display for CardanoNetwork {
59+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
60+ match self {
61+ Self :: Preview => write ! ( f, "preview" ) ,
62+ Self :: Preprod => write ! ( f, "preprod" ) ,
63+ Self :: Mainnet => write ! ( f, "mainnet" ) ,
64+ }
65+ }
66+ }
67+
4268/// Clap command to convert a restored `InMemory` Mithril snapshot to another flavor.
4369#[ derive( Parser , Debug , Clone ) ]
4470pub struct SnapshotConverterCommand {
@@ -52,6 +78,10 @@ pub struct SnapshotConverterCommand {
5278 #[ clap( long) ]
5379 cardano_node_version : String ,
5480
81+ /// Cardano network.
82+ #[ clap( long) ]
83+ cardano_network : CardanoNetwork ,
84+
5585 /// UTxO-HD flavor to convert the ledger snapshot to.
5686 #[ clap( long) ]
5787 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
@@ -128,6 +171,120 @@ impl SnapshotConverterCommand {
128171
129172 Ok ( archive_path)
130173 }
174+
175+ // 1. Find the `snapshot-converter` binary
176+ // 2. Find the configuration file
177+ // 3. Find the less recent ledger snapshot in the db directory
178+ // 4. Copy the ledger snapshot to the distribution directory (backup)
179+ // 5. Run the `snapshot-converter` command with the appropriate arguments
180+ // - Legacy: snapshot-converter Mem <PATH-IN> Legacy <PATH-OUT> cardano --config <CONFIG>
181+ // - LMDB: snapshot-converter Mem <PATH-IN> LMDB <PATH-OUT> cardano --config <CONFIG>
182+ fn convert_ledger_snapshot (
183+ db_dir : & Path ,
184+ cardano_network : & CardanoNetwork ,
185+ distribution_dir : & Path ,
186+ utxo_hd_flavor : & UTxOHDFlavor ,
187+ ) -> MithrilResult < ( ) > {
188+ let snapshot_converter_bin_path =
189+ Self :: get_snapshot_converter_binary_path ( distribution_dir, env:: consts:: OS ) ?;
190+ // TODO: check if this configuration file is enough to convert the snapshot.
191+ let config_path =
192+ Self :: get_snapshot_converter_config_path ( distribution_dir, cardano_network) ;
193+
194+ let ( slot_number, ledger_snapshot_path) = Self :: find_less_recent_ledger_snapshot ( db_dir) ?;
195+ let snapshot_backup_path = distribution_dir. join ( ledger_snapshot_path. file_name ( ) . unwrap ( ) ) ;
196+ std:: fs:: copy ( ledger_snapshot_path. clone ( ) , snapshot_backup_path. clone ( ) ) ?;
197+
198+ let snapshot_converted_output_path = distribution_dir
199+ . join ( slot_number. to_string ( ) )
200+ . join ( utxo_hd_flavor. to_string ( ) . to_lowercase ( ) ) ;
201+
202+ // TODO: verify if the paths are correct, the command needs relative path.
203+ Command :: new ( snapshot_converter_bin_path. clone ( ) )
204+ . arg ( "Mem" )
205+ . arg ( snapshot_backup_path)
206+ . arg ( utxo_hd_flavor. to_string ( ) )
207+ . arg ( snapshot_converted_output_path)
208+ . arg ( "cardano" )
209+ . arg ( "--config" )
210+ . arg ( config_path)
211+ . status ( )
212+ . with_context ( || {
213+ format ! (
214+ "Failed to get help of snapshot-converter: {}" ,
215+ snapshot_converter_bin_path. display( )
216+ )
217+ } ) ?;
218+
219+ println ! (
220+ "Snapshot converter executed successfully. The ledger snapshot has been converted to {} flavor in {}." ,
221+ utxo_hd_flavor,
222+ distribution_dir. display( )
223+ ) ;
224+ Ok ( ( ) )
225+ }
226+
227+ fn get_snapshot_converter_binary_path (
228+ distribution_dir : & Path ,
229+ target_os : & str ,
230+ ) -> MithrilResult < PathBuf > {
231+ let base_path = distribution_dir. join ( SNAPSHOT_CONVERTER_BIN_DIR ) ;
232+
233+ let binary_name = match target_os {
234+ "linux" | "macos" => SNAPSHOT_CONVERTER_BIN_NAME_UNIX ,
235+ "windows" => SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS ,
236+ _ => return Err ( anyhow ! ( "Unsupported platform: {}" , target_os) ) ,
237+ } ;
238+
239+ Ok ( base_path. join ( binary_name) )
240+ }
241+
242+ fn get_snapshot_converter_config_path (
243+ distribution_dir : & Path ,
244+ network : & CardanoNetwork ,
245+ ) -> PathBuf {
246+ distribution_dir
247+ . join ( SNAPSHOT_CONVERTER_CONFIG_DIR )
248+ . join ( network. to_string ( ) )
249+ . join ( SNAPSHOT_CONVERTER_CONFIG_FILE )
250+ }
251+
252+ // TODO: quick dirty code to go further in `convert_ledger_snapshot` function, must be enhanced and tested.
253+ fn find_less_recent_ledger_snapshot ( db_dir : & Path ) -> MithrilResult < ( u64 , PathBuf ) > {
254+ let ledger_dir = db_dir. join ( LEDGER_DIR ) ;
255+
256+ let entries = std:: fs:: read_dir ( & ledger_dir) . with_context ( || {
257+ format ! ( "Failed to read ledger directory: {}" , ledger_dir. display( ) )
258+ } ) ?;
259+
260+ let mut min_slot: Option < ( u64 , PathBuf ) > = None ;
261+
262+ for entry in entries {
263+ let entry = entry?;
264+ let file_name = entry. file_name ( ) ;
265+ let file_name_str = file_name. to_str ( ) . unwrap ( ) ;
266+
267+ let slot = match file_name_str. parse :: < u64 > ( ) {
268+ Ok ( n) => n,
269+ Err ( _) => continue ,
270+ } ;
271+
272+ let path = entry. path ( ) ;
273+ if path. is_dir ( ) {
274+ match & min_slot {
275+ Some ( ( current_min, _) ) if * current_min <= slot => { }
276+ _ => min_slot = Some ( ( slot, path) ) ,
277+ }
278+ }
279+ }
280+
281+ min_slot. ok_or_else ( || {
282+ anyhow ! (
283+ "No valid ledger snapshot found in: {}" ,
284+ ledger_dir. display( )
285+ )
286+ } )
287+ }
131288}
132289
133290#[ cfg( test) ]
@@ -244,4 +401,115 @@ mod tests {
244401 . await
245402 . unwrap ( ) ;
246403 }
404+
405+ #[ test]
406+ fn get_snapshot_converter_binary_path_linux ( ) {
407+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
408+
409+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
410+ & distribution_dir,
411+ "linux" ,
412+ )
413+ . unwrap ( ) ;
414+
415+ assert_eq ! (
416+ binary_path,
417+ distribution_dir
418+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
419+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
420+ ) ;
421+ }
422+
423+ #[ test]
424+ fn get_snapshot_converter_binary_path_macos ( ) {
425+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
426+
427+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
428+ & distribution_dir,
429+ "macos" ,
430+ )
431+ . unwrap ( ) ;
432+
433+ assert_eq ! (
434+ binary_path,
435+ distribution_dir
436+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
437+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
438+ ) ;
439+ }
440+
441+ #[ test]
442+ fn get_snapshot_converter_binary_path_windows ( ) {
443+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
444+
445+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
446+ & distribution_dir,
447+ "windows" ,
448+ )
449+ . unwrap ( ) ;
450+
451+ assert_eq ! (
452+ binary_path,
453+ distribution_dir
454+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
455+ . join( SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS )
456+ ) ;
457+ }
458+
459+ #[ test]
460+ fn get_snapshot_converter_config_path_mainnet ( ) {
461+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
462+ let network = CardanoNetwork :: Mainnet ;
463+
464+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
465+ & distribution_dir,
466+ & network,
467+ ) ;
468+
469+ assert_eq ! (
470+ config_path,
471+ distribution_dir
472+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
473+ . join( network. to_string( ) )
474+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
475+ ) ;
476+ }
477+
478+ #[ test]
479+ fn get_snapshot_converter_config_path_preprod ( ) {
480+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
481+ let network = CardanoNetwork :: Preprod ;
482+
483+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
484+ & distribution_dir,
485+ & network,
486+ ) ;
487+
488+ assert_eq ! (
489+ config_path,
490+ distribution_dir
491+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
492+ . join( network. to_string( ) )
493+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
494+ ) ;
495+ }
496+
497+ #[ test]
498+ fn get_snapshot_converter_config_path_preview ( ) {
499+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
500+ let network = CardanoNetwork :: Preview ;
501+
502+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
503+ & distribution_dir,
504+ & network,
505+ ) ;
506+
507+ assert_eq ! (
508+ config_path,
509+ distribution_dir
510+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
511+ . join( network. to_string( ) )
512+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
513+ ) ;
514+ }
247515}
0 commit comments