@@ -2311,7 +2311,7 @@ where
2311
2311
fn construct_recv_pending_htlc_info (
2312
2312
& self , hop_data : msgs:: OnionHopData , shared_secret : [ u8 ; 32 ] , payment_hash : PaymentHash ,
2313
2313
amt_msat : u64 , cltv_expiry : u32 , phantom_shared_secret : Option < [ u8 ; 32 ] > ,
2314
- chan_retrieval_info : ChanMapRetrieval
2314
+ counterparty_skimmed_fee_msat : Option < u64 > , chan_retrieval_info : ChanMapRetrieval
2315
2315
) -> Result < PendingHTLCInfo , ReceiveError > {
2316
2316
// final_incorrect_cltv_expiry
2317
2317
if hop_data. outgoing_cltv_value > cltv_expiry {
@@ -2340,6 +2340,7 @@ where
2340
2340
}
2341
2341
if hop_data. amt_to_forward > amt_msat {
2342
2342
let allow_underpay = loop {
2343
+ if counterparty_skimmed_fee_msat. is_none ( ) { break false }
2343
2344
let ( counterparty_node_id, channel_id) = match chan_retrieval_info {
2344
2345
ChanMapRetrieval :: Scid ( scid) => {
2345
2346
match self . short_to_chan_info . read ( ) . unwrap ( ) . get ( & scid) . cloned ( ) {
@@ -2362,7 +2363,12 @@ where
2362
2363
} ;
2363
2364
break chan. config ( ) . accept_underpaying_htlcs
2364
2365
} ;
2365
- if !allow_underpay {
2366
+ if !allow_underpay ||
2367
+ // This check needs to match the extra fee calculation in
2368
+ // [`Self::forward_intercepted_htlc`].
2369
+ amt_msat. saturating_add ( counterparty_skimmed_fee_msat. unwrap_or ( 0 ) ) !=
2370
+ hop_data. amt_to_forward
2371
+ {
2366
2372
return Err ( ReceiveError {
2367
2373
err_code : 19 ,
2368
2374
err_data : amt_msat. to_be_bytes ( ) . to_vec ( ) ,
@@ -2494,7 +2500,7 @@ where
2494
2500
onion_utils:: Hop :: Receive ( next_hop_data) => {
2495
2501
// OUR PAYMENT!
2496
2502
match self . construct_recv_pending_htlc_info ( next_hop_data, shared_secret, msg. payment_hash ,
2497
- msg. amount_msat , msg. cltv_expiry , None ,
2503
+ msg. amount_msat , msg. cltv_expiry , None , msg . skimmed_fee_msat ,
2498
2504
ChanMapRetrieval :: IdPubkey ( ( * counterparty_node_id, msg. channel_id ) ) )
2499
2505
{
2500
2506
Ok ( info) => {
@@ -3404,7 +3410,9 @@ where
3404
3410
}
3405
3411
}
3406
3412
}
3407
- if let PendingHTLCRouting :: Forward { onion_packet, .. } = routing {
3413
+ if let PendingHTLCRouting :: Forward {
3414
+ onion_packet, skimmed_fee_msat, ..
3415
+ } = routing {
3408
3416
let phantom_pubkey_res = self . node_signer. get_node_id( Recipient :: PhantomNode ) ;
3409
3417
if phantom_pubkey_res. is_ok( ) && fake_scid:: is_valid_phantom( & self . fake_scid_rand_bytes, short_chan_id, & self . genesis_hash) {
3410
3418
let phantom_shared_secret = self . node_signer. ecdh( Recipient :: PhantomNode , & onion_packet. public_key. unwrap( ) , None ) . unwrap( ) . secret_bytes( ) ;
@@ -3427,7 +3435,7 @@ where
3427
3435
match self . construct_recv_pending_htlc_info( hop_data,
3428
3436
incoming_shared_secret, payment_hash, outgoing_amt_msat,
3429
3437
outgoing_cltv_value, Some ( phantom_shared_secret) ,
3430
- ChanMapRetrieval :: Scid ( prev_short_channel_id) )
3438
+ skimmed_fee_msat , ChanMapRetrieval :: Scid ( prev_short_channel_id) )
3431
3439
{
3432
3440
Ok ( info) => phantom_receives. push( ( prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![ ( info, prev_htlc_id) ] ) ) ,
3433
3441
Err ( ReceiveError { err_code, err_data, msg } ) => failed_payment!( msg, err_code, err_data, Some ( phantom_shared_secret) )
@@ -9292,6 +9300,55 @@ mod tests {
9292
9300
get_event_msg ! ( nodes[ 1 ] , MessageSendEvent :: SendAcceptChannel , last_random_pk) ;
9293
9301
}
9294
9302
9303
+ #[ test]
9304
+ fn reject_excessively_underpaying_htlcs ( ) {
9305
+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
9306
+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
9307
+ let mut underpay_config = test_default_channel_config ( ) ;
9308
+ underpay_config. channel_config . accept_underpaying_htlcs = true ;
9309
+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , Some ( underpay_config) ] ) ;
9310
+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
9311
+ let scid = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 10_000 , 0 ) . 0 . contents . short_channel_id ;
9312
+ let sender_intended_amt_msat = 100 ;
9313
+ let extra_fee_msat = 10 ;
9314
+ let hop_data = msgs:: OnionHopData {
9315
+ amt_to_forward : 100 ,
9316
+ outgoing_cltv_value : 42 ,
9317
+ format : msgs:: OnionHopDataFormat :: FinalNode {
9318
+ keysend_preimage : None ,
9319
+ payment_metadata : None ,
9320
+ payment_data : Some ( msgs:: FinalOnionHopData {
9321
+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) , total_msat : sender_intended_amt_msat,
9322
+ } ) ,
9323
+ }
9324
+ } ;
9325
+ // Check that if the amount we received + the penultimate hop extra fee is less than the sender
9326
+ // intended amount, we fail the payment.
9327
+ if let Err ( crate :: ln:: channelmanager:: ReceiveError { err_code, .. } ) =
9328
+ nodes[ 1 ] . node . construct_recv_pending_htlc_info ( hop_data, [ 0 ; 32 ] , PaymentHash ( [ 0 ; 32 ] ) ,
9329
+ sender_intended_amt_msat - extra_fee_msat - 1 , 42 , None , Some ( extra_fee_msat) ,
9330
+ crate :: ln:: channelmanager:: ChanMapRetrieval :: Scid ( scid) )
9331
+ {
9332
+ assert_eq ! ( err_code, 19 ) ;
9333
+ } else { panic ! ( ) ; }
9334
+
9335
+ // If amt_received + extra_fee is equal to the sender intended amount, we're fine.
9336
+ let hop_data = msgs:: OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
9337
+ amt_to_forward : 100 ,
9338
+ outgoing_cltv_value : 42 ,
9339
+ format : msgs:: OnionHopDataFormat :: FinalNode {
9340
+ keysend_preimage : None ,
9341
+ payment_metadata : None ,
9342
+ payment_data : Some ( msgs:: FinalOnionHopData {
9343
+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) , total_msat : sender_intended_amt_msat,
9344
+ } ) ,
9345
+ }
9346
+ } ;
9347
+ assert ! ( nodes[ 1 ] . node. construct_recv_pending_htlc_info( hop_data, [ 0 ; 32 ] , PaymentHash ( [ 0 ; 32 ] ) ,
9348
+ sender_intended_amt_msat - extra_fee_msat, 42 , None , Some ( extra_fee_msat) ,
9349
+ crate :: ln:: channelmanager:: ChanMapRetrieval :: Scid ( scid) ) . is_ok( ) ) ;
9350
+ }
9351
+
9295
9352
#[ cfg( anchors) ]
9296
9353
#[ test]
9297
9354
fn test_anchors_zero_fee_htlc_tx_fallback ( ) {
0 commit comments