Skip to content

Commit 08d80f0

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 0b1f09d commit 08d80f0

File tree

1 file changed

+62
-5
lines changed

1 file changed

+62
-5
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2311,7 +2311,7 @@ where
23112311
fn construct_recv_pending_htlc_info(
23122312
&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
23132313
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
23152315
) -> Result<PendingHTLCInfo, ReceiveError> {
23162316
// final_incorrect_cltv_expiry
23172317
if hop_data.outgoing_cltv_value > cltv_expiry {
@@ -2340,6 +2340,7 @@ where
23402340
}
23412341
if hop_data.amt_to_forward > amt_msat {
23422342
let allow_underpay = loop {
2343+
if counterparty_skimmed_fee_msat.is_none() { break false }
23432344
let (counterparty_node_id, channel_id) = match chan_retrieval_info {
23442345
ChanMapRetrieval::Scid(scid) => {
23452346
match self.short_to_chan_info.read().unwrap().get(&scid).cloned() {
@@ -2362,7 +2363,12 @@ where
23622363
};
23632364
break chan.config().accept_underpaying_htlcs
23642365
};
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+
{
23662372
return Err(ReceiveError {
23672373
err_code: 19,
23682374
err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2494,7 +2500,7 @@ where
24942500
onion_utils::Hop::Receive(next_hop_data) => {
24952501
// OUR PAYMENT!
24962502
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,
24982504
ChanMapRetrieval::IdPubkey((*counterparty_node_id, msg.channel_id)))
24992505
{
25002506
Ok(info) => {
@@ -3404,7 +3410,9 @@ where
34043410
}
34053411
}
34063412
}
3407-
if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
3413+
if let PendingHTLCRouting::Forward {
3414+
onion_packet, skimmed_fee_msat, ..
3415+
} = routing {
34083416
let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode);
34093417
if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.genesis_hash) {
34103418
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
@@ -3427,7 +3435,7 @@ where
34273435
match self.construct_recv_pending_htlc_info(hop_data,
34283436
incoming_shared_secret, payment_hash, outgoing_amt_msat,
34293437
outgoing_cltv_value, Some(phantom_shared_secret),
3430-
ChanMapRetrieval::Scid(prev_short_channel_id))
3438+
skimmed_fee_msat, ChanMapRetrieval::Scid(prev_short_channel_id))
34313439
{
34323440
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
34333441
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
@@ -9292,6 +9300,55 @@ mod tests {
92929300
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
92939301
}
92949302

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+
92959352
#[cfg(anchors)]
92969353
#[test]
92979354
fn test_anchors_zero_fee_htlc_tx_fallback() {

0 commit comments

Comments
 (0)