From 1dcb3ecb6c6f145bd22a64ce149e939c75aeae3d Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 13 Feb 2023 17:55:42 -0500 Subject: [PATCH] Change PaymentPathFailed's optional network update to a Failure enum This let us capture the errors when we fail without committing to an HTLC vs failing via update_fail. --- lightning-background-processor/src/lib.rs | 16 +++---- lightning/src/ln/functional_test_utils.rs | 39 ++++++++-------- lightning/src/ln/functional_tests.rs | 29 +++++------- lightning/src/ln/onion_route_tests.rs | 4 +- lightning/src/ln/outbound_payment.rs | 4 +- lightning/src/ln/payment_tests.rs | 12 +++-- lightning/src/util/events.rs | 55 ++++++++++++++++++----- pending_changelog/2043.txt | 5 +++ 8 files changed, 103 insertions(+), 61 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 3b692ac07..51d8a7f86 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -33,7 +33,7 @@ use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::utxo::UtxoLookup; use lightning::routing::router::Router; use lightning::routing::scoring::{Score, WriteableScore}; -use lightning::util::events::{Event, EventHandler, EventsProvider}; +use lightning::util::events::{Event, EventHandler, EventsProvider, PathFailure}; use lightning::util::logger::Logger; use lightning::util::persist::Persister; use lightning_rapid_gossip_sync::RapidGossipSync; @@ -215,10 +215,10 @@ where fn handle_network_graph_update( network_graph: &NetworkGraph, event: &Event ) where L::Target: Logger { - if let Event::PaymentPathFailed { ref network_update, .. } = event { - if let Some(network_update) = network_update { - network_graph.handle_network_update(&network_update); - } + if let Event::PaymentPathFailed { + failure: PathFailure::OnPath { network_update: Some(ref upd) }, .. } = event + { + network_graph.handle_network_update(upd); } } @@ -673,7 +673,7 @@ mod tests { use lightning::routing::router::{DefaultRouter, RouteHop}; use lightning::routing::scoring::{ChannelUsage, Score}; use lightning::util::config::UserConfig; - use lightning::util::events::{Event, MessageSendEventsProvider, MessageSendEvent}; + use lightning::util::events::{Event, PathFailure, MessageSendEventsProvider, MessageSendEvent}; use lightning::util::ser::Writeable; use lightning::util::test_utils; use lightning::util::persist::KVStorePersister; @@ -1365,7 +1365,7 @@ mod tests { payment_id: None, payment_hash: PaymentHash([42; 32]), payment_failed_permanently: false, - network_update: None, + failure: PathFailure::OnPath { network_update: None }, path: path.clone(), short_channel_id: Some(scored_scid), retry: None, @@ -1385,7 +1385,7 @@ mod tests { payment_id: None, payment_hash: PaymentHash([42; 32]), payment_failed_permanently: true, - network_update: None, + failure: PathFailure::OnPath { network_update: None }, path: path.clone(), short_channel_id: None, retry: None, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 2786d754a..d1203297d 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -24,7 +24,7 @@ use crate::util::enforcing_trait_impls::EnforcingSigner; use crate::util::scid_utils; use crate::util::test_utils; use crate::util::test_utils::{panicking, TestChainMonitor}; -use crate::util::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose}; +use crate::util::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose}; use crate::util::errors::APIError; use crate::util::config::UserConfig; use crate::util::ser::{ReadableArgs, Writeable}; @@ -1818,7 +1818,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( ) { if conditions.expected_mpp_parts_remain { assert_eq!(payment_failed_events.len(), 1); } else { assert_eq!(payment_failed_events.len(), 2); } let expected_payment_id = match &payment_failed_events[0] { - Event::PaymentPathFailed { payment_hash, payment_failed_permanently, path, retry, payment_id, network_update, short_channel_id, + Event::PaymentPathFailed { payment_hash, payment_failed_permanently, path, retry, payment_id, failure, short_channel_id, #[cfg(test)] error_code, #[cfg(test)] @@ -1843,23 +1843,24 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( } if let Some(chan_closed) = conditions.expected_blamed_chan_closed { - match network_update { - Some(NetworkUpdate::ChannelUpdateMessage { ref msg }) if !chan_closed => { - if let Some(scid) = conditions.expected_blamed_scid { - assert_eq!(msg.contents.short_channel_id, scid); - } - const CHAN_DISABLED_FLAG: u8 = 2; - assert_eq!(msg.contents.flags & CHAN_DISABLED_FLAG, 0); - }, - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent }) if chan_closed => { - if let Some(scid) = conditions.expected_blamed_scid { - assert_eq!(*short_channel_id, scid); - } - assert!(is_permanent); - }, - Some(_) => panic!("Unexpected update type"), - None => panic!("Expected update"), - } + if let PathFailure::OnPath { network_update: Some(upd) } = failure { + match upd { + NetworkUpdate::ChannelUpdateMessage { ref msg } if !chan_closed => { + if let Some(scid) = conditions.expected_blamed_scid { + assert_eq!(msg.contents.short_channel_id, scid); + } + const CHAN_DISABLED_FLAG: u8 = 2; + assert_eq!(msg.contents.flags & CHAN_DISABLED_FLAG, 0); + }, + NetworkUpdate::ChannelFailure { short_channel_id, is_permanent } if chan_closed => { + if let Some(scid) = conditions.expected_blamed_scid { + assert_eq!(*short_channel_id, scid); + } + assert!(is_permanent); + }, + _ => panic!("Unexpected update type"), + } + } else { panic!("Expected network update"); } } payment_id.unwrap() diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 32c3b5168..03eef4372 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -31,7 +31,7 @@ use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction}; use crate::util::enforcing_trait_impls::EnforcingSigner; use crate::util::test_utils; -use crate::util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason, HTLCDestination}; +use crate::util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination}; use crate::util::errors::APIError; use crate::util::ser::{Writeable, ReadableArgs}; use crate::util::config::UserConfig; @@ -3235,12 +3235,12 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 6); match events[0] { - Event::PaymentPathFailed { ref payment_hash, ref network_update, .. } => { + Event::PaymentPathFailed { ref payment_hash, ref failure, .. } => { assert!(failed_htlcs.insert(payment_hash.0)); // If we delivered B's RAA we got an unknown preimage error, not something // that we should update our routing table for. if !deliver_bs_raa { - assert!(network_update.is_some()); + if let PathFailure::OnPath { network_update: Some(_) } = failure { } else { panic!("Unexpected path failure") } } }, _ => panic!("Unexpected event"), @@ -3252,9 +3252,8 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use _ => panic!("Unexpected event"), } match events[2] { - Event::PaymentPathFailed { ref payment_hash, ref network_update, .. } => { + Event::PaymentPathFailed { ref payment_hash, failure: PathFailure::OnPath { network_update: Some(_) }, .. } => { assert!(failed_htlcs.insert(payment_hash.0)); - assert!(network_update.is_some()); }, _ => panic!("Unexpected event"), } @@ -3265,9 +3264,8 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use _ => panic!("Unexpected event"), } match events[4] { - Event::PaymentPathFailed { ref payment_hash, ref network_update, .. } => { + Event::PaymentPathFailed { ref payment_hash, failure: PathFailure::OnPath { network_update: Some(_) }, .. } => { assert!(failed_htlcs.insert(payment_hash.0)); - assert!(network_update.is_some()); }, _ => panic!("Unexpected event"), } @@ -5148,14 +5146,14 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno let mut as_failds = HashSet::new(); let mut as_updates = 0; for event in as_events.iter() { - if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref network_update, .. } = event { + if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref failure, .. } = event { assert!(as_failds.insert(*payment_hash)); if *payment_hash != payment_hash_2 { assert_eq!(*payment_failed_permanently, deliver_last_raa); } else { assert!(!payment_failed_permanently); } - if network_update.is_some() { + if let PathFailure::OnPath { network_update: Some(_) } = failure { as_updates += 1; } } else if let &Event::PaymentFailed { .. } = event { @@ -5174,14 +5172,14 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno let mut bs_failds = HashSet::new(); let mut bs_updates = 0; for event in bs_events.iter() { - if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref network_update, .. } = event { + if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref failure, .. } = event { assert!(bs_failds.insert(*payment_hash)); if *payment_hash != payment_hash_1 && *payment_hash != payment_hash_5 { assert_eq!(*payment_failed_permanently, deliver_last_raa); } else { assert!(!payment_failed_permanently); } - if network_update.is_some() { + if let PathFailure::OnPath { network_update: Some(_) } = failure { bs_updates += 1; } } else if let &Event::PaymentFailed { .. } = event { @@ -5695,11 +5693,10 @@ fn test_fail_holding_cell_htlc_upon_free() { let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); match &events[0] { - &Event::PaymentPathFailed { ref payment_id, ref payment_hash, ref payment_failed_permanently, ref network_update, ref short_channel_id, .. } => { + &Event::PaymentPathFailed { ref payment_id, ref payment_hash, ref payment_failed_permanently, failure: PathFailure::OnPath { network_update: None }, ref short_channel_id, .. } => { assert_eq!(PaymentId(our_payment_hash.0), *payment_id.as_ref().unwrap()); assert_eq!(our_payment_hash.clone(), *payment_hash); assert_eq!(*payment_failed_permanently, false); - assert_eq!(*network_update, None); assert_eq!(*short_channel_id, Some(route.paths[0][0].short_channel_id)); }, _ => panic!("Unexpected event"), @@ -5785,11 +5782,10 @@ fn test_free_and_fail_holding_cell_htlcs() { let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); match &events[0] { - &Event::PaymentPathFailed { ref payment_id, ref payment_hash, ref payment_failed_permanently, ref network_update, ref short_channel_id, .. } => { + &Event::PaymentPathFailed { ref payment_id, ref payment_hash, ref payment_failed_permanently, failure: PathFailure::OnPath { network_update: None }, ref short_channel_id, .. } => { assert_eq!(payment_id_2, *payment_id.as_ref().unwrap()); assert_eq!(payment_hash_2.clone(), *payment_hash); assert_eq!(*payment_failed_permanently, false); - assert_eq!(*network_update, None); assert_eq!(*short_channel_id, Some(route_2.paths[0][0].short_channel_id)); }, _ => panic!("Unexpected event"), @@ -6687,8 +6683,7 @@ fn test_channel_failed_after_message_with_badonion_node_perm_bits_set() { // Expect a PaymentPathFailed event with a ChannelFailure network update for the channel between // the node originating the error to its next hop. match events_5[0] { - Event::PaymentPathFailed { network_update: - Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent }), error_code, .. + Event::PaymentPathFailed { error_code, failure: PathFailure::OnPath { network_update: Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent }) }, .. } => { assert_eq!(short_channel_id, chan_2.0.contents.short_channel_id); assert!(is_permanent); diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index bc6dd1168..6ea8ab4ce 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -23,7 +23,7 @@ use crate::ln::features::{InitFeatures, InvoiceFeatures}; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate}; use crate::ln::wire::Encode; -use crate::util::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider}; +use crate::util::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure}; use crate::util::ser::{Writeable, Writer}; use crate::util::test_utils; use crate::util::config::{UserConfig, ChannelConfig}; @@ -167,7 +167,7 @@ fn run_onion_failure_test_with_fail_intercept(_name: &str, test_case: let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); - if let &Event::PaymentPathFailed { ref payment_failed_permanently, ref network_update, ref short_channel_id, ref error_code, .. } = &events[0] { + if let &Event::PaymentPathFailed { ref payment_failed_permanently, ref short_channel_id, ref error_code, failure: PathFailure::OnPath { ref network_update }, .. } = &events[0] { assert_eq!(*payment_failed_permanently, !expected_retryable); assert_eq!(*error_code, expected_error_code); if expected_channel_update.is_some() { diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 473e050de..e6454b0ae 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -793,7 +793,7 @@ impl OutboundPayments { payment_id: Some(payment_id), payment_hash, payment_failed_permanently: false, - network_update: None, + failure: events::PathFailure::InitialSend { err: e }, path, short_channel_id: failed_scid, retry: None, @@ -1255,7 +1255,7 @@ impl OutboundPayments { payment_id: Some(*payment_id), payment_hash: payment_hash.clone(), payment_failed_permanently: !payment_retryable, - network_update, + failure: events::PathFailure::OnPath { network_update }, path: path.clone(), short_channel_id, retry, diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 1d30bceff..4aa14dfa8 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -24,7 +24,7 @@ use crate::ln::outbound_payment::Retry; use crate::routing::gossip::{EffectiveCapacity, RoutingFees}; use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RouteParameters}; use crate::routing::scoring::ChannelUsage; -use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider}; +use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure}; use crate::util::test_utils; use crate::util::errors::APIError; use crate::util::ser::Writeable; @@ -2139,9 +2139,12 @@ fn retry_multi_path_single_failed_payment() { assert_eq!(events.len(), 1); match events[0] { Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently: false, - network_update: None, short_channel_id: Some(expected_scid), .. } => { + failure: PathFailure::InitialSend { err: APIError::ChannelUnavailable { err: ref err_msg }}, + short_channel_id: Some(expected_scid), .. } => + { assert_eq!(payment_hash, ev_payment_hash); assert_eq!(expected_scid, route.paths[1][0].short_channel_id); + assert!(err_msg.contains("max HTLC")); }, _ => panic!("Unexpected event"), } @@ -2212,9 +2215,12 @@ fn immediate_retry_on_failure() { assert_eq!(events.len(), 1); match events[0] { Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently: false, - network_update: None, short_channel_id: Some(expected_scid), .. } => { + failure: PathFailure::InitialSend { err: APIError::ChannelUnavailable { err: ref err_msg }}, + short_channel_id: Some(expected_scid), .. } => + { assert_eq!(payment_hash, ev_payment_hash); assert_eq!(expected_scid, route.paths[1][0].short_channel_id); + assert!(err_msg.contains("max HTLC")); }, _ => panic!("Unexpected event"), } diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index e083036bd..5bc8a5084 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -23,6 +23,7 @@ use crate::ln::features::ChannelTypeFeatures; use crate::ln::msgs; use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::routing::gossip::NetworkUpdate; +use crate::util::errors::APIError; use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength}; use crate::routing::router::{RouteHop, RouteParameters}; @@ -81,6 +82,39 @@ impl_writeable_tlv_based_enum!(PaymentPurpose, (2, SpontaneousPayment) ); +/// When the payment path failure took place and extra details about it. [`PathFailure::OnPath`] may +/// contain a [`NetworkUpdate`] that needs to be applied to the [`NetworkGraph`]. +/// +/// [`NetworkUpdate`]: crate::routing::gossip::NetworkUpdate +/// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PathFailure { + /// We failed to initially send the payment and no HTLC was committed to. Contains the relevant + /// error. + InitialSend { + /// The error surfaced from initial send. + err: APIError, + }, + /// A hop on the path failed to forward our payment. + OnPath { + /// If present, this [`NetworkUpdate`] should be applied to the [`NetworkGraph`] so that routing + /// decisions can take into account the update. + /// + /// [`NetworkUpdate`]: crate::routing::gossip::NetworkUpdate + /// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph + network_update: Option, + }, +} + +impl_writeable_tlv_based_enum_upgradable!(PathFailure, + (0, OnPath) => { + (0, network_update, upgradable_option), + }, + (2, InitialSend) => { + (0, err, upgradable_required), + }, +); + #[derive(Clone, Debug, PartialEq, Eq)] /// The reason the channel was closed. See individual variants more details. pub enum ClosureReason { @@ -588,7 +622,7 @@ pub enum Event { fee_paid_msat: Option, }, /// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events - /// provide failure information for each MPP part in the payment. + /// provide failure information for each path attempt in the payment, including retries. /// /// This event is provided once there are no further pending HTLCs for the payment and the /// payment is no longer retryable, due either to the [`Retry`] provided or @@ -651,14 +685,11 @@ pub enum Event { /// the payment has failed, not just the route in question. If this is not set, the payment may /// be retried via a different route. payment_failed_permanently: bool, - /// Any failure information conveyed via the Onion return packet by a node along the failed - /// payment route. - /// - /// Should be applied to the [`NetworkGraph`] so that routing decisions can take into - /// account the update. + /// Extra error details based on the failure type. May contain an update that needs to be + /// applied to the [`NetworkGraph`]. /// /// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph - network_update: Option, + failure: PathFailure, /// The payment path that failed. path: Vec, /// The channel responsible for the failed payment path. @@ -960,7 +991,7 @@ impl Writeable for Event { }); }, &Event::PaymentPathFailed { - ref payment_id, ref payment_hash, ref payment_failed_permanently, ref network_update, + ref payment_id, ref payment_hash, ref payment_failed_permanently, ref failure, ref path, ref short_channel_id, ref retry, #[cfg(test)] ref error_code, @@ -974,13 +1005,14 @@ impl Writeable for Event { error_data.write(writer)?; write_tlv_fields!(writer, { (0, payment_hash, required), - (1, network_update, option), + (1, None::, option), // network_update in LDK versions prior to 0.0.114 (2, payment_failed_permanently, required), (3, false, required), // all_paths_failed in LDK versions prior to 0.0.114 (5, *path, vec_type), (7, short_channel_id, option), (9, retry, option), (11, payment_id, option), + (13, failure, required), }); }, &Event::PendingHTLCsForwardable { time_forwardable: _ } => { @@ -1197,6 +1229,7 @@ impl MaybeReadable for Event { let mut short_channel_id = None; let mut retry = None; let mut payment_id = None; + let mut failure_opt = None; read_tlv_fields!(reader, { (0, payment_hash, required), (1, network_update, upgradable_option), @@ -1205,12 +1238,14 @@ impl MaybeReadable for Event { (7, short_channel_id, option), (9, retry, option), (11, payment_id, option), + (13, failure_opt, upgradable_option), }); + let failure = failure_opt.unwrap_or_else(|| PathFailure::OnPath { network_update }); Ok(Some(Event::PaymentPathFailed { payment_id, payment_hash, payment_failed_permanently, - network_update, + failure, path: path.unwrap(), short_channel_id, retry, diff --git a/pending_changelog/2043.txt b/pending_changelog/2043.txt index 2fc7c750c..bcd460ed8 100644 --- a/pending_changelog/2043.txt +++ b/pending_changelog/2043.txt @@ -1,8 +1,13 @@ ## API Updates +- `Event::PaymentPathFailed::network_update` has been replaced by a new `Failure` enum, which may + contain the `network_update` within it. See `Event::PaymentPathFailed::failure` and `Failure` docs + for more - `Event::PaymentPathFailed::all_paths_failed` has been removed, as we've dropped support for manual payment retries. ## Backwards Compatibility +- If downgrading from 0.0.114 to a previous version, `Event::PaymentPathFailed::network_update` will + always be `None`. - If downgrading from 0.0.114 to a previous version, `Event::PaymentPathFailed::all_paths_failed` will always be set to `false`. Users who wish to support downgrading and currently rely on the field should should first migrate to always calling `ChannelManager::abandon_payment` and awaiting -- 2.39.5