Skip to content

Commit da670f7

Browse files
committed
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`.
1 parent fc175db commit da670f7

File tree

6 files changed

+57
-22
lines changed

6 files changed

+57
-22
lines changed

lightning/src/events/mod.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,18 @@ pub enum Event {
797797
/// `PaymentForwarded` events are generated for the same payment iff `total_fee_earned_msat` is
798798
/// `None`.
799799
total_fee_earned_msat: Option<u64>,
800+
/// The share of the total fee, in milli-satoshis, which was withheld in addition to the
801+
/// forwarding fee.
802+
///
803+
/// This will only be `Some` if we forwarded an intercepted HTLC with less than the
804+
/// expected amount. This means our counterparty accepted to receive less than the invoice
805+
/// amount, e.g., by claiming the payment featuring a corresponding
806+
/// [`PaymentClaimable::counterparty_skimmed_fee_msat`]
807+
///
808+
/// The caveat described above the `total_fee_earned_msat` field applies here as well.
809+
///
810+
/// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: Self::PaymentClaimable::counterparty_skimmed_fee_msat
811+
skimmed_fee_msat: Option<u64>,
800812
/// If this is `true`, the forwarded HTLC was claimed by our counterparty via an on-chain
801813
/// transaction.
802814
claim_from_onchain_tx: bool,
@@ -1084,7 +1096,7 @@ impl Writeable for Event {
10841096
}
10851097
&Event::PaymentForwarded {
10861098
total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
1087-
next_channel_id, outbound_amount_forwarded_msat
1099+
next_channel_id, outbound_amount_forwarded_msat, skimmed_fee_msat,
10881100
} => {
10891101
7u8.write(writer)?;
10901102
write_tlv_fields!(writer, {
@@ -1093,6 +1105,7 @@ impl Writeable for Event {
10931105
(2, claim_from_onchain_tx, required),
10941106
(3, next_channel_id, option),
10951107
(5, outbound_amount_forwarded_msat, option),
1108+
(7, skimmed_fee_msat, option),
10961109
});
10971110
},
10981111
&Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason,
@@ -1389,16 +1402,18 @@ impl MaybeReadable for Event {
13891402
let mut claim_from_onchain_tx = false;
13901403
let mut next_channel_id = None;
13911404
let mut outbound_amount_forwarded_msat = None;
1405+
let mut skimmed_fee_msat = None;
13921406
read_tlv_fields!(reader, {
13931407
(0, total_fee_earned_msat, option),
13941408
(1, prev_channel_id, option),
13951409
(2, claim_from_onchain_tx, required),
13961410
(3, next_channel_id, option),
13971411
(5, outbound_amount_forwarded_msat, option),
1412+
(7, skimmed_fee_msat, option),
13981413
});
13991414
Ok(Some(Event::PaymentForwarded {
14001415
total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
1401-
outbound_amount_forwarded_msat
1416+
outbound_amount_forwarded_msat, skimmed_fee_msat,
14021417
}))
14031418
};
14041419
f()

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
//! here. See also the chanmon_fail_consistency fuzz test.
1414
1515
use bitcoin::blockdata::constants::genesis_block;
16-
use bitcoin::hash_types::BlockHash;
17-
use bitcoin::network::constants::Network;
16+
use bitcoin::hash_types::BlockHash; use bitcoin::network::constants::Network;
1817
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ChannelMonitor};
1918
use crate::chain::transaction::OutPoint;
2019
use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch};
@@ -3404,7 +3403,8 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) {
34043403
let bc_update_id = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_bc).unwrap().2;
34053404
let mut events = nodes[1].node.get_and_clear_pending_events();
34063405
assert_eq!(events.len(), if close_during_reload { 2 } else { 1 });
3407-
expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), close_during_reload, false);
3406+
expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000),
3407+
None, close_during_reload, false);
34083408
if close_during_reload {
34093409
match events[0] {
34103410
Event::ChannelClosed { .. } => {},

lightning/src/ln/channel.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,15 +3304,15 @@ impl<SP: Deref> Channel<SP> where
33043304
Err(ChannelError::Close("Remote tried to fulfill/fail an HTLC we couldn't find".to_owned()))
33053305
}
33063306

3307-
pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64), ChannelError> {
3307+
pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64, Option<u64>), ChannelError> {
33083308
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
33093309
return Err(ChannelError::Close("Got fulfill HTLC message when channel was not in an operational state".to_owned()));
33103310
}
33113311
if self.context.channel_state.is_peer_disconnected() {
33123312
return Err(ChannelError::Close("Peer sent update_fulfill_htlc when we needed a channel_reestablish".to_owned()));
33133313
}
33143314

3315-
self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat))
3315+
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))
33163316
}
33173317

33183318
pub fn update_fail_htlc(&mut self, msg: &msgs::UpdateFailHTLC, fail_reason: HTLCFailReason) -> Result<(), ChannelError> {

lightning/src/ln/channelmanager.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5668,8 +5668,9 @@ where
56685668
}
56695669

56705670
fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
5671-
forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, startup_replay: bool,
5672-
next_channel_counterparty_node_id: Option<PublicKey>, next_channel_outpoint: OutPoint
5671+
forwarded_htlc_value_msat: Option<u64>, skimmed_fee_msat: Option<u64>, from_onchain: bool,
5672+
startup_replay: bool, next_channel_counterparty_node_id: Option<PublicKey>,
5673+
next_channel_outpoint: OutPoint
56735674
) {
56745675
match source {
56755676
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
@@ -5767,13 +5768,16 @@ where
57675768
Some(claimed_htlc_value - forwarded_htlc_value)
57685769
} else { None }
57695770
} else { None };
5771+
debug_assert!(skimmed_fee_msat <= total_fee_earned_msat,
5772+
"skimmed_fee_msat must always be included in total_fee_earned_msat");
57705773
Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
57715774
event: events::Event::PaymentForwarded {
57725775
total_fee_earned_msat,
57735776
claim_from_onchain_tx: from_onchain,
57745777
prev_channel_id: Some(prev_outpoint.to_channel_id()),
57755778
next_channel_id: Some(next_channel_outpoint.to_channel_id()),
57765779
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
5780+
skimmed_fee_msat,
57775781
},
57785782
downstream_counterparty_and_funding_outpoint: chan_to_release,
57795783
})
@@ -6712,7 +6716,7 @@ where
67126716

67136717
fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> {
67146718
let funding_txo;
6715-
let (htlc_source, forwarded_htlc_value) = {
6719+
let (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = {
67166720
let per_peer_state = self.per_peer_state.read().unwrap();
67176721
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
67186722
.ok_or_else(|| {
@@ -6750,7 +6754,11 @@ where
67506754
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))
67516755
}
67526756
};
6753-
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, false, Some(*counterparty_node_id), funding_txo);
6757+
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(),
6758+
Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id),
6759+
funding_txo
6760+
);
6761+
67546762
Ok(())
67556763
}
67566764

@@ -7246,7 +7254,7 @@ where
72467254
let logger = WithContext::from(&self.logger, counterparty_node_id, Some(funding_outpoint.to_channel_id()));
72477255
if let Some(preimage) = htlc_update.payment_preimage {
72487256
log_trace!(logger, "Claiming HTLC with preimage {} from our monitor", preimage);
7249-
self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, false, counterparty_node_id, funding_outpoint);
7257+
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);
72507258
} else {
72517259
log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash);
72527260
let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id: funding_outpoint.to_channel_id() };
@@ -11119,7 +11127,7 @@ where
1111911127
// We use `downstream_closed` in place of `from_onchain` here just as a guess - we
1112011128
// don't remember in the `ChannelMonitor` where we got a preimage from, but if the
1112111129
// channel is closed we just assume that it probably came from an on-chain claim.
11122-
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value),
11130+
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None,
1112311131
downstream_closed, true, downstream_node_id, downstream_funding);
1112411132
}
1112511133

lightning/src/ln/functional_test_utils.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,14 +2201,19 @@ macro_rules! expect_payment_path_successful {
22012201

22022202
pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
22032203
event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
2204-
upstream_force_closed: bool, downstream_force_closed: bool
2204+
expected_extra_fee_limit_msat: Option<u64>, upstream_force_closed: bool,
2205+
downstream_force_closed: bool
22052206
) {
22062207
match event {
22072208
Event::PaymentForwarded {
22082209
total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
2209-
outbound_amount_forwarded_msat: _
2210+
outbound_amount_forwarded_msat: _, skimmed_fee_msat
22102211
} => {
22112212
assert_eq!(total_fee_earned_msat, expected_fee);
2213+
2214+
// Check that the (knowingly) withheld amount is always less or equal to the expected
2215+
// overpaid amount.
2216+
assert!(skimmed_fee_msat == expected_extra_fee_limit_msat);
22122217
if !upstream_force_closed {
22132218
// Is the event prev_channel_id in one of the channels between the two nodes?
22142219
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()));
@@ -2224,13 +2229,14 @@ pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
22242229
}
22252230
}
22262231

2232+
#[macro_export]
22272233
macro_rules! expect_payment_forwarded {
22282234
($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr, $upstream_force_closed: expr, $downstream_force_closed: expr) => {
22292235
let mut events = $node.node.get_and_clear_pending_events();
22302236
assert_eq!(events.len(), 1);
2231-
$crate::ln::functional_test_utils::expect_payment_forwarded(
2232-
events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee,
2233-
$upstream_force_closed, $downstream_force_closed);
2237+
$crate::ln::functional_test_utils::expect_payment_forwarded( events.pop().unwrap(), &$node,
2238+
&$prev_node, &$next_node, $expected_fee, None, $upstream_force_closed,
2239+
$downstream_force_closed);
22342240
}
22352241
}
22362242

@@ -2694,11 +2700,17 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
26942700
channel.context().config().forwarding_fee_base_msat
26952701
}
26962702
};
2703+
2704+
let mut expected_extra_fee = None;
26972705
if $idx == 1 {
26982706
fee += expected_extra_fees[i];
26992707
fee += expected_min_htlc_overpay[i];
2708+
expected_extra_fee = if expected_extra_fees[i] > 0 { Some(expected_extra_fees[i] as u64) } else { None };
27002709
}
2701-
expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false);
2710+
let mut events = $node.node.get_and_clear_pending_events();
2711+
assert_eq!(events.len(), 1);
2712+
expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
2713+
Some(fee as u64), expected_extra_fee, false, false);
27022714
expected_total_fee_msat += fee as u64;
27032715
check_added_monitors!($node, 1);
27042716
let new_next_msgs = if $new_msgs {

lightning/src/ln/functional_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,7 +2890,7 @@ fn test_htlc_on_chain_success() {
28902890
let chan_id = Some(chan_1.2);
28912891
match forwarded_events[1] {
28922892
Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
2893-
next_channel_id, outbound_amount_forwarded_msat
2893+
next_channel_id, outbound_amount_forwarded_msat, ..
28942894
} => {
28952895
assert_eq!(total_fee_earned_msat, Some(1000));
28962896
assert_eq!(prev_channel_id, chan_id);
@@ -2902,7 +2902,7 @@ fn test_htlc_on_chain_success() {
29022902
}
29032903
match forwarded_events[2] {
29042904
Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
2905-
next_channel_id, outbound_amount_forwarded_msat
2905+
next_channel_id, outbound_amount_forwarded_msat, ..
29062906
} => {
29072907
assert_eq!(total_fee_earned_msat, Some(1000));
29082908
assert_eq!(prev_channel_id, chan_id);
@@ -4917,7 +4917,7 @@ fn test_onchain_to_onchain_claim() {
49174917
}
49184918
match events[1] {
49194919
Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
4920-
next_channel_id, outbound_amount_forwarded_msat
4920+
next_channel_id, outbound_amount_forwarded_msat, ..
49214921
} => {
49224922
assert_eq!(total_fee_earned_msat, Some(1000));
49234923
assert_eq!(prev_channel_id, Some(chan_1.2));

0 commit comments

Comments
 (0)