Skip to content

Commit e368bec

Browse files
committed
Expose tier storage configuration across the FFI boundary
Add UniFFI-facing store abstractions and builder APIs so foreign-language callers can configure backup and ephemeral stores when constructing nodes with custom storage. This introduces `FfiDynStoreTrait` as an FFI-safe equivalent of `DynStoreTrait`, along with a Rust-side adapter that bridges foreign store implementations into the internal dynamic store abstraction used by the builder. As part of this change: - add UniFFI bindings for custom primary, backup, and ephemeral stores - expose `Builder::set_backup_store`, `Builder::set_ephemeral_store`, and `Builder::build_with_store` on the FFI surface - route FFI-backed builder construction through the native dyn-store path - move FFI IO-related types into a dedicated module - preserve per-key write ordering across the FFI boundary - route Rust-side sync access through the async mutation path to avoid runtime-sensitive locking behavior
1 parent 7720935 commit e368bec

File tree

7 files changed

+731
-24
lines changed

7 files changed

+731
-24
lines changed

bindings/ldk_node.udl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ interface LogWriter {
3232
void log(LogRecord record);
3333
};
3434

35+
[Trait, WithForeign]
36+
interface FfiDynStoreTrait {
37+
[Throws=IOError, Async]
38+
bytes read_async(string primary_namespace, string secondary_namespace, string key);
39+
[Throws=IOError, Async]
40+
void write_async(string primary_namespace, string secondary_namespace, string key, bytes buf);
41+
[Throws=IOError, Async]
42+
void remove_async(string primary_namespace, string secondary_namespace, string key, boolean lazy);
43+
[Throws=IOError, Async]
44+
sequence<string> list_async(string primary_namespace, string secondary_namespace);
45+
46+
[Throws=IOError]
47+
bytes read(string primary_namespace, string secondary_namespace, string key);
48+
[Throws=IOError]
49+
void write(string primary_namespace, string secondary_namespace, string key, bytes buf);
50+
[Throws=IOError]
51+
void remove(string primary_namespace, string secondary_namespace, string key, boolean lazy);
52+
[Throws=IOError]
53+
sequence<string> list(string primary_namespace, string secondary_namespace);
54+
};
55+
56+
3557
interface Builder {
3658
constructor();
3759
[Name=from_config]
@@ -58,6 +80,8 @@ interface Builder {
5880
void set_tor_config(TorConfig tor_config);
5981
[Throws=BuildError]
6082
void set_node_alias(string node_alias);
83+
void set_backup_store(FfiDynStoreTrait backup_store);
84+
void set_ephemeral_store(FfiDynStoreTrait ephemeral_store);
6185
[Throws=BuildError]
6286
void set_async_payments_role(AsyncPaymentsRole? role);
6387
void set_wallet_recovery_mode();
@@ -73,6 +97,8 @@ interface Builder {
7397
Node build_with_vss_store_and_fixed_headers(NodeEntropy node_entropy, string vss_url, string store_id, record<string, string> fixed_headers);
7498
[Throws=BuildError]
7599
Node build_with_vss_store_and_header_provider(NodeEntropy node_entropy, string vss_url, string store_id, VssHeaderProvider header_provider);
100+
[Throws=BuildError]
101+
Node build_with_store(NodeEntropy node_entropy, FfiDynStoreTrait store);
76102
};
77103

78104
interface Node {
@@ -227,6 +253,8 @@ enum NodeError {
227253
"InvalidLnurl",
228254
};
229255

256+
typedef enum IOError;
257+
230258
typedef dictionary NodeStatus;
231259

232260
[Remote]

src/builder.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ use crate::connection::ConnectionManager;
5252
use crate::entropy::NodeEntropy;
5353
use crate::event::EventQueue;
5454
use crate::fee_estimator::OnchainFeeEstimator;
55+
#[cfg(feature = "uniffi")]
56+
use crate::ffi::{FfiDynStore, FfiDynStoreTrait};
5557
use crate::gossip::GossipSource;
5658
use crate::io::sqlite_store::SqliteStore;
5759
use crate::io::tier_store::TierStore;
@@ -1152,6 +1154,32 @@ impl ArcedNodeBuilder {
11521154
self.inner.write().unwrap().set_wallet_recovery_mode();
11531155
}
11541156

1157+
/// Configures the backup store for local disaster recovery.
1158+
///
1159+
/// When building with tiered storage, this store receives a second durable
1160+
/// copy of data written to the primary store.
1161+
///
1162+
/// Writes and removals for primary-backed data only succeed once both the
1163+
/// primary and backup stores complete successfully.
1164+
///
1165+
/// If not set, durable data will be stored only in the primary store.
1166+
pub fn set_backup_store(&self, backup_store: Arc<dyn FfiDynStoreTrait>) {
1167+
let store: Arc<DynStore> = Arc::new(FfiDynStore::from_store(backup_store));
1168+
self.inner.write().unwrap().set_backup_store(store);
1169+
}
1170+
1171+
/// Configures the ephemeral store for non-critical, frequently-accessed data.
1172+
///
1173+
/// When building with tiered storage, this store is used for ephemeral data like
1174+
/// the network graph and scorer data to reduce latency for reads. Data stored here
1175+
/// can be rebuilt if lost.
1176+
///
1177+
/// If not set, non-critical data will be stored in the primary store.
1178+
pub fn set_ephemeral_store(&self, ephemeral_store: Arc<dyn FfiDynStoreTrait>) {
1179+
let store: Arc<DynStore> = Arc::new(FfiDynStore::from_store(ephemeral_store));
1180+
self.inner.write().unwrap().set_ephemeral_store(store);
1181+
}
1182+
11551183
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
11561184
/// previously configured.
11571185
pub fn build(&self, node_entropy: Arc<NodeEntropy>) -> Result<Arc<Node>, BuildError> {
@@ -1280,12 +1308,19 @@ impl ArcedNodeBuilder {
12801308
}
12811309

12821310
/// Builds a [`Node`] instance according to the options previously configured.
1283-
// Note that the generics here don't actually work for Uniffi, but we don't currently expose
1284-
// this so its not needed.
1285-
pub fn build_with_store<S: SyncAndAsyncKVStore + Send + Sync + 'static>(
1286-
&self, node_entropy: Arc<NodeEntropy>, kv_store: S,
1311+
///
1312+
/// The provided `kv_store` will be used as the primary storage backend. Optionally,
1313+
/// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
1314+
/// and a backup store for local disaster recovery can be configured via
1315+
/// [`set_ephemeral_store`] and [`set_backup_store`].
1316+
///
1317+
/// [`set_ephemeral_store`]: Self::set_ephemeral_store
1318+
/// [`set_backup_store`]: Self::set_backup_store
1319+
pub fn build_with_store(
1320+
&self, node_entropy: Arc<NodeEntropy>, kv_store: Arc<dyn FfiDynStoreTrait>,
12871321
) -> Result<Arc<Node>, BuildError> {
1288-
self.inner.read().unwrap().build_with_store(*node_entropy, kv_store).map(Arc::new)
1322+
let store: Arc<DynStore> = Arc::new(FfiDynStore::from_store(kv_store));
1323+
self.inner.read().unwrap().build_with_dynstore(*node_entropy, store).map(Arc::new)
12891324
}
12901325
}
12911326

0 commit comments

Comments
 (0)