Skip to content

Commit be7cb4f

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 92dffeb commit be7cb4f

File tree

5 files changed

+51
-24
lines changed

5 files changed

+51
-24
lines changed

bindings/ldk_node.udl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ enum NodeError {
8181
"OnchainTxCreationFailed",
8282
"ConnectionFailed",
8383
"InvoiceCreationFailed",
84-
"PaymentFailed",
84+
"PaymentSendingFailed",
8585
"ChannelCreationFailed",
8686
"ChannelClosingFailed",
8787
"PersistenceFailed",
@@ -122,6 +122,7 @@ enum PaymentDirection {
122122

123123
enum PaymentStatus {
124124
"Pending",
125+
"SendingFailed",
125126
"Succeeded",
126127
"Failed",
127128
};

src/error.rs

Lines changed: 3 additions & 3 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.
@@ -69,7 +69,7 @@ impl fmt::Display for Error {
6969
}
7070
Self::ConnectionFailed => write!(f, "Network connection closed."),
7171
Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."),
72-
Self::PaymentFailed => write!(f, "Failed to send the given payment."),
72+
Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."),
7373
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
7474
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
7575
Self::PersistenceFailed => write!(f, "Failed to persist data."),

src/lib.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,9 +1402,11 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
14021402

14031403
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
14041404

1405-
if self.payment_store.contains(&payment_hash) {
1406-
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1407-
return Err(Error::DuplicatePayment);
1405+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1406+
if payment.status != PaymentStatus::SendingFailed {
1407+
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1408+
return Err(Error::DuplicatePayment);
1409+
}
14081410
}
14091411

14101412
let payment_secret = Some(*invoice.payment_secret());
@@ -1444,11 +1446,16 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
14441446
secret: payment_secret,
14451447
amount_msat: invoice.amount_milli_satoshis(),
14461448
direction: PaymentDirection::Outbound,
1447-
status: PaymentStatus::Failed,
1449+
status: PaymentStatus::SendingFailed,
14481450
};
14491451
self.payment_store.insert(payment)?;
14501452

1451-
Err(Error::PaymentFailed)
1453+
match e {
1454+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1455+
Err(Error::DuplicatePayment)
1456+
}
1457+
_ => Err(Error::PaymentSendingFailed),
1458+
}
14521459
}
14531460
}
14541461
}
@@ -1477,9 +1484,11 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
14771484
}
14781485

14791486
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
1480-
if self.payment_store.contains(&payment_hash) {
1481-
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1482-
return Err(Error::DuplicatePayment);
1487+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1488+
if payment.status != PaymentStatus::SendingFailed {
1489+
log_error!(self.logger, "Payment error: an invoice must not get paid twice.");
1490+
return Err(Error::DuplicatePayment);
1491+
}
14831492
}
14841493

14851494
let payment_id = PaymentId(invoice.payment_hash().into_inner());
@@ -1538,11 +1547,16 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15381547
secret: payment_secret,
15391548
amount_msat: Some(amount_msat),
15401549
direction: PaymentDirection::Outbound,
1541-
status: PaymentStatus::Failed,
1550+
status: PaymentStatus::SendingFailed,
15421551
};
15431552
self.payment_store.insert(payment)?;
15441553

1545-
Err(Error::PaymentFailed)
1554+
match e {
1555+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1556+
Err(Error::DuplicatePayment)
1557+
}
1558+
_ => Err(Error::PaymentSendingFailed),
1559+
}
15461560
}
15471561
}
15481562
}
@@ -1559,6 +1573,13 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15591573
let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes());
15601574
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
15611575

1576+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1577+
if payment.status != PaymentStatus::SendingFailed {
1578+
log_error!(self.logger, "Payment error: must not send duplicate payments.");
1579+
return Err(Error::DuplicatePayment);
1580+
}
1581+
}
1582+
15621583
let route_params = RouteParameters {
15631584
payment_params: PaymentParameters::from_node_id(
15641585
node_id,
@@ -1597,13 +1618,18 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15971618
hash: payment_hash,
15981619
preimage: Some(payment_preimage),
15991620
secret: None,
1600-
status: PaymentStatus::Failed,
1621+
status: PaymentStatus::SendingFailed,
16011622
direction: PaymentDirection::Outbound,
16021623
amount_msat: Some(amount_msat),
16031624
};
16041625
self.payment_store.insert(payment)?;
16051626

1606-
Err(Error::PaymentFailed)
1627+
match e {
1628+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1629+
Err(Error::DuplicatePayment)
1630+
}
1631+
_ => Err(Error::PaymentSendingFailed),
1632+
}
16071633
}
16081634
}
16091635
}

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)]
@@ -139,10 +142,6 @@ where
139142
self.payments.lock().unwrap().get(hash).cloned()
140143
}
141144

142-
pub(crate) fn contains(&self, hash: &PaymentHash) -> bool {
143-
self.payments.lock().unwrap().contains_key(hash)
144-
}
145-
146145
pub(crate) fn update(&self, update: &PaymentDetailsUpdate) -> Result<bool, Error> {
147146
let mut updated = false;
148147
let mut locked_payments = self.payments.lock().unwrap();
@@ -216,7 +215,7 @@ mod tests {
216215
let payment_store = PaymentStore::new(Vec::new(), Arc::clone(&store), logger);
217216

218217
let hash = PaymentHash([42u8; 32]);
219-
assert!(!payment_store.contains(&hash));
218+
assert!(!payment_store.get(&hash).is_some());
220219

221220
let payment = PaymentDetails {
222221
hash,

src/test/functional_tests.rs

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

105105
println!("\nA send_payment");
106106
let payment_hash = node_a.send_payment(&invoice).unwrap();
107+
assert_eq!(node_a.send_payment(&invoice), Err(Error::DuplicatePayment));
107108

108109
let outbound_payments_a =
109110
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);

0 commit comments

Comments
 (0)