Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add cancel_invoice to Bolt11Payment by benthecarman · Pull Request #846 · lightningdevkit/ldk-node · GitHub
Skip to content

Add cancel_invoice to Bolt11Payment - #846

Open
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice
Open

Add cancel_invoice to Bolt11Payment#846
benthecarman wants to merge 1 commit into
lightningdevkit:mainfrom
benthecarman:cancel-invoice

Conversation

@benthecarman

@benthecarmanbenthecarman commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Allow cancelling a previously created BOLT11 invoice by payment hash. Enterprise integrators need this when they short-circuit an invoice with an internal database transfer or when an alternative payment method is used (e.g., on-chain payment via unified URI).

The PaymentClaimable event handler rejects HTLCs for cancelled invoices only when the preimage is known (auto-claim payments), preserving retry behavior for manual-claim (_for_hash) payments.

fail_for_hash already did the logic we needed so cancel_invoice just does a little bit extra pre-validation and then calls it. Could make the argument to just use fail_for_hash, update its docs, and the if statement to reject payments mark as failed in the PaymentClaimable. The only functionality change would be that if someone retries to do a payment after a fail_for_hash, we don't let the user evaluate again.

@ldk-reviews-bot

ldk-reviews-bot commented Mar 25, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Allow canceling a previously created BOLT11 invoice by payment
hash. Enterprise integrators need this when they short-circuit an
invoice with an internal database transfer or when an alternative
payment method is used (e.g., on-chain payment via unified URI).
cancel_invoice validates the payment is inbound and unclaimed,
then delegates to fail_for_hash for the shared logic of marking
the payment as failed and failing back any pending HTLCs.
The PaymentClaimable event handler rejects HTLCs for cancelled
invoices only when the preimage is known (auto-claim payments),
preserving retry behavior for manual-claim (_for_hash) payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/payment/bolt11.rs
///
/// Will check that the payment is known and has not already been claimed, and will return an
/// error otherwise.
pub fn cancel_invoice(&self, payment_hash: PaymentHash) -> Result<(), Error> {

@tnulltnullMar 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I always found LND's terminology of 'cancelling an invoice' very confusing. In LDK Node it's even more so confusing as we don't store invoices (yet, cf. #811), so I really don't know what 'cancelling an invoice' is supposed to mean in this context.

As part of the move towards the payment metadata store we will also finally stop tracking expected inbound BOLT11 payments in the payment store (but then rather store the actual invoices/offers for the user), which means the logic in the event handler won't work as it does now. Let me see to finish #811 and #784 finally, and then we can decide how invoice 'cancellation' could make the most sense.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonally I do wonder if it would be best to actually do this properly in LDK, i.e., add a remove_payment method on ChannelManager that has it forget the preimage, simply?

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this whole concept is an anti-pattern. If someone wants to pay you extra money in LN you cannot stop them, there are a number of ways in which they can do it, and we're not gonna try to close them all. Further, storing invoices/offers before they're paid is an anti-pattern - creation is stateless for a reason, and tons of people are going to expose APIs where you can request an invoice be created, suddenly subjecting yourself to arbitrary DoS attacks filling your payment store. Unless you actually have to pay for it, free DoS is a really shitty API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is possible, invoice creation is stateless, its not storing the preimage.

Ah, lol, true.

IMO this whole concept is an anti-pattern.

+1 on this, though users seem to expect to have the functionality by now (see elsewhere). But probably we should simply push back on that.

Further, storing invoices/offers before they're paid is an anti-patter

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily. In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that in turn has been a big feature request by users for a long time now. And someone will need to store the invoice/offers so just handing the responsibility to the user doesn't actually solve the issue necessarily.

I'd like to better understand the use-cases here. For something like a merchant running an online store, their current architecture probably includes storing invoices/payment hashes in their payments/orders db already, so that when they receive a payment they can match it to the order. LDK Node (or any lightning node) could of course allow the downstream logic to store additional metadata in a payment store around an invoice, allowing the matching to be done by putting the order id in the lightning node, but you still have that order information elsewhere.

If we end up offering storage to downstream code in an invoice store, though, we should actually not store it at all and put it in the invoice payment-metadata. I think its now been more than long enough that that is likely reliably usable on the network, and it avoids storing entirely, at the cost of being rather data limited because it ends up in the QR code and onion. This only works for the "store only an order id" usecase, not full order info.

Are there other reasons why people want to store the full generated-invoice history?

In our case we also need to store some metadata based on the payment type (pre-negotiated LSP fees, but preimages could also count towards that). So we will add storage for it, but will attempt to make it optional (cf. #51).

If its small enough, this seems like a great use for payment metadata. We might want to have the payment secret commit to the (encrypted, but not-MAC'd) payment metadata, though...

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@benthecarman@ldk-reviews-bot@tnull@TheBlueMatt