Skip to content

Commit a0bd8a5

Browse files
committed
Only avoid duplicate payments if we failed sending
Previously, we denied retrying any payment for which we already had seen the payment hash. However, this is rather invasive as payments might fail due to local connections issues or unavailability of usable channels, in which case the invoices/payment hashes would be 'burnt' and couldn't get retried. Here, we introduce a new `PaymentStatus::SendingFailed` to differentiate send failures and allow retrying tracked payments if they failed to send immediately. We also rename some error types accordingly for further clarification.
1 parent b090a8f commit a0bd8a5

File tree

5 files changed

+54
-25
lines changed

5 files changed

+54
-25
lines changed

bindings/ldk_node.udl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ enum NodeError {
7373
"OnchainTxCreationFailed",
7474
"ConnectionFailed",
7575
"InvoiceCreationFailed",
76-
"PaymentFailed",
76+
"PaymentSendingFailed",
7777
"ChannelCreationFailed",
7878
"ChannelClosingFailed",
7979
"PersistenceFailed",
@@ -113,6 +113,7 @@ enum PaymentDirection {
113113

114114
enum PaymentStatus {
115115
"Pending",
116+
"SendingFailed",
116117
"Succeeded",
117118
"Failed",
118119
};

src/error.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ pub enum Error {
1313
ConnectionFailed,
1414
/// Invoice creation failed.
1515
InvoiceCreationFailed,
16-
/// An attempted payment has failed.
17-
PaymentFailed,
16+
/// Sending a payment has failed.
17+
PaymentSendingFailed,
1818
/// A channel could not be opened.
1919
ChannelCreationFailed,
2020
/// A channel could not be closed.
@@ -67,7 +67,7 @@ impl fmt::Display for Error {
6767
}
6868
Self::ConnectionFailed => write!(f, "Network connection closed."),
6969
Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."),
70-
Self::PaymentFailed => write!(f, "Failed to send the given payment."),
70+
Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."),
7171
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
7272
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
7373
Self::PersistenceFailed => write!(f, "Failed to persist data."),
@@ -86,7 +86,9 @@ impl fmt::Display for Error {
8686
Self::InvalidInvoice => write!(f, "The given invoice is invalid."),
8787
Self::InvalidChannelId => write!(f, "The given channel ID is invalid."),
8888
Self::InvalidNetwork => write!(f, "The given network is invalid."),
89-
Self::DuplicatePayment => write!(f, "A payment with the given hash has already been initiated."),
89+
Self::DuplicatePayment => {
90+
write!(f, "A payment with the given hash has already been initiated.")
91+
}
9092
Self::InsufficientFunds => {
9193
write!(f, "There are insufficient funds to complete the given operation.")
9294
}

src/lib.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,9 +1297,11 @@ impl Node {
12971297

12981298
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
12991299

1300-
if self.payment_store.contains(&payment_hash) {
1301-
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1302-
return Err(Error::DuplicatePayment);
1300+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1301+
if payment.status != PaymentStatus::SendingFailed {
1302+
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1303+
return Err(Error::DuplicatePayment);
1304+
}
13031305
}
13041306

13051307
let payment_secret = Some(*invoice.payment_secret());
@@ -1339,11 +1341,16 @@ impl Node {
13391341
secret: payment_secret,
13401342
amount_msat: invoice.amount_milli_satoshis(),
13411343
direction: PaymentDirection::Outbound,
1342-
status: PaymentStatus::Failed,
1344+
status: PaymentStatus::SendingFailed,
13431345
};
13441346
self.payment_store.insert(payment)?;
13451347

1346-
Err(Error::PaymentFailed)
1348+
match e {
1349+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1350+
Err(Error::DuplicatePayment)
1351+
}
1352+
_ => Err(Error::PaymentSendingFailed),
1353+
}
13471354
}
13481355
}
13491356
}
@@ -1372,9 +1379,11 @@ impl Node {
13721379
}
13731380

13741381
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
1375-
if self.payment_store.contains(&payment_hash) {
1376-
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1377-
return Err(Error::DuplicatePayment);
1382+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1383+
if payment.status != PaymentStatus::SendingFailed {
1384+
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1385+
return Err(Error::DuplicatePayment);
1386+
}
13781387
}
13791388

13801389
let payment_id = PaymentId(invoice.payment_hash().into_inner());
@@ -1433,11 +1442,16 @@ impl Node {
14331442
secret: payment_secret,
14341443
amount_msat: Some(amount_msat),
14351444
direction: PaymentDirection::Outbound,
1436-
status: PaymentStatus::Failed,
1445+
status: PaymentStatus::SendingFailed,
14371446
};
14381447
self.payment_store.insert(payment)?;
14391448

1440-
Err(Error::PaymentFailed)
1449+
match e {
1450+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1451+
Err(Error::DuplicatePayment)
1452+
}
1453+
_ => Err(Error::PaymentSendingFailed),
1454+
}
14411455
}
14421456
}
14431457
}
@@ -1454,6 +1468,13 @@ impl Node {
14541468
let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes());
14551469
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
14561470

1471+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1472+
if payment.status != PaymentStatus::SendingFailed {
1473+
log_error!(self.logger, "Payment error: must not send duplicate payments.");
1474+
return Err(Error::DuplicatePayment);
1475+
}
1476+
}
1477+
14571478
let route_params = RouteParameters {
14581479
payment_params: PaymentParameters::from_node_id(
14591480
node_id,
@@ -1492,13 +1513,18 @@ impl Node {
14921513
hash: payment_hash,
14931514
preimage: Some(payment_preimage),
14941515
secret: None,
1495-
status: PaymentStatus::Failed,
1516+
status: PaymentStatus::SendingFailed,
14961517
direction: PaymentDirection::Outbound,
14971518
amount_msat: Some(amount_msat),
14981519
};
14991520
self.payment_store.insert(payment)?;
15001521

1501-
Err(Error::PaymentFailed)
1522+
match e {
1523+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1524+
Err(Error::DuplicatePayment)
1525+
}
1526+
_ => Err(Error::PaymentSendingFailed),
1527+
}
15021528
}
15031529
}
15041530
}

src/payment_store.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,19 @@ impl_writeable_tlv_based_enum!(PaymentDirection,
5757
pub enum PaymentStatus {
5858
/// The payment is still pending.
5959
Pending,
60+
/// The sending of the payment failed and is safe to be retried.
61+
SendingFailed,
6062
/// The payment suceeded.
6163
Succeeded,
62-
/// The payment failed.
64+
/// The payment failed and is not retryable.
6365
Failed,
6466
}
6567

6668
impl_writeable_tlv_based_enum!(PaymentStatus,
6769
(0, Pending) => {},
68-
(1, Succeeded) => {},
69-
(2, Failed) => {};
70+
(2, SendingFailed) => {},
71+
(4, Succeeded) => {},
72+
(6, Failed) => {};
7073
);
7174

7275
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -141,10 +144,6 @@ where
141144
self.payments.lock().unwrap().get(hash).cloned()
142145
}
143146

144-
pub(crate) fn contains(&self, hash: &PaymentHash) -> bool {
145-
self.payments.lock().unwrap().contains_key(hash)
146-
}
147-
148147
pub(crate) fn update(&self, update: &PaymentDetailsUpdate) -> Result<bool, Error> {
149148
let mut updated = false;
150149
let mut locked_payments = self.payments.lock().unwrap();
@@ -237,7 +236,7 @@ mod tests {
237236
let payment_store = PaymentStore::new(Vec::new(), Arc::clone(&store), logger);
238237

239238
let hash = PaymentHash([42u8; 32]);
240-
assert!(!payment_store.contains(&hash));
239+
assert!(!payment_store.get(&hash).is_some());
241240

242241
let payment = PaymentDetails {
243242
hash,

src/test/functional_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ fn channel_full_cycle() {
100100

101101
println!("\nA send_payment");
102102
let payment_hash = node_a.send_payment(&invoice).unwrap();
103+
assert_eq!(node_a.send_payment(&invoice), Err(Error::DuplicatePayment));
103104

104105
let outbound_payments_a =
105106
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);

0 commit comments

Comments
 (0)