Skip to content

Commit c5bb00a

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 8e47266 commit c5bb00a

File tree

6 files changed

+59
-24
lines changed

6 files changed

+59
-24
lines changed

lightning/src/events/mod.rs

+17-2
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

+3-3
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -3327,15 +3327,15 @@ impl<SP: Deref> Channel<SP> where
33273327
Err(ChannelError::Close("Remote tried to fulfill/fail an HTLC we couldn't find".to_owned()))
33283328
}
33293329

3330-
pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64), ChannelError> {
3330+
pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64, Option<u64>), ChannelError> {
33313331
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
33323332
return Err(ChannelError::Close("Got fulfill HTLC message when channel was not in an operational state".to_owned()));
33333333
}
33343334
if self.context.channel_state.is_peer_disconnected() {
33353335
return Err(ChannelError::Close("Peer sent update_fulfill_htlc when we needed a channel_reestablish".to_owned()));
33363336
}
33373337

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

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

lightning/src/ln/channelmanager.rs

+16-8
Original file line numberDiff line numberDiff line change
@@ -5690,9 +5690,9 @@ where
56905690
}
56915691

56925692
fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
5693-
forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, startup_replay: bool,
5694-
next_channel_counterparty_node_id: Option<PublicKey>, next_channel_outpoint: OutPoint,
5695-
next_channel_id: ChannelId,
5693+
forwarded_htlc_value_msat: Option<u64>, skimmed_fee_msat: Option<u64>, from_onchain: bool,
5694+
startup_replay: bool, next_channel_counterparty_node_id: Option<PublicKey>,
5695+
next_channel_outpoint: OutPoint, next_channel_id: ChannelId,
56965696
) {
56975697
match source {
56985698
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
@@ -5793,13 +5793,16 @@ where
57935793
Some(claimed_htlc_value - forwarded_htlc_value)
57945794
} else { None }
57955795
} else { None };
5796+
debug_assert!(skimmed_fee_msat <= total_fee_earned_msat,
5797+
"skimmed_fee_msat must always be included in total_fee_earned_msat");
57965798
Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
57975799
event: events::Event::PaymentForwarded {
57985800
total_fee_earned_msat,
57995801
claim_from_onchain_tx: from_onchain,
58005802
prev_channel_id: Some(prev_channel_id),
58015803
next_channel_id: Some(next_channel_id),
58025804
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
5805+
skimmed_fee_msat,
58035806
},
58045807
downstream_counterparty_and_funding_outpoint: chan_to_release,
58055808
})
@@ -6739,7 +6742,7 @@ where
67396742

67406743
fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> {
67416744
let funding_txo;
6742-
let (htlc_source, forwarded_htlc_value) = {
6745+
let (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = {
67436746
let per_peer_state = self.per_peer_state.read().unwrap();
67446747
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
67456748
.ok_or_else(|| {
@@ -6777,8 +6780,11 @@ where
67776780
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))
67786781
}
67796782
};
6780-
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value),
6781-
false, false, Some(*counterparty_node_id), funding_txo, msg.channel_id);
6783+
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(),
6784+
Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id),
6785+
funding_txo, msg.channel_id
6786+
);
6787+
67826788
Ok(())
67836789
}
67846790

@@ -7276,7 +7282,9 @@ where
72767282
let logger = WithContext::from(&self.logger, counterparty_node_id, Some(channel_id));
72777283
if let Some(preimage) = htlc_update.payment_preimage {
72787284
log_trace!(logger, "Claiming HTLC with preimage {} from our monitor", preimage);
7279-
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);
7285+
self.claim_funds_internal(htlc_update.source, preimage,
7286+
htlc_update.htlc_value_satoshis.map(|v| v * 1000), None, true,
7287+
false, counterparty_node_id, funding_outpoint, channel_id);
72807288
} else {
72817289
log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash);
72827290
let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id };
@@ -11168,7 +11176,7 @@ where
1116811176
// We use `downstream_closed` in place of `from_onchain` here just as a guess - we
1116911177
// don't remember in the `ChannelMonitor` where we got a preimage from, but if the
1117011178
// channel is closed we just assume that it probably came from an on-chain claim.
11171-
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value),
11179+
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None,
1117211180
downstream_closed, true, downstream_node_id, downstream_funding, downstream_channel_id);
1117311181
}
1117411182

lightning/src/ln/functional_test_utils.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -2203,14 +2203,19 @@ macro_rules! expect_payment_path_successful {
22032203

22042204
pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
22052205
event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
2206-
upstream_force_closed: bool, downstream_force_closed: bool
2206+
expected_extra_fees_msat: Option<u64>, upstream_force_closed: bool,
2207+
downstream_force_closed: bool
22072208
) {
22082209
match event {
22092210
Event::PaymentForwarded {
22102211
total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
2211-
outbound_amount_forwarded_msat: _
2212+
outbound_amount_forwarded_msat: _, skimmed_fee_msat
22122213
} => {
22132214
assert_eq!(total_fee_earned_msat, expected_fee);
2215+
2216+
// Check that the (knowingly) withheld amount is always less or equal to the expected
2217+
// overpaid amount.
2218+
assert!(skimmed_fee_msat == expected_extra_fees_msat);
22142219
if !upstream_force_closed {
22152220
// Is the event prev_channel_id in one of the channels between the two nodes?
22162221
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,14 @@ pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
22262231
}
22272232
}
22282233

2234+
#[macro_export]
22292235
macro_rules! expect_payment_forwarded {
22302236
($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr, $upstream_force_closed: expr, $downstream_force_closed: expr) => {
22312237
let mut events = $node.node.get_and_clear_pending_events();
22322238
assert_eq!(events.len(), 1);
2233-
$crate::ln::functional_test_utils::expect_payment_forwarded(
2234-
events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee,
2235-
$upstream_force_closed, $downstream_force_closed);
2239+
$crate::ln::functional_test_utils::expect_payment_forwarded( events.pop().unwrap(), &$node,
2240+
&$prev_node, &$next_node, $expected_fee, None, $upstream_force_closed,
2241+
$downstream_force_closed);
22362242
}
22372243
}
22382244

@@ -2696,11 +2702,17 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
26962702
channel.context().config().forwarding_fee_base_msat
26972703
}
26982704
};
2705+
2706+
let mut expected_extra_fee = None;
26992707
if $idx == 1 {
27002708
fee += expected_extra_fees[i];
27012709
fee += expected_min_htlc_overpay[i];
2710+
expected_extra_fee = if expected_extra_fees[i] > 0 { Some(expected_extra_fees[i] as u64) } else { None };
27022711
}
2703-
expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false);
2712+
let mut events = $node.node.get_and_clear_pending_events();
2713+
assert_eq!(events.len(), 1);
2714+
expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
2715+
Some(fee as u64), expected_extra_fee, false, false);
27042716
expected_total_fee_msat += fee as u64;
27052717
check_added_monitors!($node, 1);
27062718
let new_next_msgs = if $new_msgs {

lightning/src/ln/functional_tests.rs

+3-3
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)