From 42490efa2c517d1b946c7c578774a3574ca0cdd2 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 20 Dec 2023 15:45:34 -0500 Subject: [PATCH 1/4] Struct-ify claim_along_route args. Lays groundwork to make claim_payment* test utils easier to adapt without changing a million callsites. --- lightning/src/ln/functional_test_utils.rs | 49 +++++++++++++++++------ lightning/src/ln/payment_tests.rs | 17 ++++---- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index e7fc68924ef..0e18e3f3f31 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2552,24 +2552,49 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>( origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage ) -> u64 { - let extra_fees = vec![0; expected_paths.len()]; - do_claim_payment_along_route_with_extra_penultimate_hop_fees(origin_node, expected_paths, - &extra_fees[..], skip_last, our_payment_preimage) -} - -pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>( - origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees: - &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage -) -> u64 { - assert_eq!(expected_paths.len(), expected_extra_fees.len()); for path in expected_paths.iter() { assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id()); } expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage); - pass_claimed_payment_along_route(origin_node, expected_paths, expected_extra_fees, skip_last, our_payment_preimage) + pass_claimed_payment_along_route( + ClaimAlongRouteArgs::new(origin_node, expected_paths, our_payment_preimage) + .skip_last(skip_last) + ) +} + +pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { + pub origin_node: &'a Node<'b, 'c, 'd>, + pub expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]], + pub expected_extra_fees: Vec, + pub skip_last: bool, + pub payment_preimage: PaymentPreimage, +} + +impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { + pub fn new( + origin_node: &'a Node<'b, 'c, 'd>, expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]], + payment_preimage: PaymentPreimage, + ) -> Self { + Self { + origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()], + skip_last: false, payment_preimage, + } + } + pub fn skip_last(mut self, skip_last: bool) -> Self { + self.skip_last = skip_last; + self + } + pub fn with_expected_extra_fees(mut self, extra_fees: Vec) -> Self { + self.expected_extra_fees = extra_fees; + self + } } -pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees: &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage) -> u64 { +pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 { + let ClaimAlongRouteArgs { + origin_node, expected_paths, expected_extra_fees, skip_last, + payment_preimage: our_payment_preimage + } = args; let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events(); assert_eq!(claim_event.len(), 1); match claim_event[0] { diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 0db815a7085..aee26e98671 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -277,10 +277,12 @@ fn mpp_retry_overpay() { // Can't use claim_payment_along_route as it doesn't support overpayment, so we break out the // individual steps here. + nodes[3].node.claim_funds(payment_preimage); let extra_fees = vec![0, total_overpaid_amount]; - let expected_total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees( - &nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], &extra_fees[..], false, - payment_preimage); + let expected_route = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]][..]]; + let args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage) + .with_expected_extra_fees(extra_fees); + let expected_total_fee_msat = pass_claimed_payment_along_route(args); expect_payment_sent!(&nodes[0], payment_preimage, Some(expected_total_fee_msat)); } @@ -2155,9 +2157,10 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) { let mut expected_paths = Vec::new(); for _ in 0..num_mpp_parts { expected_paths_vecs.push(vec!(&nodes[1], &nodes[2])); } for i in 0..num_mpp_parts { expected_paths.push(&expected_paths_vecs[i][..]); } - let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees( - &nodes[0], &expected_paths[..], &vec![skimmed_fee_msat as u32; num_mpp_parts][..], false, - payment_preimage); + expected_paths[0].last().unwrap().node.claim_funds(payment_preimage); + let args = ClaimAlongRouteArgs::new(&nodes[0], &expected_paths[..], payment_preimage) + .with_expected_extra_fees(vec![skimmed_fee_msat as u32; num_mpp_parts]); + let total_fee_msat = pass_claimed_payment_along_route(args); // The sender doesn't know that the penultimate hop took an extra fee. expect_payment_sent(&nodes[0], payment_preimage, Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true, true); @@ -3722,7 +3725,7 @@ fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) { match (known_tlvs, even_tlvs) { (true, _) => { nodes[1].node.claim_funds_with_known_custom_tlvs(our_payment_preimage); - let expected_total_fee_msat = pass_claimed_payment_along_route(&nodes[0], &[&[&nodes[1]]], &[0; 1], false, our_payment_preimage); + let expected_total_fee_msat = pass_claimed_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], our_payment_preimage)); expect_payment_sent!(&nodes[0], our_payment_preimage, Some(expected_total_fee_msat)); }, (false, false) => { From 2616f020167891f9dcb770571776fb40167c1d48 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 30 Jan 2024 14:43:59 -0500 Subject: [PATCH 2/4] Track min HTLC overpay separately from skimmed fees in test utils --- lightning/src/ln/functional_test_utils.rs | 14 +++++++++++--- lightning/src/ln/payment_tests.rs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 0e18e3f3f31..0dbf0f01cb7 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2566,6 +2566,7 @@ pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { pub origin_node: &'a Node<'b, 'c, 'd>, pub expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]], pub expected_extra_fees: Vec, + pub expected_min_htlc_overpay: Vec, pub skip_last: bool, pub payment_preimage: PaymentPreimage, } @@ -2577,7 +2578,7 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { ) -> Self { Self { origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()], - skip_last: false, payment_preimage, + expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage, } } pub fn skip_last(mut self, skip_last: bool) -> Self { @@ -2588,11 +2589,15 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { self.expected_extra_fees = extra_fees; self } + pub fn with_expected_min_htlc_overpay(mut self, extra_fees: Vec) -> Self { + self.expected_min_htlc_overpay = extra_fees; + self + } } pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 { let ClaimAlongRouteArgs { - origin_node, expected_paths, expected_extra_fees, skip_last, + origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last, payment_preimage: our_payment_preimage } = args; let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events(); @@ -2691,7 +2696,10 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg channel.context().config().forwarding_fee_base_msat } }; - if $idx == 1 { fee += expected_extra_fees[i]; } + if $idx == 1 { + fee += expected_extra_fees[i]; + fee += expected_min_htlc_overpay[i]; + } expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false); expected_total_fee_msat += fee as u64; check_added_monitors!($node, 1); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index aee26e98671..09c022a4c87 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -281,7 +281,7 @@ fn mpp_retry_overpay() { let extra_fees = vec![0, total_overpaid_amount]; let expected_route = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]][..]]; let args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage) - .with_expected_extra_fees(extra_fees); + .with_expected_min_htlc_overpay(extra_fees); let expected_total_fee_msat = pass_claimed_payment_along_route(args); expect_payment_sent!(&nodes[0], payment_preimage, Some(expected_total_fee_msat)); } From 8e472669a1326d13424f3ebde40b6b6ff4e0a94d Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 30 Jan 2024 09:18:28 +0100 Subject: [PATCH 3/4] Rename `PaymentForwarded::fee_earned_msat` to `total_fee_earned_msat` --- lightning/src/events/mod.rs | 18 +++++++++--------- lightning/src/ln/channelmanager.rs | 4 ++-- lightning/src/ln/functional_test_utils.rs | 4 ++-- lightning/src/ln/functional_tests.rs | 18 ++++++++++++------ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 77f6937c545..201431bbed7 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -783,7 +783,7 @@ pub enum Event { /// The outgoing channel between the next node and us. This is only `None` for events /// generated or serialized by versions prior to 0.0.107. next_channel_id: Option, - /// The fee, in milli-satoshis, which was earned as a result of the payment. + /// The total fee, in milli-satoshis, which was earned as a result of the payment. /// /// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC /// was pending, the amount the next hop claimed will have been rounded down to the nearest @@ -794,15 +794,15 @@ pub enum Event { /// If the channel which sent us the payment has been force-closed, we will claim the funds /// via an on-chain transaction. In that case we do not yet know the on-chain transaction /// fees which we will spend and will instead set this to `None`. It is possible duplicate - /// `PaymentForwarded` events are generated for the same payment iff `fee_earned_msat` is + /// `PaymentForwarded` events are generated for the same payment iff `total_fee_earned_msat` is /// `None`. - fee_earned_msat: Option, + total_fee_earned_msat: Option, /// If this is `true`, the forwarded HTLC was claimed by our counterparty via an on-chain /// transaction. claim_from_onchain_tx: bool, /// The final amount forwarded, in milli-satoshis, after the fee is deducted. /// - /// The caveat described above the `fee_earned_msat` field applies here as well. + /// The caveat described above the `total_fee_earned_msat` field applies here as well. outbound_amount_forwarded_msat: Option, }, /// Used to indicate that a channel with the given `channel_id` is being opened and pending @@ -1083,12 +1083,12 @@ impl Writeable for Event { }); } &Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { 7u8.write(writer)?; write_tlv_fields!(writer, { - (0, fee_earned_msat, option), + (0, total_fee_earned_msat, option), (1, prev_channel_id, option), (2, claim_from_onchain_tx, required), (3, next_channel_id, option), @@ -1384,20 +1384,20 @@ impl MaybeReadable for Event { }, 7u8 => { let f = || { - let mut fee_earned_msat = None; + let mut total_fee_earned_msat = None; let mut prev_channel_id = None; let mut claim_from_onchain_tx = false; let mut next_channel_id = None; let mut outbound_amount_forwarded_msat = None; read_tlv_fields!(reader, { - (0, fee_earned_msat, option), + (0, total_fee_earned_msat, option), (1, prev_channel_id, option), (2, claim_from_onchain_tx, required), (3, next_channel_id, option), (5, outbound_amount_forwarded_msat, option), }); Ok(Some(Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat })) }; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b5fc9996e29..43bd4efd5da 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5788,14 +5788,14 @@ where }) } else { None } } else { - let fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { + let total_fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { if let Some(claimed_htlc_value) = htlc_claim_value_msat { Some(claimed_htlc_value - forwarded_htlc_value) } else { None } } else { None }; Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { event: events::Event::PaymentForwarded { - fee_earned_msat, + total_fee_earned_msat, claim_from_onchain_tx: from_onchain, prev_channel_id: Some(prev_channel_id), next_channel_id: Some(next_channel_id), diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 0dbf0f01cb7..45e5de51a0e 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2207,10 +2207,10 @@ pub fn expect_payment_forwarded>( ) { match event { Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat: _ } => { - assert_eq!(fee_earned_msat, expected_fee); + assert_eq!(total_fee_earned_msat, expected_fee); if !upstream_force_closed { // Is the event prev_channel_id in one of the channels between the two nodes? assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap())); diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 14aa14b4398..c98b7231a21 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -2889,8 +2889,10 @@ fn test_htlc_on_chain_success() { } let chan_id = Some(chan_1.2); match forwarded_events[1] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); @@ -2899,8 +2901,10 @@ fn test_htlc_on_chain_success() { _ => panic!() } match forwarded_events[2] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); @@ -4912,8 +4916,10 @@ fn test_onchain_to_onchain_claim() { _ => panic!("Unexpected event"), } match events[1] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, Some(chan_1.2)); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); From 96445880f6401d178893a3cec40fe26ebc755a30 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 29 Jan 2024 16:16:08 +0100 Subject: [PATCH 4/4] Expose `skimmed_fee_msat` in `PaymentForwarded` We generally allow routing nodes to forward less than the expected HTLC amount, if the receiver knowingly accepts this and claims the underpaying HTLC (see `ChannelConfig::accept_underpaying_htlcs`). This use case is in particular useful for the LSPS2/JIT channel setting where the intial underpaying HTLC pays for the channel open. While we previously exposed the withheld amount as `PaymentClaimable::counterparty_skimmed_fee_msat` on the receiver side, we did not individually provide it on the forwarding node's side. Here, we therefore expose this additionally withheld amount via `PaymentForwarded::skimmed_fee_msat`. --- lightning/src/events/mod.rs | 21 ++++++++++++++-- lightning/src/ln/chanmon_update_fail_tests.rs | 3 ++- lightning/src/ln/channel.rs | 4 ++-- lightning/src/ln/channelmanager.rs | 24 ++++++++++++------- lightning/src/ln/functional_test_utils.rs | 23 ++++++++++++++---- lightning/src/ln/functional_tests.rs | 6 ++--- 6 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 201431bbed7..801e8695c81 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -797,6 +797,20 @@ pub enum Event { /// `PaymentForwarded` events are generated for the same payment iff `total_fee_earned_msat` is /// `None`. total_fee_earned_msat: Option, + /// The share of the total fee, in milli-satoshis, which was withheld in addition to the + /// forwarding fee. + /// + /// This will only be `Some` if we forwarded an intercepted HTLC with less than the + /// expected amount. This means our counterparty accepted to receive less than the invoice + /// amount, e.g., by claiming the payment featuring a corresponding + /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]. + /// + /// Will also always be `None` for events serialized with LDK prior to version 0.0.122. + /// + /// The caveat described above the `total_fee_earned_msat` field applies here as well. + /// + /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: Self::PaymentClaimable::counterparty_skimmed_fee_msat + skimmed_fee_msat: Option, /// If this is `true`, the forwarded HTLC was claimed by our counterparty via an on-chain /// transaction. claim_from_onchain_tx: bool, @@ -1084,7 +1098,7 @@ impl Writeable for Event { } &Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, - next_channel_id, outbound_amount_forwarded_msat + next_channel_id, outbound_amount_forwarded_msat, skimmed_fee_msat, } => { 7u8.write(writer)?; write_tlv_fields!(writer, { @@ -1093,6 +1107,7 @@ impl Writeable for Event { (2, claim_from_onchain_tx, required), (3, next_channel_id, option), (5, outbound_amount_forwarded_msat, option), + (7, skimmed_fee_msat, option), }); }, &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason, @@ -1389,16 +1404,18 @@ impl MaybeReadable for Event { let mut claim_from_onchain_tx = false; let mut next_channel_id = None; let mut outbound_amount_forwarded_msat = None; + let mut skimmed_fee_msat = None; read_tlv_fields!(reader, { (0, total_fee_earned_msat, option), (1, prev_channel_id, option), (2, claim_from_onchain_tx, required), (3, next_channel_id, option), (5, outbound_amount_forwarded_msat, option), + (7, skimmed_fee_msat, option), }); Ok(Some(Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, - outbound_amount_forwarded_msat + outbound_amount_forwarded_msat, skimmed_fee_msat, })) }; f() diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 02253a003fb..5f7c72be1dc 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -3404,7 +3404,8 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) { let bc_update_id = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_bc).unwrap().2; let mut events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), if close_during_reload { 2 } else { 1 }); - expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), close_during_reload, false); + expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), + None, close_during_reload, false); if close_during_reload { match events[0] { Event::ChannelClosed { .. } => {}, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2007a9e772c..e56fa978953 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3327,7 +3327,7 @@ impl Channel where Err(ChannelError::Close("Remote tried to fulfill/fail an HTLC we couldn't find".to_owned())) } - pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64), ChannelError> { + pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64, Option), ChannelError> { if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { return Err(ChannelError::Close("Got fulfill HTLC message when channel was not in an operational state".to_owned())); } @@ -3335,7 +3335,7 @@ impl Channel where return Err(ChannelError::Close("Peer sent update_fulfill_htlc when we needed a channel_reestablish".to_owned())); } - self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat)) + self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat, htlc.skimmed_fee_msat)) } pub fn update_fail_htlc(&mut self, msg: &msgs::UpdateFailHTLC, fail_reason: HTLCFailReason) -> Result<(), ChannelError> { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 43bd4efd5da..d61e0005fea 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5690,9 +5690,9 @@ where } fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage, - forwarded_htlc_value_msat: Option, from_onchain: bool, startup_replay: bool, - next_channel_counterparty_node_id: Option, next_channel_outpoint: OutPoint, - next_channel_id: ChannelId, + forwarded_htlc_value_msat: Option, skimmed_fee_msat: Option, from_onchain: bool, + startup_replay: bool, next_channel_counterparty_node_id: Option, + next_channel_outpoint: OutPoint, next_channel_id: ChannelId, ) { match source { HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => { @@ -5793,6 +5793,8 @@ where Some(claimed_htlc_value - forwarded_htlc_value) } else { None } } else { None }; + debug_assert!(skimmed_fee_msat <= total_fee_earned_msat, + "skimmed_fee_msat must always be included in total_fee_earned_msat"); Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { event: events::Event::PaymentForwarded { total_fee_earned_msat, @@ -5800,6 +5802,7 @@ where prev_channel_id: Some(prev_channel_id), next_channel_id: Some(next_channel_id), outbound_amount_forwarded_msat: forwarded_htlc_value_msat, + skimmed_fee_msat, }, downstream_counterparty_and_funding_outpoint: chan_to_release, }) @@ -6739,7 +6742,7 @@ where fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> { let funding_txo; - let (htlc_source, forwarded_htlc_value) = { + let (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = { let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -6777,8 +6780,11 @@ where hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) } }; - self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), - false, false, Some(*counterparty_node_id), funding_txo, msg.channel_id); + self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), + Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id), + funding_txo, msg.channel_id + ); + Ok(()) } @@ -7276,7 +7282,9 @@ where let logger = WithContext::from(&self.logger, counterparty_node_id, Some(channel_id)); if let Some(preimage) = htlc_update.payment_preimage { log_trace!(logger, "Claiming HTLC with preimage {} from our monitor", preimage); - self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, false, counterparty_node_id, funding_outpoint, channel_id); + self.claim_funds_internal(htlc_update.source, preimage, + htlc_update.htlc_value_satoshis.map(|v| v * 1000), None, true, + false, counterparty_node_id, funding_outpoint, channel_id); } else { log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash); let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id }; @@ -11168,7 +11176,7 @@ where // We use `downstream_closed` in place of `from_onchain` here just as a guess - we // don't remember in the `ChannelMonitor` where we got a preimage from, but if the // channel is closed we just assume that it probably came from an on-chain claim. - channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), + channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None, downstream_closed, true, downstream_node_id, downstream_funding, downstream_channel_id); } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 45e5de51a0e..e53fd6b0c1e 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2203,14 +2203,19 @@ macro_rules! expect_payment_path_successful { pub fn expect_payment_forwarded>( event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option, - upstream_force_closed: bool, downstream_force_closed: bool + expected_extra_fees_msat: Option, upstream_force_closed: bool, + downstream_force_closed: bool ) { match event { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, - outbound_amount_forwarded_msat: _ + outbound_amount_forwarded_msat: _, skimmed_fee_msat } => { assert_eq!(total_fee_earned_msat, expected_fee); + + // Check that the (knowingly) withheld amount is always less or equal to the expected + // overpaid amount. + assert!(skimmed_fee_msat == expected_extra_fees_msat); if !upstream_force_closed { // Is the event prev_channel_id in one of the channels between the two nodes? assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap())); @@ -2226,13 +2231,15 @@ pub fn expect_payment_forwarded>( } } +#[macro_export] macro_rules! expect_payment_forwarded { ($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr, $upstream_force_closed: expr, $downstream_force_closed: expr) => { let mut events = $node.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); $crate::ln::functional_test_utils::expect_payment_forwarded( - events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, - $upstream_force_closed, $downstream_force_closed); + events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, None, + $upstream_force_closed, $downstream_force_closed + ); } } @@ -2696,11 +2703,17 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg channel.context().config().forwarding_fee_base_msat } }; + + let mut expected_extra_fee = None; if $idx == 1 { fee += expected_extra_fees[i]; fee += expected_min_htlc_overpay[i]; + expected_extra_fee = if expected_extra_fees[i] > 0 { Some(expected_extra_fees[i] as u64) } else { None }; } - expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false); + let mut events = $node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node, + Some(fee as u64), expected_extra_fee, false, false); expected_total_fee_msat += fee as u64; check_added_monitors!($node, 1); let new_next_msgs = if $new_msgs { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index c98b7231a21..5f989103170 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -2890,7 +2890,7 @@ fn test_htlc_on_chain_success() { let chan_id = Some(chan_1.2); match forwarded_events[1] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, - next_channel_id, outbound_amount_forwarded_msat + next_channel_id, outbound_amount_forwarded_msat, .. } => { assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); @@ -2902,7 +2902,7 @@ fn test_htlc_on_chain_success() { } match forwarded_events[2] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, - next_channel_id, outbound_amount_forwarded_msat + next_channel_id, outbound_amount_forwarded_msat, .. } => { assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); @@ -4917,7 +4917,7 @@ fn test_onchain_to_onchain_claim() { } match events[1] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, - next_channel_id, outbound_amount_forwarded_msat + next_channel_id, outbound_amount_forwarded_msat, .. } => { assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, Some(chan_1.2));