Skip to content

Commit 5765ddb

Browse files
Allow receiving less than the onion claims to pay
Useful for penultimate hops in routes to take an extra fee, if for example they opened a JIT channel to the payee and want them to help bear the channel open cost.
1 parent 636ace7 commit 5765ddb

File tree

3 files changed

+161
-13
lines changed

3 files changed

+161
-13
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,9 +2526,10 @@ where
25262526
}
25272527
}
25282528

2529-
fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
2530-
payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, ReceiveError>
2531-
{
2529+
fn construct_recv_pending_htlc_info(
2530+
&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
2531+
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool
2532+
) -> Result<PendingHTLCInfo, ReceiveError> {
25322533
// final_incorrect_cltv_expiry
25332534
if hop_data.outgoing_cltv_value > cltv_expiry {
25342535
return Err(ReceiveError {
@@ -2554,7 +2555,7 @@ where
25542555
msg: "The final CLTV expiry is too soon to handle",
25552556
});
25562557
}
2557-
if hop_data.amt_to_forward > amt_msat {
2558+
if !allow_underpay && hop_data.amt_to_forward > amt_msat {
25582559
return Err(ReceiveError {
25592560
err_code: 19,
25602561
err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2841,7 +2842,7 @@ where
28412842

28422843
fn construct_pending_htlc_status<'a>(
28432844
&self, msg: &msgs::UpdateAddHTLC, shared_secret: [u8; 32], decoded_hop: onion_utils::Hop,
2844-
next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
2845+
allow_underpay: bool, next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
28452846
) -> PendingHTLCStatus {
28462847
macro_rules! return_err {
28472848
($msg: expr, $err_code: expr, $data: expr) => {
@@ -2859,7 +2860,9 @@ where
28592860
match decoded_hop {
28602861
onion_utils::Hop::Receive(next_hop_data) => {
28612862
// OUR PAYMENT!
2862-
match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None) {
2863+
match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
2864+
msg.amount_msat, msg.cltv_expiry, None, allow_underpay)
2865+
{
28632866
Ok(info) => {
28642867
// Note that we could obviously respond immediately with an update_fulfill_htlc
28652868
// message, however that would leak that we are the recipient of this payment, so
@@ -3675,7 +3678,10 @@ where
36753678
};
36763679
match next_hop {
36773680
onion_utils::Hop::Receive(hop_data) => {
3678-
match self.construct_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, Some(phantom_shared_secret)) {
3681+
match self.construct_recv_pending_htlc_info(hop_data,
3682+
incoming_shared_secret, payment_hash, outgoing_amt_msat,
3683+
outgoing_cltv_value, Some(phantom_shared_secret), false)
3684+
{
36793685
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
36803686
Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
36813687
}
@@ -5424,7 +5430,8 @@ where
54245430

54255431
let pending_forward_info = match decoded_hop_res {
54265432
Ok((next_hop, shared_secret, next_packet_pk_opt)) =>
5427-
self.construct_pending_htlc_status(msg, shared_secret, next_hop, next_packet_pk_opt),
5433+
self.construct_pending_htlc_status(msg, shared_secret, next_hop,
5434+
chan.get().context.config().accept_underpaying_htlcs, next_packet_pk_opt),
54285435
Err(e) => PendingHTLCStatus::Fail(e)
54295436
};
54305437
let create_pending_htlc_status = |chan: &Channel<<SP::Target as SignerProvider>::Signer>, pending_forward_info: PendingHTLCStatus, error_code: u16| {

lightning/src/ln/functional_test_utils.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,7 +2186,20 @@ pub fn send_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, route: Route
21862186
(our_payment_preimage, our_payment_hash, our_payment_secret, payment_id)
21872187
}
21882188

2189-
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 {
2189+
pub fn do_claim_payment_along_route<'a, 'b, 'c>(
2190+
origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool,
2191+
our_payment_preimage: PaymentPreimage
2192+
) -> u64 {
2193+
let extra_fees = vec![0; expected_paths.len()];
2194+
do_claim_payment_along_route_with_extra_penultimate_hop_fees(origin_node, expected_paths,
2195+
&extra_fees[..], skip_last, our_payment_preimage)
2196+
}
2197+
2198+
pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>(
2199+
origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees:
2200+
&[u32], skip_last: bool, our_payment_preimage: PaymentPreimage
2201+
) -> u64 {
2202+
assert_eq!(expected_paths.len(), expected_extra_fees.len());
21902203
for path in expected_paths.iter() {
21912204
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
21922205
}
@@ -2236,7 +2249,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
22362249
}
22372250
}
22382251

2239-
for (expected_route, (path_msgs, next_hop)) in expected_paths.iter().zip(per_path_msgs.drain(..)) {
2252+
for (i, (expected_route, (path_msgs, next_hop))) in expected_paths.iter().zip(per_path_msgs.drain(..)).enumerate() {
22402253
let mut next_msgs = Some(path_msgs);
22412254
let mut expected_next_node = next_hop;
22422255

@@ -2251,10 +2264,10 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
22512264
}
22522265
}
22532266
macro_rules! mid_update_fulfill_dance {
2254-
($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
2267+
($idx: expr, $node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
22552268
{
22562269
$node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
2257-
let fee = {
2270+
let mut fee = {
22582271
let per_peer_state = $node.node.per_peer_state.read().unwrap();
22592272
let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
22602273
.unwrap().lock().unwrap();
@@ -2265,6 +2278,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
22652278
channel.context.config().forwarding_fee_base_msat
22662279
}
22672280
};
2281+
if $idx == 1 { fee += expected_extra_fees[i]; }
22682282
expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
22692283
expected_total_fee_msat += fee as u64;
22702284
check_added_monitors!($node, 1);
@@ -2296,7 +2310,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
22962310
} else {
22972311
next_node = expected_route[expected_route.len() - 1 - idx - 1];
22982312
}
2299-
mid_update_fulfill_dance!(node, prev_node, next_node, update_next_msgs);
2313+
mid_update_fulfill_dance!(idx, node, prev_node, next_node, update_next_msgs);
23002314
} else {
23012315
assert!(!update_next_msgs);
23022316
assert!(node.node.get_and_clear_pending_msg_events().is_empty());

lightning/src/ln/payment_tests.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,133 @@ fn do_test_intercepted_payment(test: InterceptTest) {
17361736
}
17371737
}
17381738

1739+
#[test]
1740+
fn accept_underpaying_htlcs_config() {
1741+
do_accept_underpaying_htlcs_config(1);
1742+
do_accept_underpaying_htlcs_config(2);
1743+
do_accept_underpaying_htlcs_config(3);
1744+
}
1745+
1746+
fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) {
1747+
let chanmon_cfgs = create_chanmon_cfgs(3);
1748+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1749+
let mut intercept_forwards_config = test_default_channel_config();
1750+
intercept_forwards_config.accept_intercept_htlcs = true;
1751+
let mut underpay_config = test_default_channel_config();
1752+
underpay_config.channel_config.accept_underpaying_htlcs = true;
1753+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), Some(underpay_config)]);
1754+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1755+
1756+
let mut chan_ids = Vec::new();
1757+
for _ in 0..num_mpp_parts {
1758+
let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000, 0);
1759+
let channel_id = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 2_000_000, 0).0.channel_id;
1760+
chan_ids.push(channel_id);
1761+
}
1762+
1763+
// Send the initial payment.
1764+
let amt_msat = 900_000;
1765+
let skimmed_fee_msat = 20;
1766+
let mut route_hints = Vec::new();
1767+
for _ in 0..num_mpp_parts {
1768+
route_hints.push(RouteHint(vec![RouteHintHop {
1769+
src_node_id: nodes[1].node.get_our_node_id(),
1770+
short_channel_id: nodes[1].node.get_intercept_scid(),
1771+
fees: RoutingFees {
1772+
base_msat: 1000,
1773+
proportional_millionths: 0,
1774+
},
1775+
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
1776+
htlc_minimum_msat: None,
1777+
htlc_maximum_msat: Some(amt_msat / num_mpp_parts as u64 + 5),
1778+
}]));
1779+
}
1780+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
1781+
.with_route_hints(route_hints).unwrap()
1782+
.with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
1783+
let route_params = RouteParameters {
1784+
payment_params,
1785+
final_value_msat: amt_msat,
1786+
};
1787+
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
1788+
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
1789+
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
1790+
check_added_monitors!(nodes[0], num_mpp_parts); // one monitor per path
1791+
let mut events: Vec<SendEvent> = nodes[0].node.get_and_clear_pending_msg_events().into_iter().map(|e| SendEvent::from_event(e)).collect();
1792+
assert_eq!(events.len(), num_mpp_parts);
1793+
1794+
// Forward the intercepted payments.
1795+
for (idx, ev) in events.into_iter().enumerate() {
1796+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &ev.msgs[0]);
1797+
do_commitment_signed_dance(&nodes[1], &nodes[0], &ev.commitment_msg, false, true);
1798+
1799+
let events = nodes[1].node.get_and_clear_pending_events();
1800+
assert_eq!(events.len(), 1);
1801+
let (intercept_id, expected_outbound_amt_msat) = match events[0] {
1802+
crate::events::Event::HTLCIntercepted {
1803+
intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, ..
1804+
} => {
1805+
assert_eq!(pmt_hash, payment_hash);
1806+
(intercept_id, expected_outbound_amount_msat)
1807+
},
1808+
_ => panic!()
1809+
};
1810+
nodes[1].node.forward_intercepted_htlc(intercept_id, &chan_ids[idx],
1811+
nodes[2].node.get_our_node_id(), expected_outbound_amt_msat - skimmed_fee_msat).unwrap();
1812+
expect_pending_htlcs_forwardable!(nodes[1]);
1813+
let payment_event = {
1814+
{
1815+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
1816+
assert_eq!(added_monitors.len(), 1);
1817+
added_monitors.clear();
1818+
}
1819+
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
1820+
assert_eq!(events.len(), 1);
1821+
SendEvent::from_event(events.remove(0))
1822+
};
1823+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
1824+
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, false, true);
1825+
if idx == num_mpp_parts - 1 {
1826+
expect_pending_htlcs_forwardable!(nodes[2]);
1827+
}
1828+
}
1829+
1830+
// Claim the payment and check that the skimmed fee is as expected.
1831+
let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
1832+
let events = nodes[2].node.get_and_clear_pending_events();
1833+
assert_eq!(events.len(), 1);
1834+
match events[0] {
1835+
crate::events::Event::PaymentClaimable {
1836+
ref payment_hash, ref purpose, amount_msat, counterparty_skimmed_fee_msat, receiver_node_id, ..
1837+
} => {
1838+
assert_eq!(payment_hash, payment_hash);
1839+
assert_eq!(amt_msat - skimmed_fee_msat * num_mpp_parts as u64, amount_msat);
1840+
assert_eq!(skimmed_fee_msat * num_mpp_parts as u64, counterparty_skimmed_fee_msat);
1841+
assert_eq!(nodes[2].node.get_our_node_id(), receiver_node_id.unwrap());
1842+
match purpose {
1843+
crate::events::PaymentPurpose::InvoicePayment { payment_preimage: ev_payment_preimage,
1844+
payment_secret: ev_payment_secret, .. } =>
1845+
{
1846+
assert_eq!(payment_preimage, ev_payment_preimage.unwrap());
1847+
assert_eq!(payment_secret, *ev_payment_secret);
1848+
},
1849+
_ => panic!(),
1850+
}
1851+
},
1852+
_ => panic!("Unexpected event"),
1853+
}
1854+
let mut expected_paths_vecs = Vec::new();
1855+
let mut expected_paths = Vec::new();
1856+
for _ in 0..num_mpp_parts { expected_paths_vecs.push(vec!(&nodes[1], &nodes[2])); }
1857+
for i in 0..num_mpp_parts { expected_paths.push(&expected_paths_vecs[i][..]); }
1858+
let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees(
1859+
&nodes[0], &expected_paths[..], &vec![skimmed_fee_msat as u32; num_mpp_parts][..], false,
1860+
payment_preimage);
1861+
// The sender doesn't know that the penultimate hop took an extra fee.
1862+
expect_payment_sent(&nodes[0], payment_preimage,
1863+
Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true);
1864+
}
1865+
17391866
#[derive(PartialEq)]
17401867
enum AutoRetry {
17411868
Success,

0 commit comments

Comments
 (0)