@@ -45,6 +45,12 @@ use crate::jit_channel::msgs::{
4545
4646const 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+
4854struct ChannelStateError ( String ) ;
4955
5056impl From < ChannelStateError > for LightningError {
@@ -186,50 +192,86 @@ impl InboundJITChannel {
186192
187193#[ derive( PartialEq , Debug ) ]
188194enum OutboundJITChannelState {
189- InvoiceParametersGenerated {
190- short_channel_id : u64 ,
191- cltv_expiry_delta : u32 ,
195+ AwaitingPayment {
196+ min_fee_msat : u64 ,
197+ proportional_fee : u32 ,
198+ htlcs : Vec < InterceptedHTLC > ,
192199 payment_size_msat : Option < u64 > ,
193- opening_fee_params : OpeningFeeParams ,
194200 } ,
195201 PendingChannelOpen {
196- intercept_id : InterceptId ,
202+ htlcs : Vec < InterceptedHTLC > ,
197203 opening_fee_msat : u64 ,
198204 amt_to_forward_msat : u64 ,
199205 } ,
200206 ChannelReady {
201- intercept_id : InterceptId ,
207+ htlcs : Vec < InterceptedHTLC > ,
202208 amt_to_forward_msat : u64 ,
203209 } ,
204210}
205211
206212impl OutboundJITChannelState {
207- pub fn new (
208- short_channel_id : u64 , cltv_expiry_delta : u32 , payment_size_msat : Option < u64 > ,
209- opening_fee_params : OpeningFeeParams ,
210- ) -> Self {
211- OutboundJITChannelState :: InvoiceParametersGenerated {
212- short_channel_id,
213- cltv_expiry_delta,
213+ pub fn new ( payment_size_msat : Option < u64 > , opening_fee_params : OpeningFeeParams ) -> Self {
214+ OutboundJITChannelState :: AwaitingPayment {
215+ min_fee_msat : opening_fee_params. min_fee_msat ,
216+ proportional_fee : opening_fee_params. proportional ,
217+ htlcs : vec ! [ ] ,
214218 payment_size_msat,
215- opening_fee_params,
216219 }
217220 }
218221
219- pub fn htlc_intercepted (
220- & self , expected_outbound_amount_msat : u64 , intercept_id : InterceptId ,
221- ) -> Result < Self , ChannelStateError > {
222+ pub fn htlc_intercepted ( & self , htlc : InterceptedHTLC ) -> Result < Self , ChannelStateError > {
222223 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 . into ( ) ,
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) ) )
224+ OutboundJITChannelState :: AwaitingPayment {
225+ htlcs,
226+ payment_size_msat,
227+ min_fee_msat,
228+ proportional_fee,
229+ } => {
230+ let mut htlcs = htlcs. clone ( ) ;
231+ htlcs. push ( htlc) ;
232+
233+ let total_expected_outbound_amount_msat =
234+ htlcs. iter ( ) . map ( |htlc| htlc. expected_outbound_amount_msat ) . sum ( ) ;
235+
236+ let expected_payment_size_msat =
237+ payment_size_msat. unwrap_or ( total_expected_outbound_amount_msat) ;
238+
239+ let opening_fee_msat = compute_opening_fee (
240+ expected_payment_size_msat,
241+ * min_fee_msat,
242+ ( * proportional_fee) . into ( ) ,
243+ ) . ok_or ( ChannelStateError (
244+ format ! ( "Could not compute valid opening fee with min_fee_msat = {}, proportional = {}, and total_expected_outbound_amount_msat = {}" ,
245+ min_fee_msat,
246+ proportional_fee,
247+ total_expected_outbound_amount_msat
248+ )
249+ ) ) ?;
250+
251+ let amt_to_forward_msat =
252+ expected_payment_size_msat. saturating_sub ( opening_fee_msat) ;
253+
254+ if total_expected_outbound_amount_msat >= expected_payment_size_msat
255+ && amt_to_forward_msat > 0
256+ {
257+ Ok ( OutboundJITChannelState :: PendingChannelOpen {
258+ htlcs,
259+ opening_fee_msat,
260+ amt_to_forward_msat,
261+ } )
262+ } else {
263+ // payment size being specified means MPP is supported
264+ if payment_size_msat. is_some ( ) {
265+ Ok ( OutboundJITChannelState :: AwaitingPayment {
266+ min_fee_msat : * min_fee_msat,
267+ proportional_fee : * proportional_fee,
268+ htlcs,
269+ payment_size_msat : * payment_size_msat,
270+ } )
271+ } else {
272+ Err ( ChannelStateError ( "HTLC is too small to pay opening fee" . to_string ( ) ) )
273+ }
274+ }
233275 }
234276 state => Err ( ChannelStateError ( format ! (
235277 "Invoice params received when JIT Channel was in state: {:?}" ,
@@ -240,14 +282,12 @@ impl OutboundJITChannelState {
240282
241283 pub fn channel_ready ( & self ) -> Result < Self , ChannelStateError > {
242284 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- } ) ,
285+ OutboundJITChannelState :: PendingChannelOpen { htlcs, amt_to_forward_msat, .. } => {
286+ Ok ( OutboundJITChannelState :: ChannelReady {
287+ htlcs : htlcs. clone ( ) ,
288+ amt_to_forward_msat : * amt_to_forward_msat,
289+ } )
290+ }
251291 state => Err ( ChannelStateError ( format ! (
252292 "Channel ready received when JIT Channel was in state: {:?}" ,
253293 state
@@ -258,34 +298,39 @@ impl OutboundJITChannelState {
258298
259299struct OutboundJITChannel {
260300 state : OutboundJITChannelState ,
301+ scid : u64 ,
302+ cltv_expiry_delta : u32 ,
303+ client_trusts_lsp : bool ,
261304}
262305
263306impl OutboundJITChannel {
264307 pub fn new (
265- scid : u64 , cltv_expiry_delta : u32 , payment_size_msat : Option < u64 > ,
308+ scid : u64 , cltv_expiry_delta : u32 , client_trusts_lsp : bool , payment_size_msat : Option < u64 > ,
266309 opening_fee_params : OpeningFeeParams ,
267310 ) -> Self {
268311 Self {
269- state : OutboundJITChannelState :: new (
270- scid,
271- cltv_expiry_delta,
272- payment_size_msat,
273- opening_fee_params,
274- ) ,
312+ scid,
313+ cltv_expiry_delta,
314+ client_trusts_lsp,
315+ state : OutboundJITChannelState :: new ( payment_size_msat, opening_fee_params) ,
275316 }
276317 }
277318
278319 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 ) ?;
320+ & mut self , htlc : InterceptedHTLC ,
321+ ) -> Result < Option < ( u64 , u64 ) > , LightningError > {
322+ self . state = self . state . htlc_intercepted ( htlc ) ?;
282323
283324 match & self . state {
325+ OutboundJITChannelState :: AwaitingPayment { htlcs, payment_size_msat, .. } => {
326+ // TODO: log that we received an htlc but are still awaiting payment
327+ Ok ( None )
328+ }
284329 OutboundJITChannelState :: PendingChannelOpen {
285330 opening_fee_msat,
286331 amt_to_forward_msat,
287332 ..
288- } => Ok ( ( * opening_fee_msat, * amt_to_forward_msat) ) ,
333+ } => Ok ( Some ( ( * opening_fee_msat, * amt_to_forward_msat) ) ) ,
289334 impossible_state => Err ( LightningError {
290335 err : format ! (
291336 "Impossible state transition during htlc_intercepted to {:?}" ,
@@ -296,12 +341,12 @@ impl OutboundJITChannel {
296341 }
297342 }
298343
299- pub fn channel_ready ( & mut self ) -> Result < ( InterceptId , u64 ) , LightningError > {
344+ pub fn channel_ready ( & mut self ) -> Result < ( Vec < InterceptedHTLC > , u64 ) , LightningError > {
300345 self . state = self . state . channel_ready ( ) ?;
301346
302347 match & self . state {
303- OutboundJITChannelState :: ChannelReady { intercept_id , amt_to_forward_msat } => {
304- Ok ( ( * intercept_id , * amt_to_forward_msat) )
348+ OutboundJITChannelState :: ChannelReady { htlcs , amt_to_forward_msat } => {
349+ Ok ( ( htlcs . clone ( ) , * amt_to_forward_msat) )
305350 }
306351 impossible_state => Err ( LightningError {
307352 err : format ! (
@@ -585,6 +630,7 @@ where
585630 let outbound_jit_channel = OutboundJITChannel :: new (
586631 scid,
587632 cltv_expiry_delta,
633+ client_trusts_lsp,
588634 buy_request. payment_size_msat ,
589635 buy_request. opening_fee_params ,
590636 ) ;
@@ -615,8 +661,7 @@ where
615661 }
616662
617663 pub ( crate ) fn htlc_intercepted (
618- & self , scid : u64 , intercept_id : InterceptId , inbound_amount_msat : u64 ,
619- expected_outbound_amount_msat : u64 ,
664+ & self , scid : u64 , intercept_id : InterceptId , expected_outbound_amount_msat : u64 ,
620665 ) -> Result < ( ) , APIError > {
621666 let peer_by_scid = self . peer_by_scid . read ( ) . unwrap ( ) ;
622667 if let Some ( counterparty_node_id) = peer_by_scid. get ( & scid) {
@@ -625,25 +670,17 @@ where
625670 Some ( inner_state_lock) => {
626671 let mut peer_state = inner_state_lock. lock ( ) . unwrap ( ) ;
627672 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) ) => {
673+ let htlc = InterceptedHTLC { intercept_id, expected_outbound_amount_msat } ;
674+ match jit_channel. htlc_intercepted ( htlc) {
675+ Ok ( Some ( ( opening_fee_msat, amt_to_forward_msat) ) ) => {
638676 self . enqueue_event ( Event :: LSPS2 ( LSPS2Event :: OpenChannel {
639677 their_network_key : counterparty_node_id. clone ( ) ,
640- inbound_amount_msat,
641- expected_outbound_amount_msat,
642678 amt_to_forward_msat,
643679 opening_fee_msat,
644680 user_channel_id : scid as u128 ,
645681 } ) ) ;
646682 }
683+ Ok ( None ) => { }
647684 Err ( e) => {
648685 self . channel_manager . fail_intercepted_htlc ( intercept_id) ?;
649686 peer_state. outbound_channels_by_scid . remove ( & scid) ;
@@ -675,13 +712,22 @@ where
675712 let mut peer_state = inner_state_lock. lock ( ) . unwrap ( ) ;
676713 if let Some ( jit_channel) = peer_state. outbound_channels_by_scid . get_mut ( & scid) {
677714 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- ) ?;
715+ Ok ( ( htlcs, total_amt_to_forward_msat) ) => {
716+ let amounts_to_forward_msat = calculate_amount_to_forward_per_htlc (
717+ & htlcs,
718+ total_amt_to_forward_msat,
719+ ) ;
720+
721+ for ( intercept_id, amount_to_forward_msat) in
722+ amounts_to_forward_msat
723+ {
724+ self . channel_manager . forward_intercepted_htlc (
725+ intercept_id,
726+ channel_id,
727+ * counterparty_node_id,
728+ amount_to_forward_msat,
729+ ) ?;
730+ }
685731 }
686732 Err ( e) => {
687733 return Err ( APIError :: APIMisuseError {
@@ -1237,3 +1283,71 @@ where
12371283 }
12381284 }
12391285}
1286+
1287+ fn calculate_amount_to_forward_per_htlc (
1288+ htlcs : & [ InterceptedHTLC ] , total_amt_to_forward_msat : u64 ,
1289+ ) -> Vec < ( InterceptId , u64 ) > {
1290+ let total_received_msat: u64 =
1291+ htlcs. iter ( ) . map ( |htlc| htlc. expected_outbound_amount_msat ) . sum ( ) ;
1292+
1293+ let mut fee_remaining_msat = total_received_msat - total_amt_to_forward_msat;
1294+ let total_fee_msat = fee_remaining_msat;
1295+
1296+ let mut per_htlc_forwards = vec ! [ ] ;
1297+
1298+ for ( index, htlc) in htlcs. iter ( ) . enumerate ( ) {
1299+ let proportional_fee_amt_msat =
1300+ total_fee_msat * htlc. expected_outbound_amount_msat / total_received_msat;
1301+
1302+ let mut actual_fee_amt_msat = std:: cmp:: min ( fee_remaining_msat, proportional_fee_amt_msat) ;
1303+ fee_remaining_msat -= actual_fee_amt_msat;
1304+
1305+ if index == htlcs. len ( ) - 1 {
1306+ actual_fee_amt_msat += fee_remaining_msat;
1307+ }
1308+
1309+ let amount_to_forward_msat = htlc. expected_outbound_amount_msat - actual_fee_amt_msat;
1310+
1311+ per_htlc_forwards. push ( ( htlc. intercept_id , amount_to_forward_msat) )
1312+ }
1313+
1314+ per_htlc_forwards
1315+ }
1316+
1317+ #[ cfg( test) ]
1318+ mod tests {
1319+
1320+ use super :: * ;
1321+
1322+ #[ test]
1323+ fn test_calculate_amount_to_forward ( ) {
1324+ // TODO: Use proptest to generate random allocations
1325+ let htlcs = vec ! [
1326+ InterceptedHTLC {
1327+ intercept_id: InterceptId ( [ 0 ; 32 ] ) ,
1328+ expected_outbound_amount_msat: 1000 ,
1329+ } ,
1330+ InterceptedHTLC {
1331+ intercept_id: InterceptId ( [ 1 ; 32 ] ) ,
1332+ expected_outbound_amount_msat: 2000 ,
1333+ } ,
1334+ InterceptedHTLC {
1335+ intercept_id: InterceptId ( [ 2 ; 32 ] ) ,
1336+ expected_outbound_amount_msat: 3000 ,
1337+ } ,
1338+ ] ;
1339+
1340+ let total_amt_to_forward_msat = 5000 ;
1341+
1342+ let result = calculate_amount_to_forward_per_htlc ( & htlcs, total_amt_to_forward_msat) ;
1343+
1344+ assert_eq ! ( result[ 0 ] . 0 , htlcs[ 0 ] . intercept_id) ;
1345+ assert_eq ! ( result[ 0 ] . 1 , 834 ) ;
1346+
1347+ assert_eq ! ( result[ 1 ] . 0 , htlcs[ 1 ] . intercept_id) ;
1348+ assert_eq ! ( result[ 1 ] . 1 , 1667 ) ;
1349+
1350+ assert_eq ! ( result[ 2 ] . 0 , htlcs[ 2 ] . intercept_id) ;
1351+ assert_eq ! ( result[ 2 ] . 1 , 2499 ) ;
1352+ }
1353+ }
0 commit comments