@@ -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,108 @@ 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+ // fsync the renames
661+ fs:: File :: open ( parent_dir)
662+ . and_then ( |f| f. sync_all ( ) )
663+ . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
664+
665+ FilesystemStoreV2 :: new ( storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed )
666+ } ,
667+ Err ( _) => Err ( BuildError :: KVStoreSetupFailed ) ,
668+ }
669+ }
670+
671+ fn fs_store_sibling_path ( storage_dir_path : & Path , file_name : & str ) -> PathBuf {
672+ let mut sibling_path = storage_dir_path. to_path_buf ( ) ;
673+ sibling_path. set_file_name ( file_name) ;
674+ sibling_path
675+ }
676+
677+ fn recover_incomplete_fs_store_migration ( storage_dir_path : & Path ) -> Result < ( ) , BuildError > {
678+ let v2_dir = fs_store_sibling_path ( storage_dir_path, "fs_store_v2_migrating" ) ;
679+ let backup_dir = fs_store_sibling_path ( storage_dir_path, "fs_store_v1_backup" ) ;
680+
681+ if storage_dir_path. exists ( ) {
682+ if v2_dir. exists ( ) {
683+ // The original store is still in place, so a temp migration dir is from a crash before
684+ // the rename step and can be discarded before retrying migration.
685+ fs:: remove_dir_all ( & v2_dir) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
686+ }
687+ return Ok ( ( ) ) ;
688+ }
689+
690+ if backup_dir. exists ( ) {
691+ if v2_dir. exists ( ) {
692+ // Prefer retrying from the v1 backup instead of deciding here whether the temp v2 dir is
693+ // usable. open_or_migrate_fs_store owns the actual v1-to-v2 migration.
694+ fs:: remove_dir_all ( & v2_dir) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
695+ }
696+ // The crash happened after moving v1 aside; restore it so normal startup can migrate it.
697+ fs:: rename ( & backup_dir, storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
698+ return Ok ( ( ) ) ;
699+ }
700+
701+ if v2_dir. exists ( ) {
702+ // There is no v1 backup to retry from. Move the temp dir into place and let
703+ // open_or_migrate_fs_store decide whether it is a valid v2 store.
704+ fs:: rename ( & v2_dir, storage_dir_path) . map_err ( |_| BuildError :: KVStoreSetupFailed ) ?;
705+ }
706+
707+ Ok ( ( ) )
708+ }
709+
622710#[ cfg( test) ]
623711mod tests {
624- use super :: read_or_generate_seed_file;
712+ use std:: fs;
713+ use std:: path:: { Path , PathBuf } ;
714+
715+ use lightning:: util:: persist:: { migrate_kv_store_data, KVStoreSync } ;
716+ use lightning_persister:: fs_store:: v1:: FilesystemStore ;
717+ use lightning_persister:: fs_store:: v2:: FilesystemStoreV2 ;
718+
625719 use super :: test_utils:: random_storage_path;
720+ use super :: { open_or_migrate_fs_store, read_or_generate_seed_file} ;
721+
722+ const TEST_PRIMARY_NAMESPACE : & str = "test_primary_namespace" ;
723+ const TEST_SECONDARY_NAMESPACE : & str = "test_secondary_namespace" ;
724+ const TEST_KEY : & str = "test_key" ;
725+ const TEST_VALUE : & [ u8 ] = b"test_value" ;
626726
627727 #[ test]
628728 fn generated_seed_is_readable ( ) {
@@ -632,4 +732,158 @@ mod tests {
632732 let read_seed_bytes = read_or_generate_seed_file ( & rand_path. to_str ( ) . unwrap ( ) ) . unwrap ( ) ;
633733 assert_eq ! ( expected_seed_bytes, read_seed_bytes) ;
634734 }
735+
736+ #[ test]
737+ fn fs_store_migration_recovers_before_v1_backup_rename ( ) {
738+ let fs_store_path = fs_store_path ( ) ;
739+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
740+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
741+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
742+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
743+
744+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
745+ assert_eq ! (
746+ KVStoreSync :: read(
747+ & migrated_store,
748+ TEST_PRIMARY_NAMESPACE ,
749+ TEST_SECONDARY_NAMESPACE ,
750+ TEST_KEY
751+ )
752+ . unwrap( ) ,
753+ TEST_VALUE
754+ ) ;
755+ assert ! ( fs_store_path. exists( ) ) ;
756+ assert ! ( !v2_migrating_path. exists( ) ) ;
757+ }
758+
759+ #[ test]
760+ fn fs_store_migration_recovers_after_v1_backup_rename ( ) {
761+ let fs_store_path = fs_store_path ( ) ;
762+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
763+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
764+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
765+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
766+
767+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
768+ fs:: rename ( & fs_store_path, backup_path) . unwrap ( ) ;
769+
770+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
771+ assert_eq ! (
772+ KVStoreSync :: read(
773+ & migrated_store,
774+ TEST_PRIMARY_NAMESPACE ,
775+ TEST_SECONDARY_NAMESPACE ,
776+ TEST_KEY
777+ )
778+ . unwrap( ) ,
779+ TEST_VALUE
780+ ) ;
781+ assert ! ( fs_store_path. exists( ) ) ;
782+ assert ! ( !v2_migrating_path. exists( ) ) ;
783+ }
784+
785+ #[ test]
786+ fn fs_store_migration_recovers_after_v2_rename ( ) {
787+ let fs_store_path = fs_store_path ( ) ;
788+ let mut v1_store = write_v1_test_data ( & fs_store_path) ;
789+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
790+ let mut v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
791+ migrate_kv_store_data ( & mut v1_store, & mut v2_store) . unwrap ( ) ;
792+
793+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
794+ fs:: rename ( & fs_store_path, & backup_path) . unwrap ( ) ;
795+ fs:: rename ( & v2_migrating_path, & fs_store_path) . unwrap ( ) ;
796+
797+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
798+ assert_eq ! (
799+ KVStoreSync :: read(
800+ & migrated_store,
801+ TEST_PRIMARY_NAMESPACE ,
802+ TEST_SECONDARY_NAMESPACE ,
803+ TEST_KEY
804+ )
805+ . unwrap( ) ,
806+ TEST_VALUE
807+ ) ;
808+ assert ! ( fs_store_path. exists( ) ) ;
809+ assert ! ( backup_path. exists( ) ) ;
810+ assert ! ( !v2_migrating_path. exists( ) ) ;
811+ }
812+
813+ #[ test]
814+ fn fs_store_migration_recovers_backup_without_migrating_dir ( ) {
815+ let fs_store_path = fs_store_path ( ) ;
816+ write_v1_test_data ( & fs_store_path) ;
817+
818+ let backup_path = sibling_path ( & fs_store_path, "fs_store_v1_backup" ) ;
819+ fs:: rename ( & fs_store_path, backup_path) . unwrap ( ) ;
820+
821+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
822+ assert_eq ! (
823+ KVStoreSync :: read(
824+ & migrated_store,
825+ TEST_PRIMARY_NAMESPACE ,
826+ TEST_SECONDARY_NAMESPACE ,
827+ TEST_KEY
828+ )
829+ . unwrap( ) ,
830+ TEST_VALUE
831+ ) ;
832+ assert ! ( fs_store_path. exists( ) ) ;
833+ assert ! ( !sibling_path( & fs_store_path, "fs_store_v1_backup" ) . exists( ) ) ;
834+ }
835+
836+ #[ test]
837+ fn fs_store_migration_recovers_unexpected_migrating_dir_without_backup ( ) {
838+ let fs_store_path = fs_store_path ( ) ;
839+ let v2_migrating_path = sibling_path ( & fs_store_path, "fs_store_v2_migrating" ) ;
840+ let v2_store = FilesystemStoreV2 :: new ( v2_migrating_path. clone ( ) ) . unwrap ( ) ;
841+ KVStoreSync :: write (
842+ & v2_store,
843+ TEST_PRIMARY_NAMESPACE ,
844+ TEST_SECONDARY_NAMESPACE ,
845+ TEST_KEY ,
846+ TEST_VALUE . to_vec ( ) ,
847+ )
848+ . unwrap ( ) ;
849+
850+ let migrated_store = open_or_migrate_fs_store ( fs_store_path. clone ( ) ) . unwrap ( ) ;
851+ assert_eq ! (
852+ KVStoreSync :: read(
853+ & migrated_store,
854+ TEST_PRIMARY_NAMESPACE ,
855+ TEST_SECONDARY_NAMESPACE ,
856+ TEST_KEY
857+ )
858+ . unwrap( ) ,
859+ TEST_VALUE
860+ ) ;
861+ assert ! ( fs_store_path. exists( ) ) ;
862+ assert ! ( !v2_migrating_path. exists( ) ) ;
863+ }
864+
865+ fn fs_store_path ( ) -> PathBuf {
866+ let mut fs_store_path = random_storage_path ( ) ;
867+ fs_store_path. push ( "fs_store" ) ;
868+ fs_store_path
869+ }
870+
871+ fn sibling_path ( path : & Path , file_name : & str ) -> PathBuf {
872+ let mut sibling_path = path. to_path_buf ( ) ;
873+ sibling_path. set_file_name ( file_name) ;
874+ sibling_path
875+ }
876+
877+ fn write_v1_test_data ( fs_store_path : & Path ) -> FilesystemStore {
878+ let v1_store = FilesystemStore :: new ( fs_store_path. to_path_buf ( ) ) ;
879+ KVStoreSync :: write (
880+ & v1_store,
881+ TEST_PRIMARY_NAMESPACE ,
882+ TEST_SECONDARY_NAMESPACE ,
883+ TEST_KEY ,
884+ TEST_VALUE . to_vec ( ) ,
885+ )
886+ . unwrap ( ) ;
887+ v1_store
888+ }
635889}
0 commit comments