Skip to content

Commit fcf73f0

Browse files
committed
Add a separate PaymentSendFailure for idempotency violation
When a user attempts to send a payment but it fails due to idempotency key violation, they need to know that this was the reason as they need to handle the error programmatically differently from other errors. Here we simply add a new `PaymentSendFailure` enum variant for `DuplicatePayment` to allow for that.
1 parent c90aac2 commit fcf73f0

File tree

4 files changed

+31
-12
lines changed

4 files changed

+31
-12
lines changed

fuzz/src/chanmon_consistency.rs

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ fn check_payment_err(send_err: PaymentSendFailure) {
289289
PaymentSendFailure::PartialFailure { results, .. } => {
290290
for res in results { if let Err(api_err) = res { check_api_err(api_err); } }
291291
},
292+
PaymentSendFailure::DuplicatePayment => panic!(),
292293
}
293294
}
294295

lightning-invoice/src/payment.rs

+5
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ where
543543
Err(e) => match e {
544544
PaymentSendFailure::ParameterError(_) => Err(e),
545545
PaymentSendFailure::PathParameterError(_) => Err(e),
546+
PaymentSendFailure::DuplicatePayment => Err(e),
546547
PaymentSendFailure::AllFailedResendSafe(_) => {
547548
let mut payment_cache = self.payment_cache.lock().unwrap();
548549
let payment_info = payment_cache.get_mut(&payment_hash).unwrap();
@@ -658,6 +659,10 @@ where
658659
Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
659660
self.retry_payment(payment_id, payment_hash, params)
660661
},
662+
Err(PaymentSendFailure::DuplicatePayment) => {
663+
log_error!(self.logger, "Got a DuplicatePayment error when attempting to retry a payment, this shouldn't happen.");
664+
Err(())
665+
}
661666
Err(PaymentSendFailure::PartialFailure { failed_paths_retry, results, .. }) => {
662667
// If a `PartialFailure` error contains a result that is an `Ok()`, it means that
663668
// part of our payment is retried. When we receive `MonitorUpdateInProgress`, it

lightning/src/ln/channelmanager.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -1188,16 +1188,25 @@ impl ChannelDetails {
11881188
#[derive(Clone, Debug)]
11891189
pub enum PaymentSendFailure {
11901190
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
1191-
/// send the payment at all. No channel state has been changed or messages sent to peers, and
1192-
/// once you've changed the parameter at error, you can freely retry the payment in full.
1191+
/// send the payment at all.
1192+
///
1193+
/// You can freely resend the payment in full (with the parameter error fixed).
1194+
///
1195+
/// Because the payment failed outright, no payment tracking is done, you do not need to call
1196+
/// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
1197+
/// for this payment.
11931198
ParameterError(APIError),
11941199
/// A parameter in a single path which was passed to send_payment was invalid, preventing us
1195-
/// from attempting to send the payment at all. No channel state has been changed or messages
1196-
/// sent to peers, and once you've changed the parameter at error, you can freely retry the
1197-
/// payment in full.
1200+
/// from attempting to send the payment at all.
1201+
///
1202+
/// You can freely resend the payment in full (with the parameter error fixed).
11981203
///
11991204
/// The results here are ordered the same as the paths in the route object which was passed to
12001205
/// send_payment.
1206+
///
1207+
/// Because the payment failed outright, no payment tracking is done, you do not need to call
1208+
/// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
1209+
/// for this payment.
12011210
PathParameterError(Vec<Result<(), APIError>>),
12021211
/// All paths which were attempted failed to send, with no channel state change taking place.
12031212
/// You can freely resend the payment in full (though you probably want to do so over different
@@ -1207,6 +1216,12 @@ pub enum PaymentSendFailure {
12071216
/// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
12081217
/// for this payment.
12091218
AllFailedResendSafe(Vec<APIError>),
1219+
/// Indicates that a payment for the provided [`PaymentId`] is already in-flight and has not
1220+
/// yet completed (i.e. generated an [`Event::PaymentSent`]) or been abandoned (via
1221+
/// [`ChannelManager::abandon_payment`]).
1222+
///
1223+
/// [`Event::PaymentSent`]: events::Event::PaymentSent
1224+
DuplicatePayment,
12101225
/// Some paths which were attempted failed to send, though possibly not all. At least some
12111226
/// paths have irrevocably committed to the HTLC and retrying the payment in full would result
12121227
/// in over-/re-payment.
@@ -2611,9 +2626,7 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
26112626

26122627
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
26132628
match pending_outbounds.entry(payment_id) {
2614-
hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::ParameterError(APIError::RouteError {
2615-
err: "Payment already in progress"
2616-
})),
2629+
hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
26172630
hash_map::Entry::Vacant(entry) => {
26182631
let payment = entry.insert(PendingOutboundPayment::Retryable {
26192632
session_privs: HashSet::new(),

lightning/src/ln/payment_tests.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1275,15 +1275,15 @@ fn claimed_send_payment_idempotent() {
12751275
// payment_id, it should be rejected.
12761276
let send_result = nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret), payment_id);
12771277
match send_result {
1278-
Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
1278+
Err(PaymentSendFailure::DuplicatePayment) => {},
12791279
_ => panic!("Unexpected send result: {:?}", send_result),
12801280
}
12811281

12821282
// Further, if we try to send a spontaneous payment with the same payment_id it should
12831283
// also be rejected.
12841284
let send_result = nodes[0].node.send_spontaneous_payment(&route, None, payment_id);
12851285
match send_result {
1286-
Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
1286+
Err(PaymentSendFailure::DuplicatePayment) => {},
12871287
_ => panic!("Unexpected send result: {:?}", send_result),
12881288
}
12891289
}
@@ -1347,15 +1347,15 @@ fn abandoned_send_payment_idempotent() {
13471347
// payment_id, it should be rejected.
13481348
let send_result = nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret), payment_id);
13491349
match send_result {
1350-
Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
1350+
Err(PaymentSendFailure::DuplicatePayment) => {},
13511351
_ => panic!("Unexpected send result: {:?}", send_result),
13521352
}
13531353

13541354
// Further, if we try to send a spontaneous payment with the same payment_id it should
13551355
// also be rejected.
13561356
let send_result = nodes[0].node.send_spontaneous_payment(&route, None, payment_id);
13571357
match send_result {
1358-
Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
1358+
Err(PaymentSendFailure::DuplicatePayment) => {},
13591359
_ => panic!("Unexpected send result: {:?}", send_result),
13601360
}
13611361
}

0 commit comments

Comments
 (0)