@@ -72,6 +72,7 @@ use crate::gossip::GossipSource;
7272use crate :: io:: fs_store:: open_or_migrate_fs_store;
7373#[ cfg( feature = "storage-sqlite" ) ]
7474use crate :: io:: sqlite_store:: SqliteStore ;
75+ use crate :: io:: tier_store:: { setup_index_store, TierStore } ;
7576use crate :: io:: utils:: {
7677 read_all_objects, read_event_queue, read_external_pathfinding_scores_from_cache,
7778 read_n_objects, read_network_graph, read_node_metrics, read_output_sweeper, read_peer_info,
@@ -173,6 +174,12 @@ impl std::fmt::Debug for LogWriterConfig {
173174 }
174175}
175176
177+ #[ derive( Default , Debug ) ]
178+ struct TierStoreConfig {
179+ ephemeral_storage_dir_path : Option < PathBuf > ,
180+ backup_storage_dir_path : Option < PathBuf > ,
181+ }
182+
176183/// An error encountered during building a [`Node`].
177184///
178185/// [`Node`]: crate::Node
@@ -326,6 +333,7 @@ pub struct NodeBuilder {
326333 liquidity_source_config : Option < LiquiditySourceConfig > ,
327334 log_writer_config : Option < LogWriterConfig > ,
328335 async_payments_role : Option < AsyncPaymentsRole > ,
336+ tier_store_config : Option < TierStoreConfig > ,
329337 runtime_handle : Option < tokio:: runtime:: Handle > ,
330338 pathfinding_scores_sync_config : Option < PathfindingScoresSyncConfig > ,
331339 probing_config : Option < ProbingConfig > ,
@@ -347,6 +355,7 @@ impl NodeBuilder {
347355 let gossip_source_config = None ;
348356 let liquidity_source_config = None ;
349357 let log_writer_config = None ;
358+ let tier_store_config = None ;
350359 let runtime_handle = None ;
351360 let pathfinding_scores_sync_config = None ;
352361 let probing_config = None ;
@@ -356,6 +365,7 @@ impl NodeBuilder {
356365 gossip_source_config,
357366 liquidity_source_config,
358367 log_writer_config,
368+ tier_store_config,
359369 runtime_handle,
360370 async_payments_role : None ,
361371 pathfinding_scores_sync_config,
@@ -686,6 +696,41 @@ impl NodeBuilder {
686696 self
687697 }
688698
699+ /// Configures a local SQLite backup store for disaster recovery.
700+ ///
701+ /// When building with tiered storage, a SQLite store will be created at the
702+ /// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
703+ /// file name. It receives a second durable copy of data written to the
704+ /// primary store.
705+ ///
706+ /// Writes and removals for primary-backed data only succeed once both the
707+ /// primary and backup SQLite stores complete successfully.
708+ ///
709+ /// If not set, durable data will be stored only in the primary store.
710+ ///
711+ /// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
712+ #[ cfg( not( feature = "uniffi" ) ) ]
713+ pub fn set_backup_storage_dir_path ( & mut self , backup_storage_dir_path : String ) -> & mut Self {
714+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
715+ tier_store_config. backup_storage_dir_path = Some ( backup_storage_dir_path. into ( ) ) ;
716+ self
717+ }
718+
719+ /// Configures the ephemeral storage directory path for non-critical, frequently-accessed data.
720+ ///
721+ /// When set, a local SQLite store is created at this path for ephemeral data like
722+ /// the network graph and scorer. Data stored here can be rebuilt if lost.
723+ ///
724+ /// If not set, non-critical data will be stored in the primary store.
725+ #[ cfg( not( feature = "uniffi" ) ) ]
726+ pub fn set_ephemeral_storage_dir_path (
727+ & mut self , ephemeral_storage_dir_path : String ,
728+ ) -> & mut Self {
729+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
730+ tier_store_config. ephemeral_storage_dir_path = Some ( ephemeral_storage_dir_path. into ( ) ) ;
731+ self
732+ }
733+
689734 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
690735 /// previously configured.
691736 #[ cfg( feature = "storage-sqlite" ) ]
@@ -901,11 +946,18 @@ impl NodeBuilder {
901946 }
902947
903948 /// Builds a [`Node`] instance according to the options previously configured.
949+ ///
950+ /// The provided `kv_store` will be used as the primary storage backend. Optionally,
951+ /// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
952+ /// and a local SQLite backup store for disaster recovery can be configured via
953+ /// [`set_ephemeral_storage_dir_path`] and [`set_backup_storage_dir_path`].
954+ ///
955+ /// [`set_ephemeral_storage_dir_path`]: Self::set_ephemeral_storage_dir_path
956+ /// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
904957 pub fn build_with_store < S : PaginatedKVStore + Send + Sync + ' static > (
905958 & self , node_entropy : NodeEntropy , kv_store : S ,
906959 ) -> Result < Node , BuildError > {
907960 let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
908-
909961 self . build_with_store_and_logger ( node_entropy, kv_store, logger)
910962 }
911963
@@ -930,6 +982,46 @@ impl NodeBuilder {
930982 fn build_with_store_runtime_and_logger < S : PaginatedKVStore + Send + Sync + ' static > (
931983 & self , node_entropy : NodeEntropy , kv_store : S , runtime : Arc < Runtime > , logger : Arc < Logger > ,
932984 ) -> Result < Node , BuildError > {
985+ let ts_config = self . tier_store_config . as_ref ( ) ;
986+ let primary_store = Arc :: new ( DynStoreWrapper ( kv_store) ) ;
987+ let mut tier_store = TierStore :: new ( primary_store, Arc :: clone ( & logger) ) ;
988+ if let Some ( config) = ts_config {
989+ if let Some ( ephemeral_storage_dir_path) = config. ephemeral_storage_dir_path . as_ref ( ) {
990+ let index_store = runtime
991+ . block_on ( setup_index_store ( self . config . storage_dir_path . clone ( ) . into ( ) ) )
992+ . map_err ( |e| {
993+ log_error ! ( logger, "Failed to setup tier-store index: {}" , e) ;
994+ BuildError :: KVStoreSetupFailed
995+ } ) ?;
996+ let ephemeral_store = SqliteStore :: new (
997+ ephemeral_storage_dir_path. clone ( ) ,
998+ Some ( io:: sqlite_store:: SQLITE_EPHEMERAL_DB_FILE_NAME . to_string ( ) ) ,
999+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
1000+ )
1001+ . map_err ( |e| {
1002+ log_error ! ( logger, "Failed to setup ephemeral SQLite store: {}" , e) ;
1003+ BuildError :: KVStoreSetupFailed
1004+ } ) ?;
1005+ let ephemeral_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( ephemeral_store) ) ;
1006+ tier_store. set_index_store ( index_store) ;
1007+ tier_store. set_ephemeral_store ( ephemeral_store) ;
1008+ }
1009+
1010+ if let Some ( backup_storage_dir_path) = config. backup_storage_dir_path . as_ref ( ) {
1011+ let backup_store = SqliteStore :: new (
1012+ backup_storage_dir_path. clone ( ) ,
1013+ Some ( io:: sqlite_store:: SQLITE_BACKUP_DB_FILE_NAME . to_string ( ) ) ,
1014+ Some ( io:: sqlite_store:: KV_TABLE_NAME . to_string ( ) ) ,
1015+ )
1016+ . map_err ( |e| {
1017+ log_error ! ( logger, "Failed to setup backup SQLite store: {}" , e) ;
1018+ BuildError :: KVStoreSetupFailed
1019+ } ) ?;
1020+ let backup_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( backup_store) ) ;
1021+ tier_store. set_backup_store ( backup_store) ;
1022+ }
1023+ }
1024+
9331025 let seed_bytes = node_entropy. to_seed_bytes ( ) ;
9341026 let config = Arc :: new ( self . config . clone ( ) ) ;
9351027
@@ -944,7 +1036,7 @@ impl NodeBuilder {
9441036 seed_bytes,
9451037 runtime,
9461038 logger,
947- Arc :: new ( DynStoreWrapper ( kv_store ) ) ,
1039+ Arc :: new ( DynStoreWrapper ( tier_store ) ) ,
9481040 )
9491041 }
9501042}
0 commit comments