From 0297a1e4ee2fb577cdfc635eceb0a7f122be33f9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 4 Sep 2024 15:24:26 -0400 Subject: [PATCH] Support sending async payments as an always-online sender. Async receive is not yet supported. Here we process inbound release_htlc onion messages, check that they actually correspond to one of our outbound payments, and actually forward the HTLCs. Valid release_htlc receipt indicates that the recipient has now come online to receive. --- lightning/src/ln/channelmanager.rs | 27 +++++++++++- lightning/src/ln/outbound_payment.rs | 61 ++++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f52542855..fe5008eac 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4378,6 +4378,21 @@ where res } + #[cfg(async_payments)] + fn send_payment_for_static_invoice( + &self, payment_id: PaymentId, payment_release_secret: [u8; 32] + ) -> Result<(), Bolt12PaymentError> { + let best_block_height = self.best_block.read().unwrap().height; + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.pending_outbound_payments + .send_payment_for_static_invoice( + payment_id, payment_release_secret, &self.router, self.list_usable_channels(), + || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, &self, + &self.secp_ctx, best_block_height, &self.logger, &self.pending_events, + |args| self.send_payment_along_path(args) + ) + } + /// Signals that no further attempts for the given payment should occur. Useful if you have a /// pending outbound payment with retries remaining, but wish to stop retrying the payment before /// retries are exhausted. @@ -11171,7 +11186,17 @@ where None } - fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {} + fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { + #[cfg(async_payments)] { + let AsyncPaymentsContext::OutboundPayment { payment_id } = _context; + if let Err(e) = self.send_payment_for_static_invoice(payment_id, _message.payment_release_secret) { + log_trace!( + self.logger, "Failed to release held HTLC with payment id {} and release secret {:02x?}: {:?}", + payment_id, _message.payment_release_secret, e + ); + } + } + } fn release_pending_messages(&self) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> { core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap()) diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index bc1123ee2..c86aab511 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -856,16 +856,17 @@ impl OutboundPayments { route_params.max_total_routing_fee_msat = Some(max_fee_msat); } self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, route_params, retry_strategy, router, first_hops, inflight_htlcs, - entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, logger, - pending_events, send_payment_along_path + payment_id, payment_hash, None, route_params, retry_strategy, router, first_hops, + inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, + logger, pending_events, send_payment_along_path ) } fn send_payment_for_bolt12_invoice_internal< R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref >( - &self, payment_id: PaymentId, payment_hash: PaymentHash, mut route_params: RouteParameters, + &self, payment_id: PaymentId, payment_hash: PaymentHash, + keysend_preimage: Option, mut route_params: RouteParameters, retry_strategy: Retry, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, logger: &L, @@ -923,12 +924,13 @@ impl OutboundPayments { let payment_params = Some(route_params.payment_params.clone()); let (retryable_payment, onion_session_privs) = self.create_pending_payment( - payment_hash, recipient_onion.clone(), None, &route, Some(retry_strategy), payment_params, - entropy_source, best_block_height + payment_hash, recipient_onion.clone(), keysend_preimage, &route, Some(retry_strategy), + payment_params, entropy_source, best_block_height ); match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { hash_map::Entry::Occupied(entry) => match entry.get() { - PendingOutboundPayment::InvoiceReceived { .. } => { + PendingOutboundPayment::InvoiceReceived { .. } + | PendingOutboundPayment::StaticInvoiceReceived { .. } => { *entry.into_mut() = retryable_payment; }, _ => return Err(Bolt12PaymentError::DuplicateInvoice), @@ -937,7 +939,7 @@ impl OutboundPayments { } let result = self.pay_route_internal( - &route, payment_hash, &recipient_onion, None, payment_id, + &route, payment_hash, &recipient_onion, keysend_preimage, payment_id, Some(route_params.final_value_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path ); @@ -1033,6 +1035,49 @@ impl OutboundPayments { }; } + #[cfg(async_payments)] + pub(super) fn send_payment_for_static_invoice< + R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref + >( + &self, payment_id: PaymentId, payment_release_secret: [u8; 32], router: &R, + first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, + node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, logger: &L, + pending_events: &Mutex)>>, + send_payment_along_path: SP, + ) -> Result<(), Bolt12PaymentError> + where + R::Target: Router, + ES::Target: EntropySource, + NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, + L::Target: Logger, + IH: Fn() -> InFlightHtlcs, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, + { + let (payment_hash, keysend_preimage, route_params, retry_strategy) = + match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { + hash_map::Entry::Occupied(entry) => match entry.get() { + PendingOutboundPayment::StaticInvoiceReceived { + payment_hash, payment_release_secret: release_secret, route_params, retry_strategy, + keysend_preimage, .. + } => { + if payment_release_secret != *release_secret { + return Err(Bolt12PaymentError::UnexpectedInvoice) + } + (*payment_hash, *keysend_preimage, route_params.clone(), *retry_strategy) + }, + _ => return Err(Bolt12PaymentError::DuplicateInvoice), + }, + hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), + }; + + self.send_payment_for_bolt12_invoice_internal( + payment_id, payment_hash, Some(keysend_preimage), route_params, retry_strategy, router, + first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, + best_block_height, logger, pending_events, send_payment_along_path + ) + } + pub(super) fn check_retry_payments( &self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, best_block_height: u32, -- 2.39.5