Skip to content

Commit 9c271d7

Browse files
committed
Fix BOLT11 DuplicatePayment triggering on-chain fallback in unified payments
UnifiedPayment::send previously treated any error from the BOLT11 leg of a unified payment as non-terminal and fell through to the on-chain payment method. This meant a retried BOLT11 payment that returns Error::DuplicatePayment would still result in an on-chain transaction being broadcast for the same invoice — a duplicate payment. Error::DuplicatePayment is now terminal in UnifiedPayment::send: the unified payment aborts instead of falling back to on-chain. Fixes #1033. unified_send_receive_bip21_uri already funds two nodes, opens a channel, and sends a successful BOLT11 payment via uri_str_without_offer partway through. Add the regression assertion right there — retry the same uri_str_without_offer and assert DuplicatePayment, not a new on-chain payment — instead of duplicating that setup in a standalone test.
1 parent 0428cab commit 9c271d7

2 files changed

Lines changed: 45 additions & 9 deletions

File tree

src/payment/unified.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,22 @@ impl UnifiedPayment {
291291

292292
let payment_result = if let Ok(hrn) = HumanReadableName::from_encoded(uri_str) {
293293
let hrn = maybe_wrap(hrn.clone());
294-
self.bolt12_payment.send_using_amount_inner(&offer, amount_msat.unwrap_or(0), None, None, route_parameters, Some(hrn))
294+
self.bolt12_payment.send_using_amount_inner(
295+
&offer,
296+
amount_msat.unwrap_or(0),
297+
None,
298+
None,
299+
route_parameters,
300+
Some(hrn),
301+
)
295302
} else if let Some(amount_msat) = amount_msat {
296-
self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None, route_parameters)
303+
self.bolt12_payment.send_using_amount(
304+
&offer,
305+
amount_msat,
306+
None,
307+
None,
308+
route_parameters,
309+
)
297310
} else {
298311
self.bolt12_payment.send(&offer, None, None, route_parameters)
299312
}
@@ -308,14 +321,21 @@ impl UnifiedPayment {
308321
},
309322
PaymentMethod::LightningBolt11(invoice) => {
310323
let invoice = maybe_wrap(invoice.clone());
311-
let payment_result = self.bolt11_invoice.send(&invoice, route_parameters)
312-
.map_err(|e| {
324+
let payment_result = self.bolt11_invoice.send(&invoice, route_parameters);
325+
326+
match payment_result {
327+
Ok(payment_id) => {
328+
return Ok(UnifiedPaymentResult::Bolt11 { payment_id });
329+
},
330+
// A duplicate payment already exists, so falling back to the
331+
// on-chain method would pay the same invoice a second time.
332+
Err(Error::DuplicatePayment) => {
333+
log_error!(self.logger, "Failed to send BOLT11 invoice: DuplicatePayment. This is part of a unified payment. Aborting to avoid duplicate payment.");
334+
return Err(Error::DuplicatePayment);
335+
},
336+
Err(e) => {
313337
log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e);
314-
e
315-
});
316-
317-
if let Ok(payment_id) = payment_result {
318-
return Ok(UnifiedPaymentResult::Bolt11 { payment_id });
338+
},
319339
}
320340
},
321341
PaymentMethod::OnChain(address) => {

tests/integration_tests_rust.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3395,6 +3395,22 @@ async fn unified_send_receive_bip21_uri() {
33953395
};
33963396
expect_payment_successful_event!(node_a, invoice_payment_id, None);
33973397

3398+
// Regression test for https://github.com/lightningdevkit/ldk-node/issues/1033: retrying
3399+
// the same BOLT11 invoice must return DuplicatePayment, not fall back to on-chain.
3400+
let duplicate_result = node_a.unified_payment().send(uri_str_without_offer, None, None).await;
3401+
match duplicate_result {
3402+
Err(NodeError::DuplicatePayment) => {
3403+
// Expected — this is the fix for #1033.
3404+
},
3405+
Ok(UnifiedPaymentResult::Onchain { txid }) => {
3406+
panic!(
3407+
"Regression: duplicate BOLT11 payment fell back to on-chain. txid={}. See #1033",
3408+
txid
3409+
);
3410+
},
3411+
other => panic!("Expected DuplicatePayment error on retry, got: {:?}", other),
3412+
}
3413+
33983414
let expect_onchain_amount_sats = 800_000;
33993415
let onchain_uni_payment =
34003416
node_b.unified_payment().receive(expect_onchain_amount_sats, "asdf", 4_000).unwrap();

0 commit comments

Comments
 (0)