Skip to content

Commit dbc5bf8

Browse files
fix: rescale HTLC in-flight caps on splice promotion (#17)
When a splice promotes a new FundingScope, `holder_max_htlc_value_in_flight_msat` and `counterparty_max_htlc_value_in_flight_msat` stay pinned at their pre-splice values instead of scaling with the new channel capacity. Any HTLC larger than the pre-splice cap is rejected: - on the sender with HTLCMaximum inside send_htlc - on the receiver with "Remote HTLC add would put them over our max HTLC value", which force-closes the channel Both manifestations break LSPS4 JIT splice flows where the client's embedded node first opens a small JIT channel, then splices in capacity when a larger payment arrives. The receiver force-close is particularly damaging because it destroys the channel along with any pending fees. Rescale both caps proportionally to the channel-value change on splice promotion. Also tightens the test_splice_in assertions (the previous check compared msats to sats).
1 parent 070b31d commit dbc5bf8

2 files changed

Lines changed: 50 additions & 6 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6443,6 +6443,19 @@ fn get_holder_max_htlc_value_in_flight_msat(
64436443
channel_value_satoshis * 10 * configured_percent
64446444
}
64456445

6446+
fn rescale_max_htlc_value_in_flight_msat(
6447+
max_htlc_value_in_flight_msat: u64, old_channel_value_satoshis: u64,
6448+
new_channel_value_satoshis: u64,
6449+
) -> u64 {
6450+
debug_assert_ne!(old_channel_value_satoshis, 0);
6451+
if old_channel_value_satoshis == 0 {
6452+
return max_htlc_value_in_flight_msat;
6453+
}
6454+
6455+
((max_htlc_value_in_flight_msat as u128 * new_channel_value_satoshis as u128)
6456+
/ old_channel_value_satoshis as u128) as u64
6457+
}
6458+
64466459
/// Returns a minimum channel reserve value the remote needs to maintain,
64476460
/// required by us according to the configured or default
64486461
/// [`ChannelHandshakeConfig::their_channel_reserve_proportional_millionths`]
@@ -11185,12 +11198,41 @@ where
1118511198
.find(|funding| funding.get_funding_txid() == Some(splice_txid))
1118611199
.unwrap();
1118711200
let prev_funding_txid = self.funding.get_funding_txid();
11201+
let prev_channel_value_satoshis = self.funding.get_value_satoshis();
1118811202

1118911203
if let Some(scid) = self.funding.short_channel_id {
1119011204
self.context.historical_scids.push(scid);
1119111205
}
1119211206

1119311207
core::mem::swap(&mut self.funding, funding);
11208+
let new_channel_value_satoshis = self.funding.get_value_satoshis();
11209+
let prev_holder_max_htlc_value_in_flight_msat =
11210+
self.context.holder_max_htlc_value_in_flight_msat;
11211+
let prev_counterparty_max_htlc_value_in_flight_msat =
11212+
self.context.counterparty_max_htlc_value_in_flight_msat;
11213+
self.context.holder_max_htlc_value_in_flight_msat =
11214+
rescale_max_htlc_value_in_flight_msat(
11215+
self.context.holder_max_htlc_value_in_flight_msat,
11216+
prev_channel_value_satoshis,
11217+
new_channel_value_satoshis,
11218+
);
11219+
self.context.counterparty_max_htlc_value_in_flight_msat =
11220+
rescale_max_htlc_value_in_flight_msat(
11221+
self.context.counterparty_max_htlc_value_in_flight_msat,
11222+
prev_channel_value_satoshis,
11223+
new_channel_value_satoshis,
11224+
);
11225+
log_info!(
11226+
logger,
11227+
"Splice promotion HTLC caps for channel {}: holder {} -> {}, counterparty {} -> {}, value {} -> {} sats",
11228+
&self.context.channel_id,
11229+
prev_holder_max_htlc_value_in_flight_msat,
11230+
self.context.holder_max_htlc_value_in_flight_msat,
11231+
prev_counterparty_max_htlc_value_in_flight_msat,
11232+
self.context.counterparty_max_htlc_value_in_flight_msat,
11233+
prev_channel_value_satoshis,
11234+
new_channel_value_satoshis,
11235+
);
1119411236

1119511237
// The swap above places the previous `FundingScope` into `pending_funding`.
1119611238
pending_splice

lightning/src/ln/splicing_tests.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -759,15 +759,17 @@ fn test_splice_in() {
759759
mine_transaction(&nodes[0], &splice_tx);
760760
mine_transaction(&nodes[1], &splice_tx);
761761

762-
let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
763-
assert!(htlc_limit_msat < initial_channel_value_sat * 1000);
764-
let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
762+
let pre_splice_htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
763+
assert!(pre_splice_htlc_limit_msat < initial_channel_value_sat * 1000);
764+
let _ = send_payment(&nodes[0], &[&nodes[1]], pre_splice_htlc_limit_msat);
765765

766766
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
767767

768-
let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
769-
assert!(htlc_limit_msat > initial_channel_value_sat);
770-
let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
768+
let post_splice_htlc_limit_msat =
769+
nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
770+
assert!(post_splice_htlc_limit_msat > pre_splice_htlc_limit_msat);
771+
assert!(post_splice_htlc_limit_msat > initial_channel_value_sat * 1000);
772+
let _ = send_payment(&nodes[0], &[&nodes[1]], post_splice_htlc_limit_msat);
771773
}
772774

773775
#[test]

0 commit comments

Comments
 (0)