Skip to content

Commit ad259dd

Browse files
Support skimming fees when forwarding to phantom nodes
1 parent 3572ff3 commit ad259dd

File tree

2 files changed

+129
-5
lines changed

2 files changed

+129
-5
lines changed

lightning/src/ln/channelmanager.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -2533,12 +2533,14 @@ where
25332533
routing: PendingHTLCRouting::Forward {
25342534
onion_packet: outgoing_packet,
25352535
short_channel_id,
2536-
skimmed_fee_msat: None,
2536+
skimmed_fee_msat: msg.skimmed_fee_msat,
25372537
},
25382538
payment_hash: msg.payment_hash.clone(),
25392539
incoming_shared_secret: shared_secret,
25402540
incoming_amt_msat: Some(msg.amount_msat),
2541-
outgoing_amt_msat: next_hop_data.amt_to_forward,
2541+
// If an underpaying HTLC was sent to a phantom node, then the update_add's amount will be
2542+
// less than the sender-intended amount for this hop and we should use that.
2543+
outgoing_amt_msat: cmp::min(next_hop_data.amt_to_forward, msg.amount_msat),
25422544
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
25432545
})
25442546
}
@@ -3460,8 +3462,9 @@ where
34603462
}
34613463
}
34623464
}
3463-
let (counterparty_node_id, forward_chan_id) = match self.short_to_chan_info.read().unwrap().get(&short_chan_id) {
3464-
Some((cp_id, chan_id)) => (cp_id.clone(), chan_id.clone()),
3465+
let id_opt = self.short_to_chan_info.read().unwrap().get(&short_chan_id).cloned();
3466+
let (counterparty_node_id, forward_chan_id) = match id_opt {
3467+
Some((cp_id, chan_id)) => (cp_id, chan_id),
34653468
None => {
34663469
forwarding_channel_not_found!();
34673470
continue;

lightning/src/ln/payment_tests.rs

+122-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
1515
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
16-
use crate::sign::EntropySource;
16+
use crate::sign::{EntropySource, NodeSigner, PhantomKeysManager, Recipient};
1717
use crate::chain::transaction::OutPoint;
1818
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason};
1919
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
@@ -1673,6 +1673,127 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) {
16731673
Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true);
16741674
}
16751675

1676+
#[test]
1677+
fn underpay_to_phantom() {
1678+
// Weird case, but it's possible for a user to set up their route hints as:
1679+
// Liquidity provider -(intercept scid)-> user -(phantom scid)-> phantom
1680+
let mut chanmon_cfgs = create_chanmon_cfgs(3);
1681+
let seed = [42u8; 32];
1682+
let cross_node_seed = [43u8; 32];
1683+
chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed, 43, 44, &cross_node_seed);
1684+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1685+
let mut intercept_forwards_config = test_default_channel_config();
1686+
intercept_forwards_config.accept_intercept_htlcs = true;
1687+
let mut underpay_config = test_default_channel_config();
1688+
underpay_config.channel_config.accept_underpaying_htlcs = true;
1689+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), Some(underpay_config)]);
1690+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1691+
1692+
let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 2_000_000, 0);
1693+
let channel_id = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 2_000_000, 0).0.channel_id;
1694+
let amt_msat = 900_000;
1695+
let skimmed_fee_msat = 20;
1696+
let route_hint = vec!(RouteHint(vec![RouteHintHop {
1697+
src_node_id: nodes[1].node.get_our_node_id(),
1698+
short_channel_id: nodes[1].node.get_intercept_scid(),
1699+
fees: RoutingFees {
1700+
base_msat: 1000,
1701+
proportional_millionths: 0,
1702+
},
1703+
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
1704+
htlc_minimum_msat: None,
1705+
htlc_maximum_msat: None,
1706+
}, RouteHintHop {
1707+
src_node_id: nodes[2].node.get_our_node_id(),
1708+
short_channel_id: nodes[2].node.get_phantom_scid(),
1709+
fees: RoutingFees {
1710+
base_msat: 0,
1711+
proportional_millionths: 0,
1712+
},
1713+
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
1714+
htlc_minimum_msat: None,
1715+
htlc_maximum_msat: None,
1716+
}]));
1717+
let phantom_node_id = chanmon_cfgs[2].keys_manager.backing.get_node_id(Recipient::PhantomNode).unwrap();
1718+
let payment_params = PaymentParameters::from_node_id(phantom_node_id, TEST_FINAL_CLTV)
1719+
.with_route_hints(route_hint).unwrap()
1720+
.with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
1721+
let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, };
1722+
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
1723+
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
1724+
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
1725+
check_added_monitors!(nodes[0], 1);
1726+
let mut events: Vec<SendEvent> = nodes[0].node.get_and_clear_pending_msg_events().into_iter().map(|e| SendEvent::from_event(e)).collect();
1727+
assert_eq!(events.len(), 1);
1728+
1729+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &events[0].msgs[0]);
1730+
do_commitment_signed_dance(&nodes[1], &nodes[0], &events[0].commitment_msg, false, true);
1731+
1732+
let events = nodes[1].node.get_and_clear_pending_events();
1733+
assert_eq!(events.len(), 1);
1734+
let (intercept_id, expected_outbound_amt_msat) = match events[0] {
1735+
crate::events::Event::HTLCIntercepted {
1736+
intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, ..
1737+
} => {
1738+
assert_eq!(pmt_hash, payment_hash);
1739+
assert_eq!(expected_outbound_amount_msat, amt_msat);
1740+
(intercept_id, expected_outbound_amount_msat)
1741+
},
1742+
_ => panic!()
1743+
};
1744+
nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id,
1745+
nodes[2].node.get_our_node_id(), expected_outbound_amt_msat - skimmed_fee_msat).unwrap();
1746+
expect_pending_htlcs_forwardable!(nodes[1]);
1747+
let payment_event = {
1748+
{
1749+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
1750+
assert_eq!(added_monitors.len(), 1);
1751+
added_monitors.clear();
1752+
}
1753+
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
1754+
assert_eq!(events.len(), 1);
1755+
SendEvent::from_event(events.remove(0))
1756+
};
1757+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
1758+
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, false, true);
1759+
expect_pending_htlcs_forwardable!(nodes[2]);
1760+
1761+
// Claim the payment and check that the skimmed fee is as expected.
1762+
let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
1763+
let events = nodes[2].node.get_and_clear_pending_events();
1764+
assert_eq!(events.len(), 2);
1765+
match events[0] {
1766+
crate::events::Event::PendingHTLCsForwardable { .. } => {},
1767+
_ => { panic!() }
1768+
}
1769+
match events[1] {
1770+
crate::events::Event::PaymentClaimable {
1771+
ref payment_hash, ref purpose, amount_msat, counterparty_skimmed_fee_msat, receiver_node_id, ..
1772+
} => {
1773+
assert_eq!(payment_hash, payment_hash);
1774+
assert_eq!(skimmed_fee_msat, counterparty_skimmed_fee_msat);
1775+
assert_eq!(amt_msat - skimmed_fee_msat, amount_msat);
1776+
assert_eq!(phantom_node_id, receiver_node_id.unwrap());
1777+
match purpose {
1778+
crate::events::PaymentPurpose::InvoicePayment { payment_preimage: ev_payment_preimage,
1779+
payment_secret: ev_payment_secret, .. } =>
1780+
{
1781+
assert_eq!(payment_preimage, ev_payment_preimage.unwrap());
1782+
assert_eq!(payment_secret, *ev_payment_secret);
1783+
},
1784+
_ => panic!(),
1785+
}
1786+
},
1787+
_ => panic!("Unexpected event"),
1788+
}
1789+
let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees(
1790+
&nodes[0], &[&[&nodes[1], &nodes[2]]], &vec![skimmed_fee_msat as u32; 1][..], false,
1791+
payment_preimage);
1792+
// The sender doesn't know that the penultimate hop took an extra fee.
1793+
expect_payment_sent(&nodes[0], payment_preimage,
1794+
Some(Some(total_fee_msat - skimmed_fee_msat)), true);
1795+
}
1796+
16761797
#[derive(PartialEq)]
16771798
enum AutoRetry {
16781799
Success,

0 commit comments

Comments
 (0)