Skip to content

Commit 9cdf7a0

Browse files
committed
Add Rust integration tests for tiered storage
Extend the Rust test harness to support tiered store configurations and add integration coverage for routing data across primary, backup, and ephemeral stores. This introduces tier-store-aware test helpers, including support for configuring separate stores per node, opening the internally-created SQLite backup store for inspection, and reading test stores in both native and UniFFI-enabled builds. Add an integration test covering the tiered-storage channel lifecycle and verifying that: - durable node data is persisted to both the primary and backup stores - ephemeral-routed data is stored in the ephemeral tier - ephemeral data is not mirrored back into the durable tiers
1 parent f88bdee commit 9cdf7a0

3 files changed

Lines changed: 297 additions & 39 deletions

File tree

benches/payments.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ fn payment_benchmark(c: &mut Criterion) {
127127
true,
128128
false,
129129
common::TestStoreType::Sqlite,
130+
common::TestStoreType::Sqlite,
130131
);
131132

132133
let runtime =

tests/common/mod.rs

Lines changed: 135 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ use ldk_node::config::{
4343
use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
4444
use ldk_node::io::sqlite_store::SqliteStore;
4545
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
46-
#[cfg(feature = "uniffi")]
4746
use ldk_node::DynStoreWrapper;
4847
use ldk_node::{
4948
Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError,
@@ -414,10 +413,19 @@ pub(crate) enum TestChainSource<'a> {
414413
BitcoindRestSync(&'a BitcoinD),
415414
}
416415

417-
#[derive(Clone, Copy)]
416+
#[cfg(feature = "uniffi")]
417+
use ldk_node::FfiDynStoreTrait;
418+
419+
#[cfg(feature = "uniffi")]
420+
type TestDynStore = Arc<dyn FfiDynStoreTrait>;
421+
#[cfg(not(feature = "uniffi"))]
422+
type TestDynStore = TestSyncStore;
423+
424+
#[derive(Clone)]
418425
pub(crate) enum TestStoreType {
419426
TestSyncStore,
420427
Sqlite,
428+
TierStore { primary: TestDynStore, ephemeral: Option<TestDynStore> },
421429
}
422430

423431
impl Default for TestStoreType {
@@ -434,6 +442,7 @@ pub(crate) struct TestConfig {
434442
pub node_entropy: NodeEntropy,
435443
pub async_payments_role: Option<AsyncPaymentsRole>,
436444
pub recovery_mode: bool,
445+
pub backup_storage_dir_path: Option<String>,
437446
}
438447

439448
impl Default for TestConfig {
@@ -446,13 +455,15 @@ impl Default for TestConfig {
446455
let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
447456
let async_payments_role = None;
448457
let recovery_mode = false;
458+
let backup_storage_dir_path = None;
449459
TestConfig {
450460
node_config,
451461
log_writer,
452462
store_type,
453463
node_entropy,
454464
async_payments_role,
455465
recovery_mode,
466+
backup_storage_dir_path,
456467
}
457468
}
458469
}
@@ -469,6 +480,35 @@ pub(crate) use setup_builder;
469480
#[cfg(any(cln_test, lnd_test, eclair_test))]
470481
pub(crate) mod scenarios;
471482

483+
// Helper function to create primary and ephemeral tier stores.
484+
pub(crate) fn create_primary_and_ephemeral_stores(
485+
base_path: PathBuf,
486+
) -> (TestDynStore, TestDynStore) {
487+
let primary = TestSyncStore::new(base_path.join("primary"));
488+
let ephemeral = TestSyncStore::new(base_path.join("ephemeral"));
489+
490+
#[cfg(feature = "uniffi")]
491+
{
492+
(
493+
Arc::new(DynStoreWrapper(primary)) as Arc<dyn FfiDynStoreTrait>,
494+
Arc::new(DynStoreWrapper(ephemeral)) as Arc<dyn FfiDynStoreTrait>,
495+
)
496+
}
497+
#[cfg(not(feature = "uniffi"))]
498+
{
499+
(primary, ephemeral)
500+
}
501+
}
502+
503+
pub(crate) fn open_test_backup_store(path: PathBuf) -> SqliteStore {
504+
SqliteStore::new(
505+
path,
506+
Some(ldk_node::io::sqlite_store::SQLITE_BACKUP_DB_FILE_NAME.to_string()),
507+
Some(ldk_node::io::sqlite_store::KV_TABLE_NAME.to_string()),
508+
)
509+
.unwrap()
510+
}
511+
472512
pub(crate) fn setup_two_nodes(
473513
chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool,
474514
anchors_trusted_no_reserve: bool,
@@ -479,16 +519,15 @@ pub(crate) fn setup_two_nodes(
479519
anchor_channels,
480520
anchors_trusted_no_reserve,
481521
TestStoreType::TestSyncStore,
522+
TestStoreType::TestSyncStore,
482523
)
483524
}
484525

485-
pub(crate) fn setup_two_nodes_with_store(
526+
pub(crate) fn setup_two_nodes_with_config(
486527
chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool,
487-
anchors_trusted_no_reserve: bool, store_type: TestStoreType,
528+
anchors_trusted_no_reserve: bool, mut config_a: TestConfig, mut config_b: TestConfig,
488529
) -> (TestNode, TestNode) {
489530
println!("== Node A ==");
490-
let mut config_a = random_config(anchor_channels);
491-
config_a.store_type = store_type;
492531

493532
if cfg!(hrn_tests) {
494533
config_a.node_config.hrn_config =
@@ -498,8 +537,6 @@ pub(crate) fn setup_two_nodes_with_store(
498537
let node_a = setup_node(chain_source, config_a);
499538

500539
println!("\n== Node B ==");
501-
let mut config_b = random_config(anchor_channels);
502-
config_b.store_type = store_type;
503540

504541
if cfg!(hrn_tests) {
505542
config_b.node_config.hrn_config = HumanReadableNamesConfig {
@@ -522,10 +559,31 @@ pub(crate) fn setup_two_nodes_with_store(
522559
.trusted_peers_no_reserve
523560
.push(node_a.node_id());
524561
}
562+
525563
let node_b = setup_node(chain_source, config_b);
526564
(node_a, node_b)
527565
}
528566

567+
pub(crate) fn setup_two_nodes_with_store(
568+
chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool,
569+
anchors_trusted_no_reserve: bool, store_type_a: TestStoreType, store_type_b: TestStoreType,
570+
) -> (TestNode, TestNode) {
571+
let mut config_a = random_config(anchor_channels);
572+
config_a.store_type = store_type_a;
573+
574+
let mut config_b = random_config(anchor_channels);
575+
config_b.store_type = store_type_b;
576+
577+
setup_two_nodes_with_config(
578+
chain_source,
579+
allow_0conf,
580+
anchor_channels,
581+
anchors_trusted_no_reserve,
582+
config_a,
583+
config_b,
584+
)
585+
}
586+
529587
pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestNode {
530588
setup_node_with_builder(chain_source, config, |_| {})
531589
}
@@ -600,15 +658,46 @@ where
600658

601659
let node = match config.store_type {
602660
TestStoreType::TestSyncStore => {
603-
#[cfg(not(feature = "uniffi"))]
604661
let kv_store = TestSyncStore::new(config.node_config.storage_dir_path.into());
662+
605663
#[cfg(feature = "uniffi")]
606-
let kv_store = Arc::new(DynStoreWrapper(TestSyncStore::new(
607-
config.node_config.storage_dir_path.into(),
608-
)));
609-
builder.build_with_store(config.node_entropy.into(), kv_store).unwrap()
664+
{
665+
let kv_store: Arc<dyn FfiDynStoreTrait> = Arc::new(DynStoreWrapper(kv_store));
666+
builder.build_with_store(config.node_entropy.into(), kv_store).unwrap()
667+
}
668+
669+
#[cfg(not(feature = "uniffi"))]
670+
{
671+
builder.build_with_store(config.node_entropy.into(), kv_store).unwrap()
672+
}
610673
},
611674
TestStoreType::Sqlite => builder.build(config.node_entropy.into()).unwrap(),
675+
TestStoreType::TierStore { primary, ephemeral } => {
676+
if let Some(backup_storage_dir_path) = config.backup_storage_dir_path {
677+
builder.set_backup_storage_dir_path(backup_storage_dir_path);
678+
}
679+
680+
if let Some(ephemeral) = ephemeral {
681+
#[cfg(feature = "uniffi")]
682+
{
683+
builder.set_ephemeral_store(ephemeral);
684+
}
685+
#[cfg(not(feature = "uniffi"))]
686+
{
687+
use ldk_node::DynStore;
688+
let store: Arc<DynStore> = Arc::new(DynStoreWrapper(ephemeral));
689+
builder.set_ephemeral_store(store);
690+
}
691+
}
692+
#[cfg(feature = "uniffi")]
693+
{
694+
builder.build_with_store(config.node_entropy.into(), primary).unwrap()
695+
}
696+
#[cfg(not(feature = "uniffi"))]
697+
{
698+
builder.build_with_store(config.node_entropy, primary).unwrap()
699+
}
700+
},
612701
};
613702

614703
node.start().unwrap();
@@ -1931,3 +2020,36 @@ impl TestSyncStoreInner {
19312020
self.do_list(primary_namespace, secondary_namespace)
19322021
}
19332022
}
2023+
2024+
pub fn test_kv_read(
2025+
store: &TestDynStore, primary_ns: &str, secondary_ns: &str, key: &str,
2026+
) -> Result<Vec<u8>, bitcoin::io::Error> {
2027+
#[cfg(feature = "uniffi")]
2028+
{
2029+
ldk_node::FfiDynStoreTrait::read(
2030+
&**store,
2031+
primary_ns.to_string(),
2032+
secondary_ns.to_string(),
2033+
key.to_string(),
2034+
)
2035+
.map_err(Into::into)
2036+
}
2037+
#[cfg(not(feature = "uniffi"))]
2038+
{
2039+
KVStoreSync::read(store, primary_ns, secondary_ns, key)
2040+
}
2041+
}
2042+
2043+
pub fn test_kv_list(
2044+
store: &TestDynStore, primary_ns: &str, secondary_ns: &str,
2045+
) -> Result<Vec<String>, bitcoin::io::Error> {
2046+
#[cfg(feature = "uniffi")]
2047+
{
2048+
ldk_node::FfiDynStoreTrait::list(&**store, primary_ns.to_string(), secondary_ns.to_string())
2049+
.map_err(Into::into)
2050+
}
2051+
#[cfg(not(feature = "uniffi"))]
2052+
{
2053+
KVStoreSync::list(store, primary_ns, secondary_ns)
2054+
}
2055+
}

0 commit comments

Comments
 (0)