Skip to content

Commit cadc8b7

Browse files
committed
Consolidate LSPS4 usability checks to execute time
Channel usability (is_usable) was checked at four separate points: htlc_intercepted, peer_connected, process_pending_htlcs, and calculate_htlc_actions_for_peer. Each had its own deferral logic, and they had to coordinate. Move the usability check to execute_htlc_actions, right before forward_intercepted_htlc. If no usable channel exists, the forward is skipped and the HTLC stays in store for the timer to retry. htlc_intercepted, peer_connected, and process_pending_htlcs now all call process_htlcs_for_peer unconditionally. Change the pre-forward guard from is_peer_connected to has_usable_channel, which covers the disconnect+reconnect race where the peer is connected but the channel has not finished reestablishing.
1 parent b89fc71 commit cadc8b7

1 file changed

Lines changed: 29 additions & 63 deletions

File tree

lightning-liquidity/src/lsps4/service.rs

Lines changed: 29 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ where
218218
.get_cm()
219219
.list_channels_with_counterparty(&counterparty_node_id);
220220
let any_usable = channels.iter().any(|ch| ch.is_usable);
221-
let has_channels = !channels.is_empty();
222221

223222
log_info!(
224223
self.logger,
@@ -240,40 +239,23 @@ where
240239
);
241240
}
242241

243-
if has_channels && !any_usable {
244-
// Channels exist but none usable yet (channel_reestablish in progress).
245-
// Defer until process_pending_htlcs picks them up.
246-
log_info!(
247-
self.logger,
248-
"[LSPS4] htlc_intercepted: peer {} has {} channels but none usable, \
249-
deferring HTLC until channel_reestablish completes. payment_hash: {}",
250-
counterparty_node_id,
251-
channels.len(),
252-
payment_hash
253-
);
254-
self.htlc_store.insert(htlc).unwrap();
255-
} else {
256-
// Either channels are usable, or no channels exist (need JIT open).
257-
// calculate_htlc_actions_for_peer handles both: forward through usable
258-
// channels, or emit OpenChannel event when no capacity exists.
259-
let actions = self.calculate_htlc_actions_for_peer(
260-
counterparty_node_id,
261-
vec![htlc.clone()],
262-
);
242+
let actions = self.calculate_htlc_actions_for_peer(
243+
counterparty_node_id,
244+
vec![htlc.clone()],
245+
);
263246

264-
if actions.needs_liquidity_action() {
265-
self.htlc_store.insert(htlc).unwrap();
266-
}
247+
if actions.needs_liquidity_action() {
248+
self.htlc_store.insert(htlc).unwrap();
249+
}
267250

268-
log_debug!(
269-
self.logger,
270-
"[LSPS4] htlc_intercepted: calculated actions for peer {}: {:?}",
271-
counterparty_node_id,
272-
actions
273-
);
251+
log_debug!(
252+
self.logger,
253+
"[LSPS4] htlc_intercepted: calculated actions for peer {}: {:?}",
254+
counterparty_node_id,
255+
actions
256+
);
274257

275-
self.execute_htlc_actions(actions, counterparty_node_id.clone());
276-
}
258+
self.execute_htlc_actions(actions, counterparty_node_id.clone());
277259
}
278260
} else {
279261
log_error!(
@@ -414,28 +396,7 @@ where
414396
}
415397
}
416398

417-
if self.has_usable_channel(&counterparty_node_id) || channels.is_empty() {
418-
// Either channels are usable (forward immediately) or no channels exist
419-
// at all (process_htlcs_for_peer will trigger OpenChannel for JIT).
420-
self.process_htlcs_for_peer(counterparty_node_id.clone(), htlcs);
421-
} else {
422-
// Channels exist but none usable yet (reestablish in progress).
423-
// We still call process_htlcs_for_peer because calculate_htlc_actions
424-
// skips non-usable channels. If existing capacity is insufficient, this
425-
// will emit OpenChannel now rather than deferring to process_pending_htlcs
426-
// (which would never open a channel). Forwards through existing channels
427-
// will be empty since none are usable, so no premature forwarding occurs.
428-
// The actual forwards happen later via channel_ready or process_pending_htlcs
429-
// once reestablish completes.
430-
log_info!(
431-
self.logger,
432-
"[LSPS4] peer_connected: {} has {} pending HTLCs, channels not yet usable \
433-
(reestablish in progress) - checking if new channel needed",
434-
counterparty_node_id,
435-
htlcs.len()
436-
);
437-
self.process_htlcs_for_peer(counterparty_node_id.clone(), htlcs);
438-
}
399+
self.process_htlcs_for_peer(counterparty_node_id.clone(), htlcs);
439400

440401
log_info!(
441402
self.logger,
@@ -589,11 +550,14 @@ where
589550
}
590551

591552
if self.has_usable_channel(&node_id) {
592-
let actions = self.calculate_htlc_actions_for_peer(node_id, htlcs);
593-
if actions.is_empty() {
594-
continue;
595-
}
596-
self.execute_htlc_actions(actions, node_id);
553+
// Channel reestablish completed — attempt to forward the deferred HTLCs.
554+
log_info!(
555+
self.logger,
556+
"[LSPS4] process_pending_htlcs: forwarding {} HTLCs for peer {} (channel now usable)",
557+
htlcs.len(),
558+
node_id
559+
);
560+
self.process_htlcs_for_peer(node_id, htlcs);
597561
}
598562
}
599563
}
@@ -835,12 +799,14 @@ where
835799

836800
// Execute forwards
837801
for forward_action in forwards {
838-
// Re-check peer liveness right before forwarding to narrow the
839-
// TOCTOU window between the usability check and the actual forward.
840-
if !self.is_peer_connected(&their_node_id) {
802+
// Re-check channel usability right before forwarding to narrow the
803+
// TOCTOU window. Covers both peer disconnect and disconnect+reconnect
804+
// (where the peer is connected but the channel is still reestablishing).
805+
if !self.has_usable_channel(&their_node_id) {
841806
log_info!(
842807
self.logger,
843-
"[LSPS4] execute_htlc_actions: peer {} disconnected before forward, skipping HTLC {:?} (will retry on next timer tick). payment_hash: {}",
808+
"[LSPS4] execute_htlc_actions: no usable channel for peer {}, \
809+
skipping HTLC {:?} (will retry on next timer tick). payment_hash: {}",
844810
their_node_id,
845811
forward_action.htlc.id(),
846812
forward_action.htlc.payment_hash()

0 commit comments

Comments
 (0)