Skip to content

Commit adef9c2

Browse files
committed
Utility for creating and sending InvoiceRequests
Add a utility to ChannelManager for creating an InvoiceRequest for an Offer such that derived keys are used for the payer id. This allows for stateless verification of any Invoice messages before it is paid. Also tracks future payments using the given PaymentId such that the corresponding Invoice is paid only once.
1 parent 1998bc8 commit adef9c2

File tree

1 file changed

+87
-2
lines changed

1 file changed

+87
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5656
use crate::ln::outbound_payment;
5757
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
5858
use crate::ln::wire::Encode;
59-
use crate::offers::offer::{DerivedMetadata, OfferBuilder};
59+
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
6060
use crate::offers::parse::Bolt12SemanticError;
6161
use crate::offers::refund::RefundBuilder;
62-
use crate::onion_message::{OffersMessage, PendingOnionMessage};
62+
use crate::onion_message::{Destination, OffersMessage, PendingOnionMessage};
6363
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
6464
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
6565
use crate::util::wakers::{Future, Notifier};
@@ -7158,6 +7158,91 @@ where
71587158
Ok(builder)
71597159
}
71607160

7161+
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
7162+
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
7163+
/// [`Bolt12Invoice`] once it is received.
7164+
///
7165+
/// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by
7166+
/// the [`ChannelManager`] when handling a [`Bolt12Invoice`] message in response to the request.
7167+
/// The optional parameters are used in the builder, if `Some`:
7168+
/// - `quantity` for [`InvoiceRequest::quantity`] which must be set if
7169+
/// [`Offer::expects_quantity`] is `true`.
7170+
/// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and
7171+
/// - `payer_note` for [`InvoiceRequest::payer_note`].
7172+
///
7173+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the request
7174+
/// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
7175+
/// been sent. Errors if a duplicate `payment_id` is provided given the caveats in the
7176+
/// aforementioned link.
7177+
///
7178+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
7179+
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
7180+
/// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note
7181+
/// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder
7182+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
7183+
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
7184+
pub fn pay_for_offer(
7185+
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
7186+
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
7187+
max_total_routing_fee_msat: Option<u64>
7188+
) -> Result<(), Bolt12SemanticError> {
7189+
let expanded_key = &self.inbound_payment_key;
7190+
let entropy = &*self.entropy_source;
7191+
let secp_ctx = &self.secp_ctx;
7192+
7193+
if !offer.supports_chain(self.chain_hash) {
7194+
return Err(Bolt12SemanticError::UnsupportedChain);
7195+
}
7196+
7197+
let builder = offer.request_invoice_deriving_payer_id(
7198+
expanded_key, entropy, secp_ctx, payment_id
7199+
)?;
7200+
let builder = match quantity {
7201+
None => builder,
7202+
Some(quantity) => builder.quantity(quantity)?,
7203+
};
7204+
let builder = match amount_msats {
7205+
None => builder,
7206+
Some(amount_msats) => builder.amount_msats(amount_msats)?,
7207+
};
7208+
let builder = match payer_note {
7209+
None => builder,
7210+
Some(payer_note) => builder.payer_note(payer_note),
7211+
};
7212+
7213+
let invoice_request = builder.build_and_sign()?;
7214+
let reply_path = self.create_one_hop_blinded_path();
7215+
7216+
self.pending_outbound_payments
7217+
.add_new_awaiting_invoice(payment_id, retry_strategy, max_total_routing_fee_msat)
7218+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
7219+
7220+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
7221+
if offer.paths().is_empty() {
7222+
let message = PendingOnionMessage {
7223+
contents: OffersMessage::InvoiceRequest(invoice_request),
7224+
destination: Destination::Node(offer.signing_pubkey()),
7225+
reply_path: Some(reply_path),
7226+
};
7227+
pending_offers_messages.push(message);
7228+
} else {
7229+
// Send as many invoice requests as there are paths in the offer (with an upper bound).
7230+
// Using only one path could result in a failure if the path no longer exists. But only
7231+
// one invoice for a given payment id will be paid, even if more than one is received.
7232+
const REQUEST_LIMIT: usize = 10;
7233+
for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
7234+
let message = PendingOnionMessage {
7235+
contents: OffersMessage::InvoiceRequest(invoice_request.clone()),
7236+
destination: Destination::BlindedPath(path.clone()),
7237+
reply_path: Some(reply_path.clone()),
7238+
};
7239+
pending_offers_messages.push(message);
7240+
}
7241+
}
7242+
7243+
Ok(())
7244+
}
7245+
71617246
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
71627247
/// to pay us.
71637248
///

0 commit comments

Comments
 (0)