Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit e2be22b

Browse files
add support for mpp
1 parent 40cc652 commit e2be22b

File tree

3 files changed

+128
-58
lines changed

3 files changed

+128
-58
lines changed

src/jit_channel/channel_manager.rs

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ use crate::jit_channel::msgs::{
4545

4646
const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1];
4747

48+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
49+
struct InterceptedHTLC {
50+
intercept_id: InterceptId,
51+
expected_outbound_amount_msat: u64,
52+
}
53+
4854
struct ChannelStateError(String);
4955

5056
impl From<ChannelStateError> for LightningError {
@@ -192,13 +198,19 @@ enum OutboundJITChannelState {
192198
payment_size_msat: Option<u64>,
193199
opening_fee_params: OpeningFeeParams,
194200
},
201+
PendingPaymentReceived {
202+
htlcs: Vec<InterceptedHTLC>,
203+
payment_size_msat: u64,
204+
amt_to_forward_msat: u64,
205+
opening_fee_msat: u64,
206+
},
195207
PendingChannelOpen {
196-
intercept_id: InterceptId,
208+
htlcs: Vec<InterceptedHTLC>,
197209
opening_fee_msat: u64,
198210
amt_to_forward_msat: u64,
199211
},
200212
ChannelReady {
201-
intercept_id: InterceptId,
213+
htlcs: Vec<InterceptedHTLC>,
202214
amt_to_forward_msat: u64,
203215
},
204216
}
@@ -216,20 +228,68 @@ impl OutboundJITChannelState {
216228
}
217229
}
218230

219-
pub fn htlc_intercepted(
220-
&self, expected_outbound_amount_msat: u64, intercept_id: InterceptId,
221-
) -> Result<Self, ChannelStateError> {
231+
pub fn htlc_intercepted(&self, htlc: InterceptedHTLC) -> Result<Self, ChannelStateError> {
222232
match self {
223-
OutboundJITChannelState::InvoiceParametersGenerated { opening_fee_params, .. } => {
224-
compute_opening_fee(
225-
expected_outbound_amount_msat,
226-
opening_fee_params.min_fee_msat,
227-
opening_fee_params.proportional,
228-
).map(|opening_fee_msat| OutboundJITChannelState::PendingChannelOpen {
229-
intercept_id,
230-
opening_fee_msat,
231-
amt_to_forward_msat: expected_outbound_amount_msat - opening_fee_msat,
232-
}).ok_or(ChannelStateError(format!("Could not compute valid opening fee with min_fee_msat = {}, proportional = {}, and expected_outbound_amount_msat = {}", opening_fee_params.min_fee_msat, opening_fee_params.proportional, expected_outbound_amount_msat)))
233+
OutboundJITChannelState::InvoiceParametersGenerated {
234+
opening_fee_params,
235+
payment_size_msat,
236+
..
237+
} => {
238+
let htlcs = vec![htlc];
239+
let supports_mpp = payment_size_msat.is_some();
240+
let payment_size_msat =
241+
payment_size_msat.unwrap_or(htlc.expected_outbound_amount_msat);
242+
let opening_fee_msat = compute_opening_fee(payment_size_msat, opening_fee_params.min_fee_msat, opening_fee_params.proportional).ok_or(ChannelStateError(format!("Could not compute valid opening fee with min_fee_msat = {}, proportional = {}, and expected_outbound_amount_msat = {}", opening_fee_params.min_fee_msat, opening_fee_params.proportional, htlc.expected_outbound_amount_msat)))?;
243+
let amt_to_forward_msat = payment_size_msat - opening_fee_msat;
244+
245+
if htlc.expected_outbound_amount_msat >= payment_size_msat
246+
&& amt_to_forward_msat > 0
247+
{
248+
Ok(OutboundJITChannelState::PendingChannelOpen {
249+
htlcs,
250+
opening_fee_msat,
251+
amt_to_forward_msat,
252+
})
253+
} else {
254+
if supports_mpp {
255+
Ok(OutboundJITChannelState::PendingPaymentReceived {
256+
htlcs,
257+
amt_to_forward_msat,
258+
opening_fee_msat,
259+
payment_size_msat,
260+
})
261+
} else {
262+
Err(ChannelStateError("HTLC is too small to pay opening fee".to_string()))
263+
}
264+
}
265+
}
266+
OutboundJITChannelState::PendingPaymentReceived {
267+
htlcs,
268+
payment_size_msat,
269+
amt_to_forward_msat,
270+
opening_fee_msat,
271+
} => {
272+
let mut htlcs = htlcs.clone();
273+
htlcs.push(htlc);
274+
275+
let total_expected_outbound_amount_msat = htlcs
276+
.iter()
277+
.fold(0, |total_msat, htlc| total_msat + htlc.expected_outbound_amount_msat);
278+
279+
if total_expected_outbound_amount_msat >= *payment_size_msat {
280+
return Ok(OutboundJITChannelState::PendingPaymentReceived {
281+
htlcs,
282+
payment_size_msat: *payment_size_msat,
283+
amt_to_forward_msat: *amt_to_forward_msat,
284+
opening_fee_msat: *opening_fee_msat,
285+
});
286+
}
287+
288+
Ok(OutboundJITChannelState::PendingChannelOpen {
289+
htlcs,
290+
opening_fee_msat: *opening_fee_msat,
291+
amt_to_forward_msat: *amt_to_forward_msat,
292+
})
233293
}
234294
state => Err(ChannelStateError(format!(
235295
"Invoice params received when JIT Channel was in state: {:?}",
@@ -240,14 +300,12 @@ impl OutboundJITChannelState {
240300

241301
pub fn channel_ready(&self) -> Result<Self, ChannelStateError> {
242302
match self {
243-
OutboundJITChannelState::PendingChannelOpen {
244-
intercept_id,
245-
amt_to_forward_msat,
246-
..
247-
} => Ok(OutboundJITChannelState::ChannelReady {
248-
intercept_id: *intercept_id,
249-
amt_to_forward_msat: *amt_to_forward_msat,
250-
}),
303+
OutboundJITChannelState::PendingChannelOpen { htlcs, amt_to_forward_msat, .. } => {
304+
Ok(OutboundJITChannelState::ChannelReady {
305+
htlcs: htlcs.clone(),
306+
amt_to_forward_msat: *amt_to_forward_msat,
307+
})
308+
}
251309
state => Err(ChannelStateError(format!(
252310
"Channel ready received when JIT Channel was in state: {:?}",
253311
state
@@ -276,16 +334,17 @@ impl OutboundJITChannel {
276334
}
277335

278336
pub fn htlc_intercepted(
279-
&mut self, expected_outbound_amount_msat: u64, intercept_id: InterceptId,
280-
) -> Result<(u64, u64), LightningError> {
281-
self.state = self.state.htlc_intercepted(expected_outbound_amount_msat, intercept_id)?;
337+
&mut self, htlc: InterceptedHTLC,
338+
) -> Result<Option<(u64, u64)>, LightningError> {
339+
self.state = self.state.htlc_intercepted(htlc)?;
282340

283341
match &self.state {
342+
OutboundJITChannelState::PendingPaymentReceived { .. } => Ok(None),
284343
OutboundJITChannelState::PendingChannelOpen {
285344
opening_fee_msat,
286345
amt_to_forward_msat,
287346
..
288-
} => Ok((*opening_fee_msat, *amt_to_forward_msat)),
347+
} => Ok(Some((*opening_fee_msat, *amt_to_forward_msat))),
289348
impossible_state => Err(LightningError {
290349
err: format!(
291350
"Impossible state transition during htlc_intercepted to {:?}",
@@ -296,12 +355,12 @@ impl OutboundJITChannel {
296355
}
297356
}
298357

299-
pub fn channel_ready(&mut self) -> Result<(InterceptId, u64), LightningError> {
358+
pub fn channel_ready(&mut self) -> Result<(Vec<InterceptedHTLC>, u64), LightningError> {
300359
self.state = self.state.channel_ready()?;
301360

302361
match &self.state {
303-
OutboundJITChannelState::ChannelReady { intercept_id, amt_to_forward_msat } => {
304-
Ok((*intercept_id, *amt_to_forward_msat))
362+
OutboundJITChannelState::ChannelReady { htlcs, amt_to_forward_msat } => {
363+
Ok((htlcs.clone(), *amt_to_forward_msat))
305364
}
306365
impossible_state => Err(LightningError {
307366
err: format!(
@@ -615,8 +674,7 @@ where
615674
}
616675

617676
pub(crate) fn htlc_intercepted(
618-
&self, scid: u64, intercept_id: InterceptId, inbound_amount_msat: u64,
619-
expected_outbound_amount_msat: u64,
677+
&self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64,
620678
) -> Result<(), APIError> {
621679
let peer_by_scid = self.peer_by_scid.read().unwrap();
622680
if let Some(counterparty_node_id) = peer_by_scid.get(&scid) {
@@ -625,25 +683,17 @@ where
625683
Some(inner_state_lock) => {
626684
let mut peer_state = inner_state_lock.lock().unwrap();
627685
if let Some(jit_channel) = peer_state.outbound_channels_by_scid.get_mut(&scid) {
628-
// TODO: Need to support MPP payments. If payment_amount_msat is known, needs to queue intercepted HTLCs in a map by payment_hash
629-
// LiquidityManager will need to be regularly polled so it can continually check if the payment amount has been received
630-
// and can release the payment or if the channel valid_until has expired and should be failed.
631-
// Can perform check each time HTLC is received and on interval? I guess interval only needs to check expiration as
632-
// we can only reach threshold when htlc is intercepted.
633-
634-
match jit_channel
635-
.htlc_intercepted(expected_outbound_amount_msat, intercept_id)
636-
{
637-
Ok((opening_fee_msat, amt_to_forward_msat)) => {
686+
let htlc = InterceptedHTLC { intercept_id, expected_outbound_amount_msat };
687+
match jit_channel.htlc_intercepted(htlc) {
688+
Ok(Some((opening_fee_msat, amt_to_forward_msat))) => {
638689
self.enqueue_event(Event::LSPS2(LSPS2Event::OpenChannel {
639690
their_network_key: counterparty_node_id.clone(),
640-
inbound_amount_msat,
641-
expected_outbound_amount_msat,
642691
amt_to_forward_msat,
643692
opening_fee_msat,
644693
user_channel_id: scid as u128,
645694
}));
646695
}
696+
Ok(None) => {}
647697
Err(e) => {
648698
self.channel_manager.fail_intercepted_htlc(intercept_id)?;
649699
peer_state.outbound_channels_by_scid.remove(&scid);
@@ -675,13 +725,39 @@ where
675725
let mut peer_state = inner_state_lock.lock().unwrap();
676726
if let Some(jit_channel) = peer_state.outbound_channels_by_scid.get_mut(&scid) {
677727
match jit_channel.channel_ready() {
678-
Ok((intercept_id, amt_to_forward_msat)) => {
679-
self.channel_manager.forward_intercepted_htlc(
680-
intercept_id,
681-
channel_id,
682-
*counterparty_node_id,
683-
amt_to_forward_msat,
684-
)?;
728+
Ok((htlcs, amt_to_forward_msat)) => {
729+
// TODO: review strategy for handling batch forwarding of these htlcs
730+
let min_htlc_msat = self
731+
.channel_manager
732+
.list_channels_with_counterparty(counterparty_node_id)
733+
.iter()
734+
.find(|channel| channel.channel_id == *channel_id)
735+
.map(|details| details.next_outbound_htlc_minimum_msat)
736+
.ok_or(APIError::APIMisuseError {
737+
err: format!("Channel with id {} not found", channel_id),
738+
})?;
739+
740+
let total_msat = htlcs
741+
.iter()
742+
.fold(0, |acc, htlc| acc + htlc.expected_outbound_amount_msat);
743+
let mut fee_msat = total_msat - amt_to_forward_msat;
744+
745+
for htlc in htlcs {
746+
let max_we_can_take =
747+
htlc.expected_outbound_amount_msat - min_htlc_msat;
748+
let amount_of_fee_to_take =
749+
std::cmp::min(fee_msat, max_we_can_take);
750+
let amount_to_forward_msat =
751+
htlc.expected_outbound_amount_msat - amount_of_fee_to_take;
752+
self.channel_manager.forward_intercepted_htlc(
753+
htlc.intercept_id,
754+
channel_id,
755+
*counterparty_node_id,
756+
amount_to_forward_msat,
757+
)?;
758+
759+
fee_msat -= amount_of_fee_to_take;
760+
}
685761
}
686762
Err(e) => {
687763
return Err(APIError::APIMisuseError {

src/jit_channel/event.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,6 @@ pub enum LSPS2Event {
110110
OpenChannel {
111111
/// The node to open channel with.
112112
their_network_key: PublicKey,
113-
/// The intercepted HTLC amount in msats.
114-
inbound_amount_msat: u64,
115-
/// The amount the client expects to receive before fees are taken out.
116-
expected_outbound_amount_msat: u64,
117113
/// The amount to forward after fees.
118114
amt_to_forward_msat: u64,
119115
/// The fee earned for opening the channel.

src/transport/message_handler.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,14 +351,12 @@ where {
351351
/// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted
352352
/// [`LSPS2Event::OpenChannel`]: crate::jit_channel::LSPS2Event::OpenChannel
353353
pub fn htlc_intercepted(
354-
&self, scid: u64, intercept_id: InterceptId, inbound_amount_msat: u64,
355-
expected_outbound_amount_msat: u64,
354+
&self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64,
356355
) -> Result<(), APIError> {
357356
if let Some(lsps2_message_handler) = &self.lsps2_message_handler {
358357
lsps2_message_handler.htlc_intercepted(
359358
scid,
360359
intercept_id,
361-
inbound_amount_msat,
362360
expected_outbound_amount_msat,
363361
)?;
364362
}

0 commit comments

Comments
 (0)