]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Make PaymentFailureReason downgradable
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 9 Aug 2024 16:07:35 +0000 (11:07 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Wed, 14 Aug 2024 15:55:59 +0000 (10:55 -0500)
The PaymentFailureReason variants for invoice request failures will
cause downgrades to break. Instead, use a new TLV for the reason and
continue to write the old TLV, only use None for the new reasons.

lightning/src/events/mod.rs
pending_changelog/3192-invoice-request-failed-event.txt

index 938100a901cb3f4740bde150670f42c7896406bb..63f19ed222bc87ea8764177b76b96e204c20ad04 100644 (file)
@@ -502,6 +502,12 @@ impl_writeable_tlv_based_enum!(InterceptNextHop,
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum PaymentFailureReason {
        /// The intended recipient rejected our payment.
+       ///
+       /// Also used for [`UnknownRequiredFeatures`] and [`InvoiceRequestRejected`] when downgrading to
+       /// version prior to 0.0.124.
+       ///
+       /// [`UnknownRequiredFeatures`]: Self::UnknownRequiredFeatures
+       /// [`InvoiceRequestRejected`]: Self::InvoiceRequestRejected
        RecipientRejected,
        /// The user chose to abandon this payment by calling [`ChannelManager::abandon_payment`].
        ///
@@ -517,7 +523,10 @@ pub enum PaymentFailureReason {
        /// The payment expired while retrying, based on the provided
        /// [`PaymentParameters::expiry_time`].
        ///
+       /// Also used for [`InvoiceRequestExpired`] when downgrading to version prior to 0.0.124.
+       ///
        /// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time
+       /// [`InvoiceRequestExpired`]: Self::InvoiceRequestExpired
        PaymentExpired,
        /// We failed to find a route while retrying the payment.
        ///
@@ -878,8 +887,8 @@ pub enum Event {
                /// [`Offer`]: crate::offers::offer::Offer
                payment_hash: Option<PaymentHash>,
                /// The reason the payment failed. This is only `None` for events generated or serialized
-               /// by versions prior to 0.0.115, or when downgrading to 0.0.124 or later with a reason that
-               /// was added after.
+               /// by versions prior to 0.0.115, or when downgrading to a version with a reason that was
+               /// added after.
                reason: Option<PaymentFailureReason>,
        },
        /// Indicates that a path for an outbound payment was successful.
@@ -1554,11 +1563,30 @@ impl Writeable for Event {
                                        Some(payment_hash) => (payment_hash, true),
                                        None => (&PaymentHash([0; 32]), false),
                                };
+                               let legacy_reason = match reason {
+                                       None => &None,
+                                       // Variants available prior to version 0.0.124.
+                                       Some(PaymentFailureReason::RecipientRejected)
+                                               | Some(PaymentFailureReason::UserAbandoned)
+                                               | Some(PaymentFailureReason::RetriesExhausted)
+                                               | Some(PaymentFailureReason::PaymentExpired)
+                                               | Some(PaymentFailureReason::RouteNotFound)
+                                               | Some(PaymentFailureReason::UnexpectedError) => reason,
+                                       // Variants introduced at version 0.0.124 or later. Prior versions fail to parse
+                                       // unknown variants, while versions 0.0.124 or later will use None.
+                                       Some(PaymentFailureReason::UnknownRequiredFeatures) =>
+                                               &Some(PaymentFailureReason::RecipientRejected),
+                                       Some(PaymentFailureReason::InvoiceRequestExpired) =>
+                                               &Some(PaymentFailureReason::RetriesExhausted),
+                                       Some(PaymentFailureReason::InvoiceRequestRejected) =>
+                                               &Some(PaymentFailureReason::RecipientRejected),
+                               };
                                write_tlv_fields!(writer, {
                                        (0, payment_id, required),
-                                       (1, reason, option),
+                                       (1, legacy_reason, option),
                                        (2, payment_hash, required),
                                        (3, invoice_received, required),
+                                       (5, reason, option),
                                })
                        },
                        &Event::OpenChannelRequest { .. } => {
@@ -1926,17 +1954,20 @@ impl MaybeReadable for Event {
                                        let mut payment_hash = PaymentHash([0; 32]);
                                        let mut payment_id = PaymentId([0; 32]);
                                        let mut reason = None;
+                                       let mut legacy_reason = None;
                                        let mut invoice_received: Option<bool> = None;
                                        read_tlv_fields!(reader, {
                                                (0, payment_id, required),
-                                               (1, reason, upgradable_option),
+                                               (1, legacy_reason, upgradable_option),
                                                (2, payment_hash, required),
                                                (3, invoice_received, option),
+                                               (5, reason, upgradable_option),
                                        });
                                        let payment_hash = match invoice_received {
                                                Some(invoice_received) => invoice_received.then(|| payment_hash),
                                                None => (payment_hash != PaymentHash([0; 32])).then(|| payment_hash),
                                        };
+                                       let reason = reason.or(legacy_reason);
                                        Ok(Some(Event::PaymentFailed {
                                                payment_id,
                                                payment_hash,
index f1ab8df5bc955b8aca0378014ab3ba2f2efeb1ee..26d736c0e41193a66b2af7a1a57a76b25a2dba3f 100644 (file)
@@ -10,3 +10,9 @@
  * Any `Event::PaymentFailed` generated without a payment hash will deserialize
    with `PaymentHash([0; 32])` when downgrading. This can be treated like an
    `Event::InvoiceRequestFailed` (#3192).
+ * An `Event::PaymentFailed` generated with one of the following
+   `PaymentFailureReason`s will deserialize with the corresponding reason after
+   downgrading to a version prior to 0.0.124:
+   - `UnknownRequiredFeatures` to `RecipientRejected`,
+   - `InvoiceRequestExpired` to `RetriesExhausted`, and
+   - `InvoiceRequestRejected` to `RecipientRejected` (#3192).