Skip to content

Commit f88bdee

Browse files
committed
Expose tiered storage configuration across FFI
Add UniFFI-facing store abstractions and builder APIs so foreign-language callers can configure a custom primary store, an ephemeral store, and a local SQLite backup storage path when constructing nodes. 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, we: - add UniFFI bindings for custom primary and ephemeral stores, plus the backup storage directory - expose `Builder::set_backup_storage_dir_path`, `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 I/O-related types into a dedicated module - preserve per-key write ordering across the FFI boundary - route Rust-side synchronous access through the async mutation path so sync and async callers share the same ordering behavior
1 parent b5c3ce9 commit f88bdee

9 files changed

Lines changed: 743 additions & 29 deletions

File tree

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_storage_dir_path(string backup_storage_dir_path);
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 {
@@ -231,6 +257,8 @@ enum NodeError {
231257
"InvalidLnurl",
232258
};
233259

260+
typedef enum IOError;
261+
234262
typedef dictionary NodeStatus;
235263

236264
typedef enum BuildError;

src/builder.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ use crate::connection::ConnectionManager;
5656
use crate::entropy::NodeEntropy;
5757
use crate::event::EventQueue;
5858
use crate::fee_estimator::OnchainFeeEstimator;
59+
#[cfg(feature = "uniffi")]
60+
use crate::ffi::{FfiDynStore, FfiDynStoreTrait};
5961
use crate::gossip::GossipSource;
6062
use crate::io::sqlite_store::SqliteStore;
6163
use crate::io::tier_store::TierStore;
@@ -854,6 +856,7 @@ impl NodeBuilder {
854856
///
855857
/// [`set_ephemeral_store`]: Self::set_ephemeral_store
856858
/// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
859+
#[cfg(not(feature = "uniffi"))]
857860
pub fn build_with_store<S: SyncAndAsyncKVStore + Send + Sync + 'static>(
858861
&self, node_entropy: NodeEntropy, kv_store: S,
859862
) -> Result<Node, BuildError> {
@@ -863,6 +866,21 @@ impl NodeBuilder {
863866

864867
fn build_with_store_and_logger<S: SyncAndAsyncKVStore + Send + Sync + 'static>(
865868
&self, node_entropy: NodeEntropy, kv_store: S, logger: Arc<Logger>,
869+
) -> Result<Node, BuildError> {
870+
let primary_store: Arc<DynStore> = Arc::new(DynStoreWrapper(kv_store));
871+
self.build_with_dynstore_and_logger(node_entropy, primary_store, logger)
872+
}
873+
874+
#[cfg(feature = "uniffi")]
875+
fn build_with_dynstore(
876+
&self, node_entropy: NodeEntropy, primary_store: Arc<DynStore>,
877+
) -> Result<Node, BuildError> {
878+
let logger = setup_logger(&self.log_writer_config, &self.config)?;
879+
self.build_with_dynstore_and_logger(node_entropy, primary_store, logger)
880+
}
881+
882+
fn build_with_dynstore_and_logger(
883+
&self, node_entropy: NodeEntropy, primary_store: Arc<DynStore>, logger: Arc<Logger>,
866884
) -> Result<Node, BuildError> {
867885
let runtime = if let Some(handle) = self.runtime_handle.as_ref() {
868886
Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(&logger)))
@@ -874,10 +892,11 @@ impl NodeBuilder {
874892
};
875893

876894
let ts_config = self.tier_store_config.as_ref();
877-
let primary_store = Arc::new(DynStoreWrapper(kv_store));
878895
let mut tier_store = TierStore::new(primary_store, Arc::clone(&logger));
896+
879897
if let Some(config) = ts_config {
880898
config.ephemeral.as_ref().map(|s| tier_store.set_ephemeral_store(Arc::clone(s)));
899+
881900
if let Some(backup_storage_dir_path) = config.backup_storage_dir_path.as_ref() {
882901
let primary_storage_dir_path = PathBuf::from(&self.config.storage_dir_path);
883902
if primary_storage_dir_path == *backup_storage_dir_path {
@@ -898,13 +917,15 @@ impl NodeBuilder {
898917
log_error!(logger, "Failed to setup backup SQLite store: {}", e);
899918
BuildError::KVStoreSetupFailed
900919
})?;
920+
901921
let backup_store: Arc<DynStore> = Arc::new(DynStoreWrapper(backup_store));
902922
tier_store.set_backup_store(backup_store);
903923
}
904924
}
905925

906926
let seed_bytes = node_entropy.to_seed_bytes();
907927
let config = Arc::new(self.config.clone());
928+
let kv_store: Arc<DynStore> = Arc::new(DynStoreWrapper(tier_store));
908929

909930
build_with_store_internal(
910931
config,
@@ -917,7 +938,7 @@ impl NodeBuilder {
917938
seed_bytes,
918939
runtime,
919940
logger,
920-
Arc::new(DynStoreWrapper(tier_store)),
941+
kv_store,
921942
)
922943
}
923944
}
@@ -1240,8 +1261,9 @@ impl ArcedNodeBuilder {
12401261
/// can be rebuilt if lost.
12411262
///
12421263
/// If not set, non-critical data will be stored in the primary store.
1243-
pub fn set_ephemeral_store(&self, ephemeral_store: Arc<DynStore>) {
1244-
self.inner.write().expect("lock").set_ephemeral_store(ephemeral_store);
1264+
pub fn set_ephemeral_store(&self, ephemeral_store: Arc<dyn FfiDynStoreTrait>) {
1265+
let store: Arc<DynStore> = Arc::new(FfiDynStore::from_store(ephemeral_store));
1266+
self.inner.write().expect("lock").set_ephemeral_store(store);
12451267
}
12461268

12471269
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
@@ -1372,12 +1394,19 @@ impl ArcedNodeBuilder {
13721394
}
13731395

13741396
/// Builds a [`Node`] instance according to the options previously configured.
1375-
// Note that the generics here don't actually work for Uniffi, but we don't currently expose
1376-
// this so its not needed.
1377-
pub fn build_with_store<S: SyncAndAsyncKVStore + Send + Sync + 'static>(
1378-
&self, node_entropy: Arc<NodeEntropy>, kv_store: S,
1397+
///
1398+
/// The provided `kv_store` will be used as the primary storage backend. Optionally,
1399+
/// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
1400+
/// and a backup store for local disaster recovery can be configured via
1401+
/// [`set_ephemeral_store`] and [`set_backup_storage_dir_path`].
1402+
///
1403+
/// [`set_ephemeral_store`]: Self::set_ephemeral_store
1404+
/// [`set_backup_storage_dir_path`]: Self::set_backup_storage_dir_path
1405+
pub fn build_with_store(
1406+
&self, node_entropy: Arc<NodeEntropy>, kv_store: Arc<dyn FfiDynStoreTrait>,
13791407
) -> Result<Arc<Node>, BuildError> {
1380-
self.inner.read().expect("lock").build_with_store(*node_entropy, kv_store).map(Arc::new)
1408+
let store: Arc<DynStore> = Arc::new(FfiDynStore::from_store(kv_store));
1409+
self.inner.read().expect("lock").build_with_dynstore(*node_entropy, store).map(Arc::new)
13811410
}
13821411
}
13831412

0 commit comments

Comments
 (0)