Skip to content

Commit d4c7c44

Browse files
Support initiating an async payment to a static invoice.
Supported when the sender is an always-online node. Here we send the initial held_htlc_available onion message upon receipt of a static invoice, next we'll need to actually send HTLCs upon getting a response to said OM.
1 parent 2367611 commit d4c7c44

File tree

2 files changed

+145
-6
lines changed

2 files changed

+145
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ use crate::offers::offer::{Offer, OfferBuilder};
7171
use crate::offers::parse::Bolt12SemanticError;
7272
use crate::offers::refund::{Refund, RefundBuilder};
7373
use crate::offers::signer;
74+
#[cfg(async_payments)]
75+
use crate::offers::static_invoice::StaticInvoice;
7476
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
7577
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
7678
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
@@ -4284,6 +4286,61 @@ where
42844286
)
42854287
}
42864288

4289+
#[cfg(async_payments)]
4290+
fn initiate_async_payment(
4291+
&self, invoice: &StaticInvoice, payment_id: PaymentId
4292+
) -> Result<(), Bolt12PaymentError> {
4293+
let mut res = Ok(());
4294+
PersistenceNotifierGuard::optionally_notify(self, || {
4295+
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
4296+
invoice, payment_id, &*self.entropy_source, &self.pending_events
4297+
);
4298+
let payment_release_secret = match outbound_pmts_res {
4299+
Ok(secret) => secret,
4300+
Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => {
4301+
res = outbound_pmts_res.map(|_| ());
4302+
return NotifyOption::SkipPersistNoEvents
4303+
},
4304+
Err(e) => {
4305+
res = Err(e);
4306+
return NotifyOption::DoPersist
4307+
}
4308+
};
4309+
4310+
let reply_paths = match self.create_blinded_paths(
4311+
MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id })
4312+
) {
4313+
Ok(paths) => paths,
4314+
Err(()) => {
4315+
self.abandon_payment_with_reason(payment_id, PaymentFailureReason::RouteNotFound);
4316+
res = Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::RouteNotFound));
4317+
return NotifyOption::DoPersist
4318+
}
4319+
};
4320+
4321+
let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
4322+
const HTLC_AVAILABLE_LIMIT: usize = 10;
4323+
reply_paths
4324+
.iter()
4325+
.flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path)))
4326+
.take(HTLC_AVAILABLE_LIMIT)
4327+
.for_each(|(invoice_path, reply_path)| {
4328+
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
4329+
destination: Destination::BlindedPath(invoice_path.clone()),
4330+
reply_path: reply_path.clone(),
4331+
};
4332+
let message = AsyncPaymentsMessage::HeldHtlcAvailable(
4333+
HeldHtlcAvailable { payment_release_secret }
4334+
);
4335+
pending_async_payments_messages.push((message, instructions));
4336+
});
4337+
4338+
NotifyOption::DoPersist
4339+
});
4340+
4341+
res
4342+
}
4343+
42874344
/// Signals that no further attempts for the given payment should occur. Useful if you have a
42884345
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
42894346
/// retries are exhausted.
@@ -10948,14 +11005,34 @@ where
1094811005
}
1094911006
},
1095011007
#[cfg(async_payments)]
10951-
OffersMessage::StaticInvoice(_invoice) => {
11008+
OffersMessage::StaticInvoice(invoice) => {
11009+
let payment_id = match context {
11010+
Some(OffersContext::OutboundPayment { payment_id, nonce: _, hmac: _ }) => payment_id,
11011+
_ => return None
11012+
};
11013+
// TODO: DRY this with the above regular invoice error handling
11014+
let error = match self.initiate_async_payment(&invoice, payment_id) {
11015+
Err(Bolt12PaymentError::UnknownRequiredFeatures) => {
11016+
log_trace!(
11017+
self.logger, "Invoice requires unknown features: {:?}",
11018+
invoice.invoice_features()
11019+
);
11020+
InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)
11021+
},
11022+
Err(Bolt12PaymentError::SendingFailed(e)) => {
11023+
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
11024+
InvoiceError::from_string(format!("{:?}", e))
11025+
},
11026+
Err(Bolt12PaymentError::UnexpectedInvoice)
11027+
| Err(Bolt12PaymentError::DuplicateInvoice)
11028+
| Ok(()) => return None,
11029+
};
1095211030
match responder {
10953-
Some(responder) => {
10954-
return Some((OffersMessage::InvoiceError(
10955-
InvoiceError::from_string("Static invoices not yet supported".to_string())
10956-
), responder.respond()));
11031+
Some(responder) => Some((OffersMessage::InvoiceError(error), responder.respond())),
11032+
None => {
11033+
log_trace!(self.logger, "No reply path to send error: {:?}", error);
11034+
None
1095711035
},
10958-
None => return None,
1095911036
}
1096011037
},
1096111038
OffersMessage::InvoiceError(invoice_error) => {

lightning/src/ln/outbound_payment.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ use crate::util::logger::Logger;
3131
use crate::util::time::Instant;
3232
use crate::util::ser::ReadableArgs;
3333

34+
#[cfg(async_payments)]
35+
use {
36+
crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder},
37+
crate::offers::static_invoice::StaticInvoice,
38+
};
39+
3440
use core::fmt::{self, Display, Formatter};
3541
use core::ops::Deref;
3642
use core::time::Duration;
@@ -910,6 +916,62 @@ impl OutboundPayments {
910916
Ok(())
911917
}
912918

919+
#[cfg(async_payments)]
920+
pub(super) fn static_invoice_received<ES: Deref>(
921+
&self, invoice: &StaticInvoice, payment_id: PaymentId, entropy_source: ES,
922+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>
923+
) -> Result<[u8; 32], Bolt12PaymentError> where ES::Target: EntropySource {
924+
macro_rules! abandon_with_entry {
925+
($payment: expr, $reason: expr) => {
926+
$payment.get_mut().mark_abandoned($reason);
927+
if let PendingOutboundPayment::Abandoned { reason, .. } = $payment.get() {
928+
if $payment.get().remaining_parts() == 0 {
929+
pending_events.lock().unwrap().push_back((events::Event::PaymentFailed {
930+
payment_id,
931+
payment_hash: None,
932+
reason: *reason,
933+
}, None));
934+
$payment.remove();
935+
}
936+
}
937+
}
938+
}
939+
940+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
941+
hash_map::Entry::Occupied(mut entry) => match entry.get() {
942+
PendingOutboundPayment::AwaitingInvoice { retry_strategy, invoice_request, .. } => {
943+
let invreq = invoice_request.as_ref().ok_or(Bolt12PaymentError::UnexpectedInvoice)?;
944+
if !invoice.from_same_offer(invreq) {
945+
return Err(Bolt12PaymentError::UnexpectedInvoice)
946+
}
947+
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(&invreq) {
948+
Ok(amt) => amt,
949+
Err(_) => {
950+
debug_assert!(false, "LDK doesn't create invreqs without msat amounts");
951+
abandon_with_entry!(entry, PaymentFailureReason::UnexpectedError);
952+
return Err(Bolt12PaymentError::UnknownRequiredFeatures)
953+
}
954+
};
955+
let keysend_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes());
956+
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
957+
let payment_release_secret = entropy_source.get_secure_random_bytes();
958+
let pay_params = PaymentParameters::from_static_invoice(invoice);
959+
let route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
960+
*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
961+
payment_hash,
962+
keysend_preimage,
963+
retry_strategy: *retry_strategy,
964+
payment_release_secret,
965+
route_params,
966+
};
967+
return Ok(payment_release_secret)
968+
},
969+
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
970+
},
971+
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
972+
};
973+
}
974+
913975
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
914976
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
915977
best_block_height: u32,

0 commit comments

Comments
 (0)