@@ -57,6 +57,7 @@ use crate::event::EventQueue;
5757use crate :: fee_estimator:: OnchainFeeEstimator ;
5858use crate :: gossip:: GossipSource ;
5959use crate :: io:: sqlite_store:: SqliteStore ;
60+ use crate :: io:: tier_store:: TierStore ;
6061use crate :: io:: utils:: {
6162 open_or_migrate_fs_store, read_all_objects, read_event_queue,
6263 read_external_pathfinding_scores_from_cache, read_network_graph, read_node_metrics,
@@ -150,6 +151,12 @@ impl std::fmt::Debug for LogWriterConfig {
150151 }
151152}
152153
154+ #[ derive( Default , Debug ) ]
155+ struct TierStoreConfig {
156+ ephemeral_storage_dir_path : Option < PathBuf > ,
157+ backup_storage_dir_path : Option < PathBuf > ,
158+ }
159+
153160/// An error encountered during building a [`Node`].
154161///
155162/// [`Node`]: crate::Node
@@ -285,6 +292,7 @@ pub struct NodeBuilder {
285292 liquidity_source_config : Option < LiquiditySourceConfig > ,
286293 log_writer_config : Option < LogWriterConfig > ,
287294 async_payments_role : Option < AsyncPaymentsRole > ,
295+ tier_store_config : Option < TierStoreConfig > ,
288296 runtime_handle : Option < tokio:: runtime:: Handle > ,
289297 pathfinding_scores_sync_config : Option < PathfindingScoresSyncConfig > ,
290298 recovery_mode : bool ,
@@ -303,6 +311,7 @@ impl NodeBuilder {
303311 let gossip_source_config = None ;
304312 let liquidity_source_config = None ;
305313 let log_writer_config = None ;
314+ let tier_store_config = None ;
306315 let runtime_handle = None ;
307316 let pathfinding_scores_sync_config = None ;
308317 let recovery_mode = false ;
@@ -312,6 +321,7 @@ impl NodeBuilder {
312321 gossip_source_config,
313322 liquidity_source_config,
314323 log_writer_config,
324+ tier_store_config,
315325 runtime_handle,
316326 async_payments_role : None ,
317327 pathfinding_scores_sync_config,
@@ -612,6 +622,41 @@ impl NodeBuilder {
612622 self
613623 }
614624
625+ /// Configures a local SQLite backup store for disaster recovery.
626+ ///
627+ /// When building with tiered storage, a SQLite store will be created at the
628+ /// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
629+ /// file name. It receives a second durable copy of data written to the
630+ /// primary store.
631+ ///
632+ /// Writes and removals for primary-backed data only succeed once both the
633+ /// primary and backup SQLite stores complete successfully.
634+ ///
635+ /// If not set, durable data will be stored only in the primary store.
636+ ///
637+ /// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
638+ #[ cfg( not( feature = "uniffi" ) ) ]
639+ pub fn set_backup_storage_dir_path ( & mut self , backup_storage_dir_path : String ) -> & mut Self {
640+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
641+ tier_store_config. backup_storage_dir_path = Some ( backup_storage_dir_path. into ( ) ) ;
642+ self
643+ }
644+
645+ /// Configures the ephemeral storage directory path for non-critical, frequently-accessed data.
646+ ///
647+ /// When set, a local SQLite store is created at this path for ephemeral data like
648+ /// the network graph and scorer. Data stored here can be rebuilt if lost.
649+ ///
650+ /// If not set, non-critical data will be stored in the primary store.
651+ #[ cfg( not( feature = "uniffi" ) ) ]
652+ pub fn set_ephemeral_storage_dir_path (
653+ & mut self , ephemeral_storage_dir_path : String ,
654+ ) -> & mut Self {
655+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
656+ tier_store_config. ephemeral_storage_dir_path = Some ( ephemeral_storage_dir_path. into ( ) ) ;
657+ self
658+ }
659+
615660 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
616661 /// previously configured.
617662 pub fn build ( & self , node_entropy : NodeEntropy ) -> Result < Node , BuildError > {
@@ -813,11 +858,18 @@ impl NodeBuilder {
813858 }
814859
815860 /// Builds a [`Node`] instance according to the options previously configured.
861+ ///
862+ /// The provided `kv_store` will be used as the primary storage backend. Optionally,
863+ /// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
864+ /// and a local SQLite backup store for disaster recovery can be configured via
865+ /// [`set_ephemeral_storage_dir_path`] and [`set_backup_storage_dir_path`].
866+ ///
867+ /// [`set_ephemeral_storage_dir_path`]: Self::set_ephemeral_storage_dir_path
868+ /// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
816869 pub fn build_with_store < S : PaginatedKVStore + Send + Sync + ' static > (
817870 & self , node_entropy : NodeEntropy , kv_store : S ,
818871 ) -> Result < Node , BuildError > {
819872 let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
820-
821873 self . build_with_store_and_logger ( node_entropy, kv_store, logger)
822874 }
823875
@@ -842,6 +894,39 @@ impl NodeBuilder {
842894 fn build_with_store_runtime_and_logger < S : PaginatedKVStore + Send + Sync + ' static > (
843895 & self , node_entropy : NodeEntropy , kv_store : S , runtime : Arc < Runtime > , logger : Arc < Logger > ,
844896 ) -> Result < Node , BuildError > {
897+ let ts_config = self . tier_store_config . as_ref ( ) ;
898+ let primary_store = Arc :: new ( DynStoreWrapper ( kv_store) ) ;
899+ let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
900+ if let Some ( config) = ts_config {
901+ if let Some ( ephemeral_storage_dir_path) = config. ephemeral_storage_dir_path . as_ref ( ) {
902+ let ephemeral_store = SqliteStore :: new (
903+ ephemeral_storage_dir_path. clone ( ) ,
904+ Some ( io:: sqlite_store:: SQLITE_EPHEMERAL_DB_FILE_NAME . to_string ( ) ) ,
905+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
906+ )
907+ . map_err ( |e| {
908+ log_error ! ( logger, "Failed to setup ephemeral SQLite store: {}" , e) ;
909+ BuildError :: KVStoreSetupFailed
910+ } ) ?;
911+ let ephemeral_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( ephemeral_store) ) ;
912+ tier_store. set_ephemeral_store ( ephemeral_store) ;
913+ }
914+
915+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
916+ let backup_store = SqliteStore :: new (
917+ backup_storage_dir_path. clone ( ) ,
918+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
919+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
920+ )
921+ . map_err ( |e| {
922+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
923+ BuildError :: KVStoreSetupFailed
924+ } ) ?;
925+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
926+ tier_store. set_backup_store ( backup_store) ;
927+ }
928+ }
929+
845930 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
846931 let config = Arc :: new ( self . config . clone ( ) ) ;
847932
@@ -856,7 +941,7 @@ impl NodeBuilder {
856941 seed_bytes,
857942 runtime,
858943 logger,
859- Arc :: new ( DynStoreWrapper ( kv_store ) ) ,
944+ Arc :: new ( DynStoreWrapper ( tier_store ) ) ,
860945 )
861946 }
862947}
0 commit comments