@@ -10,7 +10,7 @@ use std::io::Write;
1010use std:: ops:: Deref ;
1111#[ cfg( unix) ]
1212use std:: os:: unix:: fs:: OpenOptionsExt ;
13- use std:: path:: Path ;
13+ use std:: path:: { Path , PathBuf } ;
1414use std:: sync:: { Arc , RwLock } ;
1515
1616use bdk_chain:: indexer:: keychain_txout:: ChangeSet as BdkIndexerChangeSet ;
@@ -26,14 +26,16 @@ use lightning::routing::scoring::{
2626 ChannelLiquidities , ProbabilisticScorer , ProbabilisticScoringDecayParameters ,
2727} ;
2828use lightning:: util:: persist:: {
29- KVStore , KVStoreSync , KVSTORE_NAMESPACE_KEY_ALPHABET , KVSTORE_NAMESPACE_KEY_MAX_LEN ,
30- NETWORK_GRAPH_PERSISTENCE_KEY , NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE ,
31- NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE , OUTPUT_SWEEPER_PERSISTENCE_KEY ,
32- OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE , OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE ,
33- SCORER_PERSISTENCE_KEY , SCORER_PERSISTENCE_PRIMARY_NAMESPACE ,
34- SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
29+ migrate_kv_store_data , KVStore , KVStoreSync , KVSTORE_NAMESPACE_KEY_ALPHABET ,
30+ KVSTORE_NAMESPACE_KEY_MAX_LEN , NETWORK_GRAPH_PERSISTENCE_KEY ,
31+ NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE , NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE ,
32+ OUTPUT_SWEEPER_PERSISTENCE_KEY , OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE ,
33+ OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE , SCORER_PERSISTENCE_KEY ,
34+ SCORER_PERSISTENCE_PRIMARY_NAMESPACE , SCORER_PERSISTENCE_SECONDARY_NAMESPACE ,
3535} ;
3636use lightning:: util:: ser:: { Readable , ReadableArgs , Writeable } ;
37+ use lightning_persister:: fs_store:: v1:: FilesystemStore ;
38+ use lightning_persister:: fs_store:: v2:: { FilesystemStoreV2 , FilesystemStoreV2Error } ;
3739use lightning_types:: string:: PrintableString ;
3840
3941use super :: * ;
@@ -47,7 +49,7 @@ use crate::logger::{log_error, LdkLogger, Logger};
4749use crate :: peer_store:: PeerStore ;
4850use crate :: types:: { Broadcaster , DynStore , KeysManager , Sweeper } ;
4951use crate :: wallet:: ser:: { ChangeSetDeserWrapper , ChangeSetSerWrapper } ;
50- use crate :: { Error , EventQueue , NodeMetrics } ;
52+ use crate :: { BuildError , Error , EventQueue , NodeMetrics } ;
5153
5254pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY : & str = "external_pathfinding_scores_cache" ;
5355
@@ -619,10 +621,103 @@ pub(crate) fn read_bdk_wallet_change_set(
619621 Ok ( Some ( change_set) )
620622}
621623
624+ /// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary.
625+ ///
626+ /// If the directory contains v1 data (files at the top level), the data is migrated to v2 format
627+ /// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated
628+ /// directory is moved into place.
629+ pub ( crate ) fn open_or_migrate_fs_store (
630+ storage_dir_path : PathBuf ,
631+ ) -> Result < FilesystemStoreV2 , BuildError > {
632+ let parent_dir = storage_dir_path. parent ( ) . ok_or ( BuildError :: StoragePathAccessFailed ) ?;
633+ fs:: create_dir_all ( parent_dir) . map_err ( |_| BuildError :: StoragePathAccessFailed ) ?;
634+ recover_incomplete_fs_store_migration ( & storage_dir_path) ?;
635+ if !storage_dir_path. exists ( ) {
636+ fs:: create_dir_all ( storage_dir_path. clone ( ) )
637+ . map_err ( |_| BuildError :: StoragePathAccessFailed ) ?;
638+ }
639+
640+ match FilesystemStoreV2 :: new ( storage_dir_path. clone ( ) ) {
641+ Ok ( store) => Ok ( store) ,
642+ Err ( FilesystemStoreV2Error :: V1DataDetected ( _) ) => {
643+ // The directory contains v1 data, migrate to v2.
644+ let mut v1_store = FilesystemStore :: new ( storage_dir_path. clone ( ) ) ;
645+
646+ let v2_dir = fs_store_sibling_path ( & storage_dir_path, "fs_store_v2_migrating" ) ;
647+ fs:: create_dir_all ( v2_dir. clone ( ) ) . map_err ( |_| BuildError :: StoragePathAccessFailed ) ?;
648+ let mut v2_store = FilesystemStoreV2 :: new ( v2_dir. clone ( ) )
649+ . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
650+
651+ migrate_kv_store_data ( & mut v1_store, & mut v2_store)
652+ . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
653+
654+ // Swap directories: rename v1 out of the way, move v2 into place.
655+ let backup_dir = fs_store_sibling_path ( & storage_dir_path, "fs_store_v1_backup" ) ;
656+ fs:: rename ( & storage_dir_path, & backup_dir)
657+ . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
658+ fs:: rename ( & v2_dir, & storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
659+
660+ FilesystemStoreV2 :: new ( storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed )
661+ } ,
662+ Err ( _) => Err ( BuildError :: KVStoreSetupFailed ) ,
663+ }
664+ }
665+
666+ fn fs_store_sibling_path ( storage_dir_path : & Path , file_name : & str ) -> PathBuf {
667+ let mut sibling_path = storage_dir_path. to_path_buf ( ) ;
668+ sibling_path. set_file_name ( file_name) ;
669+ sibling_path
670+ }
671+
672+ fn recover_incomplete_fs_store_migration ( storage_dir_path : & Path ) -> Result < ( ) , BuildError > {
673+ let v2_dir = fs_store_sibling_path ( storage_dir_path, "fs_store_v2_migrating" ) ;
674+ let backup_dir = fs_store_sibling_path ( storage_dir_path, "fs_store_v1_backup" ) ;
675+
676+ if storage_dir_path. exists ( ) {
677+ if v2_dir. exists ( ) {
678+ // The original store is still in place, so a temp migration dir is from a crash before
679+ // the rename step and can be discarded before retrying migration.
680+ fs:: remove_dir_all ( & v2_dir) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
681+ }
682+ return Ok ( ( ) ) ;
683+ }
684+
685+ if backup_dir. exists ( ) {
686+ if v2_dir. exists ( ) {
687+ // Prefer retrying from the v1 backup instead of deciding here whether the temp v2 dir is
688+ // usable. open_or_migrate_fs_store owns the actual v1-to-v2 migration.
689+ fs:: remove_dir_all ( & v2_dir) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
690+ }
691+ // The crash happened after moving v1 aside; restore it so normal startup can migrate it.
692+ fs:: rename ( & backup_dir, storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
693+ return Ok ( ( ) ) ;
694+ }
695+
696+ if v2_dir. exists ( ) {
697+ // There is no v1 backup to retry from. Move the temp dir into place and let
698+ // open_or_migrate_fs_store decide whether it is a valid v2 store.
699+ fs:: rename ( & v2_dir, storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
700+ }
701+
702+ Ok ( ( ) )
703+ }
704+
622705#[ cfg( test) ]
623706mod tests {
624- use super :: read_or_generate_seed_file;
707+ use std:: fs;
708+ use std:: path:: { Path , PathBuf } ;
709+
710+ use lightning:: util:: persist:: { migrate_kv_store_data, KVStoreSync } ;
711+ use lightning_persister:: fs_store:: v1:: FilesystemStore ;
712+ use lightning_persister:: fs_store:: v2:: FilesystemStoreV2 ;
713+
625714 use super :: test_utils:: random_storage_path;
715+ use super :: { open_or_migrate_fs_store, read_or_generate_seed_file} ;
716+
717+ const TEST_PRIMARY_NAMESPACE : & str = "test_primary_namespace" ;
718+ const TEST_SECONDARY_NAMESPACE : & str = "test_secondary_namespace" ;
719+ const TEST_KEY : & str = "test_key" ;
720+ const TEST_VALUE : & [ u8 ] = b"test_value" ;
626721
627722 #[ test]
628723 fn generated_seed_is_readable ( ) {
@@ -632,4 +727,158 @@ mod tests {
632727 let read_seed_bytes = read_or_generate_seed_file ( & rand_path. to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
633728 assert_eq ! ( expected_seed_bytes, read_seed_bytes) ;
634729 }
730+
731+ #[ test]
732+ fn fs_store_migration_recovers_before_v1_backup_rename ( ) {
733+ let fs_store_path = fs_store_path ( ) ;
734+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
735+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
736+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
737+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
738+
739+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
740+ assert_eq ! (
741+ KVStoreSync :: read(
742+ & migrated_store,
743+ TEST_PRIMARY_NAMESPACE ,
744+ TEST_SECONDARY_NAMESPACE ,
745+ TEST_KEY
746+ )
747+ . unwrap( ) ,
748+ TEST_VALUE
749+ ) ;
750+ assert ! ( fs_store_path. exists( ) ) ;
751+ assert ! ( !v2_migrating_path. exists( ) ) ;
752+ }
753+
754+ #[ test]
755+ fn fs_store_migration_recovers_after_v1_backup_rename ( ) {
756+ let fs_store_path = fs_store_path ( ) ;
757+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
758+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
759+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
760+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
761+
762+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
763+ fs:: rename ( & fs_store_path, backup_path) . unwrap ( ) ;
764+
765+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
766+ assert_eq ! (
767+ KVStoreSync :: read(
768+ & migrated_store,
769+ TEST_PRIMARY_NAMESPACE ,
770+ TEST_SECONDARY_NAMESPACE ,
771+ TEST_KEY
772+ )
773+ . unwrap( ) ,
774+ TEST_VALUE
775+ ) ;
776+ assert ! ( fs_store_path. exists( ) ) ;
777+ assert ! ( !v2_migrating_path. exists( ) ) ;
778+ }
779+
780+ #[ test]
781+ fn fs_store_migration_recovers_after_v2_rename ( ) {
782+ let fs_store_path = fs_store_path ( ) ;
783+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
784+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
785+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
786+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
787+
788+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
789+ fs:: rename ( & fs_store_path, & backup_path) . unwrap ( ) ;
790+ fs:: rename ( & v2_migrating_path, & fs_store_path) . unwrap ( ) ;
791+
792+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
793+ assert_eq ! (
794+ KVStoreSync :: read(
795+ & migrated_store,
796+ TEST_PRIMARY_NAMESPACE ,
797+ TEST_SECONDARY_NAMESPACE ,
798+ TEST_KEY
799+ )
800+ . unwrap( ) ,
801+ TEST_VALUE
802+ ) ;
803+ assert ! ( fs_store_path. exists( ) ) ;
804+ assert ! ( backup_path. exists( ) ) ;
805+ assert ! ( !v2_migrating_path. exists( ) ) ;
806+ }
807+
808+ #[ test]
809+ fn fs_store_migration_recovers_backup_without_migrating_dir ( ) {
810+ let fs_store_path = fs_store_path ( ) ;
811+ write_v1_test_data ( & fs_store_path) ;
812+
813+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
814+ fs:: rename ( & fs_store_path, backup_path) . unwrap ( ) ;
815+
816+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
817+ assert_eq ! (
818+ KVStoreSync :: read(
819+ & migrated_store,
820+ TEST_PRIMARY_NAMESPACE ,
821+ TEST_SECONDARY_NAMESPACE ,
822+ TEST_KEY
823+ )
824+ . unwrap( ) ,
825+ TEST_VALUE
826+ ) ;
827+ assert ! ( fs_store_path. exists( ) ) ;
828+ assert ! ( !sibling_path( & fs_store_path, "fs_store_v1_backup" ) . exists( ) ) ;
829+ }
830+
831+ #[ test]
832+ fn fs_store_migration_recovers_unexpected_migrating_dir_without_backup ( ) {
833+ let fs_store_path = fs_store_path ( ) ;
834+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
835+ let v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
836+ KVStoreSync :: write (
837+ & v2_store,
838+ TEST_PRIMARY_NAMESPACE ,
839+ TEST_SECONDARY_NAMESPACE ,
840+ TEST_KEY ,
841+ TEST_VALUE . to_vec ( ) ,
842+ )
843+ . unwrap ( ) ;
844+
845+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
846+ assert_eq ! (
847+ KVStoreSync :: read(
848+ & migrated_store,
849+ TEST_PRIMARY_NAMESPACE ,
850+ TEST_SECONDARY_NAMESPACE ,
851+ TEST_KEY
852+ )
853+ . unwrap( ) ,
854+ TEST_VALUE
855+ ) ;
856+ assert ! ( fs_store_path. exists( ) ) ;
857+ assert ! ( !v2_migrating_path. exists( ) ) ;
858+ }
859+
860+ fn fs_store_path ( ) -> PathBuf {
861+ let mut fs_store_path = random_storage_path ( ) ;
862+ fs_store_path. push ( "fs_store" ) ;
863+ fs_store_path
864+ }
865+
866+ fn sibling_path ( path : & Path , file_name : & str ) -> PathBuf {
867+ let mut sibling_path = path. to_path_buf ( ) ;
868+ sibling_path. set_file_name ( file_name) ;
869+ sibling_path
870+ }
871+
872+ fn write_v1_test_data ( fs_store_path : & Path ) -> FilesystemStore {
873+ let v1_store = FilesystemStore :: new ( fs_store_path. to_path_buf ( ) ) ;
874+ KVStoreSync :: write (
875+ & v1_store,
876+ TEST_PRIMARY_NAMESPACE ,
877+ TEST_SECONDARY_NAMESPACE ,
878+ TEST_KEY ,
879+ TEST_VALUE . to_vec ( ) ,
880+ )
881+ . unwrap ( ) ;
882+ v1_store
883+ }
635884}
0 commit comments