@@ -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,
@@ -151,6 +152,12 @@ impl std::fmt::Debug for LogWriterConfig {
151152 }
152153}
153154
155+ #[ derive( Default , Debug ) ]
156+ struct TierStoreConfig {
157+ ephemeral_storage_dir_path : Option < PathBuf > ,
158+ backup_storage_dir_path : Option < PathBuf > ,
159+ }
160+
154161/// An error encountered during building a [`Node`].
155162///
156163/// [`Node`]: crate::Node
@@ -304,6 +311,7 @@ pub struct NodeBuilder {
304311 liquidity_source_config : Option < LiquiditySourceConfig > ,
305312 log_writer_config : Option < LogWriterConfig > ,
306313 async_payments_role : Option < AsyncPaymentsRole > ,
314+ tier_store_config : Option < TierStoreConfig > ,
307315 runtime_handle : Option < tokio:: runtime:: Handle > ,
308316 pathfinding_scores_sync_config : Option < PathfindingScoresSyncConfig > ,
309317}
@@ -321,6 +329,7 @@ impl NodeBuilder {
321329 let gossip_source_config = None ;
322330 let liquidity_source_config = None ;
323331 let log_writer_config = None ;
332+ let tier_store_config = None ;
324333 let runtime_handle = None ;
325334 let pathfinding_scores_sync_config = None ;
326335 Self {
@@ -329,6 +338,7 @@ impl NodeBuilder {
329338 gossip_source_config,
330339 liquidity_source_config,
331340 log_writer_config,
341+ tier_store_config,
332342 runtime_handle,
333343 async_payments_role : None ,
334344 pathfinding_scores_sync_config,
@@ -629,6 +639,41 @@ impl NodeBuilder {
629639 Ok ( self )
630640 }
631641
642+ /// Configures a local SQLite backup store for disaster recovery.
643+ ///
644+ /// When building with tiered storage, a SQLite store will be created at the
645+ /// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
646+ /// file name. It receives a second durable copy of data written to the
647+ /// primary store.
648+ ///
649+ /// Writes and removals for primary-backed data only succeed once both the
650+ /// primary and backup SQLite stores complete successfully.
651+ ///
652+ /// If not set, durable data will be stored only in the primary store.
653+ ///
654+ /// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
655+ #[ cfg( not( feature = "uniffi" ) ) ]
656+ pub fn set_backup_storage_dir_path ( & mut self , backup_storage_dir_path : String ) -> & mut Self {
657+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
658+ tier_store_config. backup_storage_dir_path = Some ( backup_storage_dir_path. into ( ) ) ;
659+ self
660+ }
661+
662+ /// Configures the ephemeral storage directory path for non-critical, frequently-accessed data.
663+ ///
664+ /// When set, a local SQLite store is created at this path for ephemeral data like
665+ /// the network graph and scorer. Data stored here can be rebuilt if lost.
666+ ///
667+ /// If not set, non-critical data will be stored in the primary store.
668+ #[ cfg( not( feature = "uniffi" ) ) ]
669+ pub fn set_ephemeral_storage_dir_path (
670+ & mut self , ephemeral_storage_dir_path : String ,
671+ ) -> & mut Self {
672+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
673+ tier_store_config. ephemeral_storage_dir_path = Some ( ephemeral_storage_dir_path. into ( ) ) ;
674+ self
675+ }
676+
632677 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
633678 /// previously configured.
634679 pub fn build ( & self , node_entropy : NodeEntropy ) -> Result < Node , BuildError > {
@@ -830,11 +875,18 @@ impl NodeBuilder {
830875 }
831876
832877 /// Builds a [`Node`] instance according to the options previously configured.
878+ ///
879+ /// The provided `kv_store` will be used as the primary storage backend. Optionally,
880+ /// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
881+ /// and a local SQLite backup store for disaster recovery can be configured via
882+ /// [`set_ephemeral_storage_dir_path`] and [`set_backup_storage_dir_path`].
883+ ///
884+ /// [`set_ephemeral_storage_dir_path`]: Self::set_ephemeral_storage_dir_path
885+ /// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
833886 pub fn build_with_store < S : PaginatedKVStore + Send + Sync + ' static > (
834887 & self , node_entropy : NodeEntropy , kv_store : S ,
835888 ) -> Result < Node , BuildError > {
836889 let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
837-
838890 self . build_with_store_and_logger ( node_entropy, kv_store, logger)
839891 }
840892
@@ -859,6 +911,39 @@ impl NodeBuilder {
859911 fn build_with_store_runtime_and_logger < S : PaginatedKVStore + Send + Sync + ' static > (
860912 & self , node_entropy : NodeEntropy , kv_store : S , runtime : Arc < Runtime > , logger : Arc < Logger > ,
861913 ) -> Result < Node , BuildError > {
914+ let ts_config = self . tier_store_config . as_ref ( ) ;
915+ let primary_store = Arc :: new ( DynStoreWrapper ( kv_store) ) ;
916+ let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
917+ if let Some ( config) = ts_config {
918+ if let Some ( ephemeral_storage_dir_path) = config. ephemeral_storage_dir_path . as_ref ( ) {
919+ let ephemeral_store = SqliteStore :: new (
920+ ephemeral_storage_dir_path. clone ( ) ,
921+ Some ( io:: sqlite_store:: SQLITE_EPHEMERAL_DB_FILE_NAME . to_string ( ) ) ,
922+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
923+ )
924+ . map_err ( |e| {
925+ log_error ! ( logger, "Failed to setup ephemeral SQLite store: {}" , e) ;
926+ BuildError :: KVStoreSetupFailed
927+ } ) ?;
928+ let ephemeral_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( ephemeral_store) ) ;
929+ tier_store. set_ephemeral_store ( ephemeral_store) ;
930+ }
931+
932+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
933+ let backup_store = SqliteStore :: new (
934+ backup_storage_dir_path. clone ( ) ,
935+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
936+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
937+ )
938+ . map_err ( |e| {
939+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
940+ BuildError :: KVStoreSetupFailed
941+ } ) ?;
942+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
943+ tier_store. set_backup_store ( backup_store) ;
944+ }
945+ }
946+
862947 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
863948 let config = Arc :: new ( self . config . clone ( ) ) ;
864949
@@ -872,7 +957,7 @@ impl NodeBuilder {
872957 seed_bytes,
873958 runtime,
874959 logger,
875- Arc :: new ( DynStoreWrapper ( kv_store ) ) ,
960+ Arc :: new ( DynStoreWrapper ( tier_store ) ) ,
876961 )
877962 }
878963}
0 commit comments