Skip to content

Commit ce78cc4

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 c248064 commit ce78cc4

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2535,7 +2535,8 @@ where
25352535

25362536
fn construct_recv_pending_htlc_info(
25372537
&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
2538-
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool
2538+
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
2539+
counterparty_skimmed_fee_msat: Option<u64>,
25392540
) -> Result<PendingHTLCInfo, ReceiveError> {
25402541
// final_incorrect_cltv_expiry
25412542
if hop_data.outgoing_cltv_value > cltv_expiry {
@@ -2562,7 +2563,10 @@ where
25622563
msg: "The final CLTV expiry is too soon to handle",
25632564
});
25642565
}
2565-
if !allow_underpay && hop_data.amt_to_forward > amt_msat {
2566+
if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
2567+
(allow_underpay && hop_data.amt_to_forward >
2568+
amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
2569+
{
25662570
return Err(ReceiveError {
25672571
err_code: 19,
25682572
err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2862,7 +2866,7 @@ where
28622866
onion_utils::Hop::Receive(next_hop_data) => {
28632867
// OUR PAYMENT!
28642868
match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
2865-
msg.amount_msat, msg.cltv_expiry, None, allow_underpay)
2869+
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat)
28662870
{
28672871
Ok(info) => {
28682872
// Note that we could obviously respond immediately with an update_fulfill_htlc
@@ -3675,7 +3679,7 @@ where
36753679
onion_utils::Hop::Receive(hop_data) => {
36763680
match self.construct_recv_pending_htlc_info(hop_data,
36773681
incoming_shared_secret, payment_hash, outgoing_amt_msat,
3678-
outgoing_cltv_value, Some(phantom_shared_secret), false)
3682+
outgoing_cltv_value, Some(phantom_shared_secret), false, None)
36793683
{
36803684
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
36813685
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
@@ -9801,6 +9805,50 @@ mod tests {
98019805
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
98029806
}
98039807

9808+
#[test]
9809+
fn reject_excessively_underpaying_htlcs() {
9810+
let chanmon_cfg = create_chanmon_cfgs(1);
9811+
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
9812+
let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
9813+
let node = create_network(1, &node_cfg, &node_chanmgr);
9814+
let sender_intended_amt_msat = 100;
9815+
let extra_fee_msat = 10;
9816+
let hop_data = msgs::OnionHopData {
9817+
amt_to_forward: 100,
9818+
outgoing_cltv_value: 42,
9819+
format: msgs::OnionHopDataFormat::FinalNode {
9820+
keysend_preimage: None,
9821+
payment_metadata: None,
9822+
payment_data: Some(msgs::FinalOnionHopData {
9823+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9824+
}),
9825+
}
9826+
};
9827+
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
9828+
// intended amount, we fail the payment.
9829+
if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
9830+
node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9831+
sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
9832+
{
9833+
assert_eq!(err_code, 19);
9834+
} else { panic!(); }
9835+
9836+
// If amt_received + extra_fee is equal to the sender intended amount, we're fine.
9837+
let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
9838+
amt_to_forward: 100,
9839+
outgoing_cltv_value: 42,
9840+
format: msgs::OnionHopDataFormat::FinalNode {
9841+
keysend_preimage: None,
9842+
payment_metadata: None,
9843+
payment_data: Some(msgs::FinalOnionHopData {
9844+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9845+
}),
9846+
}
9847+
};
9848+
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9849+
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
9850+
}
9851+
98049852
#[cfg(anchors)]
98059853
#[test]
98069854
fn test_anchors_zero_fee_htlc_tx_fallback() {

0 commit comments

Comments
 (0)