Skip to content

Commit 0d94d9f

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 4cee622 commit 0d94d9f

File tree

1 file changed

+55
-5
lines changed

1 file changed

+55
-5
lines changed

lightning/src/ln/channelmanager.rs

+55-5
Original file line numberDiff line numberDiff line change
@@ -2528,7 +2528,8 @@ where
25282528

25292529
fn construct_recv_pending_htlc_info(
25302530
&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
2531-
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool
2531+
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
2532+
counterparty_skimmed_fee_msat: Option<u64>,
25322533
) -> Result<PendingHTLCInfo, ReceiveError> {
25332534
// final_incorrect_cltv_expiry
25342535
if hop_data.outgoing_cltv_value > cltv_expiry {
@@ -2555,7 +2556,10 @@ where
25552556
msg: "The final CLTV expiry is too soon to handle",
25562557
});
25572558
}
2558-
if !allow_underpay && hop_data.amt_to_forward > amt_msat {
2559+
if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
2560+
(allow_underpay && hop_data.amt_to_forward >
2561+
amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
2562+
{
25592563
return Err(ReceiveError {
25602564
err_code: 19,
25612565
err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2622,7 +2626,7 @@ where
26222626
incoming_amt_msat: Some(amt_msat),
26232627
outgoing_amt_msat: hop_data.amt_to_forward,
26242628
outgoing_cltv_value: hop_data.outgoing_cltv_value,
2625-
skimmed_fee_msat: None,
2629+
skimmed_fee_msat: counterparty_skimmed_fee_msat,
26262630
})
26272631
}
26282632

@@ -2861,7 +2865,7 @@ where
28612865
onion_utils::Hop::Receive(next_hop_data) => {
28622866
// OUR PAYMENT!
28632867
match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
2864-
msg.amount_msat, msg.cltv_expiry, None, allow_underpay)
2868+
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat)
28652869
{
28662870
Ok(info) => {
28672871
// Note that we could obviously respond immediately with an update_fulfill_htlc
@@ -3680,7 +3684,7 @@ where
36803684
onion_utils::Hop::Receive(hop_data) => {
36813685
match self.construct_recv_pending_htlc_info(hop_data,
36823686
incoming_shared_secret, payment_hash, outgoing_amt_msat,
3683-
outgoing_cltv_value, Some(phantom_shared_secret), false)
3687+
outgoing_cltv_value, Some(phantom_shared_secret), false, None)
36843688
{
36853689
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
36863690
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
@@ -3931,6 +3935,8 @@ where
39313935
htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
39323936
let counterparty_skimmed_fee_msat = htlcs.iter()
39333937
.map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
3938+
debug_assert!(total_value.saturating_sub(amount_msat) <=
3939+
counterparty_skimmed_fee_msat);
39343940
new_events.push_back((events::Event::PaymentClaimable {
39353941
receiver_node_id: Some(receiver_node_id),
39363942
payment_hash,
@@ -9743,6 +9749,50 @@ mod tests {
97439749
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
97449750
}
97459751

9752+
#[test]
9753+
fn reject_excessively_underpaying_htlcs() {
9754+
let chanmon_cfg = create_chanmon_cfgs(1);
9755+
let node_cfg = create_node_cfgs(1, &chanmon_cfg);
9756+
let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
9757+
let node = create_network(1, &node_cfg, &node_chanmgr);
9758+
let sender_intended_amt_msat = 100;
9759+
let extra_fee_msat = 10;
9760+
let hop_data = msgs::OnionHopData {
9761+
amt_to_forward: 100,
9762+
outgoing_cltv_value: 42,
9763+
format: msgs::OnionHopDataFormat::FinalNode {
9764+
keysend_preimage: None,
9765+
payment_metadata: None,
9766+
payment_data: Some(msgs::FinalOnionHopData {
9767+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9768+
}),
9769+
}
9770+
};
9771+
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
9772+
// intended amount, we fail the payment.
9773+
if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
9774+
node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9775+
sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
9776+
{
9777+
assert_eq!(err_code, 19);
9778+
} else { panic!(); }
9779+
9780+
// If amt_received + extra_fee is equal to the sender intended amount, we're fine.
9781+
let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
9782+
amt_to_forward: 100,
9783+
outgoing_cltv_value: 42,
9784+
format: msgs::OnionHopDataFormat::FinalNode {
9785+
keysend_preimage: None,
9786+
payment_metadata: None,
9787+
payment_data: Some(msgs::FinalOnionHopData {
9788+
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
9789+
}),
9790+
}
9791+
};
9792+
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
9793+
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
9794+
}
9795+
97469796
#[cfg(anchors)]
97479797
#[test]
97489798
fn test_anchors_zero_fee_htlc_tx_fallback() {

0 commit comments

Comments
 (0)