Skip to content

Commit d2d75a9

Browse files
authored
Merge pull request #96 from tnull/2023-05-list-all-payments
Only avoid duplicate payments if we didn't fail sending (and expose `list_payments`)
2 parents ab6c009 + ecd1b64 commit d2d75a9

File tree

6 files changed

+111
-60
lines changed

6 files changed

+111
-60
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ class LibraryTest {
181181
assert(paymentReceivedEvent is Event.PaymentReceived)
182182
node2.eventHandled()
183183

184+
assert(node1.listPayments().size == 1)
185+
assert(node2.listPayments().size == 1)
186+
184187
node2.closeChannel(channelId, nodeId1)
185188

186189
val channelClosedEvent1 = node1.waitNextEvent()
@@ -196,7 +199,7 @@ class LibraryTest {
196199
mine(1u)
197200

198201
// Sleep a bit to allow for the block to propagate to esplora
199-
Thread.sleep(3_000)
202+
Thread.sleep(5_000)
200203

201204
node1.syncWallets()
202205
node2.syncWallets()

bindings/ldk_node.udl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ interface LDKNode {
6767
PaymentDetails? payment([ByRef]PaymentHash payment_hash);
6868
[Throws=NodeError]
6969
boolean remove_payment([ByRef]PaymentHash payment_hash);
70+
sequence<PaymentDetails> list_payments();
7071
sequence<PeerDetails> list_peers();
7172
sequence<ChannelDetails> list_channels();
7273
[Throws=NodeError]
@@ -81,7 +82,7 @@ enum NodeError {
8182
"OnchainTxCreationFailed",
8283
"ConnectionFailed",
8384
"InvoiceCreationFailed",
84-
"PaymentFailed",
85+
"PaymentSendingFailed",
8586
"ChannelCreationFailed",
8687
"ChannelClosingFailed",
8788
"PersistenceFailed",
@@ -101,7 +102,7 @@ enum NodeError {
101102
"InvalidInvoice",
102103
"InvalidChannelId",
103104
"InvalidNetwork",
104-
"NonUniquePaymentHash",
105+
"DuplicatePayment",
105106
"InsufficientFunds",
106107
};
107108

@@ -122,6 +123,7 @@ enum PaymentDirection {
122123

123124
enum PaymentStatus {
124125
"Pending",
126+
"SendingFailed",
125127
"Succeeded",
126128
"Failed",
127129
};

src/error.rs

Lines changed: 8 additions & 6 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.
@@ -53,8 +53,8 @@ pub enum Error {
5353
InvalidChannelId,
5454
/// The given network is invalid.
5555
InvalidNetwork,
56-
/// Payment of the given invoice has already been intiated.
57-
NonUniquePaymentHash,
56+
/// A payment with the given hash has already been intiated.
57+
DuplicatePayment,
5858
/// There are insufficient funds to complete the given operation.
5959
InsufficientFunds,
6060
}
@@ -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."),
@@ -89,7 +89,9 @@ impl fmt::Display for Error {
8989
Self::InvalidInvoice => write!(f, "The given invoice is invalid."),
9090
Self::InvalidChannelId => write!(f, "The given channel ID is invalid."),
9191
Self::InvalidNetwork => write!(f, "The given network is invalid."),
92-
Self::NonUniquePaymentHash => write!(f, "An invoice must not get payed twice."),
92+
Self::DuplicatePayment => {
93+
write!(f, "A payment with the given hash has already been initiated.")
94+
}
9395
Self::InsufficientFunds => {
9496
write!(f, "There are insufficient funds to complete the given operation.")
9597
}

src/lib.rs

Lines changed: 76 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::NonUniquePaymentHash);
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::NonUniquePaymentHash);
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
}
@@ -1697,6 +1728,11 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
16971728
self.payment_store.list_filter(f)
16981729
}
16991730

1731+
/// Retrieves all payments.
1732+
pub fn list_payments(&self) -> Vec<PaymentDetails> {
1733+
self.payment_store.list_filter(|_| true)
1734+
}
1735+
17001736
/// Retrieves a list of known peers.
17011737
pub fn list_peers(&self) -> Vec<PeerDetails> {
17021738
let active_connected_peers: Vec<PublicKey> =

src/payment_store.rs

Lines changed: 8 additions & 9 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();
@@ -210,13 +209,13 @@ mod tests {
210209
use std::sync::Arc;
211210

212211
#[test]
213-
fn persistence_guard_persists_on_drop() {
212+
fn payment_info_is_persisted() {
214213
let store = Arc::new(TestStore::new());
215214
let logger = Arc::new(TestLogger::new());
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: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ 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));
108+
109+
assert_eq!(node_a.list_payments().first().unwrap().hash, payment_hash);
107110

108111
let outbound_payments_a =
109112
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
@@ -130,8 +133,14 @@ fn channel_full_cycle() {
130133
assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound);
131134
assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat));
132135

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

136145
// Test under-/overpayment
137146
let invoice_amount_2_msat = 1000_000;

0 commit comments

Comments
 (0)