Skip to content

Commit 22698fc

Browse files
Return new RetryableSendFailure on auto-retryable send error
PaymentSendFailure doesn't work for auto retry send methods because they will never return partial failure
1 parent 2d4c713 commit 22698fc

File tree

4 files changed

+91
-35
lines changed

4 files changed

+91
-35
lines changed

lightning-invoice/src/payment.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use lightning::chain;
1818
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1919
use lightning::chain::keysinterface::{NodeSigner, SignerProvider, EntropySource};
2020
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
21-
use lightning::ln::channelmanager::{ChannelManager, PaymentId, PaymentSendFailure, Retry};
21+
use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure};
2222
use lightning::routing::router::{PaymentParameters, RouteParameters, Router};
2323
use lightning::util::logger::Logger;
2424

@@ -249,7 +249,7 @@ pub enum PaymentError {
249249
/// An error resulting from the provided [`Invoice`] or payment hash.
250250
Invoice(&'static str),
251251
/// An error occurring when sending a payment.
252-
Sending(PaymentSendFailure),
252+
Sending(RetryableSendFailure),
253253
}
254254

255255
/// A trait defining behavior of an [`Invoice`] payer.

lightning/src/ln/channelmanager.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ use core::time::Duration;
7676
use core::ops::Deref;
7777

7878
// Re-export this for use in the public API.
79-
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry};
79+
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure};
8080

8181
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
8282
//
@@ -2543,7 +2543,7 @@ where
25432543

25442544
/// Similar to [`ChannelManager::send_payment`], but will automatically find a route based on
25452545
/// `route_params` and retry failed payment paths based on `retry_strategy`.
2546-
pub fn send_payment_with_retry(&self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), PaymentSendFailure> {
2546+
pub fn send_payment_with_retry(&self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), RetryableSendFailure> {
25472547
let best_block_height = self.best_block.read().unwrap().height();
25482548
self.pending_outbound_payments
25492549
.send_payment(payment_hash, payment_secret, payment_id, retry_strategy, route_params,
@@ -2616,7 +2616,7 @@ where
26162616

26172617
/// Similar to [`ChannelManager::send_spontaneous_payment`], but will automatically find a route
26182618
/// based on `route_params` and retry failed payment paths based on `retry_strategy`.
2619-
pub fn send_spontaneous_payment_with_retry(&self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<PaymentHash, PaymentSendFailure> {
2619+
pub fn send_spontaneous_payment_with_retry(&self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<PaymentHash, RetryableSendFailure> {
26202620
let best_block_height = self.best_block.read().unwrap().height();
26212621
self.pending_outbound_payments.send_spontaneous_payment(payment_preimage, payment_id,
26222622
retry_strategy, route_params, &self.router, self.list_usable_channels(),

lightning/src/ln/outbound_payment.rs

+84-28
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,44 @@ impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
313313
}
314314
}
315315

316-
/// If a payment fails to send, it can be in one of several states. This enum is returned as the
317-
/// Err() type describing which state the payment is in, see the description of individual enum
318-
/// states for more.
316+
/// If a payment fails to send with [`ChannelManager::send_payment_with_retry`], it can be in one of
317+
/// several states. This enum is returned as the Err() type describing which state the payment is
318+
/// in, see the description of individual enum states for more.
319+
///
320+
/// [`ChannelManager::send_payment_with_retry`]: crate::ln::channelmanager::ChannelManager::send_payment_with_retry
321+
#[derive(Clone, Debug)]
322+
pub enum RetryableSendFailure {
323+
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
324+
/// send the payment at all.
325+
///
326+
/// You can freely resend the payment in full (with the parameter error fixed).
327+
ParameterError(APIError),
328+
/// A parameter in a single path which was passed to send_payment was invalid, preventing us
329+
/// from attempting to send the payment at all.
330+
///
331+
/// You can freely resend the payment in full (with the parameter error fixed).
332+
///
333+
/// The results here are ordered the same as the paths in the route object which was passed to
334+
/// send_payment.
335+
PathParameterError(Vec<Result<(), APIError>>),
336+
/// All paths which were attempted failed to send, with no channel state change taking place.
337+
/// You can freely resend the payment in full (though you probably want to do so over different
338+
/// paths than the ones selected).
339+
AllFailedResendSafe(Vec<APIError>),
340+
/// Indicates that a payment for the provided [`PaymentId`] is already in-flight and has not
341+
/// yet completed (i.e. generated an [`Event::PaymentSent`] or [`Event::PaymentFailed`]).
342+
///
343+
/// [`PaymentId`]: crate::ln::channelmanager::PaymentId
344+
/// [`Event::PaymentSent`]: crate::util::events::Event::PaymentSent
345+
/// [`Event::PaymentFailed`]: crate::util::events::Event::PaymentFailed
346+
DuplicatePayment,
347+
}
348+
349+
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
350+
/// states. This enum is returned as the Err() type describing which state the payment is in, see
351+
/// the description of individual enum states for more.
352+
///
353+
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
319354
#[derive(Clone, Debug)]
320355
pub enum PaymentSendFailure {
321356
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
@@ -379,7 +414,7 @@ impl OutboundPayments {
379414
retry_strategy: Retry, route_params: RouteParameters, router: &R,
380415
first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
381416
node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F,
382-
) -> Result<(), PaymentSendFailure>
417+
) -> Result<(), RetryableSendFailure>
383418
where
384419
R::Target: Router,
385420
ES::Target: EntropySource,
@@ -388,10 +423,18 @@ impl OutboundPayments {
388423
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
389424
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
390425
{
391-
self.pay_internal(payment_id, Some((payment_hash, payment_secret, None, retry_strategy)),
426+
let res = self.pay_internal(payment_id, Some((payment_hash, payment_secret, None, retry_strategy)),
392427
route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
393428
best_block_height, logger, &send_payment_along_path)
394-
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
429+
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e });
430+
match res {
431+
Err(PaymentSendFailure::ParameterError(e)) => Err(RetryableSendFailure::ParameterError(e)),
432+
Err(PaymentSendFailure::PathParameterError(e)) => Err(RetryableSendFailure::PathParameterError(e)),
433+
Err(PaymentSendFailure::AllFailedResendSafe(e)) => Err(RetryableSendFailure::AllFailedResendSafe(e)),
434+
Err(PaymentSendFailure::DuplicatePayment) => Err(RetryableSendFailure::DuplicatePayment),
435+
Err(PaymentSendFailure::PartialFailure { .. }) => Ok(()),
436+
Ok(()) => Ok(())
437+
}
395438
}
396439

397440
pub(super) fn send_payment_with_route<ES: Deref, NS: Deref, F>(
@@ -416,7 +459,7 @@ impl OutboundPayments {
416459
retry_strategy: Retry, route_params: RouteParameters, router: &R,
417460
first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
418461
node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F
419-
) -> Result<PaymentHash, PaymentSendFailure>
462+
) -> Result<PaymentHash, RetryableSendFailure>
420463
where
421464
R::Target: Router,
422465
ES::Target: EntropySource,
@@ -428,11 +471,18 @@ impl OutboundPayments {
428471
let preimage = payment_preimage
429472
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
430473
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
431-
self.pay_internal(payment_id, Some((payment_hash, &None, Some(preimage), retry_strategy)),
474+
let res = self.pay_internal(payment_id, Some((payment_hash, &None, Some(preimage), retry_strategy)),
432475
route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
433476
best_block_height, logger, &send_payment_along_path)
434-
.map(|()| payment_hash)
435-
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
477+
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e });
478+
match res {
479+
Err(PaymentSendFailure::ParameterError(e)) => Err(RetryableSendFailure::ParameterError(e)),
480+
Err(PaymentSendFailure::PathParameterError(e)) => Err(RetryableSendFailure::PathParameterError(e)),
481+
Err(PaymentSendFailure::AllFailedResendSafe(e)) => Err(RetryableSendFailure::AllFailedResendSafe(e)),
482+
Err(PaymentSendFailure::DuplicatePayment) => Err(RetryableSendFailure::DuplicatePayment),
483+
Err(PaymentSendFailure::PartialFailure { .. }) => Ok(payment_hash),
484+
Ok(()) => Ok(payment_hash),
485+
}
436486
}
437487

438488
pub(super) fn send_spontaneous_payment_with_route<ES: Deref, NS: Deref, F>(
@@ -1222,19 +1272,22 @@ mod tests {
12221272
final_value_msat: 0,
12231273
final_cltv_expiry_delta: 0,
12241274
};
1225-
let err = if on_retry {
1226-
outbound_payments.pay_internal(
1275+
if on_retry {
1276+
let err = outbound_payments.pay_internal(
12271277
PaymentId([0; 32]), None, expired_route_params, &&router, vec![], InFlightHtlcs::new(),
1228-
&&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
1278+
&&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err();
1279+
if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1280+
assert!(err.contains("Invoice expired"));
1281+
} else { panic!("Unexpected error"); }
12291282
} else {
1230-
outbound_payments.send_payment(
1283+
let err = outbound_payments.send_payment(
12311284
PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), expired_route_params,
12321285
&&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
1233-
|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
1234-
};
1235-
if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1236-
assert!(err.contains("Invoice expired"));
1237-
} else { panic!("Unexpected error"); }
1286+
|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err();
1287+
if let RetryableSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1288+
assert!(err.contains("Invoice expired"));
1289+
} else { panic!("Unexpected error"); }
1290+
}
12381291
}
12391292

12401293
#[test]
@@ -1261,22 +1314,25 @@ mod tests {
12611314
router.expect_find_route(route_params.clone(),
12621315
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }));
12631316

1264-
let err = if on_retry {
1317+
if on_retry {
12651318
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]), None,
12661319
&Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)),
12671320
Some(route_params.payment_params.clone()), &&keys_manager, 0).unwrap();
1268-
outbound_payments.pay_internal(
1321+
let err = outbound_payments.pay_internal(
12691322
PaymentId([0; 32]), None, route_params, &&router, vec![], InFlightHtlcs::new(),
1270-
&&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
1323+
&&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err();
1324+
if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1325+
assert!(err.contains("Failed to find a route"));
1326+
} else { panic!("Unexpected error"); }
12711327
} else {
1272-
outbound_payments.send_payment(
1328+
let err = outbound_payments.send_payment(
12731329
PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), route_params,
12741330
&&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
1275-
|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
1276-
};
1277-
if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1278-
assert!(err.contains("Failed to find a route"));
1279-
} else { panic!("Unexpected error"); }
1331+
|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err();
1332+
if let RetryableSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
1333+
assert!(err.contains("Failed to find a route"));
1334+
} else { panic!("Unexpected error"); }
1335+
}
12801336
}
12811337

12821338
#[test]

lightning/src/ln/payment_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
1515
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
1616
use crate::chain::keysinterface::EntropySource;
1717
use crate::chain::transaction::OutPoint;
18-
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS, RecentPaymentDetails};
18+
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS, RecentPaymentDetails, RetryableSendFailure};
1919
use crate::ln::features::InvoiceFeatures;
2020
use crate::ln::msgs;
2121
use crate::ln::msgs::ChannelMessageHandler;
@@ -1924,7 +1924,7 @@ fn auto_retry_zero_attempts_send_error() {
19241924

19251925
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
19261926
let err = nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap_err();
1927-
if let PaymentSendFailure::AllFailedResendSafe(_) = err {
1927+
if let RetryableSendFailure::AllFailedResendSafe(_) = err {
19281928
} else { panic!("Unexpected error"); }
19291929
assert_eq!(nodes[0].node.get_and_clear_pending_msg_events().len(), 2); // channel close messages
19301930
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 1); // channel close event

0 commit comments

Comments
 (0)