@@ -158,14 +158,14 @@ impl std::fmt::Debug for LogWriterConfig {
158158#[ derive( Default ) ]
159159struct TierStoreConfig {
160160 ephemeral : Option < Arc < DynStore > > ,
161- backup : Option < Arc < DynStore > > ,
161+ backup_storage_dir_path : Option < PathBuf > ,
162162}
163163
164164impl std:: fmt:: Debug for TierStoreConfig {
165165 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
166166 f. debug_struct ( "TierStoreConfig" )
167167 . field ( "ephemeral" , & self . ephemeral . as_ref ( ) . map ( |_| "Arc<DynStore>" ) )
168- . field ( "backup " , & self . backup . as_ref ( ) . map ( |_| "Arc<DynStore>" ) )
168+ . field ( "backup_storage_dir_path " , & self . backup_storage_dir_path )
169169 . finish ( )
170170 }
171171}
@@ -216,6 +216,11 @@ pub enum BuildError {
216216 AsyncPaymentsConfigMismatch ,
217217 /// An attempt to setup a DNS Resolver failed.
218218 DNSResolverSetupFailed ,
219+ /// The configured backup storage path conflicts with the primary storage path.
220+ ///
221+ /// Backup storage must use a distinct local directory so that the primary and
222+ /// backup stores do not point to the same SQLite database.
223+ BackupStorePathConflict ,
219224}
220225
221226impl fmt:: Display for BuildError {
@@ -253,6 +258,12 @@ impl fmt::Display for BuildError {
253258 Self :: DNSResolverSetupFailed => {
254259 write ! ( f, "An attempt to setup a DNS resolver has failed." )
255260 } ,
261+ Self :: BackupStorePathConflict => {
262+ write ! (
263+ f,
264+ "The configured backup storage path conflicts with the primary storage path."
265+ )
266+ } ,
256267 }
257268 }
258269}
@@ -644,18 +655,26 @@ impl NodeBuilder {
644655 self
645656 }
646657
647- /// Configures the backup store for local disaster recovery.
658+ /// Configures a local SQLite backup store for disaster recovery.
648659 ///
649- /// When building with tiered storage, this store receives a second durable
650- /// copy of data written to the primary store.
660+ /// When building with tiered storage, a SQLite store will be created at the
661+ /// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
662+ /// file name. It receives a second durable copy of data written to the
663+ /// primary store.
651664 ///
652665 /// Writes and removals for primary-backed data only succeed once both the
653- /// primary and backup stores complete successfully.
666+ /// primary and backup SQLite stores complete successfully.
667+ ///
668+ /// The configured path must point to a distinct local directory from the
669+ /// primary storage path. If the backup path equals the primary storage path,
670+ /// building will fail with [`BuildError::BackupStorePathConflict`].
654671 ///
655672 /// If not set, durable data will be stored only in the primary store.
656- pub fn set_backup_store ( & mut self , backup_store : Arc < DynStore > ) -> & mut Self {
673+ ///
674+ /// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
675+ pub fn set_backup_storage_dir_path ( & mut self , backup_storage_dir_path : String ) -> & mut Self {
657676 let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
658- tier_store_config. backup = Some ( backup_store ) ;
677+ tier_store_config. backup_storage_dir_path = Some ( backup_storage_dir_path . into ( ) ) ;
659678 self
660679 }
661680
@@ -830,11 +849,11 @@ impl NodeBuilder {
830849 ///
831850 /// The provided `kv_store` will be used as the primary storage backend. Optionally,
832851 /// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
833- /// and a backup store for local disaster recovery can be configured via
834- /// [`set_ephemeral_store`] and [`set_backup_store `].
852+ /// and a local SQLite backup store for disaster recovery can be configured via
853+ /// [`set_ephemeral_store`] and [`set_backup_storage_dir_path `].
835854 ///
836855 /// [`set_ephemeral_store`]: Self::set_ephemeral_store
837- /// [`set_backup_store `]: Self::set_backup_store
856+ /// [`set_backup_storage_dir_path `]: Self::set_backup_storage_dir_path
838857 pub fn build_with_store < S : SyncAndAsyncKVStore + Send + Sync + ' static > (
839858 & self , node_entropy : NodeEntropy , kv_store : S ,
840859 ) -> Result < Node , BuildError > {
@@ -859,7 +878,29 @@ impl NodeBuilder {
859878 let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
860879 if let Some ( config) = ts_config {
861880 config. ephemeral . as_ref ( ) . map ( |s| tier_store. set_ephemeral_store ( Arc :: clone ( s) ) ) ;
862- config. backup . as_ref ( ) . map ( |s| tier_store. set_backup_store ( Arc :: clone ( s) ) ) ;
881+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
882+ let primary_storage_dir_path = PathBuf :: from ( & self . config . storage_dir_path ) ;
883+ if primary_storage_dir_path == * backup_storage_dir_path {
884+ log_error ! (
885+ logger,
886+ "Backup storage path must differ from primary storage path: {}" ,
887+ backup_storage_dir_path. display( )
888+ ) ;
889+ return Err ( BuildError :: BackupStorePathConflict ) ;
890+ }
891+
892+ let backup_store = SqliteStore :: new (
893+ backup_storage_dir_path. clone ( ) ,
894+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
895+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
896+ )
897+ . map_err ( |e| {
898+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
899+ BuildError :: KVStoreSetupFailed
900+ } ) ?;
901+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
902+ tier_store. set_backup_store ( backup_store) ;
903+ }
863904 }
864905
865906 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
0 commit comments