Skip to content

Commit 9f17217

Browse files
authored
Merge pull request #14 from moneydevkit/austin_only-splice-when-needed
Only splice when needed
2 parents d16a16e + 0651fb0 commit 9f17217

1 file changed

Lines changed: 66 additions & 10 deletions

File tree

lightning-liquidity/src/lsps4/service.rs

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,22 +239,58 @@ where
239239
);
240240
}
241241

242+
// Always persist before calculating actions. execute_htlc_actions
243+
// removes the HTLC on successful forward. For the liquidity path
244+
// (splice/open) the HTLC must survive in the store so the timer
245+
// can forward it once the new channel is ready.
246+
let persisted = match self.htlc_store.insert(htlc.clone()) {
247+
Ok(_) => true,
248+
Err(e) => {
249+
log_error!(
250+
self.logger,
251+
"[LSPS4] htlc_intercepted: failed to persist HTLC {:?}, \
252+
payment_hash: {}, error: {}",
253+
intercept_id,
254+
payment_hash,
255+
e
256+
);
257+
false
258+
}
259+
};
260+
242261
let actions = self.calculate_htlc_actions_for_peer(
243262
counterparty_node_id,
244-
vec![htlc.clone()],
263+
vec![htlc],
245264
);
246265

247-
if actions.needs_liquidity_action() {
248-
self.htlc_store.insert(htlc).unwrap();
249-
}
250-
251266
log_debug!(
252267
self.logger,
253268
"[LSPS4] htlc_intercepted: calculated actions for peer {}: {:?}",
254269
counterparty_node_id,
255270
actions
256271
);
257272

273+
// Liquidity actions (splice/open) are async — the timer forwards
274+
// the HTLC once the channel is ready. Without persistence the
275+
// splice/open fires but the HTLC is never forwarded, wasting
276+
// on-chain fees for a payment that times out anyway.
277+
if !persisted && actions.needs_liquidity_action() {
278+
log_error!(
279+
self.logger,
280+
"[LSPS4] htlc_intercepted: liquidity action needed but HTLC {:?} \
281+
not persisted, failing back to sender. payment_hash: {}",
282+
intercept_id,
283+
payment_hash
284+
);
285+
let _ = self.channel_manager.get_cm().fail_intercepted_htlc(intercept_id);
286+
return Ok(());
287+
}
288+
289+
// Forward-only path is fine without persistence — the HTLC is
290+
// consumed immediately by forward_intercepted_htlc. When persisted,
291+
// the store entry also covers the TOCTOU race where the channel
292+
// becomes unusable between calculate and execute; the timer retries
293+
// from the store.
258294
self.execute_htlc_actions(actions, counterparty_node_id.clone());
259295
}
260296
} else {
@@ -670,6 +706,25 @@ where
670706
htlcs.len()
671707
);
672708

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+
673728
struct ComputedHtlc {
674729
htlc: InterceptedHtlc,
675730
amount_to_forward_msat: u64,
@@ -747,12 +802,13 @@ where
747802
.fold(required_amount, |acc, h| acc.saturating_add(h.amount_to_forward_msat));
748803

749804
// Prefer splicing into the largest usable channel over opening a new one.
750-
// Use is_channel_ready (not is_usable) so we prefer splice even during
751-
// channel_reestablish. splice_channel() will fail if the channel isn't
752-
// usable yet, and the timer will retry once reestablishment completes.
753-
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
754810
.iter()
755-
.filter(|c| c.is_channel_ready)
811+
.filter(|c| c.is_usable)
756812
.max_by_key(|c| c.channel_value_satoshis);
757813

758814
if let Some(candidate) = splice_candidate {

0 commit comments

Comments
 (0)