@@ -45,6 +45,12 @@ use crate::jit_channel::msgs::{
45
45
46
46
const SUPPORTED_SPEC_VERSIONS : [ u16 ; 1 ] = [ 1 ] ;
47
47
48
+ #[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
49
+ struct InterceptedHTLC {
50
+ intercept_id : InterceptId ,
51
+ expected_outbound_amount_msat : u64 ,
52
+ }
53
+
48
54
struct ChannelStateError ( String ) ;
49
55
50
56
impl From < ChannelStateError > for LightningError {
@@ -186,50 +192,86 @@ impl InboundJITChannel {
186
192
187
193
#[ derive( PartialEq , Debug ) ]
188
194
enum 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 > ,
192
199
payment_size_msat : Option < u64 > ,
193
- opening_fee_params : OpeningFeeParams ,
194
200
} ,
195
201
PendingChannelOpen {
196
- intercept_id : InterceptId ,
202
+ htlcs : Vec < InterceptedHTLC > ,
197
203
opening_fee_msat : u64 ,
198
204
amt_to_forward_msat : u64 ,
199
205
} ,
200
206
ChannelReady {
201
- intercept_id : InterceptId ,
207
+ htlcs : Vec < InterceptedHTLC > ,
202
208
amt_to_forward_msat : u64 ,
203
209
} ,
204
210
}
205
211
206
212
impl 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 ! [ ] ,
214
218
payment_size_msat,
215
- opening_fee_params,
216
219
}
217
220
}
218
221
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 > {
222
223
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
+ }
233
275
}
234
276
state => Err ( ChannelStateError ( format ! (
235
277
"Invoice params received when JIT Channel was in state: {:?}" ,
@@ -240,14 +282,12 @@ impl OutboundJITChannelState {
240
282
241
283
pub fn channel_ready ( & self ) -> Result < Self , ChannelStateError > {
242
284
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
+ }
251
291
state => Err ( ChannelStateError ( format ! (
252
292
"Channel ready received when JIT Channel was in state: {:?}" ,
253
293
state
@@ -258,34 +298,39 @@ impl OutboundJITChannelState {
258
298
259
299
struct OutboundJITChannel {
260
300
state : OutboundJITChannelState ,
301
+ scid : u64 ,
302
+ cltv_expiry_delta : u32 ,
303
+ client_trusts_lsp : bool ,
261
304
}
262
305
263
306
impl OutboundJITChannel {
264
307
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 > ,
266
309
opening_fee_params : OpeningFeeParams ,
267
310
) -> Self {
268
311
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) ,
275
316
}
276
317
}
277
318
278
319
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 ) ?;
282
323
283
324
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
+ }
284
329
OutboundJITChannelState :: PendingChannelOpen {
285
330
opening_fee_msat,
286
331
amt_to_forward_msat,
287
332
..
288
- } => Ok ( ( * opening_fee_msat, * amt_to_forward_msat) ) ,
333
+ } => Ok ( Some ( ( * opening_fee_msat, * amt_to_forward_msat) ) ) ,
289
334
impossible_state => Err ( LightningError {
290
335
err : format ! (
291
336
"Impossible state transition during htlc_intercepted to {:?}" ,
@@ -296,12 +341,12 @@ impl OutboundJITChannel {
296
341
}
297
342
}
298
343
299
- pub fn channel_ready ( & mut self ) -> Result < ( InterceptId , u64 ) , LightningError > {
344
+ pub fn channel_ready ( & mut self ) -> Result < ( Vec < InterceptedHTLC > , u64 ) , LightningError > {
300
345
self . state = self . state . channel_ready ( ) ?;
301
346
302
347
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) )
305
350
}
306
351
impossible_state => Err ( LightningError {
307
352
err : format ! (
@@ -585,6 +630,7 @@ where
585
630
let outbound_jit_channel = OutboundJITChannel :: new (
586
631
scid,
587
632
cltv_expiry_delta,
633
+ client_trusts_lsp,
588
634
buy_request. payment_size_msat ,
589
635
buy_request. opening_fee_params ,
590
636
) ;
@@ -615,8 +661,7 @@ where
615
661
}
616
662
617
663
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 ,
620
665
) -> Result < ( ) , APIError > {
621
666
let peer_by_scid = self . peer_by_scid . read ( ) . unwrap ( ) ;
622
667
if let Some ( counterparty_node_id) = peer_by_scid. get ( & scid) {
@@ -625,25 +670,17 @@ where
625
670
Some ( inner_state_lock) => {
626
671
let mut peer_state = inner_state_lock. lock ( ) . unwrap ( ) ;
627
672
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) ) ) => {
638
676
self . enqueue_event ( Event :: LSPS2 ( LSPS2Event :: OpenChannel {
639
677
their_network_key : counterparty_node_id. clone ( ) ,
640
- inbound_amount_msat,
641
- expected_outbound_amount_msat,
642
678
amt_to_forward_msat,
643
679
opening_fee_msat,
644
680
user_channel_id : scid as u128 ,
645
681
} ) ) ;
646
682
}
683
+ Ok ( None ) => { }
647
684
Err ( e) => {
648
685
self . channel_manager . fail_intercepted_htlc ( intercept_id) ?;
649
686
peer_state. outbound_channels_by_scid . remove ( & scid) ;
@@ -675,13 +712,22 @@ where
675
712
let mut peer_state = inner_state_lock. lock ( ) . unwrap ( ) ;
676
713
if let Some ( jit_channel) = peer_state. outbound_channels_by_scid . get_mut ( & scid) {
677
714
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
+ }
685
731
}
686
732
Err ( e) => {
687
733
return Err ( APIError :: APIMisuseError {
@@ -1237,3 +1283,71 @@ where
1237
1283
}
1238
1284
}
1239
1285
}
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