Skip to content

Commit 51d9c54

Browse files
committed
Ensure we don't ever retry a payment along a just-failed path
If we try to pay a mobile client behind an LSP, its not strange for the singular last-hop hint to fail with a Temporary Channel Failure (indicating the mobile app is not currently open and connected to the LSP). In this case, we will penalize the last-hop channel but try again along the same path anyway, because we have no other path. This changes the retryer to simply refuse to do so, failing the payment instead. Fixes #1241.
1 parent 7b6a7bb commit 51d9c54

File tree

1 file changed

+67
-16
lines changed

1 file changed

+67
-16
lines changed

lightning-invoice/src/payment.rs

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ where
382382
// recipient may misbehave and claim the funds, at which point we have to
383383
// consider the payment sent, so return `Ok()` here, ignoring any retry
384384
// errors.
385-
let _ = self.retry_payment(payment_id, payment_hash, &retry_data);
385+
let _ = self.retry_payment(payment_id, payment_hash, &retry_data, None);
386386
Ok(payment_id)
387387
} else {
388388
// This may happen if we send a payment and some paths fail, but
@@ -396,8 +396,8 @@ where
396396
}.map_err(|e| PaymentError::Sending(e))
397397
}
398398

399-
fn retry_payment(
400-
&self, payment_id: PaymentId, payment_hash: PaymentHash, params: &RouteParameters
399+
fn retry_payment(&self, payment_id: PaymentId, payment_hash: PaymentHash,
400+
params: &RouteParameters, avoid_scid: Option<u64>
401401
) -> Result<(), ()> {
402402
let max_payment_attempts = self.retry_attempts.0 + 1;
403403
let attempts = *self.payment_cache.lock().unwrap()
@@ -419,29 +419,42 @@ where
419419

420420
let payer = self.payer.node_id();
421421
let first_hops = self.payer.first_hops();
422-
let route = self.router.find_route(
423-
&payer, &params, &payment_hash, Some(&first_hops.iter().collect::<Vec<_>>()),
424-
&self.scorer.lock()
425-
);
426-
if route.is_err() {
427-
log_trace!(self.logger, "Failed to find a route for payment {}; not retrying (attempts: {})", log_bytes!(payment_hash.0), attempts);
422+
let route = match self.router.find_route(
423+
&payer, &params, &payment_hash, Some(&first_hops.iter().collect::<Vec<_>>()), &self.scorer.lock()
424+
) {
425+
Ok(r) => r,
426+
Err(_) => {
427+
log_trace!(self.logger, "Failed to find a route for payment {}; not retrying (attempts: {})", log_bytes!(payment_hash.0), attempts);
428+
return Err(());
429+
}
430+
};
431+
432+
if route.paths.iter().any(|path| path.iter().any(|hop| Some(hop.short_channel_id) == avoid_scid)) {
433+
// While hopefully there is a Scor being used to avoid taking channels which *just*
434+
// failed, its possible that there is simply no other route. This is common in cases
435+
// where we're paying a node which is behind some form of LSP - there is a single route
436+
// hint which we are required to pay through. If that route hint failed (ie the node we
437+
// are trying to pay is offline), we shouldn't try to pay the node again and again
438+
// through the same hop.
439+
log_trace!(self.logger, "Route found for payment {} re-uses just-failed channel {}; not retrying (attempts: {})",
440+
log_bytes!(payment_hash.0), avoid_scid.unwrap_or(0),attempts);
428441
return Err(());
429442
}
430443

431-
match self.payer.retry_payment(&route.unwrap(), payment_id) {
444+
match self.payer.retry_payment(&route, payment_id) {
432445
Ok(()) => Ok(()),
433446
Err(PaymentSendFailure::ParameterError(_)) |
434447
Err(PaymentSendFailure::PathParameterError(_)) => {
435448
log_trace!(self.logger, "Failed to retry for payment {} due to bogus route/payment data, not retrying.", log_bytes!(payment_hash.0));
436449
Err(())
437450
},
438451
Err(PaymentSendFailure::AllFailedRetrySafe(_)) => {
439-
self.retry_payment(payment_id, payment_hash, params)
452+
self.retry_payment(payment_id, payment_hash, params, avoid_scid)
440453
},
441454
Err(PaymentSendFailure::PartialFailure { failed_paths_retry, .. }) => {
442455
if let Some(retry) = failed_paths_retry {
443456
// Always return Ok for the same reason as noted in pay_internal.
444-
let _ = self.retry_payment(payment_id, payment_hash, &retry);
457+
let _ = self.retry_payment(payment_id, payment_hash, &retry, avoid_scid);
445458
}
446459
Ok(())
447460
},
@@ -493,7 +506,7 @@ where
493506
} else if retry.is_none() {
494507
log_trace!(self.logger, "Payment {} missing retry params; not retrying", log_bytes!(payment_hash.0));
495508
self.payer.abandon_payment(payment_id.unwrap());
496-
} else if self.retry_payment(payment_id.unwrap(), *payment_hash, retry.as_ref().unwrap()).is_ok() {
509+
} else if self.retry_payment(payment_id.unwrap(), *payment_hash, retry.as_ref().unwrap(), *short_channel_id).is_ok() {
497510
// We retried at least somewhat, don't provide the PaymentPathFailed event to the user.
498511
return;
499512
} else {
@@ -971,6 +984,40 @@ mod tests {
971984
assert_eq!(*payer.attempts.borrow(), 1);
972985
}
973986

987+
#[test]
988+
fn fails_paying_invoice_after_rejected_at_only_last_hop() {
989+
let event_handled = core::cell::RefCell::new(false);
990+
let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
991+
992+
let payment_preimage = PaymentPreimage([1; 32]);
993+
let invoice = invoice(payment_preimage);
994+
let final_value_msat = invoice.amount_milli_satoshis().unwrap();
995+
996+
let payer = TestPayer::new().expect_send(Amount::ForInvoice(final_value_msat));
997+
let router = TestRouter {};
998+
let scorer = RefCell::new(TestScorer::new());
999+
let logger = TestLogger::new();
1000+
let invoice_payer =
1001+
InvoicePayer::new(&payer, router, &scorer, &logger, event_handler, RetryAttempts(2));
1002+
1003+
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
1004+
assert_eq!(*payer.attempts.borrow(), 1);
1005+
1006+
let event = Event::PaymentPathFailed {
1007+
payment_id,
1008+
payment_hash: PaymentHash(invoice.payment_hash().clone().into_inner()),
1009+
network_update: None,
1010+
rejected_by_dest: false,
1011+
all_paths_failed: false,
1012+
path: vec![],
1013+
short_channel_id: Some(1),
1014+
retry: Some(TestRouter::retry_for_invoice(&invoice)),
1015+
};
1016+
invoice_payer.handle_event(&event);
1017+
assert_eq!(*event_handled.borrow(), true);
1018+
assert_eq!(*payer.attempts.borrow(), 1);
1019+
}
1020+
9741021
#[test]
9751022
fn fails_repaying_invoice_with_pending_payment() {
9761023
let event_handled = core::cell::RefCell::new(false);
@@ -1175,8 +1222,7 @@ mod tests {
11751222

11761223
// Expect that scorer is given short_channel_id upon handling the event.
11771224
let payer = TestPayer::new()
1178-
.expect_send(Amount::ForInvoice(final_value_msat))
1179-
.expect_send(Amount::OnRetry(final_value_msat / 2));
1225+
.expect_send(Amount::ForInvoice(final_value_msat));
11801226
let router = TestRouter {};
11811227
let scorer = RefCell::new(TestScorer::new().expect(PaymentPath::Failure {
11821228
path: path.clone(), short_channel_id: path[0].short_channel_id,
@@ -1634,6 +1680,8 @@ mod tests {
16341680

16351681
let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
16361682
let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
1683+
let chan_3_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
1684+
let chan_4_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
16371685

16381686
let mut route = Route {
16391687
paths: vec![
@@ -1672,8 +1720,11 @@ mod tests {
16721720
};
16731721
let router = ManualRouter(RefCell::new(VecDeque::new()));
16741722
router.expect_find_route(Ok(route.clone()));
1675-
// On retry, we'll only be asked for one path
1723+
// On retry, we'll only be asked for one path. We also give it a diffferent SCID set as the
1724+
// InvoicePayer will refuse to retry using a just-failed channel.
16761725
route.paths.remove(1);
1726+
route.paths[0][0].short_channel_id = chan_3_scid;
1727+
route.paths[0][1].short_channel_id = chan_4_scid;
16771728
router.expect_find_route(Ok(route.clone()));
16781729

16791730
let expected_events: RefCell<VecDeque<&dyn Fn(&Event)>> = RefCell::new(VecDeque::new());

0 commit comments

Comments
 (0)