Skip to content

Commit db93d1b

Browse files
Check UpdateAddHTLC::skimmed_fee_msat on receive
Make sure the penultimate hop took the amount of fee that they claimed to take. Without checking this TLV, we're heavily relying on the receiving wallet code to correctly implement logic to calculate that that the fee is as expected.
1 parent 593a65b commit db93d1b

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

lightning/src/ln/channelmanager.rs

+52-4
Original file line numberDiff line numberDiff line change
@@ -2410,7 +2410,8 @@ where
24102410

24112411
fn construct_recv_pending_htlc_info(
24122412
&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
2413-
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool
2413+
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
2414+
counterparty_skimmed_fee_msat: Option<u64>,
24142415
) -> Result<PendingHTLCInfo, ReceiveError> {
24152416
// final_incorrect_cltv_expiry
24162417
if hop_data.outgoing_cltv_value > cltv_expiry {
@@ -2437,7 +2438,10 @@ where
24372438
msg: "The final CLTV expiry is too soon to handle",
24382439
});
24392440
}
2440-
if !allow_underpay && hop_data.amt_to_forward > amt_msat {
2441+
if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
2442+
(allow_underpay && hop_data.amt_to_forward >
2443+
amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
2444+
{
24412445
return Err(ReceiveError {
24422446
err_code: 19,
24432447
err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2735,7 +2739,7 @@ where
27352739
onion_utils::Hop::Receive(next_hop_data) => {
27362740
// OUR PAYMENT!
27372741
match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
2738-
msg.amount_msat, msg.cltv_expiry, None, allow_underpay)
2742+
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat)
27392743
{
27402744
Ok(info) => {
27412745
// Note that we could obviously respond immediately with an update_fulfill_htlc
@@ -3553,7 +3557,7 @@ where
35533557
onion_utils::Hop::Receive(hop_data) => {
35543558
match self.construct_recv_pending_htlc_info(hop_data,
35553559
incoming_shared_secret, payment_hash, outgoing_amt_msat,
3556-
outgoing_cltv_value, Some(phantom_shared_secret), false)
3560+
outgoing_cltv_value, Some(phantom_shared_secret), false, None)
35573561
{
35583562
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
35593563
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
@@ -9555,6 +9559,50 @@ mod tests {
95559559
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
95569560
}
95579561

9562+
#[test]
9563+
fn reject_excessively_underpaying_htlcs() {
9564+
let chanmon_cfg = create_chanmon_cfgs(1);
9565+
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
9566+
let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
9567+
let node = create_network(1, &node_cfg, &node_chanmgr);
9568+
let sender_intended_amt_msat = 100;
9569+
let extra_fee_msat = 10;
9570+
let hop_data = msgs::OnionHopData {
9571+
amt_to_forward: 100,
9572+
outgoing_cltv_value: 42,
9573+
format: msgs::OnionHopDataFormat::FinalNode {
9574+
keysend_preimage: None,
9575+
payment_metadata: None,
9576+
payment_data: Some(msgs::FinalOnionHopData {
9577+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9578+
}),
9579+
}
9580+
};
9581+
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
9582+
// intended amount, we fail the payment.
9583+
if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
9584+
node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9585+
sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
9586+
{
9587+
assert_eq!(err_code, 19);
9588+
} else { panic!(); }
9589+
9590+
// If amt_received + extra_fee is equal to the sender intended amount, we're fine.
9591+
let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
9592+
amt_to_forward: 100,
9593+
outgoing_cltv_value: 42,
9594+
format: msgs::OnionHopDataFormat::FinalNode {
9595+
keysend_preimage: None,
9596+
payment_metadata: None,
9597+
payment_data: Some(msgs::FinalOnionHopData {
9598+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9599+
}),
9600+
}
9601+
};
9602+
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9603+
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
9604+
}
9605+
95589606
#[cfg(anchors)]
95599607
#[test]
95609608
fn test_anchors_zero_fee_htlc_tx_fallback() {

0 commit comments

Comments
 (0)