From a6054b58a23b4e4125bd46eefd85dc1666941816 Mon Sep 17 00:00:00 2001 From: amackillop Date: Wed, 22 Apr 2026 02:24:36 +0100 Subject: [PATCH 1/2] Remove sync_wallets from hot paths sync_wallets() issues serial HTTP requests to Esplora (fee rate estimation, lightning tx sync across multiple block heights, BDK wallet sync). This blocks the NAPI thread for a few seconds via runtime.block_on(), which prevents the JS event loop from polling nextEvent() for incoming HTLCs. The 10+ second payment latency observed traces directly to this: the sender's HTLC sits unprocessed while the receiver node grinds through chain sync before the webhook handler can start its event loop. Removed sync_wallets from start_receiving, receive_payment, get_invoice, and execute_payment. Receiving HTLCs only needs a peer connection and channel manager, both provided by node.start(). Sending payments routes via the gossip graph with fee rates already cached by node.start(). Also disabled background wallet syncing (the 30s/80s interval loop). For a webhook-based node that lives 40-60s, the immediate first tick just competes with the peer handler for Esplora bandwidth. LDK uses highest_seen_timestamp (set via best_block_updated) for inbound payment verification with a 2-hour grace window. A stale chain tip could cause valid payments to be rejected as expired. The platform's 30-minute ping cron calls sync_wallets via the balance handler, which keeps the timestamp well within that window. get_balance() also syncs explicitly before reading UTXOs. --- src/lib.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3d76dd..6735a1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ use ldk_node::{ hashes::{Hash, sha256}, secp256k1::PublicKey, }, - config::Config, + config::{Config, EsploraSyncConfig}, generate_entropy_mnemonic, lightning::ln::channelmanager::PaymentId, lightning::sign::{KeysManager as LdkKeysManager, NodeSigner, Recipient}, @@ -507,7 +507,12 @@ impl MdkNode { let mut builder = Builder::from_config(config); builder.set_network(network); - builder.set_chain_source_esplora(options.esplora_url, None); + // Disable background wallet syncing. These nodes are intended to be short + // lived and are kept in sync via other means. + let esplora_sync_config = EsploraSyncConfig { + background_sync_config: None, + }; + builder.set_chain_source_esplora(options.esplora_url, Some(esplora_sync_config)); builder.set_gossip_source_rgs(options.rgs_url); builder.set_entropy_bip39_mnemonic(mnemonic, None); let logger_arc = Arc::clone(logger_instance()); @@ -679,6 +684,7 @@ impl MdkNode { /// /// If `splice.enabled` is set on construction (the default), also spawns /// the auto-splice background task on the dedicated splice runtime. + /// Start the node. Call once before polling for events. #[napi] pub fn start_receiving(&self) -> napi::Result<()> { self.node().start().map_err(|e| { @@ -1050,11 +1056,6 @@ impl MdkNode { return received_payments; } - if let Err(err) = self.node().sync_wallets() { - eprintln!("[lightning-js] Failed to sync wallets: {err}"); - panic!("failed to sync wallets: {err}"); - } - let start_sync_at = std::time::Instant::now(); let mut last_event_time = start_sync_at; @@ -1181,10 +1182,6 @@ impl MdkNode { eprintln!("[lightning-js] Failed to start node for get_invoice: {err}"); panic!("failed to start node for get_invoice: {err}"); } - if let Err(err) = self.node().sync_wallets() { - eprintln!("[lightning-js] Failed to sync wallets: {err}"); - panic!("failed to sync wallets: {err}"); - } let result = self.get_invoice_impl(Some(amount), description, expiry_secs); @@ -1487,15 +1484,6 @@ impl MdkNode { napi::Error::new(Status::GenericFailure, format!("failed to start node: {e}")) })?; - // Sync wallets - if let Err(e) = self.node().sync_wallets() { - let _ = self.node().stop(); - return Err(napi::Error::new( - Status::GenericFailure, - format!("failed to sync wallets: {e}"), - )); - } - let result = self.execute_payment_impl(&payment_target, wait_secs); let _ = self.node().stop(); result From 5d54f3b5c3706dcdfa5a8abb591d26d85931dd89 Mon Sep 17 00:00:00 2001 From: amackillop Date: Fri, 29 May 2026 07:04:01 -0700 Subject: [PATCH 2/2] Restore background sync Keeping the background sync probably does not impact performance and good to have as a fallback in case the ping does not work. --- src/lib.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6735a1b..bd6ae51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ use ldk_node::{ hashes::{Hash, sha256}, secp256k1::PublicKey, }, - config::{Config, EsploraSyncConfig}, + config::Config, generate_entropy_mnemonic, lightning::ln::channelmanager::PaymentId, lightning::sign::{KeysManager as LdkKeysManager, NodeSigner, Recipient}, @@ -507,12 +507,7 @@ impl MdkNode { let mut builder = Builder::from_config(config); builder.set_network(network); - // Disable background wallet syncing. These nodes are intended to be short - // lived and are kept in sync via other means. - let esplora_sync_config = EsploraSyncConfig { - background_sync_config: None, - }; - builder.set_chain_source_esplora(options.esplora_url, Some(esplora_sync_config)); + builder.set_chain_source_esplora(options.esplora_url, None); builder.set_gossip_source_rgs(options.rgs_url); builder.set_entropy_bip39_mnemonic(mnemonic, None); let logger_arc = Arc::clone(logger_instance());