Skip to content

Commit daa9d60

Browse files
committed
Prevent spurious splice on reconnect
When the LSP intercepts an HTLC for an offline peer, the peer reconnects and peer_connected fires before channel_reestablish completes. All channels report is_usable=false at this point. calculate_htlc_actions_for_peer builds its capacity map from is_usable channels (empty set), finds zero capacity, then falls through to the splice candidate filter. That filter used is_channel_ready, which matches mid-reestablish channels, so it emits a splice. Once reestablish finishes, the HTLC gets forwarded through the existing channel. The splice was wasted, and the 30s liquidity cooldown it sets blocks all subsequent liquidity actions for that peer. Two fixes in calculate_htlc_actions_for_peer: 1. Early return when channels exist but none are usable. The capacity map is empty so any decision would be wrong. The HTLC stays in the store (persisted by the prior commit) and the 1Hz timer retries once channels become usable. 2. Splice candidate filter changed from is_channel_ready to is_usable. A mid-reestablish channel maye already have sufficient capacity but it's just not visible yet. Splicing into it adds unnecessary on-chain cost and a 30s cooldown for capacity that was never actually insufficient. splice_channel() would also fail if the channel does become usable in time.
1 parent 19fb094 commit daa9d60

1 file changed

Lines changed: 25 additions & 5 deletions

File tree

lightning-liquidity/src/lsps4/service.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,25 @@ where
706706
htlcs.len()
707707
);
708708

709+
// Channels exist but none are usable (reestablish in progress).
710+
// Return empty actions. We can't forward and we must not decide to splice or
711+
// open a new channel based on stale capacity. The timer retries once usable.
712+
if !channels.is_empty() && channel_capacity_map.is_empty() {
713+
log_info!(
714+
self.logger,
715+
"[LSPS4] calculate_htlc_actions: {} has {} channels but none usable yet \
716+
- deferring decision",
717+
their_node_id,
718+
channels.len()
719+
);
720+
return HtlcProcessingActions {
721+
forwards: vec![],
722+
new_channel_needed_msat: None,
723+
splice_needed: None,
724+
channel_count,
725+
};
726+
}
727+
709728
struct ComputedHtlc {
710729
htlc: InterceptedHtlc,
711730
amount_to_forward_msat: u64,
@@ -783,12 +802,13 @@ where
783802
.fold(required_amount, |acc, h| acc.saturating_add(h.amount_to_forward_msat));
784803

785804
// Prefer splicing into the largest usable channel over opening a new one.
786-
// Use is_channel_ready (not is_usable) so we prefer splice even during
787-
// channel_reestablish. splice_channel() will fail if the channel isn't
788-
// usable yet, and the timer will retry once reestablishment completes.
789-
let splice_candidate = channels
805+
// Only splice into usable channels. A mid-reestablish channel may
806+
// already have sufficient capacity that just isn't visible yet;
807+
// splice_channel() would also reject if the channel does become
808+
// usable in time.
809+
let splice_candidate = channels
790810
.iter()
791-
.filter(|c| c.is_channel_ready)
811+
.filter(|c| c.is_usable)
792812
.max_by_key(|c| c.channel_value_satoshis);
793813

794814
if let Some(candidate) = splice_candidate {

0 commit comments

Comments
 (0)