Skip to content

Provide Bolt12Invoice used for inbound payment #2929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Mar 12, 2024

Bolt12Invoices are generated internally by ChannelManager, so the payment recipient never sees a payment_hash until a PaymentClaimable is handled. This prevents tracking any pending inbound payments. Expose the Bolt12Invoice in a new InvoiceGenerated event when responding to an InvoiceRequest for an Offer. Similarly, return the invoice from ChannelManager::request_refund_payment.

For the Offer case, the offer_metadata returned by Bolt12Invoice::metadata may be used as an offer id, if desired, since it is a randomly generated 16-byte nonce for ChannelManager-originating offers. Also, a common Bolt12Invoice::payer_id may indicated redundant invoice requests.

For the Refund case, the metadata will be empty, but the payment_hash may be used as an identifier.

jkczyz added 4 commits March 11, 2024 17:02
In order to generate and InvoiceSent event, it would be cleaner to have
one location where a Bolt12Invoice is successfully generated. Refactor
the handling code to this end and clean-up line length by making some of
the type conversions more streamlined.
In order to provide an InvoiceGenerated event, it would be cleaner to
have one location where a Bolt12Invoice is successfully created.
Refactor the handling code to this end and clean-up line length by
making some of the type conversions more streamlined.
Bolt12Invoice will be added to a new Event::InvoiceGenerated variant.
These traits along with PartialEq are required to be implemented for any
type used in an Event.
When responding to an InvoiceRequest for an Offer, provide an event when
an invoice has been generated. This allows event handler to know when an
inbound payment may occur along with information from the invoice such
as metadata, payer_id, and payment_hash.
@jkczyz jkczyz requested a review from tnull March 12, 2024 00:18
@codecov-commenter
Copy link

codecov-commenter commented Mar 12, 2024

Codecov Report

Attention: Patch coverage is 77.31959% with 22 lines in your changes are missing coverage. Please review.

Project coverage is 89.30%. Comparing base (670b41a) to head (35c408f).
Report is 15 commits behind head on main.

Files Patch % Lines
lightning/src/offers/invoice_error.rs 0.00% 9 Missing ⚠️
lightning/src/events/mod.rs 57.14% 3 Missing and 3 partials ⚠️
lightning/src/ln/channelmanager.rs 93.02% 2 Missing and 1 partial ⚠️
lightning/src/offers/invoice.rs 62.50% 3 Missing ⚠️
lightning/src/ln/offers_tests.rs 95.65% 1 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2929      +/-   ##
==========================================
+ Coverage   89.11%   89.30%   +0.19%     
==========================================
  Files         117      117              
  Lines       95029    95929     +900     
  Branches    95029    95929     +900     
==========================================
+ Hits        84685    85672     +987     
+ Misses       7856     7783      -73     
+ Partials     2488     2474      -14     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

When sending an invoice for a refund, information from the invoice may
be useful for caller. For instance, the payment_hash can be used to
track whether the refund was paid. Return the invoice to facilitate this
use case.
@jkczyz jkczyz force-pushed the 2024-03-offers-events branch from fc9fe8a to 35c408f Compare March 12, 2024 03:25
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Looks good on first pass.

Two questions, and will now try it out in the LDK Node integration.

///
/// Thus, this event is largely for informational purposes as the corresponding [`Offer`] and
/// [`InvoiceRequest`] fields are accessible from the invoice. In particular:
/// - [`Bolt12Invoice::metadata`] can help identify the corresponding [`Offer`]
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make it a bit more explicit that this is the only way users can associate incoming payments with a previously-generated offer? I.e., the only way to know what was actually paid for is to handle this event and keep track of all sent-out payment hashes to be able to associate them with the offer based on metadata.

While this is probably fine for now, I really wonder if we eventually should take care of the tracking in LDK (or offer a utility for it at least), as every node receiving payments will want to know what a payment is for.

@@ -1668,6 +1708,18 @@ impl MaybeReadable for Event {
},
// Note that we do not write a length-prefixed TLV for ConnectionNeeded events.
35u8 => Ok(None),
37u8 => {
let f = || {
let mut invoice_bytes = WithoutLength(Vec::new());
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason we can't just implement Readable for Bolt12Invoice? Seems avoiding it forces users (namely LDK Node, heh.) to write the same custom deserializaiton code. Same goes for Offer, Refund, and Bolt11Invoice, fwiw.

Copy link
Contributor

Choose a reason for hiding this comment

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

serde would be great too

///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
invoice: Bolt12Invoice,
},
Copy link
Contributor

@tnull tnull Mar 12, 2024

Choose a reason for hiding this comment

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

I wonder if we should provide the preimage/payment secret also while we're here?

@TheBlueMatt
Copy link
Collaborator

I'm not quite clear on why we need to expose this when the Invoice is generated (I mean, I guess we should, as an informational and debug thing, but we historically have stayed away from informational Events). Worse, this Event is generated on any onion message, which could make it a pretty heavy DoS target (doubly so if users actually store this). We do need to expose the Bolt12Invoice, though, but the user really wants to tie it to the payment, so presumably we should store it somehow (with a small lifetime?) internally and expose it with PaymentReceived?

@TheBlueMatt
Copy link
Collaborator

TheBlueMatt commented Mar 12, 2024

I actually wonder if we should store the whole (or maybe select fields of the) invoice_request in the blinded path we give to the sender. That way it would be stateless and we could get the invoice back on payment. It'd bloat the blinded path, but in some cases that may be okay.

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 12, 2024

I'm not quite clear on why we need to expose this when the Invoice is generated (I mean, I guess we should, as an informational and debug thing, but we historically have stayed away from informational Events).

The use case is for LDK Node showing a pending payment. In BOLT 11, we do this when creating the invoice. In BOLT 12, when creating an offer (1) we don't have a payment hash and (2) there could be many invoices corresponding to a single offer.

Worse, this Event is generated on any onion message, which could make it a pretty heavy DoS target (doubly so if users actually store this). We do need to expose the Bolt12Invoice, though, but the user really wants to tie it to the payment, so presumably we should store it somehow (with a small lifetime?) internally and expose it with PaymentReceived?

Yeah, I considered there may be a DoS risk, but we already rate limit outbound onion messages. This is worth discussing though. I suppose an adversary could send many invoice requests, each with a different introduction node in the reply path. But we'd still hit our overall buffer limit.

Regarding lifetime, potentially some combination of the offer and invoice expirations could be used to expire pending payments by LDK Node.

PaymentClaimable (formerly PaymentReceived) is an option, but this doesn't solve the payment tracking use case. Or at least doesn't provide the same level of tracking as BOLT 11 as the payment would be an immediate success / failure without an intermediary pending state (@tnull may better articulate the need here). And this would require storing state for inbound payments (though see comments below on statelessness). Doesn't that just move the DoS concern elsewhere?

I actually wonder if we should store the whole (or maybe select fields of the) invoice_request in the blinded path we give to the sender.

Do you mean the invoice? For example, we'd probably want the payment hash. Possibly fields from the offer and invoice request, too, I suppose.

That way it would be stateless and we could get the invoice back on payment. It'd bloat the blinded path, but in some cases that may be okay.

Not sure I understand what you mean by "we could get the invoice back on payment". We can't put the invoice in the blinded path because the blinded path is part of the invoice. Or are you saying to elide those? Note also there may be more than one in the invoice. Or did you really mean just the invoice request earlier?

@TheBlueMatt
Copy link
Collaborator

The use case is for LDK Node showing a pending payment.

I'm not sure how to do this without turning it into a trivial DoS vector, so I'm not entirely convinced we should do this (maybe unless we receive a single HTLC of a potentially-MPP payment?

Yeah, I considered there may be a DoS risk, but we already rate limit outbound onion messages. This is worth discussing though. I suppose an adversary could send many invoice requests, each with a different introduction node in the reply path. But we'd still hit our overall buffer limit.

We rate-limit outbound onion messages to line rate (unless the user rate-limits their NIC in the OS), which is potentially a lot of onion messages. I don't think we should rely on that for pending-payments-list DoS resistance (especially if that list is stored on disk).

Regarding lifetime, potentially some combination of the offer and invoice expirations could be used to expire pending payments by LDK Node.

I don't think this is sufficient. Imagine a line-rate of 1Gbps (hosted node or someone with fiber in their home, which is increasingly common), assuming the pending payments are roughly the same size as an invoice_request (or contain the invoice_request), that means someone can fill 1Gbps * the timeout, which lets say is 1 hour, means someone can put 450 GB of crap on our disk (and burn out our SSD's write lifetime in a month or two).

PaymentClaimable (formerly PaymentReceived) is an option, but this doesn't solve the payment tracking use case. Or at least doesn't provide the same level of tracking as BOLT 11 as the payment would be an immediate success / failure without an intermediary pending state (@tnull may better articulate the need here).

Yea, indeed, you can't see when someone may have initiated a payment to your offer, but I'm not sure we can do a lot better without being a big DoS risk. The other potential approach we could do here is some kind of "tracked offer", ie create an offer which you only intend to receive payment to once, then track whether that offer gets paid (sure, it could get paid twice, but someone can always overpay us).

Not sure I understand what you mean by "we could get the invoice back on payment". We can't put the invoice in the blinded path because the blinded path is part of the invoice. Or are you saying to elide those? Note also there may be more than one in the invoice. Or did you really mean just the invoice request earlier?

Yes, I meant to elide the self-referential parts. But we could also just store the invreq if that makes sense.

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 12, 2024

I'm not sure how to do this without turning it into a trivial DoS vector, so I'm not entirely convinced we should do this (maybe unless we receive a single HTLC of a potentially-MPP payment?

Seems that would prevent having stateless inbound payments.

We rate-limit outbound onion messages to line rate (unless the user rate-limits their NIC in the OS), which is potentially a lot of onion messages. I don't think we should rely on that for pending-payments-list DoS resistance (especially if that list is stored on disk).

I don't think this is sufficient. Imagine a line-rate of 1Gbps (hosted node or someone with fiber in their home, which is increasingly common), assuming the pending payments are roughly the same size as an invoice_request (or contain the invoice_request), that means someone can fill 1Gbps * the timeout, which lets say is 1 hour, means someone can put 450 GB of crap on our disk (and burn out our SSD's write lifetime in a month or two).

Yeah, those are fair points.

Yea, indeed, you can't see when someone may have initiated a payment to your offer, but I'm not sure we can do a lot better without being a big DoS risk. The other potential approach we could do here is some kind of "tracked offer", ie create an offer which you only intend to receive payment to once, then track whether that offer gets paid (sure, it could get paid twice, but someone can always overpay us).

In this approach, would we provide an InvoiceGenerate event for each request and leave it up to the consumer (e.g., LDK Node in this case) to make that determination? Or would we rather not produce those events in the first place? Only the former allows for a pending payment state.

We could potentially not serialize those events, FWIW. Is it probable that if the event hasn't been processed yet then the onion message may not have been sent either (and therefore would be dropped on restart)?

Yes, I meant to elide the self-referential parts. But we could also just store the invreq if that makes sense.

Gotcha. It does seem like a lot of data to place in each blinded payment path, potentially blowing up the onion message beyond its limit. Maybe a subset of fields would be sufficient for this and potentially other use cases? Seems like for async payments it is only applicable for the offline case where a static invoice would be used. Therefore, this wouldn't be helpful there.

So I think this comes down to whether we want to support pending inbound payment tracking in LDK node (in which case we need the event) or only success/failure tracking by adding some data to the invoice's blinded paths such that it can be included in PaymentClaimable.

@TheBlueMatt
Copy link
Collaborator

Seems that would prevent having stateless inbound payments.

It may require some additional in-LDK tracking, not sure. Its certainly not my preferred approach.

There could be two separate APIs depending on if state tracking is desired, but that complexifies the API somewhat. Ultimately if we're worried about the cost of any state as being too high then BOLT 11 is also problematic because the ldk-node API tracks state per invoice (I believe?).

In this approach, would we provide an InvoiceGenerate event for each request and leave it up to the consumer (e.g., LDK Node in this case) to make that determination? Or would we rather not produce those events in the first place? Only the former allows for a pending payment state.

I just assume not generate any events (we'd just track a payment as "pending" once the offer is generated, the same as we do for BOLT 11 today), but we could if we want.

I'm not quite sure I understand the desire to map "invoice requested" to "pending" vs "offer generated". Certainly "invoice requested" is a better "payment is pending", but if you're a merchant expecting to receive payment, "offer is generated" is also "payment pending". More generally, if we're looking to keep a similar API for BOLT 11 and BOLT 12 (which I think we should!), "offer is generated" seems like the clear equivalent to "BOLT11 invoice generated".

We could potentially not serialize those events, FWIW. Is it probable that if the event hasn't been processed yet then the onion message may not have been sent either (and therefore would be dropped on restart)?

True, but I'm just worried that we start generating a ton of these events and the "obvious" way to handle them is to store them and suddenly we have a big DoS problem.

Gotcha. It does seem like a lot of data to place in each blinded payment path, potentially blowing up the onion message beyond its limit. Maybe a subset of fields would be sufficient for this and potentially other use cases? Seems like for async payments it is only applicable for the offline case where a static invoice would be used. Therefore, this wouldn't be helpful there.

Indeed, maybe we only store the payer_id and the payer_message?

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 13, 2024

There could be two separate APIs depending on if state tracking is desired, but that complexifies the API somewhat. Ultimately if we're worried about the cost of any state as being too high then BOLT 11 is also problematic because the ldk-node API tracks state per invoice (I believe?).

Would we store the data in ChannelManager? And where after we no longer persist ChannelManager? I suppose the same question needs to be answered for outbound payments. More wondering out loud as I also would prefer not to make the API more complex.

I just assume not generate any events (we'd just track a payment as "pending" once the offer is generated, the same as we do for BOLT 11 today), but we could if we want.

I'm not quite sure I understand the desire to map "invoice requested" to "pending" vs "offer generated". Certainly "invoice requested" is a better "payment is pending", but if you're a merchant expecting to receive payment, "offer is generated" is also "payment pending". More generally, if we're looking to keep a similar API for BOLT 11 and BOLT 12 (which I think we should!), "offer is generated" seems like the clear equivalent to "BOLT11 invoice generated".

Will let @tnull comment, but I'm fine with taking this approach instead. Then if you do want to track multiple payments per offer, you still can only upon receiving the payment.

Indeed, maybe we only store the payer_id and the payer_message?

We'll need the offer's metadata, too, if we want to correlated the payment with an offer. But seems like this is all pretty straight-forward.

@TheBlueMatt
Copy link
Collaborator

Would we store the data in ChannelManager? And where after we no longer persist ChannelManager? I suppose the same question needs to be answered for outbound payments. More wondering out loud as I also would prefer not to make the API more complex.

No, I was assuming in ldk-node. I don't think LDK should become stateless around invoices at all.

@tnull
Copy link
Contributor

tnull commented Mar 13, 2024

Sorry for the delay here, currently a bit busy due to the hackathon, hence also keeping it brief here.

So just a few brief notes on the API here, will possibly add more eventually:

  • I think the main part we're missing on the receiving API is a way for the user to learn what offer an incoming payment is for. Preferably, we get the full offer and invoice fields back on PaymentClaimable and PaymentClaimed. Otherwise we also have no way for the user to actually access/see the payer_note on the receiving end, for example.
  • The capability for 'pending payment tracking' would be a nice-to-have but not super necessary. I think we need to consider the 1:N relationship between offer and invoice/payment here though, so just marking a single payment as pending (with a single derived payment id) on offer creation is not an option, IMO.
  • On the sender side we don't expose any way to retrieve the used payment secret.

@TheBlueMatt
Copy link
Collaborator

I think the main part we're missing on the receiving API is a way for the user to learn what offer an incoming payment is for. Preferably, we get the full offer and invoice fields back on PaymentClaimable and PaymentClaimed. Otherwise we also have no way for the user to actually access/see the payer_note on the receiving end, for example.

ISTM the only way we an realistically achieve this without substantial DoS risk is to re-encode these in the blinded path.

The capability for 'pending payment tracking' would be a nice-to-have but not super necessary. I think we need to consider the 1:N relationship between offer and invoice/payment here though, so just marking a single payment as pending (with a single derived payment id) on offer creation is not an option, IMO.

I think it depends a bit on the use-case for payment tracking. If the point of payment tracking is for an store that needs to track when a payment completes, the 1:N relationship doesn't matter, they can that its been paid > 0 times. For those who do care about how many times an offer has been paid, they don't care too much about whether an invoice has been paid, only that the offer they posted as their public donation address has received N payments for M sats (maybe with a list of payer notes?). In any case, all of these can be accomplished by tracking payments on an offer -> list of payments basis, I don't think they require tracking on an invoice level.

IOW we could do tracking of offers issued in LDK Node and re-encode payer information in the blinded path + PaymentClaimable event and I think it would suffice for all the use-cases I can come up with?

On the sender side we don't expose any way to retrieve the used payment secret.

Do you mean the payment preimage? There is no payment secret anymore with BOLT 12. We should definitely give the payer the invoice we're paying, IMO - unlike the DoS issues with an event when issuing an invoice, I don't think there's any providing the invoice when paying it (its always one extra event per user action).

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 13, 2024

  • I think the main part we're missing on the receiving API is a way for the user to learn what offer an incoming payment is for. Preferably, we get the full offer and invoice fields back on PaymentClaimable and PaymentClaimed. Otherwise we also have no way for the user to actually access/see the payer_note on the receiving end, for example.

If LDK Node stores the offer upon creation keyed by its metadata (let's call it an offer id), then for a given payment we can look up the offer from storage and also store the payment with offer id as part of a hierarchical key. Then we just need fields from the invoice request. We should limit which of these we store in the invoice's blinded payment paths for size constraints. Otherwise, storing, say, a long payer note or large payer metadata in each of the invoice's blinded payment paths would prevent constructing an onion message for the invoice, IIUC.

@tnull
Copy link
Contributor

tnull commented Mar 14, 2024

I think it depends a bit on the use-case for payment tracking. If the point of payment tracking is for an store that needs to track when a payment completes, the 1:N relationship doesn't matter, they can that its been paid > 0 times.

I disagree. If a coffee shop generates an offer say, e.g., for each specific type of coffee they sell, I might pay this single offer every morning. They payee needs to be able to a) track each individual payment with a unique payment ID and b) know which invoice the payment was for and c) retrieve any data fields associated with the offer or invoice such as description (stored in the offer), payer note (sent via the invoice request, mirrored in the invoice), etc.

IOW we could do tracking of offers issued in LDK Node and re-encode payer information in the blinded path + PaymentClaimable event and I think it would suffice for all the use-cases I can come up with?

Sure, due to the 1:N relationship of offer:payment LDK Node will likely need to store offers in a separate structure and only associate payments by some kind of unique offer id. However, even if we assume payees store the generated offers, we need to expose all relevant fields from invoices/invoice requests and data that would allow us to associate payments with offers in PaymentClaimable and PaymentClaimed. Given that all users will need to do this I'd prefer if we could introduce a dedicated OfferId type and provide certain API guarantees for it (e.g., uniqueness).

Do you mean the payment preimage?

Yes, also the preimage, although we get that at least as part of PaymentSent, but providing all relevant fields earlier in the flow would be preferable.

There is no payment secret anymore with BOLT 12.

Ugh, hum, good point, but what value is then returned on the receiver side in PaymentClaimed::purpose's InvoicePayment? (i.e., if there is no secret, why doesn't this check fail: https://github.com/tnull/ldk-node/blob/a9fd39625d559ac753c243b1791927ea1aa4a365/tests/integration_tests_rust.rs#L432)

@tnull
Copy link
Contributor

tnull commented Mar 14, 2024

If LDK Node stores the offer upon creation keyed by its metadata (let's call it an offer id), then for a given payment we can look up the offer from storage and also store the payment with offer id as part of a hierarchical key.

If we think this is the only way we allow users to associate offers, we should commit to it by creating an OfferId type with clearly defined guarantees and provide in all relevant Payment* events, preferably also on the sending side.

Also, would using payer_metadata work to derive a similar identifier for Refunds? IIUC, it is derived from payer_id, which might/should be single-use, but there is no guarantee it is? So we might run into collisions if the payer reuses payer_ids?

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 14, 2024

I disagree. If a coffee shop generates an offer say, e.g., for each specific type of coffee they sell, I might pay this single offer every morning. They payee needs to be able to a) track each individual payment with a unique payment ID and b) know which invoice the payment was for and c) retrieve any data fields associated with the offer or invoice such as description (stored in the offer), payer note (sent via the invoice request, mirrored in the invoice), etc.

I think in practice you wouldn't see this type of use. Instead, you'd generate an offer per customer order. Otherwise, if the customer wants to purchase two different coffees (or a coffee and a pastry), they would need to pay two invoices. Seems this would get impractical as the number of items increase.

Somewhat related, there was some discussion on the spec PR around quantity being more of an application layer concern and can't really represent the myriad possibilities (lightning/bolts#798 (comment)). But not much came of it. I seem to recall Rusty once saying there could be an extension to the BOLT to represent multiple items.

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 14, 2024

Ugh, hum, good point, but what value is then returned on the receiver side in PaymentClaimed::purpose's InvoicePayment? (i.e., if there is no secret, why doesn't this check fail: https://github.com/tnull/ldk-node/blob/a9fd39625d559ac753c243b1791927ea1aa4a365/tests/integration_tests_rust.rs#L432)

IIUC, it's not directly in the invoice, but we include one in the blinded paths so we can use it for stateless inbound payments in the same way we do for BOLT 11.

@tnull
Copy link
Contributor

tnull commented Mar 14, 2024

I think in practice you wouldn't see this type of use. Instead, you'd generate an offer per customer order. Otherwise, if the customer wants to purchase two different coffees (or a coffee and a pastry), they would need to pay two invoices. Seems this would get impractical as the number of items increase.

Hm, I mean it could be used this way though and even if it's only happening rarely we still need to support it?

Somewhat related, there was some discussion on the spec PR around quantity being more of an application layer concern and can't really represent the myriad possibilities (lightning/bolts#798 (comment)). But not much came of it. I seem to recall Rusty once saying there could be an extension to the BOLT to represent multiple items.

Yes, I agree that the quantity field seems out-of-place. Just earlier today a user was confused by it, i.e., was wondering if it refers to a technical limit how often an offer could be paid, even though it's purely informational.

@jkczyz
Copy link
Contributor Author

jkczyz commented Mar 14, 2024

If we think this is the only way we allow users to associate offers, we should commit to it by creating an OfferId type with clearly defined guarantees and provide in all relevant Payment* events, preferably also on the sending side.

Hmm... we could possibly do this. To make it all compile-time checkable, we'd probably want the OfferBuilder to return to return it alongside the Offer. But since this is LDK-specific, we can't do it on the sending side as the offer may not have been created using LDK's ChannelManager. The sending side should already have a PaymentId, though. Since it is outbound we could potentially return the entire InvoiceRequest from pay_for_offer, which would contain offer details.

Also, would using payer_metadata work to derive a similar identifier for Refunds? IIUC, it is derived from payer_id, which might/should be single-use, but there is no guarantee it is? So we might run into collisions if the payer reuses payer_ids?

For the Refund creator, it is an outbound payment so the caller will provide a PaymentId to associate with it. This is encrypted as part of the payer_metadata. For someone requesting a payment from the Refund, we can return the entire Bolt12Invoice as is done in this PR, which has a PaymentHash.

@TheBlueMatt
Copy link
Collaborator

Hm, I mean it could be used this way though and even if it's only happening rarely we still need to support it?

I don't think we need to support particular uses that aren't well-supported by BOLT12 - we should build an interface for users that makes sense and nudges them in a sensible direction :). But, generally, I think this is supported by the separate-payment-offer tracking I suggested above. If we track the issued offers and, for each offer, each payment that has completed for it, the user can always just query "hey, for this offer, tell me the most recent payment(s)". We can also still have a semi-unified BOLT11/12 API (eventually), with BOLT11 just always having at max one payment, and the "has this been paid" query working the same across 11/12.

@tnull
Copy link
Contributor

tnull commented Mar 19, 2024

I don't think we need to support particular uses that aren't well-supported by BOLT12 - we should build an interface for users that makes sense and nudges them in a sensible direction :).

I generally agree, but I assume paying an offer multiple times isn't that unusual? Also, even if we didn't support it we need to at least make sure that we don't do something entirely buggy when users do it via other implementations, and allowing for collisions on payment ID could lead to unexpected behavior.

But, generally, I think this is supported by the separate-payment-offer tracking I suggested above. If we track the issued offers and, for each offer, each payment that has completed for it, the user can always just query "hey, for this offer, tell me the most recent payment(s)". We can also still have a semi-unified BOLT11/12 API (eventually), with BOLT11 just always having at max one payment, and the "has this been paid" query working the same across 11/12.

Yes, I agree, if we expose the offer/invoice fields and offer ID in PaymentClaimable and PaymentClaimed we should be good. One thing that would be nice-to-have is a way to detect that incoming payments are Refunds (i.e., discern them from BOLT11 and track the corresponding Refund data), but that should also be easily doable if we expose the BOLT12 invoice/InvoiceRequest fields in the events, in this case we'd just leave the offer id field None I imagine.

@TheBlueMatt
Copy link
Collaborator

I generally agree, but I assume paying an offer multiple times isn't that unusual?

In this case I was referring to, specifically, the idea that a merchant would have a few offers for specific items on the menu and customers would pay by attaching their name to the offer's payer_message. Rather, I think we should always assume (and design our API as if) multi-payment offers are used for donations, and single-payment offers for more traditional merchant flows.

@TheBlueMatt
Copy link
Collaborator

Yes, I agree, if we expose the offer/invoice fields and offer ID in PaymentClaimable and PaymentClaimed we should be good.

Okay, I think we're all on the same page - we should copy the offer/invoice_request/invoice fields that are relevant into the blinded path we include in the invoice and pipe them through to the payment events.

@jkczyz
Copy link
Contributor Author

jkczyz commented Apr 26, 2024

Closed in favor of #2970.

@jkczyz jkczyz closed this Apr 26, 2024
Sign up for free to 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.

5 participants