Skip to content

Commit cf55033

Browse files
Support initiating an async payment to a static invoice.
Supported when the sender is an always-online node. Often-offline senders will need to lock in their HTLC(s) with their always-online channel counterparty and modify the held_htlc_available reply path to terminate at said counterparty.
1 parent cd9c643 commit cf55033

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ use crate::offers::offer::{Offer, OfferBuilder};
6969
use crate::offers::parse::Bolt12SemanticError;
7070
use crate::offers::refund::{Refund, RefundBuilder};
7171
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
72+
#[cfg(async_payments)]
73+
use crate::offers::static_invoice::StaticInvoice;
7274
use crate::onion_message::messenger::{new_pending_onion_message, Destination, MessageRouter, PendingOnionMessage, Responder, ResponseInstruction};
7375
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
7476
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -4251,6 +4253,33 @@ where
42514253
)
42524254
}
42534255

4256+
#[cfg(async_payments)]
4257+
fn initiate_async_payment(
4258+
&self, invoice: &StaticInvoice, payment_id: PaymentId
4259+
) -> Result<(), Bolt12PaymentError> {
4260+
let reply_path = self.create_blinded_path(
4261+
MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id })
4262+
).map_err(|_| Bolt12PaymentError::BlindedPathNotFound)?;
4263+
4264+
let payment_release_secret = self.pending_outbound_payments.static_invoice_received(
4265+
invoice, payment_id, &*self.entropy_source
4266+
)?;
4267+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
4268+
4269+
let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
4270+
const HTLC_AVAILABLE_LIMIT: usize = 10;
4271+
for path in invoice.message_paths().into_iter().take(HTLC_AVAILABLE_LIMIT) {
4272+
let message = new_pending_onion_message(
4273+
AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable { payment_release_secret }),
4274+
Destination::BlindedPath(path.clone()),
4275+
Some(reply_path.clone()),
4276+
);
4277+
pending_async_payments_messages.push(message);
4278+
}
4279+
4280+
Ok(())
4281+
}
4282+
42544283
/// Signals that no further attempts for the given payment should occur. Useful if you have a
42554284
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
42564285
/// retries are exhausted.
@@ -10878,15 +10907,17 @@ where
1087810907
}
1087910908
},
1088010909
#[cfg(async_payments)]
10881-
OffersMessage::StaticInvoice(_invoice) => {
10882-
match responder {
10883-
Some(responder) => {
10884-
responder.respond(OffersMessage::InvoiceError(
10885-
InvoiceError::from_string("Static invoices not yet supported".to_string())
10886-
))
10887-
},
10888-
None => return ResponseInstruction::NoResponse,
10910+
OffersMessage::StaticInvoice(invoice) => {
10911+
let payment_id = match context {
10912+
OffersContext::OutboundPayment { payment_id } => payment_id,
10913+
_ => {
10914+
return ResponseInstruction::NoResponse
10915+
}
10916+
};
10917+
if let Err(e) = self.initiate_async_payment(&invoice, payment_id) {
10918+
log_trace!(self.logger, "Failed to initiate async payment to static invoice: {:?}", e);
1088910919
}
10920+
ResponseInstruction::NoResponse
1089010921
},
1089110922
OffersMessage::InvoiceError(invoice_error) => {
1089210923
abandon_if_payment(context);
@@ -10922,7 +10953,7 @@ where
1092210953
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
1092310954

1092410955
fn release_pending_messages(&self) -> Vec<PendingOnionMessage<AsyncPaymentsMessage>> {
10925-
Vec::new()
10956+
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
1092610957
}
1092710958
}
1092810959

lightning/src/ln/outbound_payment.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use crate::ln::onion_utils;
2323
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
2424
use crate::offers::invoice::Bolt12Invoice;
2525
use crate::offers::invoice_request::InvoiceRequest;
26+
#[cfg(async_payments)]
27+
use crate::offers::static_invoice::StaticInvoice;
2628
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2729
use crate::sign::{EntropySource, NodeSigner, Recipient};
2830
use crate::util::errors::APIError;
@@ -529,6 +531,10 @@ pub enum Bolt12PaymentError {
529531
///
530532
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
531533
OnionPacketSizeExceeded,
534+
/// Failed to create a [`BlindedPath`] back to ourselves.
535+
///
536+
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
537+
BlindedPathNotFound,
532538
}
533539

534540
/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
@@ -878,6 +884,38 @@ impl OutboundPayments {
878884
Ok(())
879885
}
880886

887+
#[cfg(async_payments)]
888+
pub(super) fn static_invoice_received<ES: Deref>(
889+
&self, invoice: &StaticInvoice, payment_id: PaymentId, entropy_source: ES
890+
) -> Result<[u8; 32], Bolt12PaymentError> where ES::Target: EntropySource {
891+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
892+
hash_map::Entry::Occupied(entry) => match entry.get() {
893+
PendingOutboundPayment::AwaitingInvoice { retry_strategy, invoice_request, .. } => {
894+
let invreq = invoice_request.as_ref().ok_or(Bolt12PaymentError::UnexpectedInvoice)?;
895+
if !invoice.from_same_offer(invreq) {
896+
return Err(Bolt12PaymentError::UnexpectedInvoice)
897+
}
898+
let amount_msat = invreq.amount_msats().ok_or(Bolt12PaymentError::UnexpectedInvoice)?;
899+
let keysend_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes());
900+
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
901+
let payment_release_secret = entropy_source.get_secure_random_bytes();
902+
let pay_params = PaymentParameters::from_static_invoice(invoice);
903+
let route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
904+
*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
905+
payment_hash,
906+
keysend_preimage,
907+
retry_strategy: *retry_strategy,
908+
payment_release_secret,
909+
route_params,
910+
};
911+
return Ok(payment_release_secret)
912+
},
913+
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
914+
},
915+
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
916+
};
917+
}
918+
881919
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
882920
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
883921
best_block_height: u32,

0 commit comments

Comments
 (0)