Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 85 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io::sqlite_store::SqliteStore;
use crate::io::tier_store::TierStore;
use crate::io::utils::{
open_or_migrate_fs_store, read_all_objects, read_event_queue,
read_external_pathfinding_scores_from_cache, read_network_graph, read_node_metrics,
Expand Down Expand Up @@ -154,6 +155,12 @@ impl std::fmt::Debug for LogWriterConfig {
}
}

#[derive(Default, Debug)]
struct TierStoreConfig {
ephemeral_storage_dir_path: Option<PathBuf>,
backup_storage_dir_path: Option<PathBuf>,
}

/// An error encountered during building a [`Node`].
///
/// [`Node`]: crate::Node
Expand Down Expand Up @@ -289,6 +296,7 @@ pub struct NodeBuilder {
liquidity_source_config: Option<LiquiditySourceConfig>,
log_writer_config: Option<LogWriterConfig>,
async_payments_role: Option<AsyncPaymentsRole>,
tier_store_config: Option<TierStoreConfig>,
runtime_handle: Option<tokio::runtime::Handle>,
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
recovery_mode: bool,
Expand All @@ -307,6 +315,7 @@ impl NodeBuilder {
let gossip_source_config = None;
let liquidity_source_config = None;
let log_writer_config = None;
let tier_store_config = None;
let runtime_handle = None;
let pathfinding_scores_sync_config = None;
let recovery_mode = false;
Expand All @@ -316,6 +325,7 @@ impl NodeBuilder {
gossip_source_config,
liquidity_source_config,
log_writer_config,
tier_store_config,
runtime_handle,
async_payments_role: None,
pathfinding_scores_sync_config,
Expand Down Expand Up @@ -625,6 +635,39 @@ impl NodeBuilder {
self
}

/// Configures a local SQLite backup store for disaster recovery.
///
/// When building with tiered storage, a SQLite store will be created at the
/// given directory path using [`SQLITE_BACKUP_DB_FILE_NAME`] as its database
/// file name. It receives a second durable copy of data written to the
/// primary store.
///
/// Writes and removals for primary-backed data only succeed once both the
/// primary and backup SQLite stores complete successfully.
///
/// If not set, durable data will be stored only in the primary store.
///
/// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
pub fn set_backup_storage_dir_path(&mut self, backup_storage_dir_path: String) -> &mut Self {
let tier_store_config = self.tier_store_config.get_or_insert(TierStoreConfig::default());
tier_store_config.backup_storage_dir_path = Some(backup_storage_dir_path.into());
self
}

/// Configures the ephemeral storage directory path for non-critical, frequently-accessed data.
///
/// When set, a local SQLite store is created at this path for ephemeral data like
/// the network graph and scorer. Data stored here can be rebuilt if lost.
///
/// If not set, non-critical data will be stored in the primary store.
pub fn set_ephemeral_storage_dir_path(
&mut self, ephemeral_storage_dir_path: String,
) -> &mut Self {
let tier_store_config = self.tier_store_config.get_or_insert(TierStoreConfig::default());
tier_store_config.ephemeral_storage_dir_path = Some(ephemeral_storage_dir_path.into());
self
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
Expand Down Expand Up @@ -826,11 +869,18 @@ impl NodeBuilder {
}

/// Builds a [`Node`] instance according to the options previously configured.
///
/// The provided `kv_store` will be used as the primary storage backend. Optionally,
/// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
/// and a local SQLite backup store for disaster recovery can be configured via
/// [`set_ephemeral_storage_dir_path`] and [`set_backup_storage_dir_path`].
///
/// [`set_ephemeral_storage_dir_path`]: Self::set_ephemeral_storage_dir_path
/// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
pub fn build_with_store<S: KVStore + Send + Sync + 'static>(
&self, node_entropy: NodeEntropy, kv_store: S,
) -> Result<Node, BuildError> {
let logger = setup_logger(&self.log_writer_config, &self.config)?;

self.build_with_store_and_logger(node_entropy, kv_store, logger)
}

Expand All @@ -855,6 +905,39 @@ impl NodeBuilder {
fn build_with_store_runtime_and_logger<S: KVStore + Send + Sync + 'static>(
&self, node_entropy: NodeEntropy, kv_store: S, runtime: Arc<Runtime>, logger: Arc<Logger>,
) -> Result<Node, BuildError> {
let ts_config = self.tier_store_config.as_ref();
let primary_store = Arc::new(DynStoreWrapper(kv_store));
let mut tier_store = TierStore::new(primary_store, Arc::clone(&logger));
if let Some(config) = ts_config {
if let Some(ephemeral_storage_dir_path) = config.ephemeral_storage_dir_path.as_ref() {
let ephemeral_store = SqliteStore::new(
ephemeral_storage_dir_path.clone(),
Some(io::sqlite_store::SQLITE_EPHEMERAL_DB_FILE_NAME.to_string()),
Some(io::sqlite_store::KV_TABLE_NAME.to_string()),
)
.map_err(|e| {
log_error!(logger, "Failed to setup ephemeral SQLite store: {}", e);
BuildError::KVStoreSetupFailed
})?;
let ephemeral_store: Arc<DynStore> = Arc::new(DynStoreWrapper(ephemeral_store));
tier_store.set_ephemeral_store(ephemeral_store);
}

if let Some(backup_storage_dir_path) = config.backup_storage_dir_path.as_ref() {
let backup_store = SqliteStore::new(
backup_storage_dir_path.clone(),
Some(io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME.to_string()),
Some(io::sqlite_store::KV_TABLE_NAME.to_string()),
)
.map_err(|e| {
log_error!(logger, "Failed to setup backup SQLite store: {}", e);
BuildError::KVStoreSetupFailed
})?;
let backup_store: Arc<DynStore> = Arc::new(DynStoreWrapper(backup_store));
tier_store.set_backup_store(backup_store);
}
}

let seed_bytes = node_entropy.to_seed_bytes();
let config = Arc::new(self.config.clone());

Expand All @@ -869,7 +952,7 @@ impl NodeBuilder {
seed_bytes,
runtime,
logger,
Arc::new(DynStoreWrapper(kv_store)),
Arc::new(DynStoreWrapper(tier_store)),
)
}
}
Expand Down
1 change: 1 addition & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod postgres_store;
pub mod sqlite_store;
#[cfg(test)]
pub(crate) mod test_utils;
pub(crate) mod tier_store;
pub(crate) mod utils;
pub mod vss_store;

Expand Down
4 changes: 4 additions & 0 deletions src/io/sqlite_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ mod migrations;

/// LDK Node's database file name.
pub const SQLITE_DB_FILE_NAME: &str = "ldk_node_data.sqlite";
/// LDK Node's backup database file name.
pub const SQLITE_BACKUP_DB_FILE_NAME: &str = "ldk_node_data_backup.sqlite";
/// LDK Node's ephemeral database file name.
pub const SQLITE_EPHEMERAL_DB_FILE_NAME: &str = "ldk_node_data_ephemeral.sqlite";
/// LDK Node's table in which we store all data.
pub const KV_TABLE_NAME: &str = "ldk_node_data";

Expand Down
Loading