Skip to content

Commit 13633ed

Browse files
committed
Gate tiered storage behind a Cargo feature
Add the opt-in storage-tier feature, gate tier-specific APIs and implementation, and preserve direct store usage when disabled. Document the feature, expose it on docs.rs, and run targeted tier storage tests in CI. AI-assisted: Developed with Amp.
1 parent ed08cd2 commit 13633ed

7 files changed

Lines changed: 70 additions & 42 deletions

File tree

.github/workflows/rust.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ jobs:
8686
if: "matrix.platform != 'windows-latest'"
8787
run: |
8888
RUSTFLAGS="--cfg no_download --cfg cycle_tests --cfg tokio_unstable" cargo test
89+
- name: Test tiered storage
90+
if: matrix.check-fmt
91+
run: |
92+
cargo test --lib --features storage-tier io::tier_store
93+
RUSTFLAGS="--cfg no_download" cargo test \
94+
--features storage-tier \
95+
--test integration_tests_rust \
96+
builder_configures_sqlite_backup_store
8997
- name: Test with UniFFI support on Rust ${{ matrix.toolchain }}
9098
if: "matrix.platform != 'windows-latest' && matrix.build-uniffi"
9199
run: |

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ keywords = ["bitcoin", "lightning", "ldk", "bdk"]
1313
categories = ["cryptography::cryptocurrencies"]
1414

1515
[package.metadata.docs.rs]
16-
features = ["storage-postgres-vendored-tls"]
16+
features = ["storage-postgres-vendored-tls", "storage-tier"]
1717
rustdoc-args = ["--cfg", "docsrs"]
1818

1919
[lib]
@@ -53,6 +53,7 @@ chain-electrum = [
5353
]
5454
chain-bitcoind = ["dep:lightning-block-sync"]
5555
storage-sqlite = ["dep:rusqlite"]
56+
storage-tier = ["storage-sqlite"]
5657
storage-filesystem = ["dep:lightning-persister"]
5758
storage-vss = ["dep:vss-client", "dep:prost"]
5859
storage-postgres = ["dep:tokio-postgres", "dep:native-tls", "dep:postgres-native-tls"]

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ LDK Node's optional dependencies are grouped by the functionality they provide:
7878
| `chain-electrum` | Electrum chain source |
7979
| `chain-bitcoind` | Bitcoin Core RPC and REST chain source |
8080
| `storage-sqlite` | SQLite storage |
81+
| `storage-tier` | Tiered storage with optional ephemeral and backup SQLite stores |
8182
| `storage-filesystem` | Filesystem storage |
8283
| `storage-vss` | Versioned Storage Service storage |
8384
| `storage-postgres` | PostgreSQL storage |
@@ -88,7 +89,8 @@ LDK Node's optional dependencies are grouped by the functionality they provide:
8889

8990
The `default` feature set preserves the native Rust API's previous behavior. It enables all three
9091
chain sources, SQLite, filesystem and VSS storage, and unified payments. PostgreSQL and UniFFI
91-
remain opt-in. Every build must enable at least one chain source feature.
92+
remain opt-in. Tiered storage is also opt-in; enabling `storage-tier` automatically enables
93+
`storage-sqlite`. Every build must enable at least one chain source feature.
9294

9395
On Linux, `storage-postgres` uses the system OpenSSL installation and requires the OpenSSL
9496
development headers and `pkg-config`. Enable `storage-postgres-vendored-tls` instead to build

src/builder.rs

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::convert::TryInto;
1111
use std::default::Default;
1212
#[cfg(feature = "unified-payments")]
1313
use std::net::ToSocketAddrs;
14-
#[cfg(feature = "storage-filesystem")]
14+
#[cfg(any(feature = "storage-filesystem", feature = "storage-tier"))]
1515
use std::path::PathBuf;
1616
use std::sync::{Arc, Mutex, Once, RwLock};
1717
use std::time::SystemTime;
@@ -72,6 +72,7 @@ use crate::gossip::GossipSource;
7272
use crate::io::fs_store::open_or_migrate_fs_store;
7373
#[cfg(feature = "storage-sqlite")]
7474
use crate::io::sqlite_store::SqliteStore;
75+
#[cfg(feature = "storage-tier")]
7576
use crate::io::tier_store::{setup_index_store, TierStore};
7677
use crate::io::utils::{
7778
read_all_objects, read_event_queue, read_external_pathfinding_scores_from_cache,
@@ -174,6 +175,7 @@ impl std::fmt::Debug for LogWriterConfig {
174175
}
175176
}
176177

178+
#[cfg(feature = "storage-tier")]
177179
#[derive(Default, Debug)]
178180
struct TierStoreConfig {
179181
ephemeral_storage_dir_path: Option<PathBuf>,
@@ -333,6 +335,7 @@ pub struct NodeBuilder {
333335
liquidity_source_config: Option<LiquiditySourceConfig>,
334336
log_writer_config: Option<LogWriterConfig>,
335337
async_payments_role: Option<AsyncPaymentsRole>,
338+
#[cfg(feature = "storage-tier")]
336339
tier_store_config: Option<TierStoreConfig>,
337340
runtime_handle: Option<tokio::runtime::Handle>,
338341
pathfinding_scores_sync_config: Option<PathfindingScoresSyncConfig>,
@@ -355,6 +358,7 @@ impl NodeBuilder {
355358
let gossip_source_config = None;
356359
let liquidity_source_config = None;
357360
let log_writer_config = None;
361+
#[cfg(feature = "storage-tier")]
358362
let tier_store_config = None;
359363
let runtime_handle = None;
360364
let pathfinding_scores_sync_config = None;
@@ -365,6 +369,7 @@ impl NodeBuilder {
365369
gossip_source_config,
366370
liquidity_source_config,
367371
log_writer_config,
372+
#[cfg(feature = "storage-tier")]
368373
tier_store_config,
369374
runtime_handle,
370375
async_payments_role: None,
@@ -709,7 +714,7 @@ impl NodeBuilder {
709714
/// If not set, durable data will be stored only in the primary store.
710715
///
711716
/// [`SQLITE_BACKUP_DB_FILE_NAME`]: crate::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME
712-
#[cfg(not(feature = "uniffi"))]
717+
#[cfg(all(not(feature = "uniffi"), feature = "storage-tier"))]
713718
pub fn set_backup_storage_dir_path(&mut self, backup_storage_dir_path: String) -> &mut Self {
714719
let tier_store_config = self.tier_store_config.get_or_insert(TierStoreConfig::default());
715720
tier_store_config.backup_storage_dir_path = Some(backup_storage_dir_path.into());
@@ -722,7 +727,7 @@ impl NodeBuilder {
722727
/// the network graph and scorer. Data stored here can be rebuilt if lost.
723728
///
724729
/// If not set, non-critical data will be stored in the primary store.
725-
#[cfg(not(feature = "uniffi"))]
730+
#[cfg(all(not(feature = "uniffi"), feature = "storage-tier"))]
726731
pub fn set_ephemeral_storage_dir_path(
727732
&mut self, ephemeral_storage_dir_path: String,
728733
) -> &mut Self {
@@ -982,45 +987,52 @@ impl NodeBuilder {
982987
fn build_with_store_runtime_and_logger<S: PaginatedKVStore + Send + Sync + 'static>(
983988
&self, node_entropy: NodeEntropy, kv_store: S, runtime: Arc<Runtime>, logger: Arc<Logger>,
984989
) -> 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()))
990+
#[cfg(feature = "storage-tier")]
991+
let store: Arc<DynStore> = {
992+
let ts_config = self.tier_store_config.as_ref();
993+
let primary_store = Arc::new(DynStoreWrapper(kv_store));
994+
let mut tier_store = TierStore::new(primary_store, Arc::clone(&logger));
995+
if let Some(config) = ts_config {
996+
if let Some(ephemeral_storage_dir_path) = config.ephemeral_storage_dir_path.as_ref()
997+
{
998+
let index_store = runtime
999+
.block_on(setup_index_store(self.config.storage_dir_path.clone().into()))
1000+
.map_err(|e| {
1001+
log_error!(logger, "Failed to setup tier-store index: {}", e);
1002+
BuildError::KVStoreSetupFailed
1003+
})?;
1004+
let ephemeral_store = SqliteStore::new(
1005+
ephemeral_storage_dir_path.clone(),
1006+
Some(io::sqlite_store::SQLITE_EPHEMERAL_DB_FILE_NAME.to_string()),
1007+
Some(io::sqlite_store::KV_TABLE_NAME.to_string()),
1008+
)
9921009
.map_err(|e| {
993-
log_error!(logger, "Failed to setup tier-store index: {}", e);
1010+
log_error!(logger, "Failed to setup ephemeral SQLite store: {}", e);
9941011
BuildError::KVStoreSetupFailed
9951012
})?;
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-
}
1013+
let ephemeral_store: Arc<DynStore> = Arc::new(DynStoreWrapper(ephemeral_store));
1014+
tier_store.set_index_store(index_store);
1015+
tier_store.set_ephemeral_store(ephemeral_store);
1016+
}
10091017

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);
1018+
if let Some(backup_storage_dir_path) = config.backup_storage_dir_path.as_ref() {
1019+
let backup_store = SqliteStore::new(
1020+
backup_storage_dir_path.clone(),
1021+
Some(io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME.to_string()),
1022+
Some(io::sqlite_store::KV_TABLE_NAME.to_string()),
1023+
)
1024+
.map_err(|e| {
1025+
log_error!(logger, "Failed to setup backup SQLite store: {}", e);
1026+
BuildError::KVStoreSetupFailed
1027+
})?;
1028+
let backup_store: Arc<DynStore> = Arc::new(DynStoreWrapper(backup_store));
1029+
tier_store.set_backup_store(backup_store);
1030+
}
10221031
}
1023-
}
1032+
Arc::new(DynStoreWrapper(tier_store))
1033+
};
1034+
#[cfg(not(feature = "storage-tier"))]
1035+
let store: Arc<DynStore> = Arc::new(DynStoreWrapper(kv_store));
10241036

10251037
let seed_bytes = node_entropy.to_seed_bytes();
10261038
let config = Arc::new(self.config.clone());
@@ -1036,7 +1048,7 @@ impl NodeBuilder {
10361048
seed_bytes,
10371049
runtime,
10381050
logger,
1039-
Arc::new(DynStoreWrapper(tier_store)),
1051+
store,
10401052
)
10411053
}
10421054
}

src/io/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod postgres_store;
1515
pub mod sqlite_store;
1616
#[cfg(test)]
1717
pub(crate) mod test_utils;
18+
#[cfg(feature = "storage-tier")]
1819
pub(crate) mod tier_store;
1920
pub(crate) mod utils;
2021
#[cfg(feature = "storage-vss")]

src/io/sqlite_store/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ mod migrations;
2929
/// LDK Node's database file name.
3030
pub const SQLITE_DB_FILE_NAME: &str = "ldk_node_data.sqlite";
3131
/// LDK Node's internal tier-store index database file name.
32+
#[cfg(feature = "storage-tier")]
3233
pub(crate) const SQLITE_TIER_INDEX_DB_FILE_NAME: &str = "ldk_node_tier_index.sqlite";
3334
/// LDK Node's backup database file name.
35+
#[cfg(feature = "storage-tier")]
3436
pub const SQLITE_BACKUP_DB_FILE_NAME: &str = "ldk_node_data_backup.sqlite";
3537
/// LDK Node's ephemeral database file name.
38+
#[cfg(feature = "storage-tier")]
3639
pub const SQLITE_EPHEMERAL_DB_FILE_NAME: &str = "ldk_node_data_ephemeral.sqlite";
3740
/// LDK Node's table in which we store all data.
3841
pub const KV_TABLE_NAME: &str = "ldk_node_data";
@@ -85,6 +88,7 @@ impl SqliteStore {
8588
}
8689

8790
/// Constructs a new [`SqliteStore`] which exclusively owns its database for its lifetime.
91+
#[cfg(feature = "storage-tier")]
8892
pub(crate) fn new_exclusive(
8993
data_dir: PathBuf, db_file_name: Option<String>, kv_table_name: Option<String>,
9094
) -> io::Result<Self> {

tests/integration_tests_rust.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use ldk_node::config::{
3838
AsyncPaymentsRole, EsploraSyncConfig, ADDRESS_POOL_SIZE, DEFAULT_FULL_SCAN_STOP_GAP,
3939
};
4040
use ldk_node::entropy::NodeEntropy;
41-
#[cfg(not(feature = "uniffi"))]
41+
#[cfg(all(not(feature = "uniffi"), feature = "storage-tier"))]
4242
use ldk_node::io::sqlite_store::SqliteStore;
4343
use ldk_node::liquidity::LSPS2ServiceConfig;
4444
use ldk_node::payment::{
@@ -4942,7 +4942,7 @@ async fn do_lsps2_multi_lsp_picks_cheapest(reverse_order: bool) {
49424942
}
49434943

49444944
// Builder backup-store configuration is not yet exposed via FFI (see #871)
4945-
#[cfg(not(feature = "uniffi"))]
4945+
#[cfg(all(not(feature = "uniffi"), feature = "storage-tier"))]
49464946
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
49474947
async fn builder_configures_sqlite_backup_store() {
49484948
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)