Skip to content

Commit ffe9ae2

Browse files
committed
Utility for paying for an Offer
Add a utility to ChannelManager for sending 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 34bdf22 commit ffe9ae2

File tree

2 files changed

+98
-4
lines changed

2 files changed

+98
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 88 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, StaleExpiration};
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};
@@ -7359,6 +7359,92 @@ where
73597359
Ok(builder)
73607360
}
73617361

7362+
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
7363+
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
7364+
/// [`Bolt12Invoice`] once it is received.
7365+
///
7366+
/// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by
7367+
/// the [`ChannelManager`] when handling a [`Bolt12Invoice`] message in response to the request.
7368+
/// The optional parameters are used in the builder, if `Some`:
7369+
/// - `quantity` for [`InvoiceRequest::quantity`] which must be set if
7370+
/// [`Offer::expects_quantity`] is `true`.
7371+
/// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and
7372+
/// - `payer_note` for [`InvoiceRequest::payer_note`].
7373+
///
7374+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the request
7375+
/// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
7376+
/// been sent. To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving
7377+
/// the invoice.
7378+
///
7379+
/// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link.
7380+
///
7381+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
7382+
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
7383+
/// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note
7384+
/// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder
7385+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
7386+
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
7387+
pub fn pay_for_offer(
7388+
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
7389+
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
7390+
max_total_routing_fee_msat: Option<u64>
7391+
) -> Result<(), Bolt12SemanticError> {
7392+
let expanded_key = &self.inbound_payment_key;
7393+
let entropy = &*self.entropy_source;
7394+
let secp_ctx = &self.secp_ctx;
7395+
7396+
let builder = offer
7397+
.request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
7398+
.chain_hash(self.chain_hash)?;
7399+
let builder = match quantity {
7400+
None => builder,
7401+
Some(quantity) => builder.quantity(quantity)?,
7402+
};
7403+
let builder = match amount_msats {
7404+
None => builder,
7405+
Some(amount_msats) => builder.amount_msats(amount_msats)?,
7406+
};
7407+
let builder = match payer_note {
7408+
None => builder,
7409+
Some(payer_note) => builder.payer_note(payer_note),
7410+
};
7411+
7412+
let invoice_request = builder.build_and_sign()?;
7413+
let reply_path = self.create_one_hop_blinded_path();
7414+
7415+
let expiration = StaleExpiration::TimerTicks(1);
7416+
self.pending_outbound_payments
7417+
.add_new_awaiting_invoice(
7418+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat
7419+
)
7420+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
7421+
7422+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
7423+
if offer.paths().is_empty() {
7424+
let message = PendingOnionMessage {
7425+
contents: OffersMessage::InvoiceRequest(invoice_request),
7426+
destination: Destination::Node(offer.signing_pubkey()),
7427+
reply_path: Some(reply_path),
7428+
};
7429+
pending_offers_messages.push(message);
7430+
} else {
7431+
// Send as many invoice requests as there are paths in the offer (with an upper bound).
7432+
// Using only one path could result in a failure if the path no longer exists. But only
7433+
// one invoice for a given payment id will be paid, even if more than one is received.
7434+
const REQUEST_LIMIT: usize = 10;
7435+
for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
7436+
let message = PendingOnionMessage {
7437+
contents: OffersMessage::InvoiceRequest(invoice_request.clone()),
7438+
destination: Destination::BlindedPath(path.clone()),
7439+
reply_path: Some(reply_path.clone()),
7440+
};
7441+
pending_offers_messages.push(message);
7442+
}
7443+
}
7444+
7445+
Ok(())
7446+
}
7447+
73627448
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
73637449
/// to pay us.
73647450
///

lightning/src/offers/invoice_request.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,16 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
179179
/// by the offer.
180180
///
181181
/// Successive calls to this method will override the previous setting.
182-
pub fn chain(mut self, network: Network) -> Result<Self, Bolt12SemanticError> {
183-
let chain = ChainHash::using_genesis_block(network);
182+
pub fn chain(self, network: Network) -> Result<Self, Bolt12SemanticError> {
183+
self.chain_hash(ChainHash::using_genesis_block(network))
184+
}
185+
186+
/// Sets the [`InvoiceRequest::chain`] for paying an invoice. If not called, the chain hash of
187+
/// [`Network::Bitcoin`] is assumed. Errors if the chain for `network` is not supported by the
188+
/// offer.
189+
///
190+
/// Successive calls to this method will override the previous setting.
191+
pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Result<Self, Bolt12SemanticError> {
184192
if !self.offer.supports_chain(chain) {
185193
return Err(Bolt12SemanticError::UnsupportedChain);
186194
}

0 commit comments

Comments
 (0)