@@ -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 > {
@@ -866,7 +885,29 @@ impl NodeBuilder {
866885 let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
867886 if let Some ( config) = ts_config {
868887 config. ephemeral . as_ref ( ) . map ( |s| tier_store. set_ephemeral_store ( Arc :: clone ( s) ) ) ;
869- config. backup . as_ref ( ) . map ( |s| tier_store. set_backup_store ( Arc :: clone ( s) ) ) ;
888+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
889+ let primary_storage_dir_path = PathBuf :: from ( & self . config . storage_dir_path ) ;
890+ if primary_storage_dir_path == * backup_storage_dir_path {
891+ log_error ! (
892+ logger,
893+ "Backup storage path must differ from primary storage path: {}" ,
894+ backup_storage_dir_path. display( )
895+ ) ;
896+ return Err ( BuildError :: BackupStorePathConflict ) ;
897+ }
898+
899+ let backup_store = SqliteStore :: new (
900+ backup_storage_dir_path. clone ( ) ,
901+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
902+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
903+ )
904+ . map_err ( |e| {
905+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
906+ BuildError :: KVStoreSetupFailed
907+ } ) ?;
908+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
909+ tier_store. set_backup_store ( backup_store) ;
910+ }
870911 }
871912
872913 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
0 commit comments