Skip to content

Commit f3d8e58

Browse files
committed
Allow overshooting total_msat for an MPP
While retrying a failed path of an MPP, a node may want to overshoot the `total_msat` in order to use a path with an `htlc_minimum_msat` greater than the remaining value being sent. This commit no longer fails MPPs that overshoot the `total_msat`, however it does fail HTLCs with the same payment hash that are received *after* a payment has become claimable.
1 parent b9f4ebd commit f3d8e58

File tree

4 files changed

+80
-9
lines changed

4 files changed

+80
-9
lines changed

lightning/src/events/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ pub enum HTLCDestination {
230230
///
231231
/// Some of the reasons may include:
232232
/// * HTLC Timeouts
233-
/// * Expected MPP amount to claim does not equal HTLC total
233+
/// * Expected MPP amount has already been reached
234234
/// * Claimable amount does not match expected amount
235235
FailedPayment {
236236
/// The payment hash of the payment we attempted to process.
@@ -712,7 +712,7 @@ pub enum Event {
712712
/// * Insufficient capacity in the outbound channel
713713
/// * While waiting to forward the HTLC, the channel it is meant to be forwarded through closes
714714
/// * When an unknown SCID is requested for forwarding a payment.
715-
/// * Claiming an amount for an MPP payment that exceeds the HTLC total
715+
/// * Expected MPP amount has already been reached
716716
/// * The HTLC has timed out
717717
///
718718
/// This event, however, does not get generated if an HTLC fails to meet the forwarding

lightning/src/ln/channelmanager.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,7 +2663,7 @@ where
26632663
}
26642664

26652665
#[cfg(test)]
2666-
fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> {
2666+
pub(super) fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> {
26672667
let best_block_height = self.best_block.read().unwrap().height();
26682668
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
26692669
self.pending_outbound_payments.test_send_payment_internal(route, payment_hash, payment_secret, keysend_preimage, payment_id, recv_value_msat, onion_session_privs, &self.node_signer, best_block_height,
@@ -3354,11 +3354,13 @@ where
33543354
_ => unreachable!(),
33553355
}
33563356
}
3357-
if total_value >= msgs::MAX_VALUE_MSAT || total_value > $payment_data.total_msat {
3358-
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the total value {} ran over expected value {} (or HTLCs were inconsistent)",
3359-
log_bytes!(payment_hash.0), total_value, $payment_data.total_msat);
3357+
if total_value >= msgs::MAX_VALUE_MSAT {
33603358
fail_htlc!(claimable_htlc, payment_hash);
3361-
} else if total_value == $payment_data.total_msat {
3359+
} else if total_value - claimable_htlc.value >= $payment_data.total_msat {
3360+
log_trace!(self.logger, "Failing HTLC with payment_hash {} as payment is already claimable",
3361+
log_bytes!(payment_hash.0));
3362+
fail_htlc!(claimable_htlc, payment_hash);
3363+
} else if total_value >= $payment_data.total_msat {
33623364
let prev_channel_id = prev_funding_outpoint.to_channel_id();
33633365
htlcs.push(claimable_htlc);
33643366
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
@@ -3689,7 +3691,7 @@ where
36893691
if let OnionPayload::Invoice { .. } = htlcs[0].onion_payload {
36903692
// Check if we've received all the parts we need for an MPP (the value of the parts adds to total_msat).
36913693
// In this case we're not going to handle any timeouts of the parts here.
3692-
if htlcs[0].total_msat == htlcs.iter().fold(0, |total, htlc| total + htlc.value) {
3694+
if htlcs[0].total_msat <= htlcs.iter().fold(0, |total, htlc| total + htlc.value) {
36933695
return true;
36943696
} else if htlcs.into_iter().any(|htlc| {
36953697
htlc.timer_ticks += 1;

lightning/src/ln/functional_tests.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7926,6 +7926,76 @@ fn test_can_not_accept_unknown_inbound_channel() {
79267926
}
79277927
}
79287928

7929+
fn do_test_overshoot_mpp(msat_amounts: &[u64], total_msat: u64) {
7930+
7931+
let routing_node_count = msat_amounts.len();
7932+
let node_count = routing_node_count + 2;
7933+
7934+
let chanmon_cfgs = create_chanmon_cfgs(node_count);
7935+
let node_cfgs = create_node_cfgs(node_count, &chanmon_cfgs);
7936+
let node_chanmgrs = create_node_chanmgrs(node_count, &node_cfgs, &vec![None; node_count]);
7937+
let nodes = create_network(node_count, &node_cfgs, &node_chanmgrs);
7938+
7939+
let src_idx = 0;
7940+
let dst_idx = 1;
7941+
7942+
// Create channels for each amount
7943+
let mut expected_paths = Vec::with_capacity(routing_node_count);
7944+
let mut src_chan_ids = Vec::with_capacity(routing_node_count);
7945+
let mut dst_chan_ids = Vec::with_capacity(routing_node_count);
7946+
for i in 0..routing_node_count {
7947+
let routing_node = 2 + i;
7948+
let src_chan_id = create_announced_chan_between_nodes(&nodes, src_idx, routing_node).0.contents.short_channel_id;
7949+
src_chan_ids.push(src_chan_id);
7950+
let dst_chan_id = create_announced_chan_between_nodes(&nodes, routing_node, dst_idx).0.contents.short_channel_id;
7951+
dst_chan_ids.push(dst_chan_id);
7952+
let path = vec![&nodes[routing_node], &nodes[dst_idx]];
7953+
expected_paths.push(path);
7954+
}
7955+
let expected_paths: Vec<&[&Node]> = expected_paths.iter().map(|route| route.as_slice()).collect();
7956+
7957+
// Create a route for each amount
7958+
let example_amount = 100000;
7959+
let (mut route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(&nodes[src_idx], nodes[dst_idx], example_amount);
7960+
let sample_path = route.paths.pop().unwrap();
7961+
for i in 0..routing_node_count {
7962+
let routing_node = 2 + i;
7963+
let mut path = sample_path.clone();
7964+
path[0].pubkey = nodes[routing_node].node.get_our_node_id();
7965+
path[0].short_channel_id = src_chan_ids[i];
7966+
path[1].pubkey = nodes[dst_idx].node.get_our_node_id();
7967+
path[1].short_channel_id = dst_chan_ids[i];
7968+
path[1].fee_msat = msat_amounts[i];
7969+
route.paths.push(path);
7970+
}
7971+
7972+
// Send payment with manually set total_msat
7973+
let payment_id = PaymentId(nodes[src_idx].keys_manager.backing.get_secure_random_bytes());
7974+
let onion_session_privs = nodes[src_idx].node.test_add_new_pending_payment(our_payment_hash, Some(our_payment_secret), payment_id, &route).unwrap();
7975+
nodes[src_idx].node.test_send_payment_internal(&route, our_payment_hash, &Some(our_payment_secret), None, payment_id, Some(total_msat), onion_session_privs).unwrap();
7976+
check_added_monitors!(nodes[src_idx], expected_paths.len());
7977+
7978+
let mut events = nodes[src_idx].node.get_and_clear_pending_msg_events();
7979+
assert_eq!(events.len(), expected_paths.len());
7980+
let mut amount_received = 0;
7981+
for (path_idx, expected_path) in expected_paths.iter().enumerate() {
7982+
let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events);
7983+
7984+
let current_path_amount = msat_amounts[path_idx];
7985+
amount_received += current_path_amount;
7986+
let became_claimable_now = amount_received >= total_msat && amount_received - current_path_amount < total_msat;
7987+
pass_along_path(&nodes[src_idx], expected_path, amount_received, our_payment_hash.clone(), Some(our_payment_secret), ev, became_claimable_now, None);
7988+
}
7989+
7990+
claim_payment_along_route(&nodes[src_idx], &expected_paths, false, our_payment_preimage);
7991+
}
7992+
7993+
#[test]
7994+
fn test_overshoot_mpp() {
7995+
do_test_overshoot_mpp(&[100_000, 101_000], 200_000);
7996+
do_test_overshoot_mpp(&[100_000, 10_000, 100_000], 200_000);
7997+
}
7998+
79297999
#[test]
79308000
fn test_simple_mpp() {
79318001
// Simple test of sending a multi-path payment.

lightning/src/ln/outbound_payment.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,6 @@ impl OutboundPayments {
916916
return Err(PaymentSendFailure::PathParameterError(path_errs));
917917
}
918918
if let Some(amt_msat) = recv_value_msat {
919-
debug_assert!(amt_msat >= total_value);
920919
total_value = amt_msat;
921920
}
922921

0 commit comments

Comments
 (0)