Skip to content

Commit 64cf5f1

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 64cf5f1

File tree

5 files changed

+91
-53
lines changed

5 files changed

+91
-53
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: 71 additions & 40 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 be paid twice.");
1408+
return Err(Error::DuplicatePayment);
1409+
}
14081410
}
14091411

14101412
let payment_secret = Some(*invoice.payment_secret());
@@ -1437,18 +1439,24 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
14371439
}
14381440
Err(payment::PaymentError::Sending(e)) => {
14391441
log_error!(self.logger, "Failed to send payment: {:?}", e);
1440-
1441-
let payment = PaymentDetails {
1442-
preimage: None,
1443-
hash: payment_hash,
1444-
secret: payment_secret,
1445-
amount_msat: invoice.amount_milli_satoshis(),
1446-
direction: PaymentDirection::Outbound,
1447-
status: PaymentStatus::Failed,
1448-
};
1449-
self.payment_store.insert(payment)?;
1450-
1451-
Err(Error::PaymentFailed)
1442+
match e {
1443+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1444+
Err(Error::DuplicatePayment)
1445+
}
1446+
_ => {
1447+
let payment = PaymentDetails {
1448+
preimage: None,
1449+
hash: payment_hash,
1450+
secret: payment_secret,
1451+
amount_msat: invoice.amount_milli_satoshis(),
1452+
direction: PaymentDirection::Outbound,
1453+
status: PaymentStatus::SendingFailed,
1454+
};
1455+
1456+
self.payment_store.insert(payment)?;
1457+
Err(Error::PaymentSendingFailed)
1458+
}
1459+
}
14521460
}
14531461
}
14541462
}
@@ -1477,9 +1485,11 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
14771485
}
14781486

14791487
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);
1488+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1489+
if payment.status != PaymentStatus::SendingFailed {
1490+
log_error!(self.logger, "Payment error: an invoice must not be paid twice.");
1491+
return Err(Error::DuplicatePayment);
1492+
}
14831493
}
14841494

14851495
let payment_id = PaymentId(invoice.payment_hash().into_inner());
@@ -1532,17 +1542,24 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15321542
Err(payment::PaymentError::Sending(e)) => {
15331543
log_error!(self.logger, "Failed to send payment: {:?}", e);
15341544

1535-
let payment = PaymentDetails {
1536-
hash: payment_hash,
1537-
preimage: None,
1538-
secret: payment_secret,
1539-
amount_msat: Some(amount_msat),
1540-
direction: PaymentDirection::Outbound,
1541-
status: PaymentStatus::Failed,
1542-
};
1543-
self.payment_store.insert(payment)?;
1544-
1545-
Err(Error::PaymentFailed)
1545+
match e {
1546+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1547+
Err(Error::DuplicatePayment)
1548+
}
1549+
_ => {
1550+
let payment = PaymentDetails {
1551+
hash: payment_hash,
1552+
preimage: None,
1553+
secret: payment_secret,
1554+
amount_msat: Some(amount_msat),
1555+
direction: PaymentDirection::Outbound,
1556+
status: PaymentStatus::SendingFailed,
1557+
};
1558+
self.payment_store.insert(payment)?;
1559+
1560+
Err(Error::PaymentSendingFailed)
1561+
}
1562+
}
15461563
}
15471564
}
15481565
}
@@ -1559,6 +1576,13 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15591576
let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes());
15601577
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
15611578

1579+
if let Some(payment) = self.payment_store.get(&payment_hash) {
1580+
if payment.status != PaymentStatus::SendingFailed {
1581+
log_error!(self.logger, "Payment error: must not send duplicate payments.");
1582+
return Err(Error::DuplicatePayment);
1583+
}
1584+
}
1585+
15621586
let route_params = RouteParameters {
15631587
payment_params: PaymentParameters::from_node_id(
15641588
node_id,
@@ -1593,17 +1617,24 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15931617
Err(e) => {
15941618
log_error!(self.logger, "Failed to send payment: {:?}", e);
15951619

1596-
let payment = PaymentDetails {
1597-
hash: payment_hash,
1598-
preimage: Some(payment_preimage),
1599-
secret: None,
1600-
status: PaymentStatus::Failed,
1601-
direction: PaymentDirection::Outbound,
1602-
amount_msat: Some(amount_msat),
1603-
};
1604-
self.payment_store.insert(payment)?;
1605-
1606-
Err(Error::PaymentFailed)
1620+
match e {
1621+
channelmanager::RetryableSendFailure::DuplicatePayment => {
1622+
Err(Error::DuplicatePayment)
1623+
}
1624+
_ => {
1625+
let payment = PaymentDetails {
1626+
hash: payment_hash,
1627+
preimage: Some(payment_preimage),
1628+
secret: None,
1629+
status: PaymentStatus::SendingFailed,
1630+
direction: PaymentDirection::Outbound,
1631+
amount_msat: Some(amount_msat),
1632+
};
1633+
1634+
self.payment_store.insert(payment)?;
1635+
Err(Error::PaymentSendingFailed)
1636+
}
1637+
}
16071638
}
16081639
}
16091640
}

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: 8 additions & 1 deletion
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);
@@ -130,8 +131,14 @@ fn channel_full_cycle() {
130131
assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound);
131132
assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat));
132133

133-
// Assert we fail duplicate outbound payments.
134+
// Assert we fail duplicate outbound payments and check the status hasn't changed.
134135
assert_eq!(Err(Error::DuplicatePayment), node_a.send_payment(&invoice));
136+
assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded);
137+
assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound);
138+
assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat));
139+
assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded);
140+
assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound);
141+
assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat));
135142

136143
// Test under-/overpayment
137144
let invoice_amount_2_msat = 1000_000;

0 commit comments

Comments
 (0)