Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 55 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ use tokio::runtime::Runtime;
#[macro_use]
extern crate napi_derive;

/// Polling interval for event loops and state checks.
const POLL_INTERVAL: Duration = Duration::from_millis(10);

/// Max time to wait for channels to become usable after sync.
const CHANNEL_USABLE_TIMEOUT: Duration = Duration::from_secs(10);
Copy link
Contributor Author

@amackillop amackillop Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just had a thought. Tuning these in mdk-checkout is probably easier than doing it here. Should consider passing these in from the consuming library


static GLOBAL_LOGGER: OnceLock<Arc<JsLogger>> = OnceLock::new();

fn logger_instance() -> &'static Arc<JsLogger> {
Expand Down Expand Up @@ -566,7 +572,7 @@ impl MdkNode {
last_event_time = now;
}

std::thread::sleep(std::time::Duration::from_millis(10));
std::thread::sleep(POLL_INTERVAL);
}

if let Err(err) = self.node.stop() {
Expand Down Expand Up @@ -753,7 +759,7 @@ impl MdkNode {
return Ok(());
}

std::thread::sleep(Duration::from_millis(50));
std::thread::sleep(POLL_INTERVAL);
}
}

Expand Down Expand Up @@ -830,13 +836,8 @@ impl MdkNode {
panic!("failed to sync wallets: {err}");
}

let available_balance_msat: u64 = self
.node
.list_channels()
.into_iter()
.filter(|channel| channel.is_channel_ready)
.map(|channel| channel.outbound_capacity_msat)
.sum();
wait_for_usable_channels(&self.node);
let available_balance_msat = usable_outbound_capacity_msat(&self.node);
eprintln!("[lightning-js] pay_lnurl available_balance_msat={available_balance_msat}");

if available_balance_msat == 0 {
Expand Down Expand Up @@ -927,13 +928,9 @@ impl MdkNode {
)
})?;

let available_balance_msat: u64 = self
.node
.list_channels()
.into_iter()
.filter(|channel| channel.is_channel_ready)
.map(|channel| channel.outbound_capacity_msat)
.sum();
wait_for_usable_channels(&self.node);
let available_balance_msat = usable_outbound_capacity_msat(&self.node);
eprintln!("[lightning-js] pay_bolt11 available_balance_msat={available_balance_msat}");

if available_balance_msat == 0 {
if let Err(err) = self.node.stop() {
Expand Down Expand Up @@ -1063,20 +1060,11 @@ impl MdkNode {
}
eprintln!("[lightning-js] pay_bolt12_offer wallet sync complete");

let channels = self.node.list_channels();
let ready_channels: Vec<_> = channels
.iter()
.filter(|channel| channel.is_channel_ready)
.collect();
let available_balance_msat: u64 = ready_channels
.iter()
.map(|channel| channel.outbound_capacity_msat)
.sum();
wait_for_usable_channels(&self.node);
let available_balance_msat = usable_outbound_capacity_msat(&self.node);

eprintln!(
"[lightning-js] pay_bolt12_offer channels: total={} ready={} available_balance_msat={}",
channels.len(),
ready_channels.len(),
"[lightning-js] pay_bolt12_offer available_balance_msat={}",
available_balance_msat
);

Expand Down Expand Up @@ -1186,6 +1174,45 @@ impl MdkNode {
}
}

/// Wait for all channels to become usable after node startup/sync.
fn wait_for_usable_channels(node: &Node) {
let start = Instant::now();

loop {
let channels = node.list_channels();
let total = channels.len();
let usable = channels.iter().filter(|c| c.is_usable).count();

if total > 0 && usable == total {
Comment on lines +1181 to +1186

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid blocking payouts when any channel is unusable

The new wait loop only returns early when usable == total, so if any channel is non-usable (e.g., negotiating shutdown or a disconnected peer) every payment call will sleep until CHANNEL_USABLE_TIMEOUT even if another channel is already usable and has enough outbound capacity. This is a regression from the previous readiness check and can add a consistent 10s delay (or cause upstream timeouts) in pay_lnurl, pay_bolt11, and pay_bolt12_offer whenever a single channel stays non-usable. Consider returning once at least one usable channel exists (or only waiting for channels that can become usable) to avoid blocking payments unnecessarily.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design as explained in the description. Dealing with channel closures is a problem for another time

eprintln!(
"[lightning-js] All channels usable ({usable}/{total}) after {}ms",
start.elapsed().as_millis()
);
return;
}

if start.elapsed() >= CHANNEL_USABLE_TIMEOUT {
eprintln!(
"[lightning-js] Timeout: {usable}/{total} channels usable after {}s",
CHANNEL_USABLE_TIMEOUT.as_secs()
);
return;
}

std::thread::sleep(POLL_INTERVAL);
}
}

/// Compute total outbound capacity across all usable channels.
fn usable_outbound_capacity_msat(node: &Node) -> u64 {
node
.list_channels()
.iter()
.filter(|c| c.is_usable)
.map(|c| c.outbound_capacity_msat)
.sum()
}

fn scid_from_human_readable_string(human_readable_scid: &str) -> Result<u64, ()> {
let mut parts = human_readable_scid.split('x');

Expand Down