Pass pending_events into pay_internal
[rust-lightning] / lightning / src / ln / outbound_payment.rs
index 3a49d7c2be6403da603369a63aa2876e4b5163f1..2ddd3e2e006a9935a8c7243800a98aa36278d60f 100644 (file)
@@ -15,7 +15,7 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
 
 use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
 use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
+use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
 use crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA as LDK_DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA;
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils::HTLCFailReason;
@@ -30,7 +30,6 @@ use crate::util::time::tests::SinceEpoch;
 use core::cmp;
 use core::fmt::{self, Display, Formatter};
 use core::ops::Deref;
-use core::time::Duration;
 
 use crate::prelude::*;
 use crate::sync::Mutex;
@@ -163,13 +162,13 @@ impl PendingOutboundPayment {
                let our_payment_hash;
                core::mem::swap(&mut session_privs, match self {
                        PendingOutboundPayment::Legacy { .. } |
-                               PendingOutboundPayment::Fulfilled { .. } =>
+                       PendingOutboundPayment::Fulfilled { .. } =>
                                return Err(()),
-                               PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } |
-                                       PendingOutboundPayment::Abandoned { session_privs, payment_hash, .. } => {
-                                               our_payment_hash = *payment_hash;
-                                               session_privs
-                                       },
+                       PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } |
+                       PendingOutboundPayment::Abandoned { session_privs, payment_hash, .. } => {
+                               our_payment_hash = *payment_hash;
+                               session_privs
+                       },
                });
                *self = PendingOutboundPayment::Abandoned { session_privs, payment_hash: our_payment_hash };
                Ok(())
@@ -323,68 +322,58 @@ pub enum PaymentSendFailure {
        ///
        /// You can freely resend the payment in full (with the parameter error fixed).
        ///
-       /// Because the payment failed outright, no payment tracking is done, you do not need to call
-       /// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
-       /// for this payment.
+       /// Because the payment failed outright, no payment tracking is done and no
+       /// [`Event::PaymentPathFailed`] or [`Event::PaymentFailed`] events will be generated.
        ///
-       /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
-       /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment
+       /// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
+       /// [`Event::PaymentFailed`]: crate::util::events::Event::PaymentFailed
        ParameterError(APIError),
        /// A parameter in a single path which was passed to send_payment was invalid, preventing us
        /// from attempting to send the payment at all.
        ///
        /// You can freely resend the payment in full (with the parameter error fixed).
        ///
+       /// Because the payment failed outright, no payment tracking is done and no
+       /// [`Event::PaymentPathFailed`] or [`Event::PaymentFailed`] events will be generated.
+       ///
        /// The results here are ordered the same as the paths in the route object which was passed to
        /// send_payment.
        ///
-       /// Because the payment failed outright, no payment tracking is done, you do not need to call
-       /// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
-       /// for this payment.
-       ///
-       /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
-       /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment
+       /// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
+       /// [`Event::PaymentFailed`]: crate::util::events::Event::PaymentFailed
        PathParameterError(Vec<Result<(), APIError>>),
        /// All paths which were attempted failed to send, with no channel state change taking place.
        /// You can freely resend the payment in full (though you probably want to do so over different
        /// paths than the ones selected).
        ///
-       /// Because the payment failed outright, no payment tracking is done, you do not need to call
-       /// [`ChannelManager::abandon_payment`] and [`ChannelManager::retry_payment`] will *not* work
-       /// for this payment.
+       /// Because the payment failed outright, no payment tracking is done and no
+       /// [`Event::PaymentPathFailed`] or [`Event::PaymentFailed`] events will be generated.
        ///
-       /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
-       /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment
+       /// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
+       /// [`Event::PaymentFailed`]: crate::util::events::Event::PaymentFailed
        AllFailedResendSafe(Vec<APIError>),
        /// Indicates that a payment for the provided [`PaymentId`] is already in-flight and has not
-       /// yet completed (i.e. generated an [`Event::PaymentSent`]) or been abandoned (via
-       /// [`ChannelManager::abandon_payment`]).
+       /// yet completed (i.e. generated an [`Event::PaymentSent`] or [`Event::PaymentFailed`]).
        ///
        /// [`PaymentId`]: crate::ln::channelmanager::PaymentId
        /// [`Event::PaymentSent`]: crate::util::events::Event::PaymentSent
-       /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
+       /// [`Event::PaymentFailed`]: crate::util::events::Event::PaymentFailed
        DuplicatePayment,
-       /// Some paths which were attempted failed to send, though possibly not all. At least some
-       /// paths have irrevocably committed to the HTLC and retrying the payment in full would result
-       /// in over-/re-payment.
+       /// Some paths that were attempted failed to send, though some paths may have succeeded. At least
+       /// some paths have irrevocably committed to the HTLC.
        ///
-       /// The results here are ordered the same as the paths in the route object which was passed to
-       /// send_payment, and any `Err`s which are not [`APIError::MonitorUpdateInProgress`] can be
-       /// safely retried via [`ChannelManager::retry_payment`].
+       /// The results here are ordered the same as the paths in the route object that was passed to
+       /// send_payment.
        ///
-       /// Any entries which contain `Err(APIError::MonitorUpdateInprogress)` or `Ok(())` MUST NOT be
-       /// retried as they will result in over-/re-payment. These HTLCs all either successfully sent
-       /// (in the case of `Ok(())`) or will send once a [`MonitorEvent::Completed`] is provided for
-       /// the next-hop channel with the latest update_id.
+       /// Any entries that contain `Err(APIError::MonitorUpdateInprogress)` will send once a
+       /// [`MonitorEvent::Completed`] is provided for the next-hop channel with the latest update_id.
        ///
-       /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment
        /// [`MonitorEvent::Completed`]: crate::chain::channelmonitor::MonitorEvent::Completed
        PartialFailure {
-               /// The errors themselves, in the same order as the route hops.
+               /// The errors themselves, in the same order as the paths from the route.
                results: Vec<Result<(), APIError>>,
                /// If some paths failed without irrevocably committing to the new HTLC(s), this will
-               /// contain a [`RouteParameters`] object which can be used to calculate a new route that
-               /// will pay all remaining unpaid balance.
+               /// contain a [`RouteParameters`] object for the failing paths.
                failed_paths_retry: Option<RouteParameters>,
                /// The payment id for the payment, which is now at least partially pending.
                payment_id: PaymentId,
@@ -393,32 +382,36 @@ pub enum PaymentSendFailure {
 
 pub(super) struct OutboundPayments {
        pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
+       pub(super) retry_lock: Mutex<()>,
 }
 
 impl OutboundPayments {
        pub(super) fn new() -> Self {
                Self {
-                       pending_outbound_payments: Mutex::new(HashMap::new())
+                       pending_outbound_payments: Mutex::new(HashMap::new()),
+                       retry_lock: Mutex::new(()),
                }
        }
 
-       pub(super) fn send_payment<R: Deref, ES: Deref, NS: Deref, F, L: Deref>(
+       pub(super) fn send_payment<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
                &self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId,
                retry_strategy: Retry, route_params: RouteParameters, router: &R,
-               first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
-               node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F,
+               first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
+               node_signer: &NS, best_block_height: u32, logger: &L,
+               pending_events: &Mutex<Vec<events::Event>>, send_payment_along_path: SP,
        ) -> Result<(), PaymentSendFailure>
        where
                R::Target: Router,
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
                L::Target: Logger,
-               F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+               IH: Fn() -> InFlightHtlcs,
+               SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                         u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
        {
-               self.pay_internal(payment_id, Some((payment_hash, payment_secret, None, retry_strategy)),
-                       route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
-                       best_block_height, logger, &send_payment_along_path)
+               self.pay_internal(payment_id, payment_hash, Some((payment_secret, None, retry_strategy)),
+                       route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
+                       best_block_height, logger, pending_events, &send_payment_along_path)
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
        }
 
@@ -439,28 +432,28 @@ impl OutboundPayments {
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
        }
 
-       pub(super) fn send_spontaneous_payment<R: Deref, ES: Deref, NS: Deref, F, L: Deref>(
+       pub(super) fn send_spontaneous_payment<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
                &self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId,
                retry_strategy: Retry, route_params: RouteParameters, router: &R,
-               first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
-               node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F
+               first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES,
+               node_signer: &NS, best_block_height: u32, logger: &L,
+               pending_events: &Mutex<Vec<events::Event>>, send_payment_along_path: SP
        ) -> Result<PaymentHash, PaymentSendFailure>
        where
                R::Target: Router,
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
                L::Target: Logger,
-               F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+               IH: Fn() -> InFlightHtlcs,
+               SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                         u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
        {
-               let preimage = match payment_preimage {
-                       Some(p) => p,
-                       None => PaymentPreimage(entropy_source.get_secure_random_bytes()),
-               };
+               let preimage = payment_preimage
+                       .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
                let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
-               self.pay_internal(payment_id, Some((payment_hash, &None, Some(preimage), retry_strategy)),
-                       route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
-                       best_block_height, logger, &send_payment_along_path)
+               self.pay_internal(payment_id, payment_hash, Some((&None, Some(preimage), retry_strategy)),
+                       route_params, router, first_hops, &inflight_htlcs, entropy_source, node_signer,
+                       best_block_height, logger, pending_events, &send_payment_along_path)
                        .map(|()| payment_hash)
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
        }
@@ -475,10 +468,8 @@ impl OutboundPayments {
                F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                   u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
-               let preimage = match payment_preimage {
-                       Some(p) => p,
-                       None => PaymentPreimage(entropy_source.get_secure_random_bytes()),
-               };
+               let preimage = payment_preimage
+                       .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
                let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
                let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?;
 
@@ -493,7 +484,8 @@ impl OutboundPayments {
 
        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, logger: &L, send_payment_along_path: SP,
+               best_block_height: u32, pending_events: &Mutex<Vec<events::Event>>, logger: &L,
+               send_payment_along_path: SP,
        )
        where
                R::Target: Router,
@@ -505,14 +497,15 @@ impl OutboundPayments {
                FH: Fn() -> Vec<ChannelDetails>,
                L::Target: Logger,
        {
+               let _single_thread = self.retry_lock.lock().unwrap();
                loop {
                        let mut outbounds = self.pending_outbound_payments.lock().unwrap();
                        let mut retry_id_route_params = None;
                        for (pmt_id, pmt) in outbounds.iter_mut() {
                                if pmt.is_auto_retryable_now() {
-                                       if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), .. } = pmt {
+                                       if let PendingOutboundPayment::Retryable { payment_hash, pending_amt_msat, total_msat, payment_params: Some(params), .. } = pmt {
                                                if pending_amt_msat < total_msat {
-                                                       retry_id_route_params = Some((*pmt_id, RouteParameters {
+                                                       retry_id_route_params = Some((*pmt_id, *payment_hash, RouteParameters {
                                                                final_value_msat: *total_msat - *pending_amt_msat,
                                                                final_cltv_expiry_delta:
                                                                        if let Some(delta) = params.final_cltv_expiry_delta { delta }
@@ -527,28 +520,54 @@ impl OutboundPayments {
                                        }
                                }
                        }
-                       if let Some((payment_id, route_params)) = retry_id_route_params {
-                               core::mem::drop(outbounds);
-                               if let Err(e) = self.pay_internal(payment_id, None, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, logger, &send_payment_along_path) {
+                       core::mem::drop(outbounds);
+                       if let Some((payment_id, payment_hash, route_params)) = retry_id_route_params {
+                               if let Err(e) = self.pay_internal(payment_id, payment_hash, None, route_params, router, first_hops(), &inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path) {
                                        log_info!(logger, "Errored retrying payment: {:?}", e);
+                                       // If we error on retry, there is no chance of the payment succeeding and no HTLCs have
+                                       // been irrevocably committed to, so we can safely abandon.
+                                       self.abandon_payment(payment_id, pending_events);
                                }
                        } else { break }
                }
+
+               let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+               outbounds.retain(|pmt_id, pmt| {
+                       let mut retain = true;
+                       if !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 {
+                               if pmt.mark_abandoned().is_ok() {
+                                       pending_events.lock().unwrap().push(events::Event::PaymentFailed {
+                                               payment_id: *pmt_id,
+                                               payment_hash: pmt.payment_hash().expect("PendingOutboundPayments::Retryable always has a payment hash set"),
+                                       });
+                                       retain = false;
+                               }
+                       }
+                       retain
+               });
        }
 
-       fn pay_internal<R: Deref, NS: Deref, ES: Deref, F, L: Deref>(
-               &self, payment_id: PaymentId,
-               initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Option<PaymentPreimage>, Retry)>,
+       pub(super) fn needs_abandon(&self) -> bool {
+               let outbounds = self.pending_outbound_payments.lock().unwrap();
+               outbounds.iter().any(|(_, pmt)|
+                       !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 && !pmt.is_fulfilled())
+       }
+
+       /// Will return `Ok(())` iff at least one HTLC is sent for the payment.
+       fn pay_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
+               &self, payment_id: PaymentId, payment_hash: PaymentHash,
+               initial_send_info: Option<(&Option<PaymentSecret>, Option<PaymentPreimage>, Retry)>,
                route_params: RouteParameters, router: &R, first_hops: Vec<ChannelDetails>,
-               inflight_htlcs: InFlightHtlcs, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
-               logger: &L, send_payment_along_path: &F,
+               inflight_htlcs: &IH, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
+               logger: &L, pending_events: &Mutex<Vec<events::Event>>, send_payment_along_path: &SP,
        ) -> Result<(), PaymentSendFailure>
        where
                R::Target: Router,
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
                L::Target: Logger,
-               F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+               IH: Fn() -> InFlightHtlcs,
+               SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                   u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                #[cfg(feature = "std")] {
@@ -561,12 +580,12 @@ impl OutboundPayments {
 
                let route = router.find_route(
                        &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
-                       Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs
+                       Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs(),
                ).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
                        err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
                }))?;
 
-               let res = if let Some((payment_hash, payment_secret, keysend_preimage, retry_strategy)) = initial_send_info {
+               let res = if let Some((payment_secret, keysend_preimage, retry_strategy)) = initial_send_info {
                        let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, keysend_preimage, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?;
                        self.pay_route_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path)
                } else {
@@ -574,7 +593,7 @@ impl OutboundPayments {
                };
                match res {
                        Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
-                               let retry_res = self.pay_internal(payment_id, None, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, send_payment_along_path);
+                               let retry_res = self.pay_internal(payment_id, payment_hash, None, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
                                log_info!(logger, "Result retrying payment id {}: {:?}", log_bytes!(payment_id.0), retry_res);
                                if let Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError { err })) = &retry_res {
                                        if err.starts_with("Retries exhausted ") { return res; }
@@ -585,7 +604,7 @@ impl OutboundPayments {
                                // Some paths were sent, even if we failed to send the full MPP value our recipient may
                                // misbehave and claim the funds, at which point we have to consider the payment sent, so
                                // return `Ok()` here, ignoring any retry errors.
-                               let retry_res = self.pay_internal(payment_id, None, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, send_payment_along_path);
+                               let retry_res = self.pay_internal(payment_id, payment_hash, None, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
                                log_info!(logger, "Result retrying payment id {}: {:?}", log_bytes!(payment_id.0), retry_res);
                                Ok(())
                        },
@@ -877,8 +896,8 @@ impl OutboundPayments {
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
        }
 
-       // If we failed to send any paths, we should remove the new PaymentId from the
-       // `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`.
+       // If we failed to send any paths, remove the new PaymentId from the `pending_outbound_payments`
+       // map as the payment is free to be resent.
        fn remove_outbound_if_all_failed(&self, payment_id: PaymentId, err: &PaymentSendFailure) {
                if let &PaymentSendFailure::AllFailedResendSafe(_) = err {
                        let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some();
@@ -994,34 +1013,51 @@ impl OutboundPayments {
                });
        }
 
+       // Returns a bool indicating whether a PendingHTLCsForwardable event should be generated.
        pub(super) fn fail_htlc<L: Deref>(
                &self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason,
                path: &Vec<RouteHop>, session_priv: &SecretKey, payment_id: &PaymentId,
                payment_params: &Option<PaymentParameters>, probing_cookie_secret: [u8; 32],
                secp_ctx: &Secp256k1<secp256k1::All>, pending_events: &Mutex<Vec<events::Event>>, logger: &L
-       ) where L::Target: Logger {
+       ) -> bool where L::Target: Logger {
                #[cfg(test)]
                let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
                #[cfg(not(test))]
                let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
 
+               let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
                let mut session_priv_bytes = [0; 32];
                session_priv_bytes.copy_from_slice(&session_priv[..]);
                let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+
+               // If any payments already need retry, there's no need to generate a redundant
+               // `PendingHTLCsForwardable`.
+               let already_awaiting_retry = outbounds.iter().any(|(_, pmt)| {
+                       let mut awaiting_retry = false;
+                       if pmt.is_auto_retryable_now() {
+                               if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, .. } = pmt {
+                                       if pending_amt_msat < total_msat {
+                                               awaiting_retry = true;
+                                       }
+                               }
+                       }
+                       awaiting_retry
+               });
+
                let mut all_paths_failed = false;
                let mut full_failure_ev = None;
-               let mut pending_retry_ev = None;
+               let mut pending_retry_ev = false;
                let mut retry = None;
                let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
                        if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
                                log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
-                               return
+                               return false
                        }
                        if payment.get().is_fulfilled() {
                                log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", log_bytes!(payment_hash.0));
-                               return
+                               return false
                        }
-                       let is_retryable_now = payment.get().is_auto_retryable_now();
+                       let mut is_retryable_now = payment.get().is_auto_retryable_now();
                        if let Some(scid) = short_channel_id {
                                payment.get_mut().insert_previously_failed_scid(scid);
                        }
@@ -1052,26 +1088,32 @@ impl OutboundPayments {
                                });
                        }
 
+                       if payment_is_probe || !is_retryable_now || !payment_retryable || retry.is_none() {
+                               let _ = payment.get_mut().mark_abandoned(); // we'll only Err if it's a legacy payment
+                               is_retryable_now = false;
+                       }
                        if payment.get().remaining_parts() == 0 {
                                all_paths_failed = true;
                                if payment.get().abandoned() {
-                                       full_failure_ev = Some(events::Event::PaymentFailed {
-                                               payment_id: *payment_id,
-                                               payment_hash: payment.get().payment_hash().expect("PendingOutboundPayments::RetriesExceeded always has a payment hash set"),
-                                       });
+                                       if !payment_is_probe {
+                                               full_failure_ev = Some(events::Event::PaymentFailed {
+                                                       payment_id: *payment_id,
+                                                       payment_hash: payment.get().payment_hash().expect("PendingOutboundPayments::RetriesExceeded always has a payment hash set"),
+                                               });
+                                       }
                                        payment.remove();
                                }
                        }
                        is_retryable_now
                } else {
                        log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
-                       return
+                       return false
                };
                core::mem::drop(outbounds);
                log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
 
                let path_failure = {
-                       if payment_is_probe(payment_hash, &payment_id, probing_cookie_secret) {
+                       if payment_is_probe {
                                if !payment_retryable {
                                        events::Event::ProbeSuccessful {
                                                payment_id: *payment_id,
@@ -1093,11 +1135,11 @@ impl OutboundPayments {
                                if let Some(scid) = short_channel_id {
                                        retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid));
                                }
-                               if payment_retryable && attempts_remaining && retry.is_some() {
+                               // If we miss abandoning the payment above, we *must* generate an event here or else the
+                               // payment will sit in our outbounds forever.
+                               if attempts_remaining && !already_awaiting_retry {
                                        debug_assert!(full_failure_ev.is_none());
-                                       pending_retry_ev = Some(events::Event::PendingHTLCsForwardable {
-                                               time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
-                                       });
+                                       pending_retry_ev = true;
                                }
                                events::Event::PaymentPathFailed {
                                        payment_id: Some(*payment_id),
@@ -1118,16 +1160,17 @@ impl OutboundPayments {
                let mut pending_events = pending_events.lock().unwrap();
                pending_events.push(path_failure);
                if let Some(ev) = full_failure_ev { pending_events.push(ev); }
-               if let Some(ev) = pending_retry_ev { pending_events.push(ev); }
+               pending_retry_ev
        }
 
-       pub(super) fn abandon_payment(&self, payment_id: PaymentId) -> Option<events::Event> {
-               let mut failed_ev = None;
+       pub(super) fn abandon_payment(
+               &self, payment_id: PaymentId, pending_events: &Mutex<Vec<events::Event>>
+       ) {
                let mut outbounds = self.pending_outbound_payments.lock().unwrap();
                if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) {
                        if let Ok(()) = payment.get_mut().mark_abandoned() {
                                if payment.get().remaining_parts() == 0 {
-                                       failed_ev = Some(events::Event::PaymentFailed {
+                                       pending_events.lock().unwrap().push(events::Event::PaymentFailed {
                                                payment_id,
                                                payment_hash: payment.get().payment_hash().expect("PendingOutboundPayments::RetriesExceeded always has a payment hash set"),
                                        });
@@ -1135,7 +1178,6 @@ impl OutboundPayments {
                                }
                        }
                }
-               failed_ev
        }
 
        #[cfg(test)]
@@ -1206,7 +1248,7 @@ mod tests {
        use crate::ln::outbound_payment::{OutboundPayments, Retry};
        use crate::routing::gossip::NetworkGraph;
        use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters};
-       use crate::sync::Arc;
+       use crate::sync::{Arc, Mutex};
        use crate::util::errors::APIError;
        use crate::util::test_utils;
 
@@ -1222,7 +1264,8 @@ mod tests {
                let logger = test_utils::TestLogger::new();
                let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
                let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger));
-               let router = test_utils::TestRouter::new(network_graph);
+               let scorer = Mutex::new(test_utils::TestScorer::new());
+               let router = test_utils::TestRouter::new(network_graph, &scorer);
                let secp_ctx = Secp256k1::new();
                let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
 
@@ -1236,15 +1279,17 @@ mod tests {
                        final_value_msat: 0,
                        final_cltv_expiry_delta: 0,
                };
+               let pending_events = Mutex::new(Vec::new());
                let err = if on_retry {
                        outbound_payments.pay_internal(
-                               PaymentId([0; 32]), None, expired_route_params, &&router, vec![], InFlightHtlcs::new(),
-                               &&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+                               PaymentId([0; 32]), PaymentHash([0; 32]), None, expired_route_params, &&router, vec![],
+                               &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
+                               &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
                } else {
                        outbound_payments.send_payment(
                                PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), expired_route_params,
-                               &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
-                               |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+                               &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
+                               &pending_events, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
                };
                if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
                        assert!(err.contains("Invoice expired"));
@@ -1261,7 +1306,8 @@ mod tests {
                let logger = test_utils::TestLogger::new();
                let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
                let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger));
-               let router = test_utils::TestRouter::new(network_graph);
+               let scorer = Mutex::new(test_utils::TestScorer::new());
+               let router = test_utils::TestRouter::new(network_graph, &scorer);
                let secp_ctx = Secp256k1::new();
                let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
 
@@ -1275,18 +1321,20 @@ mod tests {
                router.expect_find_route(route_params.clone(),
                        Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }));
 
+               let pending_events = Mutex::new(Vec::new());
                let err = if on_retry {
                        outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]), None,
                                &Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)),
                                Some(route_params.payment_params.clone()), &&keys_manager, 0).unwrap();
                        outbound_payments.pay_internal(
-                               PaymentId([0; 32]), None, route_params, &&router, vec![], InFlightHtlcs::new(),
-                               &&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+                               PaymentId([0; 32]), PaymentHash([0; 32]), None, route_params, &&router, vec![],
+                               &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
+                               &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
                } else {
                        outbound_payments.send_payment(
                                PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), route_params,
-                               &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
-                               |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+                               &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
+                               &pending_events, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
                };
                if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
                        assert!(err.contains("Failed to find a route"));