From: Matt Corallo <649246+TheBlueMatt@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:21:36 +0000 (+0000) Subject: Merge pull request #2818 from valentinewallace/2024-01-blinded-path-retries X-Git-Tag: v0.0.120~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;ds=sidebyside;h=871db638f23867006b4b7ec941a92b6709ccab9e;hp=-c;p=rust-lightning Merge pull request #2818 from valentinewallace/2024-01-blinded-path-retries Avoid retrying over previously failed blinded paths --- 871db638f23867006b4b7ec941a92b6709ccab9e diff --combined lightning/src/ln/blinded_payment_tests.rs index a3126b3b5,69b0e74a0..3232cd0d3 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@@ -10,7 -10,7 +10,7 @@@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; - use crate::events::{HTLCDestination, MessageSendEvent, MessageSendEventsProvider}; + use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason}; use crate::ln::PaymentSecret; use crate::ln::channelmanager; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; @@@ -21,15 -21,16 +21,16 @@@ use crate::ln::msgs::ChannelMessageHand use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::Retry; + use crate::offers::invoice::BlindedPayInfo; use crate::prelude::*; use crate::routing::router::{Payee, PaymentParameters, RouteParameters}; use crate::util::config::UserConfig; use crate::util::test_utils; - pub fn get_blinded_route_parameters( - amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec, + fn blinded_payment_path( + payment_secret: PaymentSecret, node_ids: Vec, channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface - ) -> RouteParameters { + ) -> (BlindedPayInfo, BlindedPath) { let mut intermediate_nodes = Vec::new(); for (node_id, chan_upd) in node_ids.iter().zip(channel_upds) { intermediate_nodes.push(ForwardNode { @@@ -58,13 -59,20 +59,20 @@@ }, }; let mut secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_payment( + BlindedPath::new_for_payment( &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, channel_upds.last().unwrap().htlc_maximum_msat, keys_manager, &secp_ctx - ).unwrap(); + ).unwrap() + } + pub fn get_blinded_route_parameters( + amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec, + channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface + ) -> RouteParameters { RouteParameters::from_payment_params_and_value( - PaymentParameters::blinded(vec![blinded_path]), amt_msat + PaymentParameters::blinded(vec![ + blinded_payment_path(payment_secret, node_ids, channel_upds, keys_manager) + ]), amt_msat ) } @@@ -490,29 -498,6 +498,29 @@@ fn two_hop_blinded_path_success() claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); } +#[test] +fn three_hop_blinded_path_success() { + let chanmon_cfgs = create_chanmon_cfgs(5); + let node_cfgs = create_node_cfgs(5, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]); + let mut nodes = create_network(5, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents; + let chan_upd_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + nodes.iter().skip(2).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_2_3, &chan_upd_3_4], &chanmon_cfgs[4].keys_manager); + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]], payment_preimage); +} + #[derive(PartialEq)] enum ReceiveCheckFail { // The recipient fails the payment upon `PaymentClaimable`. @@@ -560,11 -545,11 +568,11 @@@ fn do_multi_hop_receiver_fail(check: Re }; let amt_msat = 5000; - let final_cltv_delta = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck { + let excess_final_cltv_delta_opt = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck { // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards. Some(TEST_FINAL_CLTV as u16 - 2) } else { None }; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), final_cltv_delta); + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), excess_final_cltv_delta_opt); let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], &chanmon_cfgs[2].keys_manager); @@@ -572,8 -557,7 +580,8 @@@ let route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck { let mut route = get_route(&nodes[0], &route_params).unwrap(); // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards. - route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = TEST_FINAL_CLTV - 2); + route.paths[0].hops.last_mut().map(|h| h.cltv_expiry_delta += excess_final_cltv_delta_opt.unwrap() as u32); + route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = excess_final_cltv_delta_opt.unwrap() as u32); route } else if check == ReceiveCheckFail::PaymentConstraints { // Create a blinded path where the receiver's encrypted payload has an htlc_minimum_msat that is @@@ -681,7 -665,6 +689,7 @@@ commitment_signed_dance!(nodes[2], nodes[1], (), false, true, false, false); }, ReceiveCheckFail::ProcessPendingHTLCsCheck => { + assert_eq!(payment_event_1_2.msgs[0].cltv_expiry, nodes[0].best_block_info().1 + 1 + excess_final_cltv_delta_opt.unwrap() as u32); nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); @@@ -725,3 -708,108 +733,108 @@@ expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } + + #[test] + fn blinded_path_retries() { + let chanmon_cfgs = create_chanmon_cfgs(4); + // Make one blinded path's fees slightly higher so they are tried in a deterministic order. + let mut higher_fee_chan_cfg = test_default_channel_config(); + higher_fee_chan_cfg.channel_config.forwarding_fee_base_msat += 1; + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, Some(higher_fee_chan_cfg), None]); + let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology so nodes[0] has a blinded route hint to retry over. + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0); + let chan_1_3 = create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + let route_params = { + let pay_params = PaymentParameters::blinded( + vec![ + blinded_payment_path(payment_secret, + vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents], + &chanmon_cfgs[3].keys_manager + ), + blinded_payment_path(payment_secret, + vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents], + &chanmon_cfgs[3].keys_manager + ), + ] + ) + .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default())) + .unwrap(); + RouteParameters::from_payment_params_and_value(pay_params, amt_msat) + }; + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(2)).unwrap(); + check_added_monitors(&nodes[0], 1); + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]]], amt_msat, payment_hash, payment_secret); + + macro_rules! fail_payment_back { + ($intro_node: expr) => { + nodes[3].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[3].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }] + ); + nodes[3].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[3], 1); + + let updates = get_htlc_update_msgs!(nodes[3], $intro_node.node.get_our_node_id()); + assert_eq!(updates.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + $intro_node.node.handle_update_fail_malformed_htlc(&nodes[3].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&$intro_node, &nodes[3], &updates.commitment_signed, true, false); + + let updates = get_htlc_update_msgs!($intro_node, nodes[0].node.get_our_node_id()); + assert_eq!(updates.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc(&$intro_node.node.get_our_node_id(), &updates.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &$intro_node, &updates.commitment_signed, false, false); + + let mut events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match events[0] { + Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => { + assert_eq!(payment_hash, ev_payment_hash); + assert_eq!(payment_failed_permanently, false); + }, + _ => panic!("Unexpected event"), + } + match events[1] { + Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected event"), + } + nodes[0].node.process_pending_htlc_forwards(); + } + } + + fail_payment_back!(nodes[1]); + + // Pass the retry along. + check_added_monitors!(nodes[0], 1); + let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], amt_msat, payment_hash, Some(payment_secret), msg_events.pop().unwrap(), true, None); + + fail_payment_back!(nodes[2]); + let evs = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(evs.len(), 1); + match evs[0] { + Event::PaymentFailed { payment_hash: ev_payment_hash, reason, .. } => { + assert_eq!(ev_payment_hash, payment_hash); + // We have 1 retry attempt remaining, but we're out of blinded paths to try. + assert_eq!(reason, Some(PaymentFailureReason::RouteNotFound)); + }, + _ => panic!() + } + } diff --combined lightning/src/ln/onion_utils.rs index 1ea6fd83a,ea6563d44..4b39276c0 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@@ -14,8 -14,7 +14,8 @@@ use crate::ln::wire::Encode use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop}; use crate::sign::NodeSigner; -use crate::util::chacha20::{ChaCha20, ChaChaReader}; +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::streams::ChaChaReader; use crate::util::errors::{self, APIError}; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter}; use crate::util::logger::Logger; @@@ -189,10 -188,11 +189,10 @@@ pub(super) fn build_onion_payloads(path for (i, blinded_hop) in hops.iter().enumerate() { if i == hops.len() - 1 { cur_value_msat += final_value_msat; - cur_cltv += excess_final_cltv_expiry_delta; res.push(msgs::OutboundOnionPayload::BlindedReceive { - amt_msat: *final_value_msat, + sender_intended_htlc_amt_msat: *final_value_msat, total_msat, - outgoing_cltv_value: cltv, + cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta, encrypted_tlvs: blinded_hop.encrypted_payload.clone(), intro_node_blinding_point: blinding_point.take(), }); @@@ -214,8 -214,8 +214,8 @@@ payment_metadata: recipient_onion.payment_metadata.take(), keysend_preimage: *keysend_preimage, custom_tlvs: recipient_onion.custom_tlvs.clone(), - amt_msat: value_msat, - outgoing_cltv_value: cltv, + sender_intended_htlc_amt_msat: value_msat, + cltv_expiry_height: cltv, }); } } else { @@@ -429,6 -429,7 +429,7 @@@ pub(crate) struct DecodedOnionFailure pub(crate) network_update: Option, pub(crate) short_channel_id: Option, pub(crate) payment_failed_permanently: bool, + pub(crate) failed_within_blinded_path: bool, #[cfg(test)] pub(crate) onion_error_code: Option, #[cfg(test)] @@@ -463,6 -464,7 +464,7 @@@ pub(super) fn process_onion_failure, short_channel_id: Option, payment_failed_permanently: bool, + failed_within_blinded_path: bool, } let mut res: Option = None; let mut htlc_msat = *first_hop_htlc_msat; @@@ -488,7 -490,8 +490,8 @@@ error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding error_packet_ret = Some(vec![0; 32]); res = Some(FailureLearnings { - network_update: None, short_channel_id: None, payment_failed_permanently: false + network_update: None, short_channel_id: None, payment_failed_permanently: false, + failed_within_blinded_path: true, }); return }, @@@ -520,7 -523,8 +523,8 @@@ } res = Some(FailureLearnings { - network_update: None, short_channel_id: None, payment_failed_permanently: false + network_update: None, short_channel_id: None, payment_failed_permanently: false, + failed_within_blinded_path: true, }); return } @@@ -550,7 -554,8 +554,8 @@@ }); let short_channel_id = Some(route_hop.short_channel_id); res = Some(FailureLearnings { - network_update, short_channel_id, payment_failed_permanently: is_from_final_node + network_update, short_channel_id, payment_failed_permanently: is_from_final_node, + failed_within_blinded_path: false }); return } @@@ -706,7 -711,8 +711,8 @@@ res = Some(FailureLearnings { network_update, short_channel_id, - payment_failed_permanently: error_code & PERM == PERM && is_from_final_node + payment_failed_permanently: error_code & PERM == PERM && is_from_final_node, + failed_within_blinded_path: false }); let (description, title) = errors::get_onion_error_description(error_code); @@@ -717,10 -723,10 +723,10 @@@ } }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); if let Some(FailureLearnings { - network_update, short_channel_id, payment_failed_permanently + network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path }) = res { DecodedOnionFailure { - network_update, short_channel_id, payment_failed_permanently, + network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path, #[cfg(test)] onion_error_code: error_code_ret, #[cfg(test)] @@@ -731,6 -737,7 +737,7 @@@ // payment not retryable only when garbage is from the final node DecodedOnionFailure { network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node, + failed_within_blinded_path: false, #[cfg(test)] onion_error_code: None, #[cfg(test)] @@@ -878,6 -885,7 +885,7 @@@ impl HTLCFailReason network_update: None, payment_failed_permanently: false, short_channel_id: Some(path.hops[0].short_channel_id), + failed_within_blinded_path: false, #[cfg(test)] onion_error_code: Some(*failure_code), #[cfg(test)] diff --combined lightning/src/routing/router.rs index 9a5c2dfbf,c72e5cd9a..436a37144 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@@ -20,13 -20,13 +20,13 @@@ use crate::ln::channelmanager::{Channel use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice}; -use crate::onion_message::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; use crate::sign::EntropySource; use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer}; use crate::util::logger::{Level, Logger}; -use crate::util::chacha20::ChaCha20; +use crate::crypto::chacha20::ChaCha20; use crate::io; use crate::prelude::*; @@@ -114,14 -114,19 +114,14 @@@ impl None => return None, }; let payment_relay: PaymentRelay = match details.counterparty.forwarding_info { - Some(forwarding_info) => forwarding_info.into(), + Some(forwarding_info) => match forwarding_info.try_into() { + Ok(payment_relay) => payment_relay, + Err(()) => return None, + }, None => return None, }; - // Avoid exposing esoteric CLTV expiry deltas - let cltv_expiry_delta = match payment_relay.cltv_expiry_delta { - 0..=40 => 40u32, - 41..=80 => 80u32, - 81..=144 => 144u32, - 145..=216 => 216u32, - _ => return None, - }; - + let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32; let payment_constraints = PaymentConstraints { max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta, htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), @@@ -706,6 -711,11 +706,11 @@@ pub struct PaymentParameters /// payment to fail. Future attempts for the same payment shouldn't be relayed through any of /// these SCIDs. pub previously_failed_channels: Vec, + + /// A list of indices corresponding to blinded paths in [`Payee::Blinded::route_hints`] which this + /// payment was previously attempted over and which caused the payment to fail. Future attempts + /// for the same payment shouldn't be relayed through any of these blinded paths. + pub previously_failed_blinded_path_idxs: Vec, } impl Writeable for PaymentParameters { @@@ -727,6 -737,7 +732,7 @@@ (7, self.previously_failed_channels, required_vec), (8, *blinded_hints, optional_vec), (9, self.payee.final_cltv_expiry_delta(), option), + (11, self.previously_failed_blinded_path_idxs, required_vec), }); Ok(()) } @@@ -745,6 -756,7 +751,7 @@@ impl ReadableArgs for PaymentParam (7, previously_failed_channels, optional_vec), (8, blinded_route_hints, optional_vec), (9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)), + (11, previously_failed_blinded_path_idxs, optional_vec), }); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { @@@ -768,6 -780,7 +775,7 @@@ max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)), expiry_time, previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()), + previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()), }) } } @@@ -786,6 -799,7 +794,7 @@@ impl PaymentParameters max_path_count: DEFAULT_MAX_PATH_COUNT, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), + previously_failed_blinded_path_idxs: Vec::new(), } } @@@ -824,6 -838,7 +833,7 @@@ max_path_count: DEFAULT_MAX_PATH_COUNT, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), + previously_failed_blinded_path_idxs: Vec::new(), } } @@@ -899,6 -914,19 +909,19 @@@ pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self { Self { max_channel_saturation_power_of_half, ..self } } + + pub(crate) fn insert_previously_failed_blinded_path(&mut self, failed_blinded_tail: &BlindedTail) { + let mut found_blinded_tail = false; + for (idx, (_, path)) in self.payee.blinded_route_hints().iter().enumerate() { + if failed_blinded_tail.hops == path.blinded_hops && + failed_blinded_tail.blinding_point == path.blinding_point + { + self.previously_failed_blinded_path_idxs.push(idx as u64); + found_blinded_tail = true; + } + } + debug_assert!(found_blinded_tail); + } } /// The recipient of a payment, differing based on whether they've hidden their identity with route @@@ -1355,6 -1383,15 +1378,15 @@@ impl<'a> CandidateRouteHop<'a> _ => None, } } + fn blinded_hint_idx(&self) -> Option { + match self { + Self::Blinded(BlindedPathCandidate { hint_idx, .. }) | + Self::OneHopBlinded(OneHopBlindedPathCandidate { hint_idx, .. }) => { + Some(*hint_idx) + }, + _ => None, + } + } /// Returns the source node id of current hop. /// /// Source node id refers to the node forwarding the HTLC through this hop. @@@ -2106,8 -2143,15 +2138,15 @@@ where L::Target: Logger (amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat && recommended_value_msat >= $next_hops_path_htlc_minimum_msat)); - let payment_failed_on_this_channel = scid_opt.map_or(false, - |scid| payment_params.previously_failed_channels.contains(&scid)); + let payment_failed_on_this_channel = match scid_opt { + Some(scid) => payment_params.previously_failed_channels.contains(&scid), + None => match $candidate.blinded_hint_idx() { + Some(idx) => { + payment_params.previously_failed_blinded_path_idxs.contains(&(idx as u64)) + }, + None => false, + }, + }; let (should_log_candidate, first_hop_details) = match $candidate { CandidateRouteHop::FirstHop(hop) => (true, Some(hop.details)), @@@ -2701,7 -2745,7 +2740,7 @@@ } } - // Means we succesfully traversed from the payer to the payee, now + // Means we successfully traversed from the payer to the payee, now // save this path for the payment route. Also, update the liquidity // remaining on the used hops, so that we take them into account // while looking for more paths. @@@ -3190,7 -3234,7 +3229,7 @@@ mod tests use crate::offers::invoice::BlindedPayInfo; use crate::util::config::UserConfig; use crate::util::test_utils as ln_test_utils; - use crate::util::chacha20::ChaCha20; + use crate::crypto::chacha20::ChaCha20; use crate::util::ser::{Readable, Writeable}; #[cfg(c_bindings)] use crate::util::ser::Writer; @@@ -7798,7 -7842,7 +7837,7 @@@ fn do_min_htlc_overpay_violates_max_htlc(blinded_payee: bool) { // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier // hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop - // to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting + // to `targets` and build an invalid path with it, and subsequently hit a debug panic asserting // that the used liquidity for a hop was less than its available liquidity limit. let secp_ctx = Secp256k1::new(); let logger = Arc::new(ln_test_utils::TestLogger::new()); @@@ -8447,7 -8491,7 +8486,7 @@@ pub(crate) mod bench_utils } break; } - // If we couldn't find a path with a higer amount, reduce and try again. + // If we couldn't find a path with a higher amount, reduce and try again. score_amt /= 100; }