Skip to content

Commit d39397e

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 3fa2fd3 commit d39397e

File tree

2 files changed

+105
-8
lines changed

2 files changed

+105
-8
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
6666
use crate::offers::offer::{Offer, OfferBuilder};
6767
use crate::offers::parse::Bolt12SemanticError;
6868
use crate::offers::refund::{Refund, RefundBuilder};
69-
use crate::onion_message::async_payments::AsyncPaymentsMessage;
69+
#[cfg(async_payments)]
70+
use crate::offers::static_invoice::StaticInvoice;
71+
use crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc};
7072
use crate::onion_message::messenger::{new_pending_onion_message, Destination, MessageRouter, PendingOnionMessage, Responder, ResponseInstruction};
7173
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
7274
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -4045,6 +4047,34 @@ where
40454047
)
40464048
}
40474049

4050+
#[cfg(async_payments)]
4051+
fn initiate_async_payment(
4052+
&self, invoice: &StaticInvoice, payment_id: PaymentId
4053+
) -> Result<(), InvoiceError> {
4054+
if invoice.message_paths().is_empty() { return Err(Bolt12SemanticError::MissingPaths.into()) }
4055+
4056+
let reply_path = self.create_blinded_path(Some(payment_id))
4057+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
4058+
4059+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
4060+
let payment_release_secret = self.pending_outbound_payments.static_invoice_received(
4061+
invoice, payment_id, &*self.entropy_source
4062+
).map_err(|e| InvoiceError::from_string(format!("{:?}", e)))?;
4063+
4064+
let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
4065+
const HTLC_AVAILABLE_LIMIT: usize = 10;
4066+
for path in invoice.message_paths().into_iter().take(HTLC_AVAILABLE_LIMIT) {
4067+
let message = new_pending_onion_message(
4068+
AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable { payment_release_secret }),
4069+
Destination::BlindedPath(path.clone()),
4070+
Some(reply_path.clone()),
4071+
);
4072+
pending_async_payments_messages.push(message);
4073+
}
4074+
4075+
Ok(())
4076+
}
4077+
40484078
/// Signals that no further attempts for the given payment should occur. Useful if you have a
40494079
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
40504080
/// retries are exhausted.
@@ -10348,14 +10378,22 @@ where
1034810378
}
1034910379
},
1035010380
#[cfg(async_payments)]
10351-
OffersMessage::StaticInvoice(_invoice) => {
10352-
match responder {
10353-
Some(responder) => {
10354-
responder.respond(OffersMessage::InvoiceError(
10355-
InvoiceError::from_string("Static invoices not yet supported".to_string())
10356-
))
10357-
},
10381+
OffersMessage::StaticInvoice(invoice) => {
10382+
let responder = match responder {
10383+
Some(responder) => responder,
1035810384
None => return ResponseInstruction::NoResponse,
10385+
};
10386+
let payment_id = match _payment_id {
10387+
Some(id) => id,
10388+
None => {
10389+
return responder.respond(OffersMessage::InvoiceError(
10390+
InvoiceError::from_string("Unrecognized invoice".to_string())
10391+
))
10392+
}
10393+
};
10394+
match self.initiate_async_payment(&invoice, payment_id) {
10395+
Ok(()) => return ResponseInstruction::NoResponse,
10396+
Err(e) => responder.respond(OffersMessage::InvoiceError(e)),
1035910397
}
1036010398
},
1036110399
OffersMessage::InvoiceError(invoice_error) => {
@@ -10370,6 +10408,31 @@ where
1037010408
}
1037110409
}
1037210410

10411+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
10412+
AsyncPaymentsMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
10413+
where
10414+
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
10415+
T::Target: BroadcasterInterface,
10416+
ES::Target: EntropySource,
10417+
NS::Target: NodeSigner,
10418+
SP::Target: SignerProvider,
10419+
F::Target: FeeEstimator,
10420+
R::Target: Router,
10421+
L::Target: Logger,
10422+
{
10423+
fn held_htlc_available(
10424+
&self, _message: HeldHtlcAvailable, _responder: Option<Responder>
10425+
) -> ResponseInstruction<ReleaseHeldHtlc> {
10426+
ResponseInstruction::NoResponse
10427+
}
10428+
10429+
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _payment_id: Option<PaymentId>) {}
10430+
10431+
fn release_pending_messages(&self) -> Vec<PendingOnionMessage<AsyncPaymentsMessage>> {
10432+
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
10433+
}
10434+
}
10435+
1037310436
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
1037410437
NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, L>
1037510438
where

lightning/src/ln/outbound_payment.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId};
2222
use crate::ln::onion_utils;
2323
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
2424
use crate::offers::invoice::Bolt12Invoice;
25+
#[cfg(async_payments)]
26+
use crate::offers::static_invoice::StaticInvoice;
2527
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2628
use crate::util::errors::APIError;
2729
use crate::util::logger::Logger;
@@ -837,6 +839,38 @@ impl OutboundPayments {
837839
Ok(())
838840
}
839841

842+
#[cfg(async_payments)]
843+
pub(super) fn static_invoice_received<ES: Deref>(
844+
&self, invoice: &StaticInvoice, payment_id: PaymentId, entropy_source: ES
845+
) -> Result<[u8; 32], Bolt12PaymentError> where ES::Target: EntropySource {
846+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
847+
hash_map::Entry::Occupied(entry) => match entry.get() {
848+
PendingOutboundPayment::AwaitingInvoice { retry_strategy, invoice_request, .. } => {
849+
let invreq = invoice_request.as_ref().ok_or(Bolt12PaymentError::UnexpectedInvoice)?;
850+
if !invoice.matches_invreq(invreq) {
851+
return Err(Bolt12PaymentError::UnexpectedInvoice)
852+
}
853+
let amount_msat = invreq.amount_msats().ok_or(Bolt12PaymentError::UnexpectedInvoice)?;
854+
let keysend_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes());
855+
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
856+
let payment_release_secret = entropy_source.get_secure_random_bytes();
857+
let pay_params = PaymentParameters::from_static_invoice(invoice);
858+
let route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
859+
*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
860+
payment_hash,
861+
keysend_preimage,
862+
retry_strategy: *retry_strategy,
863+
payment_release_secret,
864+
route_params,
865+
};
866+
return Ok(payment_release_secret)
867+
},
868+
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
869+
},
870+
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
871+
};
872+
}
873+
840874
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
841875
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
842876
best_block_height: u32,

0 commit comments

Comments
 (0)