@@ -60,6 +60,7 @@ use crate::event::EventQueue;
6060use crate :: fee_estimator:: OnchainFeeEstimator ;
6161use crate :: gossip:: GossipSource ;
6262use crate :: io:: sqlite_store:: SqliteStore ;
63+ use crate :: io:: tier_store:: { setup_index_store, TierStore } ;
6364use crate :: io:: utils:: {
6465 open_or_migrate_fs_store, read_all_objects, read_event_queue,
6566 read_external_pathfinding_scores_from_cache, read_n_objects, read_network_graph,
@@ -158,6 +159,12 @@ impl std::fmt::Debug for LogWriterConfig {
158159 }
159160}
160161
162+ #[ derive( Default , Debug ) ]
163+ struct TierStoreConfig {
164+ ephemeral_storage_dir_path : Option < PathBuf > ,
165+ backup_storage_dir_path : Option < PathBuf > ,
166+ }
167+
161168/// An error encountered during building a [`Node`].
162169///
163170/// [`Node`]: crate::Node
@@ -311,6 +318,7 @@ pub struct NodeBuilder {
311318 liquidity_source_config : Option < LiquiditySourceConfig > ,
312319 log_writer_config : Option < LogWriterConfig > ,
313320 async_payments_role : Option < AsyncPaymentsRole > ,
321+ tier_store_config : Option < TierStoreConfig > ,
314322 runtime_handle : Option < tokio:: runtime:: Handle > ,
315323 pathfinding_scores_sync_config : Option < PathfindingScoresSyncConfig > ,
316324 probing_config : Option < ProbingConfig > ,
@@ -329,6 +337,7 @@ impl NodeBuilder {
329337 let gossip_source_config = None ;
330338 let liquidity_source_config = None ;
331339 let log_writer_config = None ;
340+ let tier_store_config = None ;
332341 let runtime_handle = None ;
333342 let pathfinding_scores_sync_config = None ;
334343 let probing_config = None ;
@@ -338,6 +347,7 @@ impl NodeBuilder {
338347 gossip_source_config,
339348 liquidity_source_config,
340349 log_writer_config,
350+ tier_store_config,
341351 runtime_handle,
342352 async_payments_role : None ,
343353 pathfinding_scores_sync_config,
@@ -663,6 +673,41 @@ impl NodeBuilder {
663673 self
664674 }
665675
676+ /// Configures a local SQLite backup store for disaster recovery.
677+ ///
678+ /// When building with tiered storage, a SQLite store will be created at the
679+ /// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
680+ /// file name. It receives a second durable copy of data written to the
681+ /// primary store.
682+ ///
683+ /// Writes and removals for primary-backed data only succeed once both the
684+ /// primary and backup SQLite stores complete successfully.
685+ ///
686+ /// If not set, durable data will be stored only in the primary store.
687+ ///
688+ /// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
689+ #[ cfg( not( feature = "uniffi" ) ) ]
690+ pub fn set_backup_storage_dir_path ( & mut self , backup_storage_dir_path : String ) -> & mut Self {
691+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
692+ tier_store_config. backup_storage_dir_path = Some ( backup_storage_dir_path. into ( ) ) ;
693+ self
694+ }
695+
696+ /// Configures the ephemeral storage directory path for non-critical, frequently-accessed data.
697+ ///
698+ /// When set, a local SQLite store is created at this path for ephemeral data like
699+ /// the network graph and scorer. Data stored here can be rebuilt if lost.
700+ ///
701+ /// If not set, non-critical data will be stored in the primary store.
702+ #[ cfg( not( feature = "uniffi" ) ) ]
703+ pub fn set_ephemeral_storage_dir_path (
704+ & mut self , ephemeral_storage_dir_path : String ,
705+ ) -> & mut Self {
706+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
707+ tier_store_config. ephemeral_storage_dir_path = Some ( ephemeral_storage_dir_path. into ( ) ) ;
708+ self
709+ }
710+
666711 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
667712 /// previously configured.
668713 pub fn build ( & self , node_entropy : NodeEntropy ) -> Result < Node , BuildError > {
@@ -872,11 +917,18 @@ impl NodeBuilder {
872917 }
873918
874919 /// Builds a [`Node`] instance according to the options previously configured.
920+ ///
921+ /// The provided `kv_store` will be used as the primary storage backend. Optionally,
922+ /// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
923+ /// and a local SQLite backup store for disaster recovery can be configured via
924+ /// [`set_ephemeral_storage_dir_path`] and [`set_backup_storage_dir_path`].
925+ ///
926+ /// [`set_ephemeral_storage_dir_path`]: Self::set_ephemeral_storage_dir_path
927+ /// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
875928 pub fn build_with_store < S : PaginatedKVStore + Send + Sync + ' static > (
876929 & self , node_entropy : NodeEntropy , kv_store : S ,
877930 ) -> Result < Node , BuildError > {
878931 let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
879-
880932 self . build_with_store_and_logger ( node_entropy, kv_store, logger)
881933 }
882934
@@ -901,6 +953,46 @@ impl NodeBuilder {
901953 fn build_with_store_runtime_and_logger < S : PaginatedKVStore + Send + Sync + ' static > (
902954 & self , node_entropy : NodeEntropy , kv_store : S , runtime : Arc < Runtime > , logger : Arc < Logger > ,
903955 ) -> Result < Node , BuildError > {
956+ let ts_config = self . tier_store_config . as_ref ( ) ;
957+ let primary_store = Arc :: new ( DynStoreWrapper ( kv_store) ) ;
958+ let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
959+ if let Some ( config) = ts_config {
960+ if let Some ( ephemeral_storage_dir_path) = config. ephemeral_storage_dir_path . as_ref ( ) {
961+ let index_store = runtime
962+ . block_on ( setup_index_store ( self . config . storage_dir_path . clone ( ) . into ( ) ) )
963+ . map_err ( |e| {
964+ log_error ! ( logger, "Failed to setup tier-store index: {}" , e) ;
965+ BuildError :: KVStoreSetupFailed
966+ } ) ?;
967+ let ephemeral_store = SqliteStore :: new (
968+ ephemeral_storage_dir_path. clone ( ) ,
969+ Some ( io:: sqlite_store:: SQLITE_EPHEMERAL_DB_FILE_NAME . to_string ( ) ) ,
970+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
971+ )
972+ . map_err ( |e| {
973+ log_error ! ( logger, "Failed to setup ephemeral SQLite store: {}" , e) ;
974+ BuildError :: KVStoreSetupFailed
975+ } ) ?;
976+ let ephemeral_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( ephemeral_store) ) ;
977+ tier_store. set_index_store ( index_store) ;
978+ tier_store. set_ephemeral_store ( ephemeral_store) ;
979+ }
980+
981+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
982+ let backup_store = SqliteStore :: new (
983+ backup_storage_dir_path. clone ( ) ,
984+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
985+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
986+ )
987+ . map_err ( |e| {
988+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
989+ BuildError :: KVStoreSetupFailed
990+ } ) ?;
991+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
992+ tier_store. set_backup_store ( backup_store) ;
993+ }
994+ }
995+
904996 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
905997 let config = Arc :: new ( self . config . clone ( ) ) ;
906998
@@ -915,7 +1007,7 @@ impl NodeBuilder {
9151007 seed_bytes,
9161008 runtime,
9171009 logger,
918- Arc :: new ( DynStoreWrapper ( kv_store ) ) ,
1010+ Arc :: new ( DynStoreWrapper ( tier_store ) ) ,
9191011 )
9201012 }
9211013}
0 commit comments