Merge pull request #2592 from TheBlueMatt/2023-09-117-alpha
[rust-lightning] / lightning / src / ln / outbound_payment.rs
index 712f33e99f235eef1dc7b79b28dcf5c57ea16021..023412e1afb56cdcdd0a2d9195064e5744ccab6c 100644 (file)
@@ -53,9 +53,11 @@ pub(crate) enum PendingOutboundPayment {
        },
        AwaitingInvoice {
                timer_ticks_without_response: u8,
+               retry_strategy: Retry,
        },
        InvoiceReceived {
                payment_hash: PaymentHash,
+               retry_strategy: Retry,
        },
        Retryable {
                retry_strategy: Option<Retry>,
@@ -156,7 +158,7 @@ impl PendingOutboundPayment {
                match self {
                        PendingOutboundPayment::Legacy { .. } => None,
                        PendingOutboundPayment::AwaitingInvoice { .. } => None,
-                       PendingOutboundPayment::InvoiceReceived { payment_hash } => Some(*payment_hash),
+                       PendingOutboundPayment::InvoiceReceived { payment_hash, .. } => Some(*payment_hash),
                        PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash),
                        PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash,
                        PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash),
@@ -186,7 +188,7 @@ impl PendingOutboundPayment {
                                payment_hash: *payment_hash,
                                reason: Some(reason)
                        };
-               } else if let PendingOutboundPayment::InvoiceReceived { payment_hash } = self {
+               } else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self {
                        *self = PendingOutboundPayment::Abandoned {
                                session_privs: HashSet::new(),
                                payment_hash: *payment_hash,
@@ -263,7 +265,7 @@ pub enum Retry {
        /// Each attempt may be multiple HTLCs along multiple paths if the router decides to split up a
        /// retry, and may retry multiple failed HTLCs at once if they failed around the same time and
        /// were retried along a route from a single call to [`Router::find_route_with_id`].
-       Attempts(usize),
+       Attempts(u32),
        #[cfg(not(feature = "no-std"))]
        /// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made;
        /// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time.
@@ -272,6 +274,19 @@ pub enum Retry {
        Timeout(core::time::Duration),
 }
 
+#[cfg(feature = "no-std")]
+impl_writeable_tlv_based_enum!(Retry,
+       ;
+       (0, Attempts)
+);
+
+#[cfg(not(feature = "no-std"))]
+impl_writeable_tlv_based_enum!(Retry,
+       ;
+       (0, Attempts),
+       (2, Timeout)
+);
+
 impl Retry {
        pub(crate) fn is_retryable_now(&self, attempts: &PaymentAttempts) -> bool {
                match (self, attempts) {
@@ -305,7 +320,7 @@ pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime<ConfiguredTime>;
 pub(crate) struct PaymentAttemptsUsingTime<T: Time> {
        /// This count will be incremented only after the result of the attempt is known. When it's 0,
        /// it means the result of the first attempt is not known yet.
-       pub(crate) count: usize,
+       pub(crate) count: u32,
        /// This field is only used when retry is `Retry::Timeout` which is only build with feature std
        #[cfg(not(feature = "no-std"))]
        first_attempted_at: T,
@@ -376,7 +391,7 @@ pub enum RetryableSendFailure {
 /// is in, see the description of individual enum states for more.
 ///
 /// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub enum PaymentSendFailure {
        /// A parameter which was passed to send_payment was invalid, preventing us from attempting to
        /// send the payment at all.
@@ -450,6 +465,18 @@ pub(super) enum Bolt12PaymentError {
        DuplicateInvoice,
 }
 
+/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
+/// [`Event::ProbeFailed`].
+///
+/// [`Event::ProbeFailed`]: crate::events::Event::ProbeFailed
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ProbeSendFailure {
+       /// We were unable to find a route to the destination.
+       RouteNotFound,
+       /// We failed to send the payment probes.
+       SendingFailed(PaymentSendFailure),
+}
+
 /// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
 ///
 /// This should generally be constructed with data communicated to us from the recipient (via a
@@ -587,8 +614,6 @@ pub(super) struct SendAlongPathArgs<'a> {
        pub session_priv_bytes: [u8; 32],
 }
 
-const BOLT_12_INVOICE_RETRY_STRATEGY: Retry = Retry::Attempts(3);
-
 pub(super) struct OutboundPayments {
        pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
        pub(super) retry_lock: Mutex<()>,
@@ -707,10 +732,15 @@ impl OutboundPayments {
        {
                let payment_hash = invoice.payment_hash();
                match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
-                       hash_map::Entry::Occupied(entry) if entry.get().is_awaiting_invoice() => {
-                               *entry.into_mut() = PendingOutboundPayment::InvoiceReceived { payment_hash };
+                       hash_map::Entry::Occupied(entry) => match entry.get() {
+                               PendingOutboundPayment::AwaitingInvoice { retry_strategy, .. } => {
+                                       *entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
+                                               payment_hash,
+                                               retry_strategy: *retry_strategy,
+                                       };
+                               },
+                               _ => return Err(Bolt12PaymentError::DuplicateInvoice),
                        },
-                       hash_map::Entry::Occupied(_) => return Err(Bolt12PaymentError::DuplicateInvoice),
                        hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
                };
 
@@ -957,14 +987,14 @@ impl OutboundPayments {
                                                        log_error!(logger, "Payment not yet sent");
                                                        return
                                                },
-                                               PendingOutboundPayment::InvoiceReceived { payment_hash } => {
+                                               PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy } => {
                                                        let total_amount = route_params.final_value_msat;
                                                        let recipient_onion = RecipientOnionFields {
                                                                payment_secret: None,
                                                                payment_metadata: None,
                                                                custom_tlvs: vec![],
                                                        };
-                                                       let retry_strategy = Some(BOLT_12_INVOICE_RETRY_STRATEGY);
+                                                       let retry_strategy = Some(*retry_strategy);
                                                        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,
@@ -1085,6 +1115,7 @@ impl OutboundPayments {
                F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
        {
                let payment_id = PaymentId(entropy_source.get_secure_random_bytes());
+               let payment_secret = PaymentSecret(entropy_source.get_secure_random_bytes());
 
                let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret);
 
@@ -1096,7 +1127,7 @@ impl OutboundPayments {
 
                let route = Route { paths: vec![path], route_params: None };
                let onion_session_privs = self.add_new_pending_payment(payment_hash,
-                       RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None,
+                       RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None,
                        entropy_source, best_block_height)?;
 
                match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
@@ -1186,13 +1217,16 @@ impl OutboundPayments {
        }
 
        #[allow(unused)]
-       pub(super) fn add_new_awaiting_invoice(&self, payment_id: PaymentId) -> Result<(), ()> {
+       pub(super) fn add_new_awaiting_invoice(
+               &self, payment_id: PaymentId, retry_strategy: Retry
+       ) -> Result<(), ()> {
                let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
                match pending_outbounds.entry(payment_id) {
                        hash_map::Entry::Occupied(_) => Err(()),
                        hash_map::Entry::Vacant(entry) => {
                                entry.insert(PendingOutboundPayment::AwaitingInvoice {
                                        timer_ticks_without_response: 0,
+                                       retry_strategy,
                                });
 
                                Ok(())
@@ -1213,7 +1247,9 @@ impl OutboundPayments {
                if route.paths.len() < 1 {
                        return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()}));
                }
-               if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 {
+               if recipient_onion.payment_secret.is_none() && route.paths.len() > 1
+                       && !route.paths.iter().any(|p| p.blinded_tail.is_some())
+               {
                        return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
                }
                let mut total_value = 0;
@@ -1224,10 +1260,6 @@ impl OutboundPayments {
                                path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
                                continue 'path_check;
                        }
-                       if path.blinded_tail.is_some() {
-                               path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
-                               continue 'path_check;
-                       }
                        let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
                                usize::max_value() } else { path.hops.len() - 1 };
                        for (idx, hop) in path.hops.iter().enumerate() {
@@ -1667,9 +1699,11 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
        },
        (5, AwaitingInvoice) => {
                (0, timer_ticks_without_response, required),
+               (2, retry_strategy, required),
        },
        (7, InvoiceReceived) => {
                (0, payment_hash, required),
+               (2, retry_strategy, required),
        },
 );
 
@@ -1829,6 +1863,7 @@ mod tests {
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 0,
                                cltv_expiry_delta: 0,
+                               maybe_announced_channel: true,
                        }], blinded_tail: None }],
                        route_params: Some(route_params.clone()),
                };
@@ -1891,7 +1926,7 @@ mod tests {
                let payment_id = PaymentId([0; 32]);
 
                assert!(!outbound_payments.has_pending_payments());
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS {
@@ -1909,10 +1944,10 @@ mod tests {
                );
                assert!(pending_events.lock().unwrap().is_empty());
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_err());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_err());
        }
 
        #[test]
@@ -1922,7 +1957,7 @@ mod tests {
                let payment_id = PaymentId([0; 32]);
 
                assert!(!outbound_payments.has_pending_payments());
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                outbound_payments.abandon_payment(
@@ -1950,7 +1985,7 @@ mod tests {
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                let created_at = now() - DEFAULT_RELATIVE_EXPIRY;
@@ -1996,7 +2031,7 @@ mod tests {
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
@@ -2049,7 +2084,7 @@ mod tests {
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
@@ -2129,6 +2164,7 @@ mod tests {
                                                                channel_features: ChannelFeatures::empty(),
                                                                fee_msat: invoice.amount_msats(),
                                                                cltv_expiry_delta: 0,
+                                                               maybe_announced_channel: true,
                                                        }
                                                ],
                                                blinded_tail: None,
@@ -2149,7 +2185,7 @@ mod tests {
                assert!(!outbound_payments.has_pending_payments());
                assert!(pending_events.lock().unwrap().is_empty());
 
-               assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok());
+               assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
                assert!(outbound_payments.has_pending_payments());
 
                assert_eq!(