Skip to content

Commit 451a392

Browse files
committed
Expose tier storage configuration across the FFI boundary
Introduce FFI-safe abstractions and builder APIs to allow foreign language targets to configure custom backup and ephemeral stores when constructing nodes with a custom store. Major changes include: - Addition of FfiDynStoreTrait, an FFI-safe equivalent of DynStoreTrait, working around uniffi's lack of support for Pin<Box<T>> - Addition of FfiDynStore, a concrete wrapper for foreign language store implementations - Provision of FfiDynStoreTrait implementation for DynStoreWrapper to bridge native Rust stores to FFI layer (useful in testing) - Extension of ArcedNodeBuilder with methods for configuring backup and ephemeral stores - Exposure of build_with_store so foreign targets can build nodes with custom store implementations - Addition of build_node_with_store test helper to abstract uniffi-gated store wrapping at build_with_store call sites
1 parent 42cae4e commit 451a392

File tree

8 files changed

+434
-19
lines changed

8 files changed

+434
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ bitcoin = "0.32.7"
6363
bip39 = { version = "2.0.0", features = ["rand"] }
6464
bip21 = { version = "0.5", features = ["std"], default-features = false }
6565

66+
async-trait = { version = "0.1.89" }
6667
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
6768
rand = { version = "0.9.2", default-features = false, features = ["std", "thread_rng", "os_rng"] }
6869
chrono = { version = "0.4", default-features = false, features = ["clock"] }

bindings/ldk_node.udl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,53 @@ interface LogWriter {
103103
void log(LogRecord record);
104104
};
105105

106+
interface FfiDynStore {
107+
[Name=from_store]
108+
constructor(FfiDynStoreTrait store);
109+
};
110+
111+
[Trait, WithForeign]
112+
interface FfiDynStoreTrait {
113+
[Throws=IOError]
114+
sequence<u8> read(string primary_namespace, string secondary_namespace, string key);
115+
[Throws=IOError]
116+
void write(string primary_namespace, string secondary_namespace, string key, sequence<u8> buf);
117+
[Throws=IOError]
118+
void remove(string primary_namespace, string secondary_namespace, string key, boolean lazy);
119+
[Throws=IOError]
120+
sequence<string> list(string primary_namespace, string secondary_namespace);
121+
[Throws=IOError, Async]
122+
sequence<u8> read_async(string primary_namespace, string secondary_namespace, string key);
123+
[Throws=IOError, Async]
124+
void write_async(string primary_namespace, string secondary_namespace, string key, sequence<u8> buf);
125+
[Throws=IOError, Async]
126+
void remove_async(string primary_namespace, string secondary_namespace, string key, boolean lazy);
127+
[Throws=IOError, Async]
128+
sequence<string> list_async(string primary_namespace, string secondary_namespace);
129+
};
130+
131+
[Error]
132+
enum IOError {
133+
"NotFound",
134+
"PermissionDenied",
135+
"ConnectionRefused",
136+
"ConnectionReset",
137+
"ConnectionAborted",
138+
"NotConnected",
139+
"AddrInUse",
140+
"AddrNotAvailable",
141+
"BrokenPipe",
142+
"AlreadyExists",
143+
"WouldBlock",
144+
"InvalidInput",
145+
"InvalidData",
146+
"TimedOut",
147+
"WriteZero",
148+
"Interrupted",
149+
"UnexpectedEof",
150+
"Other",
151+
};
152+
106153
interface Builder {
107154
constructor();
108155
[Name=from_config]
@@ -127,6 +174,8 @@ interface Builder {
127174
void set_announcement_addresses(sequence<SocketAddress> announcement_addresses);
128175
[Throws=BuildError]
129176
void set_node_alias(string node_alias);
177+
void set_backup_store(FfiDynStore backup_store);
178+
void set_ephemeral_store(FfiDynStore ephemeral_store);
130179
[Throws=BuildError]
131180
void set_async_payments_role(AsyncPaymentsRole? role);
132181
void set_wallet_recovery_mode();
@@ -140,6 +189,8 @@ interface Builder {
140189
Node build_with_vss_store_and_fixed_headers(NodeEntropy node_entropy, string vss_url, string store_id, record<string, string> fixed_headers);
141190
[Throws=BuildError]
142191
Node build_with_vss_store_and_header_provider(NodeEntropy node_entropy, string vss_url, string store_id, VssHeaderProvider header_provider);
192+
[Throws=BuildError]
193+
Node build_with_store(NodeEntropy node_entropy, FfiDynStore store);
143194
};
144195

145196
interface Node {

src/builder.rs

Lines changed: 40 additions & 7 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;
5557
use crate::gossip::GossipSource;
5658
use crate::io::sqlite_store::SqliteStore;
5759
use crate::io::tier_store::TierStore;
@@ -582,7 +584,6 @@ impl NodeBuilder {
582584
/// of all critical data written to the primary store.
583585
///
584586
/// Backup writes are non-blocking and do not affect primary store operation performance.
585-
#[allow(dead_code)]
586587
pub fn set_backup_store(&mut self, backup_store: Arc<DynStore>) -> &mut Self {
587588
let tier_store_config = self.tier_store_config.get_or_insert(TierStoreConfig::default());
588589
tier_store_config.backup = Some(backup_store);
@@ -596,7 +597,6 @@ impl NodeBuilder {
596597
/// can be rebuilt if lost.
597598
///
598599
/// If not set, non-critical data will be stored in the primary store.
599-
#[allow(dead_code)]
600600
pub fn set_ephemeral_store(&mut self, ephemeral_store: Arc<DynStore>) -> &mut Self {
601601
let tier_store_config = self.tier_store_config.get_or_insert(TierStoreConfig::default());
602602
tier_store_config.ephemeral = Some(ephemeral_store);
@@ -1005,6 +1005,32 @@ impl ArcedNodeBuilder {
10051005
self.inner.write().unwrap().set_wallet_recovery_mode();
10061006
}
10071007

1008+
/// Configures the backup store for local disaster recovery.
1009+
///
1010+
/// When building with tiered storage, this store receives asynchronous copies
1011+
/// of all critical data written to the primary store.
1012+
///
1013+
/// Backup writes are non-blocking and do not affect primary store operation performance.
1014+
///
1015+
pub fn set_backup_store(&self, backup_store: Arc<FfiDynStore>) {
1016+
let wrapper = DynStoreWrapper((*backup_store).clone());
1017+
let store: Arc<DynStore> = Arc::new(wrapper);
1018+
self.inner.write().unwrap().set_backup_store(store);
1019+
}
1020+
1021+
/// Configures the ephemeral store for non-critical, frequently-accessed data.
1022+
///
1023+
/// When building with tiered storage, this store is used for ephemeral data like
1024+
/// the network graph and scorer data to reduce latency for reads. Data stored here
1025+
/// can be rebuilt if lost.
1026+
///
1027+
/// If not set, non-critical data will be stored in the primary store.
1028+
pub fn set_ephemeral_store(&self, ephemeral_store: Arc<FfiDynStore>) {
1029+
let wrapper = DynStoreWrapper((*ephemeral_store).clone());
1030+
let store: Arc<DynStore> = Arc::new(wrapper);
1031+
self.inner.write().unwrap().set_ephemeral_store(store);
1032+
}
1033+
10081034
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
10091035
/// previously configured.
10101036
pub fn build(&self, node_entropy: Arc<NodeEntropy>) -> Result<Arc<Node>, BuildError> {
@@ -1104,12 +1130,19 @@ impl ArcedNodeBuilder {
11041130
}
11051131

11061132
/// Builds a [`Node`] instance according to the options previously configured.
1107-
// Note that the generics here don't actually work for Uniffi, but we don't currently expose
1108-
// this so its not needed.
1109-
pub fn build_with_store<S: SyncAndAsyncKVStore + Send + Sync + 'static>(
1110-
&self, node_entropy: Arc<NodeEntropy>, kv_store: S,
1133+
///
1134+
/// The provided `kv_store` will be used as the primary storage backend. Optionally,
1135+
/// an ephemeral store for frequently-accessed non-critical data (e.g., network graph, scorer)
1136+
/// and a backup store for local disaster recovery can be configured via
1137+
/// [`set_ephemeral_store`] and [`set_backup_store`].
1138+
///
1139+
/// [`set_ephemeral_store`]: Self::set_ephemeral_store
1140+
/// [`set_backup_store`]: Self::set_backup_store
1141+
pub fn build_with_store(
1142+
&self, node_entropy: Arc<NodeEntropy>, kv_store: Arc<FfiDynStore>,
11111143
) -> Result<Arc<Node>, BuildError> {
1112-
self.inner.read().unwrap().build_with_store(*node_entropy, kv_store).map(Arc::new)
1144+
let store = (*kv_store).clone();
1145+
self.inner.read().unwrap().build_with_store(*node_entropy, store).map(Arc::new)
11131146
}
11141147
}
11151148

0 commit comments

Comments
 (0)