Skip to content

Commit f34cf08

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 f34cf08

File tree

2 files changed

+80
-9
lines changed

2 files changed

+80
-9
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 42 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,37 @@ 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_paths = self.create_blinded_paths(
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+
reply_paths
4272+
.iter()
4273+
.flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path)))
4274+
.take(HTLC_AVAILABLE_LIMIT)
4275+
.for_each(|(invoice_path, reply_path)| {
4276+
let message = new_pending_onion_message(
4277+
AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable { payment_release_secret }),
4278+
Destination::BlindedPath(invoice_path.clone()),
4279+
Some(reply_path.clone()),
4280+
);
4281+
pending_async_payments_messages.push(message);
4282+
});
4283+
4284+
Ok(())
4285+
}
4286+
42544287
/// Signals that no further attempts for the given payment should occur. Useful if you have a
42554288
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
42564289
/// retries are exhausted.
@@ -10878,15 +10911,15 @@ where
1087810911
}
1087910912
},
1088010913
#[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,
10914+
OffersMessage::StaticInvoice(invoice) => {
10915+
let payment_id = match context {
10916+
OffersContext::OutboundPayment { payment_id, nonce: _ } => payment_id,
10917+
_ => return ResponseInstruction::NoResponse
10918+
};
10919+
if let Err(e) = self.initiate_async_payment(&invoice, payment_id) {
10920+
log_trace!(self.logger, "Failed to initiate async payment to static invoice: {:?}", e);
1088910921
}
10922+
ResponseInstruction::NoResponse
1089010923
},
1089110924
OffersMessage::InvoiceError(invoice_error) => {
1089210925
abandon_if_payment(context);
@@ -10922,7 +10955,7 @@ where
1092210955
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
1092310956

1092410957
fn release_pending_messages(&self) -> Vec<PendingOnionMessage<AsyncPaymentsMessage>> {
10925-
Vec::new()
10958+
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
1092610959
}
1092710960
}
1092810961

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)