diff --git a/README.md b/README.md index df072999d..22ef1a1b2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ use std::str::FromStr; fn main() { let mut builder = Builder::new(); builder.set_network(Network::Testnet); - builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); + builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); let node = builder.build().unwrap(); diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index e2bcd4c89..786534b84 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -130,9 +130,9 @@ class LibraryTest { println("Config 2: $config2") val builder1 = Builder.fromConfig(config1) - builder1.setEsploraServer(esploraEndpoint) + builder1.setChainSourceEsplora(esploraEndpoint, null) val builder2 = Builder.fromConfig(config2) - builder2.setEsploraServer(esploraEndpoint) + builder2.setChainSourceEsplora(esploraEndpoint, null) val node1 = builder1.build() val node2 = builder2.build() diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 817fd06ee..b4fc7ec79 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -9,9 +9,6 @@ dictionary Config { Network network; sequence? listening_addresses; NodeAlias? node_alias; - u64 onchain_wallet_sync_interval_secs; - u64 wallet_sync_interval_secs; - u64 fee_rate_cache_update_interval_secs; sequence trusted_peers_0conf; u64 probing_liquidity_limit_multiplier; LogLevel log_level; @@ -24,6 +21,12 @@ dictionary AnchorChannelsConfig { u64 per_channel_reserve_sats; }; +dictionary EsploraSyncConfig { + u64 onchain_wallet_sync_interval_secs; + u64 lightning_wallet_sync_interval_secs; + u64 fee_rate_cache_update_interval_secs; +}; + interface Builder { constructor(); [Name=from_config] @@ -32,7 +35,7 @@ interface Builder { [Throws=BuildError] void set_entropy_seed_bytes(sequence seed_bytes); void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase); - void set_esplora_server(string esplora_server_url); + void set_chain_source_esplora(string server_url, EsploraSyncConfig? config); void set_gossip_source_p2p(); void set_gossip_source_rgs(string rgs_server_url); void set_liquidity_source_lsps2(SocketAddress address, PublicKey node_id, string? token); diff --git a/bindings/python/src/ldk_node/test_ldk_node.py b/bindings/python/src/ldk_node/test_ldk_node.py index 4f2931440..82c493e32 100644 --- a/bindings/python/src/ldk_node/test_ldk_node.py +++ b/bindings/python/src/ldk_node/test_ldk_node.py @@ -84,7 +84,7 @@ def setup_node(tmp_dir, esplora_endpoint, listening_addresses): config = default_config() builder = Builder.from_config(config) builder.set_storage_dir_path(tmp_dir) - builder.set_esplora_server(esplora_endpoint) + builder.set_chain_source_esplora(esplora_endpoint, None) builder.set_network(DEFAULT_TEST_NETWORK) builder.set_listening_addresses(listening_addresses) return builder.build() diff --git a/src/builder.rs b/src/builder.rs index 1cfd46eed..43171db1f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -6,7 +6,7 @@ // accordance with one or both of these licenses. use crate::chain::{ChainSource, DEFAULT_ESPLORA_SERVER_URL}; -use crate::config::{default_user_config, Config, WALLET_KEYS_SEED_LEN}; +use crate::config::{default_user_config, Config, EsploraSyncConfig, WALLET_KEYS_SEED_LEN}; use crate::connection::ConnectionManager; use crate::event::EventQueue; @@ -77,7 +77,7 @@ use std::time::SystemTime; #[derive(Debug, Clone)] enum ChainDataSourceConfig { - Esplora(String), + Esplora { server_url: String, sync_config: Option }, } #[derive(Debug, Clone)] @@ -237,8 +237,14 @@ impl NodeBuilder { } /// Configures the [`Node`] instance to source its chain data from the given Esplora server. - pub fn set_esplora_server(&mut self, esplora_server_url: String) -> &mut Self { - self.chain_data_source_config = Some(ChainDataSourceConfig::Esplora(esplora_server_url)); + /// + /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more + /// information. + pub fn set_chain_source_esplora( + &mut self, server_url: String, sync_config: Option, + ) -> &mut Self { + self.chain_data_source_config = + Some(ChainDataSourceConfig::Esplora { server_url, sync_config }); self } @@ -464,8 +470,13 @@ impl ArcedNodeBuilder { } /// Configures the [`Node`] instance to source its chain data from the given Esplora server. - pub fn set_esplora_server(&self, esplora_server_url: String) { - self.inner.write().unwrap().set_esplora_server(esplora_server_url); + /// + /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more + /// information. + pub fn set_chain_source_esplora( + &self, server_url: String, sync_config: Option, + ) { + self.inner.write().unwrap().set_chain_source_esplora(server_url, sync_config); } /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer @@ -608,21 +619,27 @@ fn build_with_store_internal( )); let chain_source = match chain_data_source_config { - Some(ChainDataSourceConfig::Esplora(server_url)) => Arc::new(ChainSource::new_esplora( - server_url.clone(), - Arc::clone(&wallet), - Arc::clone(&fee_estimator), - Arc::clone(&tx_broadcaster), - Arc::clone(&kv_store), - Arc::clone(&config), - Arc::clone(&logger), - Arc::clone(&node_metrics), - )), + Some(ChainDataSourceConfig::Esplora { server_url, sync_config }) => { + let sync_config = sync_config.unwrap_or(EsploraSyncConfig::default()); + Arc::new(ChainSource::new_esplora( + server_url.clone(), + sync_config, + Arc::clone(&wallet), + Arc::clone(&fee_estimator), + Arc::clone(&tx_broadcaster), + Arc::clone(&kv_store), + Arc::clone(&config), + Arc::clone(&logger), + Arc::clone(&node_metrics), + )) + }, None => { // Default to Esplora client. let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string(); + let sync_config = EsploraSyncConfig::default(); Arc::new(ChainSource::new_esplora( server_url.clone(), + sync_config, Arc::clone(&wallet), Arc::clone(&fee_estimator), Arc::clone(&tx_broadcaster), diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 8a12dc768..043bb934c 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -6,8 +6,8 @@ // accordance with one or both of these licenses. use crate::config::{ - Config, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP, BDK_WALLET_SYNC_TIMEOUT_SECS, - FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS, LDK_WALLET_SYNC_TIMEOUT_SECS, + Config, EsploraSyncConfig, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP, + BDK_WALLET_SYNC_TIMEOUT_SECS, FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS, LDK_WALLET_SYNC_TIMEOUT_SECS, RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL, TX_BROADCAST_TIMEOUT_SECS, WALLET_SYNC_INTERVAL_MINIMUM_SECS, }; @@ -96,6 +96,7 @@ impl WalletSyncStatus { pub(crate) enum ChainSource { Esplora { + sync_config: EsploraSyncConfig, esplora_client: EsploraAsyncClient, onchain_wallet: Arc, onchain_wallet_sync_status: Mutex, @@ -112,9 +113,10 @@ pub(crate) enum ChainSource { impl ChainSource { pub(crate) fn new_esplora( - server_url: String, onchain_wallet: Arc, fee_estimator: Arc, - tx_broadcaster: Arc, kv_store: Arc, config: Arc, - logger: Arc, node_metrics: Arc>, + server_url: String, sync_config: EsploraSyncConfig, onchain_wallet: Arc, + fee_estimator: Arc, tx_broadcaster: Arc, + kv_store: Arc, config: Arc, logger: Arc, + node_metrics: Arc>, ) -> Self { let mut client_builder = esplora_client::Builder::new(&server_url); client_builder = client_builder.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS); @@ -124,6 +126,7 @@ impl ChainSource { let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed); let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed); Self::Esplora { + sync_config, esplora_client, onchain_wallet, onchain_wallet_sync_status, @@ -144,16 +147,17 @@ impl ChainSource { output_sweeper: Arc, ) { match self { - Self::Esplora { config, logger, .. } => { + Self::Esplora { sync_config, logger, .. } => { // Setup syncing intervals - let onchain_wallet_sync_interval_secs = - config.onchain_wallet_sync_interval_secs.max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); + let onchain_wallet_sync_interval_secs = sync_config + .onchain_wallet_sync_interval_secs + .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); let mut onchain_wallet_sync_interval = tokio::time::interval(Duration::from_secs(onchain_wallet_sync_interval_secs)); onchain_wallet_sync_interval .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - let fee_rate_cache_update_interval_secs = config + let fee_rate_cache_update_interval_secs = sync_config .fee_rate_cache_update_interval_secs .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); let mut fee_rate_update_interval = @@ -163,11 +167,12 @@ impl ChainSource { fee_rate_update_interval .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - let wallet_sync_interval_secs = - config.wallet_sync_interval_secs.max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); - let mut wallet_sync_interval = - tokio::time::interval(Duration::from_secs(wallet_sync_interval_secs)); - wallet_sync_interval + let lightning_wallet_sync_interval_secs = sync_config + .lightning_wallet_sync_interval_secs + .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); + let mut lightning_wallet_sync_interval = + tokio::time::interval(Duration::from_secs(lightning_wallet_sync_interval_secs)); + lightning_wallet_sync_interval .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); // Start the syncing loop. @@ -186,7 +191,7 @@ impl ChainSource { _ = fee_rate_update_interval.tick() => { let _ = self.update_fee_rate_estimates().await; } - _ = wallet_sync_interval.tick() => { + _ = lightning_wallet_sync_interval.tick() => { let _ = self.sync_lightning_wallet( Arc::clone(&channel_manager), Arc::clone(&chain_monitor), diff --git a/src/config.rs b/src/config.rs index 0fd55abdf..fb38e78b2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -119,18 +119,6 @@ pub struct Config { /// **Note**: We will only allow opening and accepting public channels if the `node_alias` and the /// `listening_addresses` are set. pub node_alias: Option, - /// The time in-between background sync attempts of the onchain wallet, in seconds. - /// - /// **Note:** A minimum of 10 seconds is always enforced. - pub onchain_wallet_sync_interval_secs: u64, - /// The time in-between background sync attempts of the LDK wallet, in seconds. - /// - /// **Note:** A minimum of 10 seconds is always enforced. - pub wallet_sync_interval_secs: u64, - /// The time in-between background update attempts to our fee rate cache, in seconds. - /// - /// **Note:** A minimum of 10 seconds is always enforced. - pub fee_rate_cache_update_interval_secs: u64, /// A list of peers that we allow to establish zero confirmation channels to us. /// /// **Note:** Allowing payments via zero-confirmation channels is potentially insecure if the @@ -180,9 +168,6 @@ impl Default for Config { log_dir_path: None, network: DEFAULT_NETWORK, listening_addresses: None, - onchain_wallet_sync_interval_secs: DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS, - wallet_sync_interval_secs: DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS, - fee_rate_cache_update_interval_secs: DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS, trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, log_level: DEFAULT_LOG_LEVEL, @@ -300,6 +285,41 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig { user_config } +/// Options related to syncing the Lightning and on-chain wallets via an Esplora backend. +/// +/// ### Defaults +/// +/// | Parameter | Value | +/// |----------------------------------------|--------------------| +/// | `onchain_wallet_sync_interval_secs` | 80 | +/// | `lightning_wallet_sync_interval_secs` | 30 | +/// | `fee_rate_cache_update_interval_secs` | 600 | +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct EsploraSyncConfig { + /// The time in-between background sync attempts of the onchain wallet, in seconds. + /// + /// **Note:** A minimum of 10 seconds is always enforced. + pub onchain_wallet_sync_interval_secs: u64, + /// The time in-between background sync attempts of the LDK wallet, in seconds. + /// + /// **Note:** A minimum of 10 seconds is always enforced. + pub lightning_wallet_sync_interval_secs: u64, + /// The time in-between background update attempts to our fee rate cache, in seconds. + /// + /// **Note:** A minimum of 10 seconds is always enforced. + pub fee_rate_cache_update_interval_secs: u64, +} + +impl Default for EsploraSyncConfig { + fn default() -> Self { + Self { + onchain_wallet_sync_interval_secs: DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS, + lightning_wallet_sync_interval_secs: DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS, + fee_rate_cache_update_interval_secs: DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS, + } + } +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/src/lib.rs b/src/lib.rs index de7613945..4c13bbcb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1187,10 +1187,14 @@ impl Node { /// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate /// cache. /// - /// **Note:** The wallets are regularly synced in the background, which is configurable via - /// [`Config::onchain_wallet_sync_interval_secs`] and [`Config::wallet_sync_interval_secs`]. - /// Therefore, using this blocking sync method is almost always redundant and should be avoided - /// where possible. + /// **Note:** The wallets are regularly synced in the background, which is configurable via the + /// respective config object, e.g., via + /// [`EsploraSyncConfig::onchain_wallet_sync_interval_secs`] and + /// [`EsploraSyncConfig::lightning_wallet_sync_interval_secs`]. Therefore, using this blocking + /// sync method is almost always redundant and should be avoided where possible. + /// + /// [`EsploraSyncConfig::onchain_wallet_sync_interval_secs`]: crate::config::EsploraSyncConfig::onchain_wallet_sync_interval_secs + /// [`EsploraSyncConfig::lightning_wallet_sync_interval_secs`]: crate::config::EsploraSyncConfig::lightning_wallet_sync_interval_secs pub fn sync_wallets(&self) -> Result<(), Error> { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index c7d4320ee..29358333e 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -10,7 +10,7 @@ // // Make sure to add any re-exported items that need to be used in uniffi below. -pub use crate::config::{default_config, AnchorChannelsConfig}; +pub use crate::config::{default_config, AnchorChannelsConfig, EsploraSyncConfig}; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus}; pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters}; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 518bcb30a..98075a23e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -8,7 +8,7 @@ #![cfg(any(test, cln_test, vss_test))] #![allow(dead_code)] -use ldk_node::config::Config; +use ldk_node::config::{Config, EsploraSyncConfig}; use ldk_node::io::sqlite_store::SqliteStore; use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; use ldk_node::{Builder, Event, LightningBalance, LogLevel, Node, NodeError, PendingSweepBalance}; @@ -217,8 +217,6 @@ pub(crate) fn random_config(anchor_channels: bool) -> Config { } config.network = Network::Regtest; - config.onchain_wallet_sync_interval_secs = 100000; - config.wallet_sync_interval_secs = 100000; println!("Setting network: {}", config.network); let rand_dir = random_storage_path(); @@ -280,8 +278,11 @@ pub(crate) fn setup_two_nodes( pub(crate) fn setup_node(electrsd: &ElectrsD, config: Config) -> TestNode { let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let mut sync_config = EsploraSyncConfig::default(); + sync_config.onchain_wallet_sync_interval_secs = 100000; + sync_config.lightning_wallet_sync_interval_secs = 100000; setup_builder!(builder, config); - builder.set_esplora_server(esplora_url.clone()); + builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into())); let node = builder.build_with_store(test_sync_store).unwrap(); node.start().unwrap(); diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index 13b5c44c6..c3ade673f 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -45,7 +45,7 @@ fn test_cln() { // Setup LDK Node let config = common::random_config(true); let mut builder = Builder::from_config(config); - builder.set_esplora_server("http://127.0.0.1:3002".to_string()); + builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None); let node = builder.build().unwrap(); node.start().unwrap(); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 6d33e80c6..13f3ab0be 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -14,6 +14,7 @@ use common::{ setup_node, setup_two_nodes, wait_for_tx, TestSyncStore, }; +use ldk_node::config::EsploraSyncConfig; use ldk_node::payment::{PaymentKind, QrPaymentResult, SendingParameters}; use ldk_node::{Builder, Event, NodeError}; @@ -102,8 +103,11 @@ fn multi_hop_sending() { let mut nodes = Vec::new(); for _ in 0..5 { let config = random_config(true); + let mut sync_config = EsploraSyncConfig::default(); + sync_config.onchain_wallet_sync_interval_secs = 100000; + sync_config.lightning_wallet_sync_interval_secs = 100000; setup_builder!(builder, config); - builder.set_esplora_server(esplora_url.clone()); + builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); let node = builder.build().unwrap(); node.start().unwrap(); nodes.push(node); @@ -182,7 +186,7 @@ fn connect_to_public_testnet_esplora() { let mut config = random_config(true); config.network = Network::Testnet; setup_builder!(builder, config); - builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); + builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); let node = builder.build().unwrap(); node.start().unwrap(); node.stop().unwrap(); @@ -198,8 +202,11 @@ fn start_stop_reinit() { let test_sync_store: Arc = Arc::new(TestSyncStore::new(config.storage_dir_path.clone().into())); + let mut sync_config = EsploraSyncConfig::default(); + sync_config.onchain_wallet_sync_interval_secs = 100000; + sync_config.lightning_wallet_sync_interval_secs = 100000; setup_builder!(builder, config); - builder.set_esplora_server(esplora_url.clone()); + builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); let node = builder.build_with_store(Arc::clone(&test_sync_store)).unwrap(); node.start().unwrap(); @@ -236,7 +243,7 @@ fn start_stop_reinit() { drop(node); setup_builder!(builder, config); - builder.set_esplora_server(esplora_url.clone()); + builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); let reinitialized_node = builder.build_with_store(Arc::clone(&test_sync_store)).unwrap(); reinitialized_node.start().unwrap(); diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index c572fbcd8..483902375 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -18,7 +18,7 @@ fn channel_full_cycle_with_vss_store() { let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = common::random_config(true); let mut builder_a = Builder::from_config(config_a); - builder_a.set_esplora_server(esplora_url.clone()); + builder_a.set_chain_source_esplora(esplora_url.clone(), None); let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); let node_a = builder_a.build_with_vss_store(vss_base_url.clone(), "node_1_store".to_string()).unwrap(); @@ -27,7 +27,7 @@ fn channel_full_cycle_with_vss_store() { println!("\n== Node B =="); let config_b = common::random_config(true); let mut builder_b = Builder::from_config(config_b); - builder_b.set_esplora_server(esplora_url); + builder_b.set_chain_source_esplora(esplora_url.clone(), None); let node_b = builder_b.build_with_vss_store(vss_base_url, "node_2_store".to_string()).unwrap(); node_b.start().unwrap();