use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
use crate::offers::signer;
+#[cfg(async_payments)]
+use crate::offers::static_invoice::StaticInvoice;
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
)
}
+ #[cfg(async_payments)]
+ fn initiate_async_payment(
+ &self, invoice: &StaticInvoice, payment_id: PaymentId
+ ) -> Result<(), Bolt12PaymentError> {
+ let mut res = Ok(());
+ PersistenceNotifierGuard::optionally_notify(self, || {
+ let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
+ invoice, payment_id, &*self.entropy_source, &self.pending_events
+ );
+ let payment_release_secret = match outbound_pmts_res {
+ Ok(secret) => secret,
+ Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => {
+ res = outbound_pmts_res.map(|_| ());
+ return NotifyOption::SkipPersistNoEvents
+ },
+ Err(e) => {
+ res = Err(e);
+ return NotifyOption::DoPersist
+ }
+ };
+
+ let reply_paths = match self.create_blinded_paths(
+ MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id })
+ ) {
+ Ok(paths) => paths,
+ Err(()) => {
+ self.abandon_payment_with_reason(payment_id, PaymentFailureReason::RouteNotFound);
+ res = Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::RouteNotFound));
+ return NotifyOption::DoPersist
+ }
+ };
+
+ let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
+ const HTLC_AVAILABLE_LIMIT: usize = 10;
+ reply_paths
+ .iter()
+ .flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path)))
+ .take(HTLC_AVAILABLE_LIMIT)
+ .for_each(|(invoice_path, reply_path)| {
+ let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
+ destination: Destination::BlindedPath(invoice_path.clone()),
+ reply_path: reply_path.clone(),
+ };
+ let message = AsyncPaymentsMessage::HeldHtlcAvailable(
+ HeldHtlcAvailable { payment_release_secret }
+ );
+ pending_async_payments_messages.push((message, instructions));
+ });
+
+ NotifyOption::DoPersist
+ });
+
+ res
+ }
+
/// 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.
}
},
#[cfg(async_payments)]
- OffersMessage::StaticInvoice(_invoice) => {
+ OffersMessage::StaticInvoice(invoice) => {
+ let payment_id = match context {
+ Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => {
+ if payment_id.verify(hmac, nonce, expanded_key).is_err() {
+ return None
+ }
+ payment_id
+ },
+ _ => return None
+ };
+ // TODO: DRY this with the above regular invoice error handling
+ let error = match self.initiate_async_payment(&invoice, payment_id) {
+ Err(Bolt12PaymentError::UnknownRequiredFeatures) => {
+ log_trace!(
+ self.logger, "Invoice requires unknown features: {:?}",
+ invoice.invoice_features()
+ );
+ InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)
+ },
+ Err(Bolt12PaymentError::SendingFailed(e)) => {
+ log_trace!(self.logger, "Failed paying invoice: {:?}", e);
+ InvoiceError::from_string(format!("{:?}", e))
+ },
+ Err(Bolt12PaymentError::UnexpectedInvoice)
+ | Err(Bolt12PaymentError::DuplicateInvoice)
+ | Ok(()) => return None,
+ };
match responder {
- Some(responder) => {
- return Some((OffersMessage::InvoiceError(
- InvoiceError::from_string("Static invoices not yet supported".to_string())
- ), responder.respond()));
+ Some(responder) => Some((OffersMessage::InvoiceError(error), responder.respond())),
+ None => {
+ log_trace!(self.logger, "No reply path to send error: {:?}", error);
+ None
},
- None => return None,
}
},
OffersMessage::InvoiceError(invoice_error) => {
use crate::util::time::Instant;
use crate::util::ser::ReadableArgs;
+#[cfg(async_payments)]
+use {
+ crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder},
+ crate::offers::static_invoice::StaticInvoice,
+};
+
use core::fmt::{self, Display, Formatter};
use core::ops::Deref;
use core::sync::atomic::{AtomicBool, Ordering};
Ok(())
}
+ #[cfg(async_payments)]
+ pub(super) fn static_invoice_received<ES: Deref>(
+ &self, invoice: &StaticInvoice, payment_id: PaymentId, entropy_source: ES,
+ pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>
+ ) -> Result<[u8; 32], Bolt12PaymentError> where ES::Target: EntropySource {
+ macro_rules! abandon_with_entry {
+ ($payment: expr, $reason: expr) => {
+ $payment.get_mut().mark_abandoned($reason);
+ if let PendingOutboundPayment::Abandoned { reason, .. } = $payment.get() {
+ if $payment.get().remaining_parts() == 0 {
+ pending_events.lock().unwrap().push_back((events::Event::PaymentFailed {
+ payment_id,
+ payment_hash: None,
+ reason: *reason,
+ }, None));
+ $payment.remove();
+ }
+ }
+ }
+ }
+
+ match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
+ hash_map::Entry::Occupied(mut entry) => match entry.get() {
+ PendingOutboundPayment::AwaitingInvoice {
+ retry_strategy, retryable_invoice_request, max_total_routing_fee_msat, ..
+ } => {
+ let invreq = &retryable_invoice_request
+ .as_ref()
+ .ok_or(Bolt12PaymentError::UnexpectedInvoice)?
+ .invoice_request;
+ if !invoice.from_same_offer(invreq) {
+ return Err(Bolt12PaymentError::UnexpectedInvoice)
+ }
+ let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq) {
+ Ok(amt) => amt,
+ Err(_) => {
+ // We check this during invoice request parsing, when constructing the invreq's
+ // contents from its TLV stream.
+ debug_assert!(false, "LDK requires an msat amount in either the invreq or the invreq's underlying offer");
+ abandon_with_entry!(entry, PaymentFailureReason::UnexpectedError);
+ return Err(Bolt12PaymentError::UnknownRequiredFeatures)
+ }
+ };
+ let keysend_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes());
+ let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array());
+ let payment_release_secret = entropy_source.get_secure_random_bytes();
+ let pay_params = PaymentParameters::from_static_invoice(invoice);
+ let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
+ route_params.max_total_routing_fee_msat = *max_total_routing_fee_msat;
+ *entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
+ payment_hash,
+ keysend_preimage,
+ retry_strategy: *retry_strategy,
+ payment_release_secret,
+ route_params,
+ };
+ return Ok(payment_release_secret)
+ },
+ _ => return Err(Bolt12PaymentError::DuplicateInvoice),
+ },
+ hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
+ };
+ }
+
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
best_block_height: u32,