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 ,
@@ -87,6 +117,19 @@ impl SnapshotConverterCommand {
87117 )
88118 } ) ?;
89119
120+ Self :: convert_ledger_snapshot (
121+ & self . db_directory ,
122+ & self . cardano_network ,
123+ & distribution_temp_dir,
124+ & self . utxo_hd_flavor ,
125+ )
126+ . with_context ( || {
127+ format ! (
128+ "Failed to convert ledger snapshot to flavor: {}" ,
129+ self . utxo_hd_flavor
130+ )
131+ } ) ?;
132+
90133 Ok ( ( ) )
91134 }
92135
@@ -140,6 +183,120 @@ impl SnapshotConverterCommand {
140183
141184 Ok ( ( ) )
142185 }
186+
187+ // 1. Find the `snapshot-converter` binary
188+ // 2. Find the configuration file
189+ // 3. Find the less recent ledger snapshot in the db directory
190+ // 4. Copy the ledger snapshot to the distribution directory (backup)
191+ // 5. Run the `snapshot-converter` command with the appropriate arguments
192+ // - Legacy: snapshot-converter Mem <PATH-IN> Legacy <PATH-OUT> cardano --config <CONFIG>
193+ // - LMDB: snapshot-converter Mem <PATH-IN> LMDB <PATH-OUT> cardano --config <CONFIG>
194+ fn convert_ledger_snapshot (
195+ db_dir : & Path ,
196+ cardano_network : & CardanoNetwork ,
197+ distribution_dir : & Path ,
198+ utxo_hd_flavor : & UTxOHDFlavor ,
199+ ) -> MithrilResult < ( ) > {
200+ let snapshot_converter_bin_path =
201+ Self :: get_snapshot_converter_binary_path ( distribution_dir, env:: consts:: OS ) ?;
202+ // TODO: check if this configuration file is enough to convert the snapshot.
203+ let config_path =
204+ Self :: get_snapshot_converter_config_path ( distribution_dir, cardano_network) ;
205+
206+ let ( slot_number, ledger_snapshot_path) = Self :: find_less_recent_ledger_snapshot ( db_dir) ?;
207+ let snapshot_backup_path = distribution_dir. join ( ledger_snapshot_path. file_name ( ) . unwrap ( ) ) ;
208+ std:: fs:: copy ( ledger_snapshot_path. clone ( ) , snapshot_backup_path. clone ( ) ) ?;
209+
210+ let snapshot_converted_output_path = distribution_dir
211+ . join ( slot_number. to_string ( ) )
212+ . join ( utxo_hd_flavor. to_string ( ) . to_lowercase ( ) ) ;
213+
214+ // TODO: verify if the paths are correct, the command needs relative path.
215+ Command :: new ( snapshot_converter_bin_path. clone ( ) )
216+ . arg ( "Mem" )
217+ . arg ( snapshot_backup_path)
218+ . arg ( utxo_hd_flavor. to_string ( ) )
219+ . arg ( snapshot_converted_output_path)
220+ . arg ( "cardano" )
221+ . arg ( "--config" )
222+ . arg ( config_path)
223+ . status ( )
224+ . with_context ( || {
225+ format ! (
226+ "Failed to get help of snapshot-converter: {}" ,
227+ snapshot_converter_bin_path. display( )
228+ )
229+ } ) ?;
230+
231+ println ! (
232+ "Snapshot converter executed successfully. The ledger snapshot has been converted to {} flavor in {}." ,
233+ utxo_hd_flavor,
234+ distribution_dir. display( )
235+ ) ;
236+ Ok ( ( ) )
237+ }
238+
239+ fn get_snapshot_converter_binary_path (
240+ distribution_dir : & Path ,
241+ target_os : & str ,
242+ ) -> MithrilResult < PathBuf > {
243+ let base_path = distribution_dir. join ( SNAPSHOT_CONVERTER_BIN_DIR ) ;
244+
245+ let binary_name = match target_os {
246+ "linux" | "macos" => SNAPSHOT_CONVERTER_BIN_NAME_UNIX ,
247+ "windows" => SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS ,
248+ _ => return Err ( anyhow ! ( "Unsupported platform: {}" , target_os) ) ,
249+ } ;
250+
251+ Ok ( base_path. join ( binary_name) )
252+ }
253+
254+ fn get_snapshot_converter_config_path (
255+ distribution_dir : & Path ,
256+ network : & CardanoNetwork ,
257+ ) -> PathBuf {
258+ distribution_dir
259+ . join ( SNAPSHOT_CONVERTER_CONFIG_DIR )
260+ . join ( network. to_string ( ) )
261+ . join ( SNAPSHOT_CONVERTER_CONFIG_FILE )
262+ }
263+
264+ // TODO: quick dirty code to go further in `convert_ledger_snapshot` function, must be enhanced and tested.
265+ fn find_less_recent_ledger_snapshot ( db_dir : & Path ) -> MithrilResult < ( u64 , PathBuf ) > {
266+ let ledger_dir = db_dir. join ( LEDGER_DIR ) ;
267+
268+ let entries = std:: fs:: read_dir ( & ledger_dir) . with_context ( || {
269+ format ! ( "Failed to read ledger directory: {}" , ledger_dir. display( ) )
270+ } ) ?;
271+
272+ let mut min_slot: Option < ( u64 , PathBuf ) > = None ;
273+
274+ for entry in entries {
275+ let entry = entry?;
276+ let file_name = entry. file_name ( ) ;
277+ let file_name_str = file_name. to_str ( ) . unwrap ( ) ;
278+
279+ let slot = match file_name_str. parse :: < u64 > ( ) {
280+ Ok ( n) => n,
281+ Err ( _) => continue ,
282+ } ;
283+
284+ let path = entry. path ( ) ;
285+ if path. is_dir ( ) {
286+ match & min_slot {
287+ Some ( ( current_min, _) ) if * current_min <= slot => { }
288+ _ => min_slot = Some ( ( slot, path) ) ,
289+ }
290+ }
291+ }
292+
293+ min_slot. ok_or_else ( || {
294+ anyhow ! (
295+ "No valid ledger snapshot found in: {}" ,
296+ ledger_dir. display( )
297+ )
298+ } )
299+ }
143300}
144301
145302#[ cfg( test) ]
@@ -256,4 +413,115 @@ mod tests {
256413 . await
257414 . unwrap ( ) ;
258415 }
416+
417+ #[ test]
418+ fn get_snapshot_converter_binary_path_linux ( ) {
419+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
420+
421+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
422+ & distribution_dir,
423+ "linux" ,
424+ )
425+ . unwrap ( ) ;
426+
427+ assert_eq ! (
428+ binary_path,
429+ distribution_dir
430+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
431+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
432+ ) ;
433+ }
434+
435+ #[ test]
436+ fn get_snapshot_converter_binary_path_macos ( ) {
437+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
438+
439+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
440+ & distribution_dir,
441+ "macos" ,
442+ )
443+ . unwrap ( ) ;
444+
445+ assert_eq ! (
446+ binary_path,
447+ distribution_dir
448+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
449+ . join( SNAPSHOT_CONVERTER_BIN_NAME_UNIX )
450+ ) ;
451+ }
452+
453+ #[ test]
454+ fn get_snapshot_converter_binary_path_windows ( ) {
455+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
456+
457+ let binary_path = SnapshotConverterCommand :: get_snapshot_converter_binary_path (
458+ & distribution_dir,
459+ "windows" ,
460+ )
461+ . unwrap ( ) ;
462+
463+ assert_eq ! (
464+ binary_path,
465+ distribution_dir
466+ . join( SNAPSHOT_CONVERTER_BIN_DIR )
467+ . join( SNAPSHOT_CONVERTER_BIN_NAME_WINDOWS )
468+ ) ;
469+ }
470+
471+ #[ test]
472+ fn get_snapshot_converter_config_path_mainnet ( ) {
473+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
474+ let network = CardanoNetwork :: Mainnet ;
475+
476+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
477+ & distribution_dir,
478+ & network,
479+ ) ;
480+
481+ assert_eq ! (
482+ config_path,
483+ distribution_dir
484+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
485+ . join( network. to_string( ) )
486+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
487+ ) ;
488+ }
489+
490+ #[ test]
491+ fn get_snapshot_converter_config_path_preprod ( ) {
492+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
493+ let network = CardanoNetwork :: Preprod ;
494+
495+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
496+ & distribution_dir,
497+ & network,
498+ ) ;
499+
500+ assert_eq ! (
501+ config_path,
502+ distribution_dir
503+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
504+ . join( network. to_string( ) )
505+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
506+ ) ;
507+ }
508+
509+ #[ test]
510+ fn get_snapshot_converter_config_path_preview ( ) {
511+ let distribution_dir = PathBuf :: from ( "/path/to/distribution" ) ;
512+ let network = CardanoNetwork :: Preview ;
513+
514+ let config_path = SnapshotConverterCommand :: get_snapshot_converter_config_path (
515+ & distribution_dir,
516+ & network,
517+ ) ;
518+
519+ assert_eq ! (
520+ config_path,
521+ distribution_dir
522+ . join( SNAPSHOT_CONVERTER_CONFIG_DIR )
523+ . join( network. to_string( ) )
524+ . join( SNAPSHOT_CONVERTER_CONFIG_FILE )
525+ ) ;
526+ }
259527}
0 commit comments