Await for invoices using an absolute expiry
[rust-lightning] / lightning / src / ln / outbound_payment.rs
index 4cc8d85ad19e29d77525c741bd1b59175f4ca7e8..d679036c1d69a25153b500ffea06ccc4b2efcfc9 100644 (file)
@@ -29,6 +29,7 @@ use crate::util::ser::ReadableArgs;
 
 use core::fmt::{self, Display, Formatter};
 use core::ops::Deref;
+use core::time::Duration;
 
 use crate::prelude::*;
 use crate::sync::Mutex;
@@ -39,12 +40,6 @@ use crate::sync::Mutex;
 /// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
 pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;
 
-/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until an invoice request without
-/// a response is timed out.
-///
-/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
-const INVOICE_REQUEST_TIMEOUT_TICKS: u8 = 3;
-
 /// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
 /// and later, also stores information for retrying the payment.
 pub(crate) enum PendingOutboundPayment {
@@ -52,7 +47,7 @@ pub(crate) enum PendingOutboundPayment {
                session_privs: HashSet<[u8; 32]>,
        },
        AwaitingInvoice {
-               timer_ticks_without_response: u8,
+               absolute_expiry: Duration,
                retry_strategy: Retry,
                max_total_routing_fee_msat: Option<u64>,
        },
@@ -594,10 +589,26 @@ impl RecipientOnionFields {
        /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
        /// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
        /// This is validated when setting this field using [`Self::with_custom_tlvs`].
+       #[cfg(not(c_bindings))]
        pub fn custom_tlvs(&self) -> &Vec<(u64, Vec<u8>)> {
                &self.custom_tlvs
        }
 
+       /// Gets the custom TLVs that will be sent or have been received.
+       ///
+       /// Custom TLVs allow sending extra application-specific data with a payment. They provide
+       /// additional flexibility on top of payment metadata, as while other implementations may
+       /// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs
+       /// do not have this restriction.
+       ///
+       /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
+       /// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
+       /// This is validated when setting this field using [`Self::with_custom_tlvs`].
+       #[cfg(c_bindings)]
+       pub fn custom_tlvs(&self) -> Vec<(u64, Vec<u8>)> {
+               self.custom_tlvs.clone()
+       }
+
        /// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
        /// have to make sure that some fields match exactly across the parts. For those that aren't
        /// required to match, if they don't match we should remove them so as to not expose data
@@ -885,12 +896,10 @@ impl OutboundPayments {
                        RetryableSendFailure::RouteNotFound
                })?;
 
-               if let Some(route_route_params) = route.route_params.as_mut() {
-                       if route_route_params.final_value_msat != route_params.final_value_msat {
-                               debug_assert!(false,
-                                       "Routers are expected to return a route which includes the requested final_value_msat");
-                               route_route_params.final_value_msat = route_params.final_value_msat;
-                       }
+               if route.route_params.as_ref() != Some(&route_params) {
+                       debug_assert!(false,
+                               "Routers are expected to return a Route which includes the requested RouteParameters");
+                       route.route_params = Some(route_params.clone());
                }
 
                let onion_session_privs = self.add_new_pending_payment(payment_hash,
@@ -947,12 +956,10 @@ impl OutboundPayments {
                        }
                };
 
-               if let Some(route_route_params) = route.route_params.as_mut() {
-                       if route_route_params.final_value_msat != route_params.final_value_msat {
-                               debug_assert!(false,
-                                       "Routers are expected to return a route which includes the requested final_value_msat");
-                               route_route_params.final_value_msat = route_params.final_value_msat;
-                       }
+               if route.route_params.as_ref() != Some(&route_params) {
+                       debug_assert!(false,
+                               "Routers are expected to return a Route which includes the requested RouteParameters");
+                       route.route_params = Some(route_params.clone());
                }
 
                for path in route.paths.iter() {
@@ -1262,15 +1269,16 @@ impl OutboundPayments {
        }
 
        #[allow(unused)]
-       pub(super) fn add_new_awaiting_invoice(
-               &self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
+       fn add_new_awaiting_invoice(
+               &self, payment_id: PaymentId, absolute_expiry: Duration, retry_strategy: Retry,
+               max_total_routing_fee_msat: Option<u64>
        ) -> 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,
+                                       absolute_expiry,
                                        retry_strategy,
                                        max_total_routing_fee_msat,
                                });
@@ -1354,12 +1362,14 @@ impl OutboundPayments {
                }
                let mut has_ok = false;
                let mut has_err = false;
-               let mut pending_amt_unsent = 0;
+               let mut has_unsent = false;
                let mut total_ok_fees_msat = 0;
+               let mut total_ok_amt_sent_msat = 0;
                for (res, path) in results.iter().zip(route.paths.iter()) {
                        if res.is_ok() {
                                has_ok = true;
                                total_ok_fees_msat += path.fee_msat();
+                               total_ok_amt_sent_msat += path.final_value_msat();
                        }
                        if res.is_err() { has_err = true; }
                        if let &Err(APIError::MonitorUpdateInProgress) = res {
@@ -1368,23 +1378,27 @@ impl OutboundPayments {
                                has_err = true;
                                has_ok = true;
                                total_ok_fees_msat += path.fee_msat();
+                               total_ok_amt_sent_msat += path.final_value_msat();
                        } else if res.is_err() {
-                               pending_amt_unsent += path.final_value_msat();
+                               has_unsent = true;
                        }
                }
                if has_err && has_ok {
                        Err(PaymentSendFailure::PartialFailure {
                                results,
                                payment_id,
-                               failed_paths_retry: if pending_amt_unsent != 0 {
+                               failed_paths_retry: if has_unsent {
                                        if let Some(route_params) = &route.route_params {
                                                let mut route_params = route_params.clone();
                                                // We calculate the leftover fee budget we're allowed to spend by
                                                // subtracting the used fee from the total fee budget.
                                                route_params.max_total_routing_fee_msat = route_params
                                                        .max_total_routing_fee_msat.map(|m| m.saturating_sub(total_ok_fees_msat));
-                                               route_params.final_value_msat = pending_amt_unsent;
 
+                                               // We calculate the remaining target amount by subtracting the succeded
+                                               // path values.
+                                               route_params.final_value_msat = route_params.final_value_msat
+                                                       .saturating_sub(total_ok_amt_sent_msat);
                                                Some(route_params)
                                        } else { None }
                                } else { None },
@@ -1493,11 +1507,15 @@ impl OutboundPayments {
        }
 
        pub(super) fn remove_stale_payments(
-               &self, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
+               &self, duration_since_epoch: Duration,
+               pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
        {
                let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
+               #[cfg(not(invreqfailed))]
+               let pending_events = pending_events.lock().unwrap();
+               #[cfg(invreqfailed)]
                let mut pending_events = pending_events.lock().unwrap();
-               pending_outbound_payments.retain(|payment_id, payment| {
+               pending_outbound_payments.retain(|payment_id, payment| match payment {
                        // If an outbound payment was completed, and no pending HTLCs remain, we should remove it
                        // from the map. However, if we did that immediately when the last payment HTLC is claimed,
                        // this could race the user making a duplicate send_payment call and our idempotency
@@ -1505,7 +1523,7 @@ impl OutboundPayments {
                        // removal. This should be more than sufficient to ensure the idempotency of any
                        // `send_payment` calls that were made at the same time the `PaymentSent` event was being
                        // processed.
-                       if let PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment {
+                       PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } => {
                                let mut no_remaining_entries = session_privs.is_empty();
                                if no_remaining_entries {
                                        for (ev, _) in pending_events.iter() {
@@ -1529,17 +1547,19 @@ impl OutboundPayments {
                                        *timer_ticks_without_htlcs = 0;
                                        true
                                }
-                       } else if let PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response, .. } = payment {
-                               *timer_ticks_without_response += 1;
-                               if *timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS {
+                       },
+                       PendingOutboundPayment::AwaitingInvoice { absolute_expiry, ..  } => {
+                               if duration_since_epoch < *absolute_expiry {
                                        true
                                } else {
+                                       #[cfg(invreqfailed)]
                                        pending_events.push_back(
                                                (events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None)
                                        );
                                        false
                                }
-                       } else { true }
+                       },
+                       _ => true,
                });
        }
 
@@ -1686,6 +1706,7 @@ impl OutboundPayments {
                                        payment.remove();
                                }
                        } else if let PendingOutboundPayment::AwaitingInvoice { .. } = payment.get() {
+                               #[cfg(invreqfailed)]
                                pending_events.lock().unwrap().push_back((events::Event::InvoiceRequestFailed {
                                        payment_id,
                                }, None));
@@ -1755,7 +1776,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
                (2, payment_hash, required),
        },
        (5, AwaitingInvoice) => {
-               (0, timer_ticks_without_response, required),
+               (0, absolute_expiry, required),
                (2, retry_strategy, required),
                (4, max_total_routing_fee_msat, option),
        },
@@ -1771,12 +1792,14 @@ mod tests {
        use bitcoin::network::constants::Network;
        use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
 
+       use core::time::Duration;
+
        use crate::events::{Event, PathFailure, PaymentFailureReason};
        use crate::ln::PaymentHash;
        use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
        use crate::ln::features::{ChannelFeatures, NodeFeatures};
        use crate::ln::msgs::{ErrorAction, LightningError};
-       use crate::ln::outbound_payment::{Bolt12PaymentError, INVOICE_REQUEST_TIMEOUT_TICKS, OutboundPayments, Retry, RetryableSendFailure};
+       use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure};
        use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
        use crate::offers::offer::OfferBuilder;
        use crate::offers::test_utils::*;
@@ -1929,7 +1952,9 @@ mod tests {
                router.expect_find_route(route_params.clone(), Ok(route.clone()));
                let mut route_params_w_failed_scid = route_params.clone();
                route_params_w_failed_scid.payment_params.previously_failed_channels.push(failed_scid);
-               router.expect_find_route(route_params_w_failed_scid, Ok(route.clone()));
+               let mut route_w_failed_scid = route.clone();
+               route_w_failed_scid.route_params = Some(route_params_w_failed_scid.clone());
+               router.expect_find_route(route_params_w_failed_scid, Ok(route_w_failed_scid));
                router.expect_find_route(route_params.clone(), Ok(route.clone()));
                router.expect_find_route(route_params.clone(), Ok(route.clone()));
 
@@ -1979,24 +2004,33 @@ mod tests {
        }
 
        #[test]
+       #[cfg(invreqfailed)]
        fn removes_stale_awaiting_invoice() {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
+               let tick_interval = 10;
 
                assert!(!outbound_payments.has_pending_payments());
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
-               for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS {
-                       outbound_payments.remove_stale_payments(&pending_events);
+               for seconds_since_epoch in (0..absolute_expiry).step_by(tick_interval) {
+                       let duration_since_epoch = Duration::from_secs(seconds_since_epoch);
+                       outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
                        assert!(outbound_payments.has_pending_payments());
                        assert!(pending_events.lock().unwrap().is_empty());
                }
 
-               outbound_payments.remove_stale_payments(&pending_events);
+               let duration_since_epoch = Duration::from_secs(absolute_expiry);
+               outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
                assert!(!outbound_payments.has_pending_payments());
                assert!(!pending_events.lock().unwrap().is_empty());
                assert_eq!(
@@ -2006,25 +2040,32 @@ mod tests {
                assert!(pending_events.lock().unwrap().is_empty());
 
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None)
-                               .is_err()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
+                       ).is_err()
                );
        }
 
        #[test]
+       #[cfg(invreqfailed)]
        fn removes_abandoned_awaiting_invoice() {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
 
                assert!(!outbound_payments.has_pending_payments());
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
@@ -2052,9 +2093,12 @@ mod tests {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
 
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
@@ -2100,6 +2144,7 @@ mod tests {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
 
                let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
                        .amount_msats(1000)
@@ -2111,9 +2156,11 @@ mod tests {
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
 
-               assert!(outbound_payments.add_new_awaiting_invoice(
-                               payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
-                       .is_ok()
+               assert!(
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
+                               Some(invoice.amount_msats() / 100 + 50_000)
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
@@ -2156,6 +2203,7 @@ mod tests {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
 
                let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
                        .amount_msats(1000)
@@ -2167,9 +2215,11 @@ mod tests {
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
 
-               assert!(outbound_payments.add_new_awaiting_invoice(
-                               payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
-                       .is_ok()
+               assert!(
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
+                               Some(invoice.amount_msats() / 100 + 50_000)
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());
 
@@ -2212,6 +2262,7 @@ mod tests {
                let pending_events = Mutex::new(VecDeque::new());
                let outbound_payments = OutboundPayments::new();
                let payment_id = PaymentId([0; 32]);
+               let absolute_expiry = 100;
 
                let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
                        .amount_msats(1000)
@@ -2263,7 +2314,9 @@ mod tests {
                assert!(pending_events.lock().unwrap().is_empty());
 
                assert!(
-                       outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
+                       outbound_payments.add_new_awaiting_invoice(
+                               payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), Some(1234)
+                       ).is_ok()
                );
                assert!(outbound_payments.has_pending_payments());