@@ -15,8 +15,10 @@ use crate::config::{
1515use crate :: connection:: ConnectionManager ;
1616use crate :: event:: EventQueue ;
1717use crate :: fee_estimator:: OnchainFeeEstimator ;
18+ use crate :: ffi:: maybe_extract_inner;
1819use crate :: gossip:: GossipSource ;
1920use crate :: io:: sqlite_store:: SqliteStore ;
21+ use crate :: io:: tier_store:: { RetryConfig , TierStore } ;
2022use crate :: io:: utils:: { read_node_metrics, write_node_metrics} ;
2123use crate :: io:: vss_store:: VssStore ;
2224use crate :: io:: {
@@ -30,14 +32,20 @@ use crate::message_handler::NodeCustomMessageHandler;
3032use crate :: peer_store:: PeerStore ;
3133use crate :: runtime:: Runtime ;
3234use crate :: tx_broadcaster:: TransactionBroadcaster ;
35+
3336use crate :: types:: {
34- ChainMonitor , ChannelManager , DynStore , GossipSync , Graph , KeysManager , MessageRouter ,
35- OnionMessenger , PaymentStore , PeerManager ,
37+ ChainMonitor , ChannelManager , GossipSync , Graph , KeysManager , MessageRouter , OnionMessenger ,
38+ PaymentStore , PeerManager ,
3639} ;
3740use crate :: wallet:: persist:: KVStoreWalletPersister ;
3841use crate :: wallet:: Wallet ;
3942use crate :: { Node , NodeMetrics } ;
4043
44+ #[ cfg( feature = "uniffi" ) ]
45+ use crate :: ffi:: DynStore ;
46+ #[ cfg( not( feature = "uniffi" ) ) ]
47+ use crate :: types:: DynStore ;
48+
4149use lightning:: chain:: { chainmonitor, BestBlock , Watch } ;
4250use lightning:: io:: Cursor ;
4351use lightning:: ln:: channelmanager:: { self , ChainParameters , ChannelManagerReadArgs } ;
@@ -150,6 +158,23 @@ impl std::fmt::Debug for LogWriterConfig {
150158 }
151159}
152160
161+ #[ derive( Default ) ]
162+ struct TierStoreConfig {
163+ ephemeral : Option < Arc < DynStore > > ,
164+ backup : Option < Arc < DynStore > > ,
165+ retry : Option < RetryConfig > ,
166+ }
167+
168+ impl std:: fmt:: Debug for TierStoreConfig {
169+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
170+ f. debug_struct ( "TierStoreConfig" )
171+ . field ( "ephemeral" , & self . ephemeral . as_ref ( ) . map ( |_| "Arc<DynStore>" ) )
172+ . field ( "backup" , & self . backup . as_ref ( ) . map ( |_| "Arc<DynStore>" ) )
173+ . field ( "retry" , & self . retry )
174+ . finish ( )
175+ }
176+ }
177+
153178/// An error encountered during building a [`Node`].
154179///
155180/// [`Node`]: crate::Node
@@ -240,6 +265,7 @@ pub struct NodeBuilder {
240265 gossip_source_config : Option < GossipSourceConfig > ,
241266 liquidity_source_config : Option < LiquiditySourceConfig > ,
242267 log_writer_config : Option < LogWriterConfig > ,
268+ tier_store_config : Option < TierStoreConfig > ,
243269 runtime_handle : Option < tokio:: runtime:: Handle > ,
244270}
245271
@@ -257,6 +283,7 @@ impl NodeBuilder {
257283 let gossip_source_config = None ;
258284 let liquidity_source_config = None ;
259285 let log_writer_config = None ;
286+ let tier_store_config = None ;
260287 let runtime_handle = None ;
261288 Self {
262289 config,
@@ -265,6 +292,7 @@ impl NodeBuilder {
265292 gossip_source_config,
266293 liquidity_source_config,
267294 log_writer_config,
295+ tier_store_config,
268296 runtime_handle,
269297 }
270298 }
@@ -544,6 +572,53 @@ impl NodeBuilder {
544572 Ok ( self )
545573 }
546574
575+ /// Configures retry behavior for transient errors when accessing the primary store.
576+ ///
577+ /// When building with [`build_with_tier_store`], controls the exponential backoff parameters
578+ /// used when retrying failed operations on the primary store due to transient errors
579+ /// (network issues, timeouts, etc.).
580+ ///
581+ /// If not set, default retry parameters are used. See [`RetryConfig`] for details.
582+ ///
583+ /// [`build_with_tier_store`]: Self::build_with_tier_store
584+ pub fn set_tier_store_retry_config ( & mut self , config : RetryConfig ) -> & mut Self {
585+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
586+ tier_store_config. retry = Some ( config) ;
587+ self
588+ }
589+
590+ /// Configures the backup store for local disaster recovery.
591+ ///
592+ /// When building with [`build_with_tier_store`], this store receives asynchronous copies
593+ /// of all critical data written to the primary store. If the primary store becomes
594+ /// unavailable, reads will fall back to this backup store.
595+ ///
596+ /// Backup writes are non-blocking and do not affect primary store operation performance.
597+ ///
598+ /// [`build_with_tier_store`]: Self::build_with_tier_store
599+ pub fn set_tier_store_backup ( & mut self , backup_store : Arc < DynStore > ) -> & mut Self {
600+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
601+ let store = maybe_extract_inner ( backup_store) ;
602+ tier_store_config. backup = Some ( store) ;
603+ self
604+ }
605+
606+ /// Configures the ephemeral store for non-critical, frequently-accessed data.
607+ ///
608+ /// When building with [`build_with_tier_store`], this store is used for data like
609+ /// the network graph and scorer data to reduce latency for reads. Data stored here
610+ /// can be rebuilt if lost.
611+ ///
612+ /// If not set, non-critical data will be stored in the primary store.
613+ ///
614+ /// [`build_with_tier_store`]: Self::build_with_tier_store
615+ pub fn set_tier_store_ephemeral ( & mut self , ephemeral_store : Arc < DynStore > ) -> & mut Self {
616+ let tier_store_config = self . tier_store_config . get_or_insert ( TierStoreConfig :: default ( ) ) ;
617+ let store = maybe_extract_inner ( ephemeral_store) ;
618+ tier_store_config. ephemeral = Some ( store) ;
619+ self
620+ }
621+
547622 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
548623 /// previously configured.
549624 pub fn build ( & self ) -> Result < Node , BuildError > {
@@ -707,6 +782,98 @@ impl NodeBuilder {
707782 )
708783 }
709784
785+ /// Builds a [`Node`] instance with tiered storage for managing data across multiple storage layers.
786+ ///
787+ /// This build method enables a three-tier storage architecture optimized for different data types
788+ /// and access patterns:
789+ ///
790+ /// ### Storage Tiers
791+ ///
792+ /// - **Primary Store** (required): The authoritative store for critical channel state and payment data.
793+ /// Typically a remote/cloud storage service for durability and accessibility across devices.
794+ ///
795+ /// - **Ephemeral Store** (optional): Local storage for non-critical, frequently-accessed data like
796+ /// the network graph and scorer. Improves performance by reducing latency for data that can be
797+ /// rebuilt if lost. Configure with [`set_tier_store_ephemeral`].
798+ ///
799+ /// - **Backup Store** (optional): Local backup of critical data for disaster recovery scenarios.
800+ /// Provides a safety net if the primary store becomes temporarily unavailable. Writes are
801+ /// asynchronous to avoid blocking primary operations. Configure with [`set_tier_store_backup`].
802+ ///
803+ /// ## Configuration
804+ ///
805+ /// Use the setter methods to configure optional stores and retry behavior:
806+ /// - [`set_tier_store_ephemeral`] - Set local store for network graph and scorer
807+ /// - [`set_tier_store_backup`] - Set local backup store for disaster recovery
808+ /// - [`set_tier_store_retry_config`] - Configure retry delays and backoff for transient errors
809+ ///
810+ /// ## Example
811+ ///
812+ /// ```ignore
813+ /// # use ldk_node::{Builder, Config};
814+ /// # use ldk_node::io::tier_store::RetryConfig;
815+ /// # use std::sync::Arc;
816+ /// let config = Config::default();
817+ /// let mut builder = NodeBuilder::from_config(config);
818+ ///
819+ /// let primary = Arc::new(VssStore::new(...));
820+ /// let ephemeral = Arc::new(FilesystemStore::new(...));
821+ /// let backup = Arc::new(SqliteStore::new(...));
822+ /// let retry_config = RetryConfig::default();
823+ ///
824+ /// builder
825+ /// .set_tier_store_ephemeral(ephemeral)
826+ /// .set_tier_store_backup(backup)
827+ /// .set_tier_store_retry_config(retry_config);
828+ ///
829+ /// let node = builder.build_with_tier_store(primary)?;
830+ /// # Ok::<(), ldk_node::BuildError>(())
831+ /// ```
832+ ///
833+ /// [`set_tier_store_ephemeral`]: Self::set_tier_store_ephemeral
834+ /// [`set_tier_store_backup`]: Self::set_tier_store_backup
835+ /// [`set_tier_store_retry_config`]: Self::set_tier_store_retry_config
836+ pub fn build_with_tier_store ( & self , primary_store : Arc < DynStore > ) -> Result < Node , BuildError > {
837+ let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
838+ let runtime = if let Some ( handle) = self . runtime_handle . as_ref ( ) {
839+ Arc :: new ( Runtime :: with_handle ( handle. clone ( ) , Arc :: clone ( & logger) ) )
840+ } else {
841+ Arc :: new ( Runtime :: new ( Arc :: clone ( & logger) ) . map_err ( |e| {
842+ log_error ! ( logger, "Failed to setup tokio runtime: {}" , e) ;
843+ BuildError :: RuntimeSetupFailed
844+ } ) ?)
845+ } ;
846+ let seed_bytes = seed_bytes_from_config (
847+ & self . config ,
848+ self . entropy_source_config . as_ref ( ) ,
849+ Arc :: clone ( & logger) ,
850+ ) ?;
851+ let config = Arc :: new ( self . config . clone ( ) ) ;
852+
853+ let ts_config = self . tier_store_config . as_ref ( ) ;
854+ let retry_config = ts_config. and_then ( |c| c. retry ) . unwrap_or_default ( ) ;
855+
856+ let primary = maybe_extract_inner ( primary_store) ;
857+ let mut tier_store =
858+ TierStore :: new ( primary, Arc :: clone ( & runtime) , Arc :: clone ( & logger) , retry_config) ;
859+
860+ if let Some ( config) = ts_config {
861+ config. ephemeral . as_ref ( ) . map ( |s| tier_store. set_ephemeral_store ( Arc :: clone ( s) ) ) ;
862+ config. backup . as_ref ( ) . map ( |s| tier_store. set_backup_store ( Arc :: clone ( s) ) ) ;
863+ }
864+
865+ build_with_store_internal (
866+ config,
867+ self . chain_data_source_config . as_ref ( ) ,
868+ self . gossip_source_config . as_ref ( ) ,
869+ self . liquidity_source_config . as_ref ( ) ,
870+ seed_bytes,
871+ runtime,
872+ logger,
873+ Arc :: new ( tier_store) ,
874+ )
875+ }
876+
710877 /// Builds a [`Node`] instance according to the options previously configured.
711878 pub fn build_with_store ( & self , kv_store : Arc < DynStore > ) -> Result < Node , BuildError > {
712879 let logger = setup_logger ( & self . log_writer_config , & self . config ) ?;
@@ -989,6 +1156,45 @@ impl ArcedNodeBuilder {
9891156 self . inner . write ( ) . unwrap ( ) . set_node_alias ( node_alias) . map ( |_| ( ) )
9901157 }
9911158
1159+ /// Configures retry behavior for transient errors when accessing the primary store.
1160+ ///
1161+ /// When building with [`build_with_tier_store`], controls the exponential backoff parameters
1162+ /// used when retrying failed operations on the primary store due to transient errors
1163+ /// (network issues, timeouts, etc.).
1164+ ///
1165+ /// If not set, default retry parameters are used. See [`RetryConfig`] for details.
1166+ ///
1167+ /// [`build_with_tier_store`]: Self::build_with_tier_store
1168+ pub fn set_tier_store_retry_config ( & self , config : RetryConfig ) {
1169+ self . inner . write ( ) . unwrap ( ) . set_tier_store_retry_config ( config) ;
1170+ }
1171+
1172+ /// Configures the backup store for local disaster recovery.
1173+ ///
1174+ /// When building with [`build_with_tier_store`], this store receives asynchronous copies
1175+ /// of all critical data written to the primary store. If the primary store becomes
1176+ /// unavailable, reads will fall back to this backup store.
1177+ ///
1178+ /// Backup writes are non-blocking and do not affect primary store operation performance.
1179+ ///
1180+ /// [`build_with_tier_store`]: Self::build_with_tier_store
1181+ pub fn set_tier_store_backup ( & mut self , backup_store : Arc < DynStore > ) {
1182+ self . inner . write ( ) . unwrap ( ) . set_tier_store_backup ( backup_store) ;
1183+ }
1184+
1185+ /// Configures the ephemeral store for non-critical, frequently-accessed data.
1186+ ///
1187+ /// When building with [`build_with_tier_store`], this store is used for data like
1188+ /// the network graph and scorer data to reduce latency for reads. Data stored here
1189+ /// can be rebuilt if lost.
1190+ ///
1191+ /// If not set, non-critical data will be stored in the primary store.
1192+ ///
1193+ /// [`build_with_tier_store`]: Self::build_with_tier_store
1194+ pub fn set_tier_store_ephemeral ( & mut self , ephemeral_store : Arc < DynStore > ) {
1195+ self . inner . write ( ) . unwrap ( ) . set_tier_store_ephemeral ( ephemeral_store) ;
1196+ }
1197+
9921198 /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
9931199 /// previously configured.
9941200 pub fn build ( & self ) -> Result < Arc < Node > , BuildError > {
0 commit comments