X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Frouter.rs;h=81312980a5a1550137078ab69506c894c6d35c69;hb=baa727ea3867a823ff6fb51abae1d7da534628cb;hp=8d37182266b11debdda0d96787358c36c87dcd59;hpb=6d5c952556aefa8f61c5a2c74ff72f426f5406ac;p=rust-lightning diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 8d371822..81312980 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -11,9 +11,9 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode}; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; -use crate::ln::PaymentHash; +use crate::ln::types::PaymentHash; use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; @@ -654,6 +654,8 @@ const DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF: u8 = 2; // The median hop CLTV expiry delta currently seen in the network. const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40; +/// Estimated maximum number of hops that can be included in a payment path. May be inaccurate if +/// payment metadata, custom TLVs, or blinded paths are included in the payment. // During routing, we only consider paths shorter than our maximum length estimate. // In the TLV onion format, there is no fixed maximum length, but the `hop_payloads` // field is always 1300 bytes. As the `tlv_payload` for each hop may vary in length, we have to @@ -665,7 +667,7 @@ const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40; // (payment_secret and total_msat) = 93 bytes for the final hop. // Since the length of the potentially included `payment_metadata` is unknown to us, we round // down from (1300-93) / 61 = 19.78... to arrive at a conservative estimate of 19. -const MAX_PATH_LENGTH_ESTIMATE: u8 = 19; +pub const MAX_PATH_LENGTH_ESTIMATE: u8 = 19; /// Information used to route a payment. #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -684,6 +686,10 @@ pub struct PaymentParameters { /// Defaults to [`DEFAULT_MAX_PATH_COUNT`]. pub max_path_count: u8, + /// The maximum number of [`Path::hops`] in any returned path. + /// Defaults to [`MAX_PATH_LENGTH_ESTIMATE`]. + pub max_path_length: u8, + /// Selects the maximum share of a channel's total capacity which will be sent over a channel, /// as a power of 1/2. A higher value prefers to send the payment using more MPP parts whereas /// a lower value prefers to send larger MPP parts, potentially saturating channels and @@ -730,6 +736,7 @@ impl Writeable for PaymentParameters { (8, *blinded_hints, optional_vec), (9, self.payee.final_cltv_expiry_delta(), option), (11, self.previously_failed_blinded_path_idxs, required_vec), + (13, self.max_path_length, required), }); Ok(()) } @@ -749,6 +756,7 @@ impl ReadableArgs for PaymentParameters { (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), + (13, max_path_length, (default_value, MAX_PATH_LENGTH_ESTIMATE)), }); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { @@ -773,6 +781,7 @@ impl ReadableArgs for PaymentParameters { 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()), + max_path_length: _init_tlv_based_struct_field!(max_path_length, (default_value, unused)), }) } } @@ -789,6 +798,7 @@ impl PaymentParameters { expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, + max_path_length: MAX_PATH_LENGTH_ESTIMATE, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), previously_failed_blinded_path_idxs: Vec::new(), @@ -828,6 +838,7 @@ impl PaymentParameters { expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, + max_path_length: MAX_PATH_LENGTH_ESTIMATE, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), previously_failed_blinded_path_idxs: Vec::new(), @@ -1144,11 +1155,11 @@ pub struct FirstHopCandidate<'a> { /// /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`]. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub details: &'a ChannelDetails, /// The node id of the payer, which is also the source side of this candidate route hop. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub payer_node_id: &'a NodeId, } @@ -1158,7 +1169,7 @@ pub struct PublicHopCandidate<'a> { /// Information about the channel, including potentially its capacity and /// direction-specific information. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub info: DirectedChannelInfo<'a>, /// The short channel ID of the channel, i.e. the identifier by which we refer to this /// channel. @@ -1170,21 +1181,26 @@ pub struct PublicHopCandidate<'a> { pub struct PrivateHopCandidate<'a> { /// Information about the private hop communicated via BOLT 11. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a RouteHintHop, /// Node id of the next hop in BOLT 11 route hint. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub target_node_id: &'a NodeId } /// A [`CandidateRouteHop::Blinded`] entry. #[derive(Clone, Debug)] pub struct BlindedPathCandidate<'a> { + /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first + /// hops. + /// + /// This is not exported to bindings users as lifetimes are not expressible in most languages. + pub source_node_id: &'a NodeId, /// Information about the blinded path including the fee, HTLC amount limits, and /// cryptographic material required to build an HTLC through the given path. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a (BlindedPayInfo, BlindedPath), /// Index of the hint in the original list of blinded hints. /// @@ -1196,12 +1212,17 @@ pub struct BlindedPathCandidate<'a> { /// A [`CandidateRouteHop::OneHopBlinded`] entry. #[derive(Clone, Debug)] pub struct OneHopBlindedPathCandidate<'a> { + /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first + /// hops. + /// + /// This is not exported to bindings users as lifetimes are not expressible in most languages. + pub source_node_id: &'a NodeId, /// Information about the blinded path including the fee, HTLC amount limits, and /// cryptographic material required to build an HTLC terminating with the given path. /// /// Note that the [`BlindedPayInfo`] is ignored here. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a (BlindedPayInfo, BlindedPath), /// Index of the hint in the original list of blinded hints. /// @@ -1409,8 +1430,8 @@ impl<'a> CandidateRouteHop<'a> { CandidateRouteHop::FirstHop(hop) => *hop.payer_node_id, CandidateRouteHop::PublicHop(hop) => *hop.info.source(), CandidateRouteHop::PrivateHop(hop) => hop.hint.src_node_id.into(), - CandidateRouteHop::Blinded(hop) => hop.hint.1.introduction_node_id.into(), - CandidateRouteHop::OneHopBlinded(hop) => hop.hint.1.introduction_node_id.into(), + CandidateRouteHop::Blinded(hop) => *hop.source_node_id, + CandidateRouteHop::OneHopBlinded(hop) => *hop.source_node_id, } } /// Returns the target node id of this hop, if known. @@ -1725,8 +1746,20 @@ impl<'a> fmt::Display for LoggedCandidateHop<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { - "blinded route hint with introduction node id ".fmt(f)?; - hint.1.introduction_node_id.fmt(f)?; + "blinded route hint with introduction node ".fmt(f)?; + match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => write!(f, "id {}", pubkey)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => { + write!(f, "one on channel with SCID {}", scid)?; + }, + Direction::NodeTwo => { + write!(f, "two on channel with SCID {}", scid)?; + }, + } + } + } " and blinding point ".fmt(f)?; hint.1.blinding_point.fmt(f) }, @@ -1827,6 +1860,7 @@ pub(crate) fn get_route( where L::Target: Logger { let payment_params = &route_params.payment_params; + let max_path_length = core::cmp::min(payment_params.max_path_length, MAX_PATH_LENGTH_ESTIMATE); let final_value_msat = route_params.final_value_msat; // If we're routing to a blinded recipient, we won't have their node id. Therefore, keep the // unblinded payee id as an option. We also need a non-optional "payee id" for path construction, @@ -1852,6 +1886,9 @@ where L::Target: Logger { return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError}); } + let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter() + .map(|(_, path)| path.public_introduction_node_id(network_graph)) + .collect::>(); match &payment_params.payee { Payee::Clear { route_hints, node_id, .. } => { for route in route_hints.iter() { @@ -1863,17 +1900,19 @@ where L::Target: Logger { } }, Payee::Blinded { route_hints, .. } => { - if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) { + if introduction_node_id_cache.iter().all(|introduction_node_id| *introduction_node_id == Some(&our_node_id)) { return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError}); } - for (_, blinded_path) in route_hints.iter() { + for ((_, blinded_path), introduction_node_id) in route_hints.iter().zip(introduction_node_id_cache.iter()) { if blinded_path.blinded_hops.len() == 0 { return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError}); - } else if &blinded_path.introduction_node_id == our_node_pubkey { + } else if *introduction_node_id == Some(&our_node_id) { log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring"); } else if blinded_path.blinded_hops.len() == 1 && - route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1 - && p.introduction_node_id != blinded_path.introduction_node_id) + route_hints + .iter().zip(introduction_node_id_cache.iter()) + .filter(|((_, p), _)| p.blinded_hops.len() == 1) + .any(|(_, p_introduction_node_id)| p_introduction_node_id != introduction_node_id) { return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError}); } @@ -2117,8 +2156,9 @@ where L::Target: Logger { // Verify the liquidity offered by this channel complies to the minimal contribution. let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat; // Do not consider candidate hops that would exceed the maximum path length. - let path_length_to_node = $next_hops_path_length + 1; - let exceeds_max_path_length = path_length_to_node > MAX_PATH_LENGTH_ESTIMATE; + let path_length_to_node = $next_hops_path_length + + if $candidate.blinded_hint_idx().is_some() { 0 } else { 1 }; + let exceeds_max_path_length = path_length_to_node > max_path_length; // Do not consider candidates that exceed the maximum total cltv expiry limit. // In order to already account for some of the privacy enhancing random CLTV @@ -2515,26 +2555,53 @@ where L::Target: Logger { // earlier than general path finding, they will be somewhat prioritized, although currently // it matters only if the fees are exactly the same. for (hint_idx, hint) in payment_params.payee.blinded_route_hints().iter().enumerate() { - let intro_node_id = NodeId::from_pubkey(&hint.1.introduction_node_id); - let have_intro_node_in_graph = - // Only add the hops in this route to our candidate set if either - // we have a direct channel to the first hop or the first hop is - // in the regular network graph. - first_hop_targets.get(&intro_node_id).is_some() || - network_nodes.get(&intro_node_id).is_some(); - if !have_intro_node_in_graph || our_node_id == intro_node_id { continue } + // Only add the hops in this route to our candidate set if either + // we have a direct channel to the first hop or the first hop is + // in the regular network graph. + let source_node_id = match introduction_node_id_cache[hint_idx] { + Some(node_id) => node_id, + None => match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(&pubkey); + match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { + Some(node_id) => node_id, + None => continue, + } + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + let first_hop = first_hop_targets.iter().find(|(_, channels)| + channels + .iter() + .any(|details| Some(*scid) == details.get_outbound_payment_scid()) + ); + match first_hop { + Some((counterparty_node_id, _)) => { + direction.select_node_id(&our_node_id, counterparty_node_id) + }, + None => continue, + } + }, + }, + }; + if our_node_id == *source_node_id { continue } let candidate = if hint.1.blinded_hops.len() == 1 { - CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, hint_idx }) - } else { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, hint_idx }) }; + CandidateRouteHop::OneHopBlinded( + OneHopBlindedPathCandidate { source_node_id, hint, hint_idx } + ) + } else { + CandidateRouteHop::Blinded(BlindedPathCandidate { source_node_id, hint, hint_idx }) + }; let mut path_contribution_msat = path_value_msat; if let Some(hop_used_msat) = add_entry!(&candidate, 0, path_contribution_msat, 0, 0_u64, 0, 0) { path_contribution_msat = hop_used_msat; } else { continue } - if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hint.1.introduction_node_id)) { - sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat, - our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(source_node_id) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, @@ -2545,9 +2612,8 @@ where L::Target: Logger { }; let path_min = candidate.htlc_minimum_msat().saturating_add( compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees())); - add_entry!(&first_hop_candidate, blinded_path_fee, - path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(), - candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8)); + add_entry!(&first_hop_candidate, blinded_path_fee, path_contribution_msat, path_min, + 0_u64, candidate.cltv_expiry_delta(), 0); } } } @@ -2630,9 +2696,11 @@ where L::Target: Logger { .saturating_add(1); // Searching for a direct channel between last checked hop and first_hop_targets - if let Some(first_channels) = first_hop_targets.get_mut(target) { - sort_first_hop_channels(first_channels, &used_liquidities, - recommended_value_msat, our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(target) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, @@ -2677,9 +2745,11 @@ where L::Target: Logger { // Note that we *must* check if the last hop was added as `add_entry` // always assumes that the third argument is a node to which we have a // path. - if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) { - sort_first_hop_channels(first_channels, &used_liquidities, - recommended_value_msat, our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, @@ -3223,7 +3293,7 @@ fn build_route_from_hops_internal( #[cfg(test)] mod tests { - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; use crate::routing::utxo::UtxoResult; use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features, @@ -3233,7 +3303,7 @@ mod tests { use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel}; use crate::chain::transaction::OutPoint; use crate::sign::EntropySource; - use crate::ln::ChannelId; + use crate::ln::types::ChannelId; use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; use crate::ln::channelmanager; @@ -3259,8 +3329,6 @@ mod tests { use crate::prelude::*; use crate::sync::Arc; - use core::convert::TryInto; - fn get_channel_details(short_channel_id: Option, node_id: PublicKey, features: InitFeatures, outbound_capacity_msat: u64) -> channelmanager::ChannelDetails { channelmanager::ChannelDetails { @@ -3305,7 +3373,7 @@ mod tests { fn simple_route_test() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42); + let mut payment_params = PaymentParameters::from_node_id(nodes[2], 42); let scorer = ln_test_utils::TestScorer::new(); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -3320,7 +3388,8 @@ mod tests { assert_eq!(err, "Cannot send a payment of 0 msat"); } else { panic!(); } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + payment_params.max_path_length = 2; + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); assert_eq!(route.paths[0].hops.len(), 2); @@ -3338,6 +3407,10 @@ mod tests { assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42); assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3)); assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4)); + + route_params.payment_params.max_path_length = 1; + get_route(&our_id, &route_params, &network_graph.read_only(), None, + Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err(); } #[test] @@ -3742,7 +3815,7 @@ mod tests { }); // If all the channels require some features we don't understand, route should fail - let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { @@ -3752,6 +3825,7 @@ mod tests { // If we specify a channel to node7, that overrides our local channel view and that gets used let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; + route_params.payment_params.max_path_length = 2; let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); @@ -3998,8 +4072,9 @@ mod tests { } else { panic!(); } } - let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + let mut payment_params = PaymentParameters::from_node_id(nodes[6], 42) .with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap(); + payment_params.max_path_length = 5; let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); @@ -4157,7 +4232,8 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); // Test through channels 2, 3, 0xff00, 0xff01. - // Test shows that multiple hop hints are considered. + // Test shows that multi-hop route hints are considered and factored correctly into the + // max path length. // Disabling channels 6 & 7 by flags=2 update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { @@ -4185,7 +4261,8 @@ mod tests { excess_data: Vec::new() }); - let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + route_params.payment_params.max_path_length = 4; let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); assert_eq!(route.paths[0].hops.len(), 4); @@ -4217,6 +4294,9 @@ mod tests { assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42); assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + route_params.payment_params.max_path_length = 3; + get_route(&our_id, &route_params, &network_graph.read_only(), None, + Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err(); } #[test] @@ -5092,7 +5172,7 @@ mod tests { // MPP to a 1-hop blinded path for nodes[2] let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }], }; @@ -5110,18 +5190,18 @@ mod tests { // MPP to 3 2-hop blinded paths let mut blinded_path_node_0 = blinded_path.clone(); - blinded_path_node_0.introduction_node_id = nodes[0]; + blinded_path_node_0.introduction_node = IntroductionNode::NodeId(nodes[0]); blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone()); let mut node_0_payinfo = blinded_payinfo.clone(); node_0_payinfo.htlc_maximum_msat = 50_000; let mut blinded_path_node_7 = blinded_path_node_0.clone(); - blinded_path_node_7.introduction_node_id = nodes[7]; + blinded_path_node_7.introduction_node = IntroductionNode::NodeId(nodes[7]); let mut node_7_payinfo = blinded_payinfo.clone(); node_7_payinfo.htlc_maximum_msat = 60_000; let mut blinded_path_node_1 = blinded_path_node_0.clone(); - blinded_path_node_1.introduction_node_id = nodes[1]; + blinded_path_node_1.introduction_node = IntroductionNode::NodeId(nodes[1]); let mut node_1_payinfo = blinded_payinfo.clone(); node_1_payinfo.htlc_maximum_msat = 180_000; @@ -5303,10 +5383,15 @@ mod tests { if let Some(bt) = &path.blinded_tail { assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2); if bt.hops.len() > 1 { - assert_eq!(path.hops.last().unwrap().pubkey, + let network_graph = network_graph.read_only(); + assert_eq!( + NodeId::from_pubkey(&path.hops.last().unwrap().pubkey), payment_params.payee.blinded_route_hints().iter() .find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat()) - .map(|(_, p)| p.introduction_node_id).unwrap()); + .and_then(|(_, p)| p.public_introduction_node_id(&network_graph)) + .copied() + .unwrap() + ); } else { assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]); } @@ -7202,7 +7287,7 @@ mod tests { // Make sure this works for blinded route hints. let blinded_path = BlindedPath { - introduction_node_id: intermed_node_id, + introduction_node: IntroductionNode::NodeId(intermed_node_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] }, @@ -7236,7 +7321,7 @@ mod tests { #[test] fn blinded_route_ser() { let blinded_path_1 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(42), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(42)), blinding_point: ln_test_utils::pubkey(43), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() }, @@ -7244,7 +7329,7 @@ mod tests { ], }; let blinded_path_2 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(46), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(46)), blinding_point: ln_test_utils::pubkey(47), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() }, @@ -7303,7 +7388,7 @@ mod tests { // account for the blinded tail's final amount_msat. let mut inflight_htlcs = InFlightHtlcs::new(); let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(48), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }], }; @@ -7318,7 +7403,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7342,7 +7427,7 @@ mod tests { fn blinded_path_cltv_shadow_offset() { // Make sure we add a shadow offset when sending to blinded paths. let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(44), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }, @@ -7360,7 +7445,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7402,7 +7487,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: Vec::with_capacity(num_blinded_hops), }; @@ -7434,7 +7519,10 @@ mod tests { assert_eq!(tail.final_value_msat, 1001); let final_hop = route.paths[0].hops.last().unwrap(); - assert_eq!(final_hop.pubkey, blinded_path.introduction_node_id); + assert_eq!( + NodeId::from_pubkey(&final_hop.pubkey), + *blinded_path.public_introduction_node_id(&network_graph).unwrap() + ); if tail.hops.len() > 1 { assert_eq!(final_hop.fee_msat, blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000); @@ -7457,7 +7545,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut invalid_blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] }, @@ -7473,7 +7561,7 @@ mod tests { }; let mut invalid_blinded_path_2 = invalid_blinded_path.clone(); - invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45); + invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(45)); let payment_params = PaymentParameters::blinded(vec![ (blinded_payinfo.clone(), invalid_blinded_path.clone()), (blinded_payinfo.clone(), invalid_blinded_path_2)]); @@ -7487,7 +7575,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = our_id; + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(our_id); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, @@ -7499,7 +7587,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46); + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(46)); invalid_blinded_path.blinded_hops.clear(); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); @@ -7528,7 +7616,7 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path_1 = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7625,7 +7713,7 @@ mod tests { get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7694,7 +7782,7 @@ mod tests { 18446744073709551615)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7750,7 +7838,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7769,7 +7857,7 @@ mod tests { (blinded_payinfo.clone(), blinded_path.clone()), (blinded_payinfo.clone(), blinded_path.clone()), ]; - blinded_hints[1].1.introduction_node_id = nodes[6]; + blinded_hints[1].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7802,7 +7890,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7826,7 +7914,7 @@ mod tests { blinded_hints[1].0.htlc_minimum_msat = 21_7020_5185_1423_0019; blinded_hints[1].0.htlc_maximum_msat = 1844_6744_0737_0955_1615; - blinded_hints[2].1.introduction_node_id = nodes[6]; + blinded_hints[2].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7873,7 +7961,7 @@ mod tests { let htlc_min = 2_5165_8240; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7953,7 +8041,7 @@ mod tests { let htlc_mins = [1_4392, 19_7401, 1027, 6_5535]; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8054,7 +8142,7 @@ mod tests { cltv_expiry_delta: 10, features: BlindedHopFeatures::empty(), }, BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8104,7 +8192,7 @@ mod tests { let htlc_mins = [49_0000, 1125_0000]; let payment_params = { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8344,17 +8432,14 @@ pub(crate) mod bench_utils { use std::time::Duration; use bitcoin::hashes::Hash; - use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use bitcoin::secp256k1::SecretKey; use crate::chain::transaction::OutPoint; use crate::routing::scoring::ScoreUpdate; - use crate::sign::{EntropySource, KeysManager}; - use crate::ln::ChannelId; - use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails}; - use crate::ln::features::Bolt11InvoiceFeatures; - use crate::routing::gossip::NetworkGraph; + use crate::sign::KeysManager; + use crate::ln::types::ChannelId; + use crate::ln::channelmanager::{self, ChannelCounterparty}; use crate::util::config::UserConfig; - use crate::util::ser::ReadableArgs; use crate::util::test_utils::TestLogger; /// Tries to open a network graph file, or panics with a URL to fetch it.