From 06eeefc5e710ea61426bb93580901e9a0739346a Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 26 Apr 2023 15:15:19 -0400 Subject: [PATCH 1/7] Groundwork for refactoring PaymentParams::Hints to ::Payee Minor changes in preparation for supporting route blinding in PaymentParameters. In the next commit, we'll be moving more unblinded-payee-specific fields from the top level parameters into the clear enum variant. --- lightning/src/routing/router.rs | 50 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 39a7e69edb0..06a36a03a73 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -490,7 +490,7 @@ const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40; // down from (1300-93) / 61 = 19.78... to arrive at a conservative estimate of 19. const MAX_PATH_LENGTH_ESTIMATE: u8 = 19; -/// The recipient of a payment. +/// Information used to route a payment. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PaymentParameters { /// The node id of the payee. @@ -504,8 +504,8 @@ pub struct PaymentParameters { /// [`for_keysend`]: Self::for_keysend pub features: Option, - /// Hints for routing to the payee, containing channels connecting the payee to public nodes. - pub route_hints: Hints, + /// Information about the payee, such as their features and route hints for their channels. + pub payee: Payee, /// Expiration of a payment to the payee, in seconds relative to the UNIX epoch. pub expiry_time: Option, @@ -546,9 +546,9 @@ impl Writeable for PaymentParameters { fn write(&self, writer: &mut W) -> Result<(), io::Error> { let mut clear_hints = &vec![]; let mut blinded_hints = &vec![]; - match &self.route_hints { - Hints::Clear(hints) => clear_hints = hints, - Hints::Blinded(hints) => blinded_hints = hints, + match &self.payee { + Payee::Clear { route_hints, .. } => clear_hints = route_hints, + Payee::Blinded { route_hints } => blinded_hints = route_hints, } write_tlv_fields!(writer, { (0, self.payee_pubkey, required), @@ -582,18 +582,18 @@ impl ReadableArgs for PaymentParameters { }); let clear_route_hints = route_hints.unwrap_or(vec![]); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); - let route_hints = if blinded_route_hints.len() != 0 { + let payee = if blinded_route_hints.len() != 0 { if clear_route_hints.len() != 0 { return Err(DecodeError::InvalidValue) } - Hints::Blinded(blinded_route_hints) + Payee::Blinded { route_hints: blinded_route_hints } } else { - Hints::Clear(clear_route_hints) + Payee::Clear { route_hints: clear_route_hints } }; Ok(Self { payee_pubkey: _init_tlv_based_struct_field!(payee_pubkey, required), max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)), features, max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)), - route_hints, + payee, 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()), @@ -612,7 +612,7 @@ impl PaymentParameters { Self { payee_pubkey, features: None, - route_hints: Hints::Clear(vec![]), + payee: Payee::Clear { route_hints: vec![] }, expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, @@ -641,7 +641,7 @@ impl PaymentParameters { /// /// This is not exported to bindings users since bindings don't support move semantics pub fn with_route_hints(self, route_hints: Vec) -> Self { - Self { route_hints: Hints::Clear(route_hints), ..self } + Self { payee: Payee::Clear { route_hints }, ..self } } /// Includes a payment expiration in seconds relative to the UNIX epoch. @@ -673,14 +673,22 @@ impl PaymentParameters { } } -/// Routing hints for the tail of the route. +/// The recipient of a payment, differing based on whether they've hidden their identity with route +/// blinding. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum Hints { +pub enum Payee { /// The recipient provided blinded paths and payinfo to reach them. The blinded paths themselves /// will be included in the final [`Route`]. - Blinded(Vec<(BlindedPayInfo, BlindedPath)>), + Blinded { + /// Aggregated routing info and blinded paths, for routing to the payee without knowing their + /// node id. + route_hints: Vec<(BlindedPayInfo, BlindedPath)>, + }, /// The recipient included these route hints in their BOLT11 invoice. - Clear(Vec), + Clear { + /// Hints for routing to the payee, containing channels connecting the payee to public nodes. + route_hints: Vec, + }, } /// A list of hops along a payment path terminating with a channel to the recipient. @@ -1131,9 +1139,9 @@ where L::Target: Logger { return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError}); } - match &payment_params.route_hints { - Hints::Clear(hints) => { - for route in hints.iter() { + match &payment_params.payee { + Payee::Clear { route_hints } => { + for route in route_hints.iter() { for hop in &route.0 { if hop.src_node_id == payment_params.payee_pubkey { return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError}); @@ -1664,8 +1672,8 @@ where L::Target: Logger { // If a caller provided us with last hops, add them to routing targets. Since this happens // earlier than general path finding, they will be somewhat prioritized, although currently // it matters only if the fees are exactly the same. - let route_hints = match &payment_params.route_hints { - Hints::Clear(hints) => hints, + let route_hints = match &payment_params.payee { + Payee::Clear { route_hints } => route_hints, _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}), }; for route in route_hints.iter().filter(|route| !route.0.is_empty()) { From cea78f585ab710e6494111420660ef11c2ea7db5 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sat, 29 Apr 2023 14:47:59 -0400 Subject: [PATCH 2/7] Error if clear hints are provided for blinded PaymentParams --- fuzz/src/router.rs | 2 +- lightning-invoice/src/payment.rs | 2 +- lightning-invoice/src/utils.rs | 4 +-- lightning/src/ln/onion_route_tests.rs | 4 +-- lightning/src/ln/payment_tests.rs | 2 +- lightning/src/ln/priv_short_conf_tests.rs | 10 +++--- lightning/src/routing/router.rs | 43 +++++++++++++---------- 7 files changed, 36 insertions(+), 31 deletions(-) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index fe6f1647f4d..7c09c860025 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -300,7 +300,7 @@ pub fn do_test(data: &[u8], out: Out) { let final_cltv_expiry_delta = slice_to_be32(get_slice!(4)); let route_params = RouteParameters { payment_params: PaymentParameters::from_node_id(*target, final_cltv_expiry_delta) - .with_route_hints(last_hops.clone()), + .with_route_hints(last_hops.clone()).unwrap(), final_value_msat, }; let _ = find_route(&our_pubkey, &route_params, &net_graph, diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 11757be2e3a..2fc895aaed5 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -152,7 +152,7 @@ fn pay_invoice_using_amount( let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), invoice.min_final_cltv_expiry_delta() as u32) .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()); + .with_route_hints(invoice.route_hints()).unwrap(); if let Some(features) = invoice.features() { payment_params = payment_params.with_features(features.clone()); } diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 4f421b9ecc0..109e03d0f8c 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -839,7 +839,7 @@ mod test { let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), invoice.min_final_cltv_expiry_delta() as u32) .with_features(invoice.features().unwrap().clone()) - .with_route_hints(invoice.route_hints()); + .with_route_hints(invoice.route_hints()).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: invoice.amount_milli_satoshis().unwrap(), @@ -1295,7 +1295,7 @@ mod test { let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), invoice.min_final_cltv_expiry_delta() as u32) .with_features(invoice.features().unwrap().clone()) - .with_route_hints(invoice.route_hints()); + .with_route_hints(invoice.route_hints()).unwrap(); let params = RouteParameters { payment_params, final_value_msat: invoice.amount_milli_satoshis().unwrap(), diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 96977e69086..209d05461a8 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -715,7 +715,7 @@ fn do_test_onion_failure_stale_channel_update(announced_channel: bool) { }])]; let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty, TEST_FINAL_CLTV) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(hop_hints); + .with_route_hints(hop_hints).unwrap(); get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT) }; send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT, @@ -987,7 +987,7 @@ macro_rules! get_phantom_route { htlc_minimum_msat: None, htlc_maximum_msat: None, } - ])]); + ])]).unwrap(); let scorer = test_utils::TestScorer::new(); let network_graph = $nodes[0].network_graph.read_only(); (get_route( diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index f7404a7b716..ecbc75b16e0 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -1409,7 +1409,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { htlc_minimum_msat: None, htlc_maximum_msat: None, }]) - ]) + ]).unwrap() .with_features(nodes[2].node.invoice_features()); let route_params = RouteParameters { payment_params, diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index 3b56bb10af4..280d967bbfa 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -68,7 +68,7 @@ fn test_priv_forwarding_rejection() { let last_hops = vec![route_hint]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(last_hops); + .with_route_hints(last_hops).unwrap(); let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000); nodes[0].node.send_payment_with_route(&route, our_payment_hash, @@ -237,7 +237,7 @@ fn test_routed_scid_alias() { }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(hop_hints); + .with_route_hints(hop_hints).unwrap(); let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000); assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); nodes[0].node.send_payment_with_route(&route, payment_hash, @@ -403,7 +403,7 @@ fn test_inbound_scid_privacy() { }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(hop_hints.clone()); + .with_route_hints(hop_hints.clone()).unwrap(); let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000); assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); nodes[0].node.send_payment_with_route(&route, payment_hash, @@ -419,7 +419,7 @@ fn test_inbound_scid_privacy() { let payment_params_2 = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(hop_hints); + .with_route_hints(hop_hints).unwrap(); let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params_2, 100_000); assert_eq!(route_2.paths[0].hops[1].short_channel_id, last_hop[0].short_channel_id.unwrap()); nodes[0].node.send_payment_with_route(&route_2, payment_hash_2, @@ -471,7 +471,7 @@ fn test_scid_alias_returned() { }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) .with_features(nodes[2].node.invoice_features()) - .with_route_hints(hop_hints); + .with_route_hints(hop_hints).unwrap(); let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000); assert_eq!(route.paths[0].hops[1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap()); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 06a36a03a73..17641c3da66 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -637,11 +637,16 @@ impl PaymentParameters { Self { features: Some(features), ..self } } - /// Includes hints for routing to the payee. + /// Includes hints for routing to the payee. Errors if the parameters were initialized with + /// blinded payment paths. /// /// This is not exported to bindings users since bindings don't support move semantics - pub fn with_route_hints(self, route_hints: Vec) -> Self { - Self { payee: Payee::Clear { route_hints }, ..self } + pub fn with_route_hints(self, route_hints: Vec) -> Result { + match self.payee { + Payee::Blinded { .. } => Err(()), + Payee::Clear { .. } => + Ok(Self { payee: Payee::Clear { route_hints }, ..self }) + } } /// Includes a payment expiration in seconds relative to the UNIX epoch. @@ -2934,13 +2939,13 @@ mod tests { let mut invalid_last_hops = last_hops_multi_private_channels(&nodes); invalid_last_hops.push(invalid_last_hop); { - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(invalid_last_hops); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(invalid_last_hops).unwrap(); if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &random_seed_bytes) { assert_eq!(err, "Route hint cannot have the payee as the source."); } else { panic!(); } } - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_multi_private_channels(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap(); let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths[0].hops.len(), 5); @@ -3010,7 +3015,7 @@ mod tests { fn ignores_empty_last_hops_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[6], 42).with_route_hints(empty_last_hop(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(empty_last_hop(&nodes)).unwrap(); 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(); @@ -3090,7 +3095,7 @@ mod tests { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx); let last_hops = multi_hop_last_hops_hint([nodes[2], nodes[3]]); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap(); 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(); @@ -3164,7 +3169,7 @@ mod tests { let non_announced_pubkey = PublicKey::from_secret_key(&secp_ctx, &non_announced_privkey); let last_hops = multi_hop_last_hops_hint([nodes[2], non_announced_pubkey]); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap(); let scorer = ln_test_utils::TestScorer::new(); // Test through channels 2, 3, 0xff00, 0xff01. // Test shows that multiple hop hints are considered. @@ -3270,7 +3275,7 @@ mod tests { fn last_hops_with_public_channel_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[6], 42).with_route_hints(last_hops_with_public_channel(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_with_public_channel(&nodes)).unwrap(); 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(); @@ -3329,7 +3334,7 @@ mod tests { // Simple test with outbound channel to 4 to test that last_hops and first_hops connect let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; let mut last_hops = last_hops(&nodes); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap(); let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::>()), 100, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths[0].hops.len(), 2); @@ -3350,7 +3355,7 @@ mod tests { last_hops[0].0[0].fees.base_msat = 1000; // Revert to via 6 as the fee on 8 goes up - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops).unwrap(); let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths[0].hops.len(), 4); @@ -3443,7 +3448,7 @@ mod tests { htlc_minimum_msat: None, htlc_maximum_msat: last_hop_htlc_max, }]); - let payment_params = PaymentParameters::from_node_id(target_node_id, 42).with_route_hints(vec![last_hops]); + let payment_params = PaymentParameters::from_node_id(target_node_id, 42).with_route_hints(vec![last_hops]).unwrap(); let our_chans = vec![get_channel_details(Some(42), middle_node_id, InitFeatures::from_le_bytes(vec![0b11]), outbound_capacity_msat)]; let scorer = ln_test_utils::TestScorer::new(); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); @@ -4644,7 +4649,7 @@ mod tests { cltv_expiry_delta: 42, htlc_minimum_msat: None, htlc_maximum_msat: None, - }])]).with_max_channel_saturation_power_of_half(0); + }])]).unwrap().with_max_channel_saturation_power_of_half(0); // Keep only two paths from us to nodes[2], both with a 99sat HTLC maximum, with one with // no fee and one with a 1msat fee. Previously, trying to route 100 sats to nodes[2] here @@ -5218,7 +5223,7 @@ mod tests { fn prefers_shorter_route_with_higher_fees() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); // Without penalizing each hop 100 msats, a longer path with lower fees is chosen. let scorer = ln_test_utils::TestScorer::new(); @@ -5291,7 +5296,7 @@ mod tests { fn avoids_routing_through_bad_channels_and_nodes() { let (secp_ctx, network, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); let network_graph = network.read_only(); // A path to nodes[6] exists when no penalties are applied to any channel. @@ -5414,7 +5419,7 @@ mod tests { // Make sure that generally there is at least one route available let feasible_max_total_cltv_delta = 1008; - let feasible_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)) + let feasible_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() .with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -5424,7 +5429,7 @@ mod tests { // But not if we exclude all paths on the basis of their accumulated CLTV delta let fail_max_total_cltv_delta = 23; - let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)) + let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() .with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta); match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &random_seed_bytes) { @@ -5444,7 +5449,7 @@ mod tests { let network_graph = network.read_only(); let scorer = ln_test_utils::TestScorer::new(); - let mut payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)) + let mut payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() .with_max_path_count(1); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -5500,7 +5505,7 @@ mod tests { let scorer = ln_test_utils::TestScorer::new(); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); From 7f49f6bf4d838196ff7b20e78207b4bfdad264c6 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sat, 29 Apr 2023 17:13:02 -0400 Subject: [PATCH 3/7] Move payee node id from top level PaymentParams to Payee::Clear Since blinded payees don't have one --- lightning/src/routing/router.rs | 102 +++++++++++------- .../blinded_pay_param_compat.txt | 3 + 2 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 pending_changelog/blinded_pay_param_compat.txt diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 17641c3da66..5c92871007c 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -29,7 +29,7 @@ use crate::io; use crate::prelude::*; use crate::sync::Mutex; use alloc::collections::BinaryHeap; -use core::cmp; +use core::{cmp, fmt}; use core::ops::Deref; /// A [`Router`] implemented using [`find_route`]. @@ -493,9 +493,6 @@ const MAX_PATH_LENGTH_ESTIMATE: u8 = 19; /// Information used to route a payment. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PaymentParameters { - /// The node id of the payee. - pub payee_pubkey: PublicKey, - /// Features supported by the payee. /// /// May be set from the payee's invoice or via [`for_keysend`]. May be `None` if the invoice @@ -551,7 +548,7 @@ impl Writeable for PaymentParameters { Payee::Blinded { route_hints } => blinded_hints = route_hints, } write_tlv_fields!(writer, { - (0, self.payee_pubkey, required), + (0, self.payee.node_id(), option), (1, self.max_total_cltv_expiry_delta, required), (2, self.features, option), (3, self.max_path_count, required), @@ -569,7 +566,7 @@ impl Writeable for PaymentParameters { impl ReadableArgs for PaymentParameters { fn read(reader: &mut R, default_final_cltv_expiry_delta: u32) -> Result { _init_and_read_tlv_fields!(reader, { - (0, payee_pubkey, required), + (0, payee_pubkey, option), (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)), (2, features, option), (3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)), @@ -583,13 +580,15 @@ impl ReadableArgs for PaymentParameters { let clear_route_hints = route_hints.unwrap_or(vec![]); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { - if clear_route_hints.len() != 0 { return Err(DecodeError::InvalidValue) } + if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) } Payee::Blinded { route_hints: blinded_route_hints } } else { - Payee::Clear { route_hints: clear_route_hints } + Payee::Clear { + route_hints: clear_route_hints, + node_id: payee_pubkey.ok_or(DecodeError::InvalidValue)?, + } }; Ok(Self { - payee_pubkey: _init_tlv_based_struct_field!(payee_pubkey, required), max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)), features, max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)), @@ -610,9 +609,8 @@ impl PaymentParameters { /// provided. pub fn from_node_id(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self { Self { - payee_pubkey, features: None, - payee: Payee::Clear { route_hints: vec![] }, + payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![] }, expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, @@ -644,8 +642,8 @@ impl PaymentParameters { pub fn with_route_hints(self, route_hints: Vec) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { .. } => - Ok(Self { payee: Payee::Clear { route_hints }, ..self }) + Payee::Clear { node_id, .. } => + Ok(Self { payee: Payee::Clear { route_hints, node_id }, ..self }) } } @@ -691,11 +689,22 @@ pub enum Payee { }, /// The recipient included these route hints in their BOLT11 invoice. Clear { + /// The node id of the payee. + node_id: PublicKey, /// Hints for routing to the payee, containing channels connecting the payee to public nodes. route_hints: Vec, }, } +impl Payee { + fn node_id(&self) -> Option { + match self { + Self::Clear { node_id, .. } => Some(*node_id), + _ => None, + } + } +} + /// A list of hops along a payment path terminating with a channel to the recipient. #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct RouteHint(pub Vec); @@ -1080,6 +1089,21 @@ fn default_node_features() -> NodeFeatures { features } +struct LoggedPayeePubkey(Option); +impl fmt::Display for LoggedPayeePubkey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Some(pk) => { + "payee node id ".fmt(f)?; + pk.fmt(f) + }, + None => { + "blinded payee".fmt(f) + }, + } + } +} + /// Finds a route from us (payer) to the given target node (payee). /// /// If the payee provided features in their invoice, they should be provided via `params.payee`. @@ -1129,10 +1153,16 @@ pub(crate) fn get_route( _random_seed_bytes: &[u8; 32] ) -> Result where L::Target: Logger { - let payee_node_id = NodeId::from_pubkey(&payment_params.payee_pubkey); + // 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, + // so use a dummy id for this in the blinded case. + let payee_node_id_opt = payment_params.payee.node_id().map(|pk| NodeId::from_pubkey(&pk)); + const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [42u8; 33]; + let maybe_dummy_payee_pk = payment_params.payee.node_id().unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap()); + let maybe_dummy_payee_node_id = NodeId::from_pubkey(&maybe_dummy_payee_pk); let our_node_id = NodeId::from_pubkey(&our_node_pubkey); - if payee_node_id == our_node_id { + if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) { return Err(LightningError{err: "Cannot generate a route to ourselves".to_owned(), action: ErrorAction::IgnoreError}); } @@ -1145,10 +1175,10 @@ where L::Target: Logger { } match &payment_params.payee { - Payee::Clear { route_hints } => { + Payee::Clear { route_hints, node_id } => { for route in route_hints.iter() { for hop in &route.0 { - if hop.src_node_id == payment_params.payee_pubkey { + if hop.src_node_id == *node_id { return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError}); } } @@ -1231,14 +1261,13 @@ where L::Target: Logger { false } else if let Some(features) = &payment_params.features { features.supports_basic_mpp() - } else if let Some(node) = network_nodes.get(&payee_node_id) { - if let Some(node_info) = node.announcement_info.as_ref() { - node_info.features.supports_basic_mpp() - } else { false } + } else if let Some(payee) = payee_node_id_opt { + network_nodes.get(&payee).map_or(false, |node| node.announcement_info.as_ref().map_or(false, + |info| info.features.supports_basic_mpp())) } else { false }; - log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP and {} first hops {}overriding the network graph", our_node_pubkey, - payment_params.payee_pubkey, if allow_mpp { "with" } else { "without" }, + log_trace!(logger, "Searching for a route from payer {} to {} {} MPP and {} first hops {}overriding the network graph", our_node_pubkey, + LoggedPayeePubkey(payment_params.payee.node_id()), if allow_mpp { "with" } else { "without" }, first_hops.map(|hops| hops.len()).unwrap_or(0), if first_hops.is_some() { "" } else { "not " }); // Step (1). @@ -1341,7 +1370,8 @@ where L::Target: Logger { }); } - log_trace!(logger, "Building path from {} (payee) to {} (us/payer) for value {} msat.", payment_params.payee_pubkey, our_node_pubkey, final_value_msat); + log_trace!(logger, "Building path from {} to payer {} for value {} msat.", + LoggedPayeePubkey(payment_params.payee.node_id()), our_node_pubkey, final_value_msat); macro_rules! add_entry { // Adds entry which goes from $src_node_id to $dest_node_id over the $candidate hop. @@ -1590,7 +1620,7 @@ where L::Target: Logger { // Entries are added to dist in add_entry!() when there is a channel from a node. // Because there are no channels from payee, it will not have a dist entry at this point. // If we're processing any other node, it is always be the result of a channel from it. - assert_eq!($node_id, payee_node_id); + debug_assert_eq!($node_id, maybe_dummy_payee_node_id); false }; @@ -1650,35 +1680,35 @@ where L::Target: Logger { // If first hop is a private channel and the only way to reach the payee, this is the only // place where it could be added. - if let Some(first_channels) = first_hop_targets.get(&payee_node_id) { + payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|first_channels| { for details in first_channels { let candidate = CandidateRouteHop::FirstHop { details }; - let added = add_entry!(candidate, our_node_id, payee_node_id, 0, path_value_msat, + let added = add_entry!(candidate, our_node_id, payee, 0, path_value_msat, 0, 0u64, 0, 0); log_trace!(logger, "{} direct route to payee via SCID {}", if added { "Added" } else { "Skipped" }, candidate.short_channel_id()); } - } + })); // Add the payee as a target, so that the payee-to-payer // search algorithm knows what to start with. - match network_nodes.get(&payee_node_id) { + payee_node_id_opt.map(|payee| match network_nodes.get(&payee) { // The payee is not in our network graph, so nothing to add here. // There is still a chance of reaching them via last_hops though, // so don't yet fail the payment here. // If not, targets.pop() will not even let us enter the loop in step 2. None => {}, Some(node) => { - add_entries_to_cheapest_to_target_node!(node, payee_node_id, 0, path_value_msat, 0, 0u64, 0, 0); + add_entries_to_cheapest_to_target_node!(node, payee, 0, path_value_msat, 0, 0u64, 0, 0); }, - } + }); // Step (2). // If a caller provided us with last hops, add them to routing targets. Since this happens // earlier than general path finding, they will be somewhat prioritized, although currently // it matters only if the fees are exactly the same. let route_hints = match &payment_params.payee { - Payee::Clear { route_hints } => route_hints, + Payee::Clear { route_hints, .. } => route_hints, _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}), }; for route in route_hints.iter().filter(|route| !route.0.is_empty()) { @@ -1693,7 +1723,7 @@ where L::Target: Logger { // We start building the path from reverse, i.e., from payee // to the first RouteHintHop in the path. let hop_iter = route.0.iter().rev(); - let prev_hop_iter = core::iter::once(&payment_params.payee_pubkey).chain( + let prev_hop_iter = core::iter::once(&maybe_dummy_payee_pk).chain( route.0.iter().skip(1).rev().map(|hop| &hop.src_node_id)); let mut hop_used = true; let mut aggregate_next_hops_fee_msat: u64 = 0; @@ -1853,7 +1883,7 @@ where L::Target: Logger { // 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. - if ordered_hops.last().unwrap().0.node_id == payee_node_id { + if ordered_hops.last().unwrap().0.node_id == maybe_dummy_payee_node_id { break 'path_walk; } @@ -1936,7 +1966,7 @@ where L::Target: Logger { // If we found a path back to the payee, we shouldn't try to process it again. This is // the equivalent of the `elem.was_processed` check in // add_entries_to_cheapest_to_target_node!() (see comment there for more info). - if node_id == payee_node_id { continue 'path_construction; } + if node_id == maybe_dummy_payee_node_id { continue 'path_construction; } // Otherwise, since the current target node is not us, // keep "unrolling" the payment graph from payee to payer by @@ -2106,7 +2136,7 @@ where L::Target: Logger { paths, payment_params: Some(payment_params.clone()), }; - log_info!(logger, "Got route to {}: {}", payment_params.payee_pubkey, log_route!(route)); + log_info!(logger, "Got route: {}", log_route!(route)); Ok(route) } diff --git a/pending_changelog/blinded_pay_param_compat.txt b/pending_changelog/blinded_pay_param_compat.txt new file mode 100644 index 00000000000..8e91e00b5c4 --- /dev/null +++ b/pending_changelog/blinded_pay_param_compat.txt @@ -0,0 +1,3 @@ +## Backwards Compatibility + +* `PaymentParameters` written with blinded path info using 0.0.115 will not be readable in 0.0.116 From 6d62b62cecc4892aa4f09e9a881e1a8d591186de Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sat, 29 Apr 2023 15:37:51 -0400 Subject: [PATCH 4/7] Error if BOLT 11 features are provided for blinded payment params --- lightning-invoice/src/payment.rs | 2 +- lightning-invoice/src/utils.rs | 4 +-- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/functional_test_utils.rs | 6 ++-- lightning/src/ln/functional_tests.rs | 20 ++++++------ lightning/src/ln/onion_route_tests.rs | 6 ++-- lightning/src/ln/payment_tests.rs | 26 ++++++++-------- lightning/src/ln/priv_short_conf_tests.rs | 10 +++--- lightning/src/ln/shutdown_tests.rs | 4 +-- lightning/src/routing/router.rs | 38 ++++++++++++----------- 10 files changed, 60 insertions(+), 58 deletions(-) diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 2fc895aaed5..4e7df6807e0 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -154,7 +154,7 @@ fn pay_invoice_using_amount( .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) .with_route_hints(invoice.route_hints()).unwrap(); if let Some(features) = invoice.features() { - payment_params = payment_params.with_features(features.clone()); + payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); } let route_params = RouteParameters { payment_params, diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 109e03d0f8c..8141f395591 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -838,7 +838,7 @@ mod test { let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), invoice.min_final_cltv_expiry_delta() as u32) - .with_features(invoice.features().unwrap().clone()) + .with_bolt11_features(invoice.features().unwrap().clone()).unwrap() .with_route_hints(invoice.route_hints()).unwrap(); let route_params = RouteParameters { payment_params, @@ -1294,7 +1294,7 @@ mod test { let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), invoice.min_final_cltv_expiry_delta() as u32) - .with_features(invoice.features().unwrap().clone()) + .with_bolt11_features(invoice.features().unwrap().clone()).unwrap() .with_route_hints(invoice.route_hints()).unwrap(); let params = RouteParameters { payment_params, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 55c796967d6..6632c33b515 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9180,7 +9180,7 @@ pub mod bench { macro_rules! send_payment { ($node_a: expr, $node_b: expr) => { let payment_params = PaymentParameters::from_node_id($node_b.get_our_node_id(), TEST_FINAL_CLTV) - .with_features($node_b.invoice_features()); + .with_bolt11_features($node_b.invoice_features()).unwrap(); let mut payment_preimage = PaymentPreimage([0; 32]); payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes()); payment_count += 1; diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 96b9dde5aa6..6e7532de875 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1723,7 +1723,7 @@ macro_rules! get_route { macro_rules! get_route_and_payment_hash { ($send_node: expr, $recv_node: expr, $recv_value: expr) => {{ let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features($recv_node.node.invoice_features()); + .with_bolt11_features($recv_node.node.invoice_features()).unwrap(); $crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value) }}; ($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr) => {{ @@ -2272,7 +2272,7 @@ pub const TEST_FINAL_CLTV: u32 = 70; pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) { let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(expected_route.last().unwrap().node.invoice_features()); + .with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap(); let route = get_route(origin_node, &payment_params, recv_value).unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), expected_route.len()); @@ -2286,7 +2286,7 @@ pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) { let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(expected_route.last().unwrap().node.invoice_features()); + .with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap(); let network_graph = origin_node.network_graph.read_only(); let scorer = test_utils::TestScorer::new(); let seed = [0u8; 32]; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 30ce176ad16..e30a0cb12a0 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1829,7 +1829,7 @@ fn test_channel_reserve_holding_cell_htlcs() { // attempt to send amt_msat > their_max_htlc_value_in_flight_msat { let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[2].node.invoice_features()).with_max_channel_saturation_power_of_half(0); + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0); let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, recv_value_0); route.paths[0].hops.last_mut().unwrap().fee_msat += 1; assert!(route.paths[0].hops.iter().rev().skip(1).all(|h| h.fee_msat == feemsat)); @@ -1856,7 +1856,7 @@ fn test_channel_reserve_holding_cell_htlcs() { } let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[2].node.invoice_features()).with_max_channel_saturation_power_of_half(0); + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0); let route = get_route!(nodes[0], payment_params, recv_value_0).unwrap(); let (payment_preimage, ..) = send_along_route(&nodes[0], route, &[&nodes[1], &nodes[2]], recv_value_0); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); @@ -4795,7 +4795,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() { // script push size limit so that the below script length checks match // ACCEPTED_HTLC_SCRIPT_WEIGHT. let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV - 40) - .with_features(nodes[3].node.invoice_features()); + .with_bolt11_features(nodes[3].node.invoice_features()).unwrap(); let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], payment_params, 800_000); send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 800_000, duplicate_payment_hash, payment_secret); @@ -6101,7 +6101,7 @@ fn test_update_add_htlc_bolt2_sender_cltv_expiry_too_high() { let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 0); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 0) - .with_features(nodes[1].node.invoice_features()); + .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_params, 100000000); route.paths[0].hops.last_mut().unwrap().cltv_expiry_delta = 500000001; unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash, @@ -7043,7 +7043,7 @@ fn test_check_htlc_underpaying() { let scorer = test_utils::TestScorer::new(); let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_features(nodes[1].node.invoice_features()); + let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, nodes[0].logger, &scorer, &random_seed_bytes).unwrap(); let (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]); let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None).unwrap(); @@ -7189,7 +7189,7 @@ fn test_bump_penalty_txn_on_revoked_commitment() { let payment_preimage = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0; let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 30) - .with_features(nodes[0].node.invoice_features()); + .with_bolt11_features(nodes[0].node.invoice_features()).unwrap(); let (route,_, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], payment_params, 3000000); send_along_route(&nodes[1], route, &vec!(&nodes[0])[..], 3000000); @@ -7294,13 +7294,13 @@ fn test_bump_penalty_txn_on_revoked_htlcs() { let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000); // Lock HTLC in both directions (using a slightly lower CLTV delay to provide timely RBF bumps) - let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 50).with_features(nodes[1].node.invoice_features()); + let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 50).with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let scorer = test_utils::TestScorer::new(); let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes(); let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 3_000_000, nodes[0].logger, &scorer, &random_seed_bytes).unwrap(); let payment_preimage = send_along_route(&nodes[0], route, &[&nodes[1]], 3_000_000).0; - let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 50).with_features(nodes[0].node.invoice_features()); + let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 50).with_bolt11_features(nodes[0].node.invoice_features()).unwrap(); let route = get_route(&nodes[1].node.get_our_node_id(), &payment_params, &nodes[1].network_graph.read_only(), None, 3_000_000, nodes[0].logger, &scorer, &random_seed_bytes).unwrap(); send_along_route(&nodes[1], route, &[&nodes[0]], 3_000_000); @@ -9300,7 +9300,7 @@ fn do_test_dup_htlc_second_rejected(test_for_second_fail_panic: bool) { let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[1].node.invoice_features()); + .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let route = get_route!(nodes[0], payment_params, 10_000).unwrap(); let (our_payment_preimage, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[1]); @@ -9409,7 +9409,7 @@ fn test_inconsistent_mpp_params() { let chan_2_3 =create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 100_000, 0); let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[3].node.invoice_features()); + .with_bolt11_features(nodes[3].node.invoice_features()).unwrap(); let mut route = get_route!(nodes[0], payment_params, 15_000_000).unwrap(); assert_eq!(route.paths.len(), 2); route.paths.sort_by(|path_a, _| { diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 209d05461a8..d8403c57cca 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -714,7 +714,7 @@ fn do_test_onion_failure_stale_channel_update(announced_channel: bool) { htlc_minimum_msat: None, }])]; let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty, TEST_FINAL_CLTV) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(hop_hints).unwrap(); get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT) }; @@ -861,7 +861,7 @@ fn test_always_create_tlv_format_onion_payloads() { create_announced_chan_between_nodes(&nodes, 1, 2); let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(InvoiceFeatures::empty()); + .with_bolt11_features(InvoiceFeatures::empty()).unwrap(); let (route, _payment_hash, _payment_preimage, _payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000); let hops = &route.paths[0].hops; @@ -963,7 +963,7 @@ macro_rules! get_phantom_route { let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap(); let phantom_route_hint = $nodes[1].node.get_phantom_route_hints(); let payment_params = PaymentParameters::from_node_id(phantom_pubkey, TEST_FINAL_CLTV) - .with_features($nodes[1].node.invoice_features()) + .with_bolt11_features($nodes[1].node.invoice_features()).unwrap() .with_route_hints(vec![RouteHint(vec![ RouteHintHop { src_node_id: $nodes[0].node.get_our_node_id(), diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index ecbc75b16e0..731360d37f4 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -857,7 +857,7 @@ fn get_ldk_payment_preimage() { let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs, None).unwrap(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[1].node.invoice_features()); + .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let scorer = test_utils::TestScorer::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -1410,7 +1410,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { htlc_maximum_msat: None, }]) ]).unwrap() - .with_features(nodes[2].node.invoice_features()); + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -1600,7 +1600,7 @@ fn do_automatic_retries(test: AutoRetry) { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -1819,7 +1819,7 @@ fn auto_retry_partial_failure() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2031,7 +2031,7 @@ fn auto_retry_zero_attempts_send_error() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2071,7 +2071,7 @@ fn fails_paying_after_rejected_by_payee() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2118,7 +2118,7 @@ fn retry_multi_path_single_failed_payment() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: amt_msat, @@ -2212,7 +2212,7 @@ fn immediate_retry_on_failure() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2301,7 +2301,7 @@ fn no_extra_retries_on_back_to_back_fail() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2503,7 +2503,7 @@ fn test_simple_partial_retry() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2669,7 +2669,7 @@ fn test_threaded_payment_retries() { invoice_features.set_basic_mpp_optional(); let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV) .with_expiry_time(payment_expiry_secs as u64) - .with_features(invoice_features); + .with_bolt11_features(invoice_features).unwrap(); let mut route_params = RouteParameters { payment_params, final_value_msat: amt_msat, @@ -2906,7 +2906,7 @@ fn do_claim_from_closed_chan(fail_payment: bool) { let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[3]); let mut route_params = RouteParameters { payment_params: PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[1].node.invoice_features()), + .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(), final_value_msat: 10_000_000, }; let mut route = nodes[0].router.find_route(&nodes[0].node.get_our_node_id(), &route_params, @@ -3050,7 +3050,7 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) { let payment_metadata = vec![44, 49, 52, 142]; let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[1].node.invoice_features()); + .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let mut route_params = RouteParameters { payment_params, final_value_msat: amt_msat, diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index 280d967bbfa..519afc85e77 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -67,7 +67,7 @@ fn test_priv_forwarding_rejection() { }]); let last_hops = vec![route_hint]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(last_hops).unwrap(); let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000); @@ -236,7 +236,7 @@ fn test_routed_scid_alias() { htlc_minimum_msat: None, }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(hop_hints).unwrap(); let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000); assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); @@ -402,7 +402,7 @@ fn test_inbound_scid_privacy() { htlc_minimum_msat: None, }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(hop_hints.clone()).unwrap(); let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000); assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); @@ -418,7 +418,7 @@ fn test_inbound_scid_privacy() { hop_hints[0].0[0].short_channel_id = last_hop[0].short_channel_id.unwrap(); let payment_params_2 = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(hop_hints).unwrap(); let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params_2, 100_000); assert_eq!(route_2.paths[0].hops[1].short_channel_id, last_hop[0].short_channel_id.unwrap()); @@ -470,7 +470,7 @@ fn test_scid_alias_returned() { htlc_minimum_msat: None, }])]; let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42) - .with_features(nodes[2].node.invoice_features()) + .with_bolt11_features(nodes[2].node.invoice_features()).unwrap() .with_route_hints(hop_hints).unwrap(); let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000); assert_eq!(route.paths[0].hops[1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap()); diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index 0fcf4175280..ae950805f49 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -94,9 +94,9 @@ fn updates_shutdown_wait() { let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[0]); - let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_features(nodes[1].node.invoice_features()); + let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap(); let route_1 = get_route(&nodes[0].node.get_our_node_id(), &payment_params_1, &nodes[0].network_graph.read_only(), None, 100000, &logger, &scorer, &random_seed_bytes).unwrap(); - let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), TEST_FINAL_CLTV).with_features(nodes[0].node.invoice_features()); + let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[0].node.invoice_features()).unwrap(); let route_2 = get_route(&nodes[1].node.get_our_node_id(), &payment_params_2, &nodes[1].network_graph.read_only(), None, 100000, &logger, &scorer, &random_seed_bytes).unwrap(); unwrap_send_err!(nodes[0].node.send_payment_with_route(&route_1, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 5c92871007c..48178249e28 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -625,14 +625,16 @@ impl PaymentParameters { /// The `final_cltv_expiry_delta` should match the expected final CLTV delta the recipient has /// provided. pub fn for_keysend(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self { - Self::from_node_id(payee_pubkey, final_cltv_expiry_delta).with_features(InvoiceFeatures::for_keysend()) + Self::from_node_id(payee_pubkey, final_cltv_expiry_delta).with_bolt11_features(InvoiceFeatures::for_keysend()).expect("PaymentParameters::from_node_id should always initialize the payee as unblinded") } - /// Includes the payee's features. + /// Includes the payee's features. Errors if the parameters were initialized with blinded payment + /// paths. /// /// This is not exported to bindings users since bindings don't support move semantics - pub fn with_features(self, features: InvoiceFeatures) -> Self { - Self { features: Some(features), ..self } + pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result { + if let Payee::Blinded { .. } = self.payee { return Err(()) } + Ok(Self { features: Some(features), ..self }) } /// Includes hints for routing to the payee. Errors if the parameters were initialized with @@ -2571,7 +2573,7 @@ mod tests { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); 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(); @@ -3544,7 +3546,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // We will use a simple single-path route from // our node to node2 via node0: channels {1, 3}. @@ -3820,7 +3822,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // Path via {node7, node2, node4} is channels {12, 13, 6, 11}. // {12, 13, 11} have the capacities of 100, {6} has a capacity of 50. @@ -3995,7 +3997,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); let payment_params = PaymentParameters::from_node_id(nodes[2], 42) - .with_features(channelmanager::provided_invoice_features(&config)); + .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // We need a route consisting of 3 paths: // From our node to node2 via node0, node7, node1 (three paths one hop each). @@ -4154,7 +4156,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // We need a route consisting of 3 paths: // From our node to node3 via {node0, node2}, {node7, node2, node4} and {node7, node2}. @@ -4319,7 +4321,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // This test checks that if we have two cheaper paths and one more expensive path, // so that liquidity-wise any 2 of 3 combination is sufficient, @@ -4489,7 +4491,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // We need a route consisting of 2 paths: // From our node to node3 via {node0, node2} and {node7, node2, node4}. @@ -4671,7 +4673,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42).with_features(channelmanager::provided_invoice_features(&config)) + let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap() .with_route_hints(vec![RouteHint(vec![RouteHintHop { src_node_id: nodes[2], short_channel_id: 42, @@ -4763,7 +4765,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_features(channelmanager::provided_invoice_features(&config)) + let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap() .with_max_channel_saturation_power_of_half(0); // We need a route consisting of 3 paths: @@ -5119,7 +5121,7 @@ mod tests { let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); // We modify the graph to set the htlc_minimum of channel 2 and 4 as needed - channel 2 // gets an htlc_maximum_msat of 80_000 and channel 4 an htlc_minimum_msat of 90_000. We @@ -5187,7 +5189,7 @@ mod tests { let network_graph = NetworkGraph::new(Network::Testnet, Arc::clone(&logger)); let scorer = ln_test_utils::TestScorer::new(); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[0], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[0], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -5681,7 +5683,7 @@ mod tests { }); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); // 100,000 sats is less than the available liquidity on each channel, set above. @@ -5766,7 +5768,7 @@ mod tests { let src = &PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); seed = seed.overflowing_mul(0xdeadbeef).0; let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); - let payment_params = PaymentParameters::from_node_id(dst, 42).with_features(channelmanager::provided_invoice_features(&config)); + let payment_params = PaymentParameters::from_node_id(dst, 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap(); let amt = seed as u64 % 200_000_000; let params = ProbabilisticScoringParameters::default(); let scorer = ProbabilisticScorer::new(params, &graph, &logger); @@ -6112,7 +6114,7 @@ mod benches { let src = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); seed *= 0xdeadbeef; let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); - let params = PaymentParameters::from_node_id(dst, 42).with_features(features.clone()); + let params = PaymentParameters::from_node_id(dst, 42).with_bolt11_features(features.clone()).unwrap(); let first_hop = first_hop(src); let amt = seed as u64 % 1_000_000; if let Ok(route) = get_route(&payer, ¶ms, &graph.read_only(), Some(&[&first_hop]), amt, &DummyLogger{}, &scorer, &random_seed_bytes) { From 91dc76721b4d6cd7b1ca812d59d70dd4c666ead9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 3 May 2023 13:04:25 -0400 Subject: [PATCH 5/7] Move BOLT11 features from top level PaymentParams to Payee::Clear Since blinded payees don't have this. --- lightning/src/routing/router.rs | 55 ++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 48178249e28..f547d9e4420 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -493,14 +493,6 @@ const MAX_PATH_LENGTH_ESTIMATE: u8 = 19; /// Information used to route a payment. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PaymentParameters { - /// Features supported by the payee. - /// - /// May be set from the payee's invoice or via [`for_keysend`]. May be `None` if the invoice - /// does not contain any features. - /// - /// [`for_keysend`]: Self::for_keysend - pub features: Option, - /// Information about the payee, such as their features and route hints for their channels. pub payee: Payee, @@ -550,7 +542,7 @@ impl Writeable for PaymentParameters { write_tlv_fields!(writer, { (0, self.payee.node_id(), option), (1, self.max_total_cltv_expiry_delta, required), - (2, self.features, option), + (2, if let Payee::Clear { features, .. } = &self.payee { features } else { &None }, option), (3, self.max_path_count, required), (4, *clear_hints, vec_type), (5, self.max_channel_saturation_power_of_half, required), @@ -586,11 +578,11 @@ impl ReadableArgs for PaymentParameters { Payee::Clear { route_hints: clear_route_hints, node_id: payee_pubkey.ok_or(DecodeError::InvalidValue)?, + features, } }; Ok(Self { max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)), - features, max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)), payee, max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)), @@ -609,8 +601,7 @@ impl PaymentParameters { /// provided. pub fn from_node_id(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self { Self { - features: None, - payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![] }, + payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![], features: None }, expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, @@ -633,8 +624,11 @@ impl PaymentParameters { /// /// This is not exported to bindings users since bindings don't support move semantics pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result { - if let Payee::Blinded { .. } = self.payee { return Err(()) } - Ok(Self { features: Some(features), ..self }) + match self.payee { + Payee::Blinded { .. } => Err(()), + Payee::Clear { route_hints, node_id, .. } => + Ok(Self { payee: Payee::Clear { route_hints, node_id, features: Some(features) }, ..self }) + } } /// Includes hints for routing to the payee. Errors if the parameters were initialized with @@ -644,8 +638,8 @@ impl PaymentParameters { pub fn with_route_hints(self, route_hints: Vec) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { node_id, .. } => - Ok(Self { payee: Payee::Clear { route_hints, node_id }, ..self }) + Payee::Clear { node_id, features, .. } => + Ok(Self { payee: Payee::Clear { route_hints, node_id, features }, ..self }) } } @@ -695,6 +689,13 @@ pub enum Payee { node_id: PublicKey, /// Hints for routing to the payee, containing channels connecting the payee to public nodes. route_hints: Vec, + /// Features supported by the payee. + /// + /// May be set from the payee's invoice or via [`for_keysend`]. May be `None` if the invoice + /// does not contain any features. + /// + /// [`for_keysend`]: PaymentParameters::for_keysend + features: Option, }, } @@ -705,6 +706,18 @@ impl Payee { _ => None, } } + fn node_features(&self) -> Option { + match self { + Self::Clear { features, .. } => features.as_ref().map(|f| f.to_context()), + _ => None, + } + } + fn supports_basic_mpp(&self) -> bool { + match self { + Self::Clear { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), + _ => false, + } + } } /// A list of hops along a payment path terminating with a channel to the recipient. @@ -1177,7 +1190,7 @@ where L::Target: Logger { } match &payment_params.payee { - Payee::Clear { route_hints, node_id } => { + Payee::Clear { route_hints, node_id, .. } => { for route in route_hints.iter() { for hop in &route.0 { if hop.src_node_id == *node_id { @@ -1261,8 +1274,8 @@ where L::Target: Logger { // work reliably. let allow_mpp = if payment_params.max_path_count == 1 { false - } else if let Some(features) = &payment_params.features { - features.supports_basic_mpp() + } else if payment_params.payee.supports_basic_mpp() { + true } else if let Some(payee) = payee_node_id_opt { network_nodes.get(&payee).map_or(false, |node| node.announcement_info.as_ref().map_or(false, |info| info.features.supports_basic_mpp())) @@ -2120,10 +2133,10 @@ where L::Target: Logger { // Make sure we would never create a route with more paths than we allow. debug_assert!(selected_paths.len() <= payment_params.max_path_count.into()); - if let Some(features) = &payment_params.features { + if let Some(node_features) = payment_params.payee.node_features() { for path in selected_paths.iter_mut() { if let Ok(route_hop) = path.last_mut().unwrap() { - route_hop.node_features = features.to_context(); + route_hop.node_features = node_features.clone(); } } } From 746f25aed06c19b3a4d4bfb25d15b3b08ed410a0 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 3 May 2023 14:05:20 -0400 Subject: [PATCH 6/7] Add BOLT 12 features to PaymentParams --- lightning/src/ln/features.rs | 9 +++++ lightning/src/routing/router.rs | 70 +++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index cf375603b37..3a7d9216904 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -532,6 +532,14 @@ impl InvoiceFeatures { } } +impl Bolt12InvoiceFeatures { + /// Converts `Bolt12InvoiceFeatures` to `Features`. Only known `Bolt12InvoiceFeatures` relevant + /// to context `C` are included in the result. + pub(crate) fn to_context(&self) -> Features { + self.to_context_internal() + } +} + impl ChannelTypeFeatures { // Maps the relevant `InitFeatures` to `ChannelTypeFeatures`. Any unknown features to // `ChannelTypeFeatures` are not included in the result. @@ -791,6 +799,7 @@ impl_feature_len_prefixed_write!(InitFeatures); impl_feature_len_prefixed_write!(ChannelFeatures); impl_feature_len_prefixed_write!(NodeFeatures); impl_feature_len_prefixed_write!(InvoiceFeatures); +impl_feature_len_prefixed_write!(Bolt12InvoiceFeatures); impl_feature_len_prefixed_write!(BlindedHopFeatures); // Some features only appear inside of TLVs, so they don't have a length prefix when serialized. diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index f547d9e4420..2ab3522d2e6 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -16,7 +16,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::ln::PaymentHash; use crate::ln::channelmanager::{ChannelDetails, PaymentId}; -use crate::ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures}; +use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, InvoiceFeatures, NodeFeatures}; use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use crate::offers::invoice::BlindedPayInfo; use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; @@ -537,12 +537,12 @@ impl Writeable for PaymentParameters { let mut blinded_hints = &vec![]; match &self.payee { Payee::Clear { route_hints, .. } => clear_hints = route_hints, - Payee::Blinded { route_hints } => blinded_hints = route_hints, + Payee::Blinded { route_hints, .. } => blinded_hints = route_hints, } write_tlv_fields!(writer, { (0, self.payee.node_id(), option), (1, self.max_total_cltv_expiry_delta, required), - (2, if let Payee::Clear { features, .. } = &self.payee { features } else { &None }, option), + (2, self.payee.features(), option), (3, self.max_path_count, required), (4, *clear_hints, vec_type), (5, self.max_channel_saturation_power_of_half, required), @@ -560,7 +560,7 @@ impl ReadableArgs for PaymentParameters { _init_and_read_tlv_fields!(reader, { (0, payee_pubkey, option), (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)), - (2, features, option), + (2, features, (option: ReadableArgs, payee_pubkey.is_some())), (3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)), (4, route_hints, vec_type), (5, max_channel_saturation_power_of_half, (default_value, 2)), @@ -573,12 +573,15 @@ impl ReadableArgs for PaymentParameters { let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) } - Payee::Blinded { route_hints: blinded_route_hints } + Payee::Blinded { + route_hints: blinded_route_hints, + features: features.and_then(|f: Features| f.bolt12()), + } } else { Payee::Clear { route_hints: clear_route_hints, node_id: payee_pubkey.ok_or(DecodeError::InvalidValue)?, - features, + features: features.and_then(|f| f.bolt11()), } }; Ok(Self { @@ -682,6 +685,11 @@ pub enum Payee { /// Aggregated routing info and blinded paths, for routing to the payee without knowing their /// node id. route_hints: Vec<(BlindedPayInfo, BlindedPath)>, + /// Features supported by the payee. + /// + /// May be set from the payee's invoice. May be `None` if the invoice does not contain any + /// features. + features: Option, }, /// The recipient included these route hints in their BOLT11 invoice. Clear { @@ -709,17 +717,63 @@ impl Payee { fn node_features(&self) -> Option { match self { Self::Clear { features, .. } => features.as_ref().map(|f| f.to_context()), - _ => None, + Self::Blinded { features, .. } => features.as_ref().map(|f| f.to_context()), } } fn supports_basic_mpp(&self) -> bool { match self { Self::Clear { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), - _ => false, + Self::Blinded { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), + } + } + fn features(&self) -> Option { + match self { + Self::Clear { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt11(f)), + Self::Blinded { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt12(f)), } } } +enum FeaturesRef<'a> { + Bolt11(&'a InvoiceFeatures), + Bolt12(&'a Bolt12InvoiceFeatures), +} +enum Features { + Bolt11(InvoiceFeatures), + Bolt12(Bolt12InvoiceFeatures), +} + +impl Features { + fn bolt12(self) -> Option { + match self { + Self::Bolt12(f) => Some(f), + _ => None, + } + } + fn bolt11(self) -> Option { + match self { + Self::Bolt11(f) => Some(f), + _ => None, + } + } +} + +impl<'a> Writeable for FeaturesRef<'a> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + match self { + Self::Bolt11(f) => Ok(f.write(w)?), + Self::Bolt12(f) => Ok(f.write(w)?), + } + } +} + +impl ReadableArgs for Features { + fn read(reader: &mut R, bolt11: bool) -> Result { + if bolt11 { return Ok(Self::Bolt11(Readable::read(reader)?)) } + Ok(Self::Bolt12(Readable::read(reader)?)) + } +} + /// A list of hops along a payment path terminating with a channel to the recipient. #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct RouteHint(pub Vec); From d56672c11d69af461f5670a6495e945ff11ea5e2 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 27 Apr 2023 17:37:38 -0400 Subject: [PATCH 7/7] Move final_cltv_expiry_delta from PaymentParams to Payee::Clear Since blinded pay params won't have this value. --- lightning/src/ln/channelmanager.rs | 8 +++-- lightning/src/routing/router.rs | 53 +++++++++++++++++++----------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6632c33b515..d373dc63322 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -45,7 +45,7 @@ use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, No #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::InvoiceFeatures; use crate::routing::gossip::NetworkGraph; -use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters, Router}; +use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, Router}; use crate::routing::scoring::ProbabilisticScorer; use crate::ln::msgs; use crate::ln::onion_utils; @@ -7061,8 +7061,10 @@ impl Readable for HTLCSource { return Err(DecodeError::InvalidValue); } if let Some(params) = payment_params.as_mut() { - if params.final_cltv_expiry_delta == 0 { - params.final_cltv_expiry_delta = path.final_cltv_expiry_delta().ok_or(DecodeError::InvalidValue)?; + if let Payee::Clear { ref mut final_cltv_expiry_delta, .. } = params.payee { + if final_cltv_expiry_delta == &0 { + *final_cltv_expiry_delta = path.final_cltv_expiry_delta().ok_or(DecodeError::InvalidValue)?; + } } } Ok(HTLCSource::OutboundRoute { diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 2ab3522d2e6..cbf2d1f0bf2 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -442,7 +442,7 @@ impl Writeable for RouteParameters { (2, self.final_value_msat, required), // LDK versions prior to 0.0.114 had the `final_cltv_expiry_delta` parameter in // `RouteParameters` directly. For compatibility, we write it here. - (4, self.payment_params.final_cltv_expiry_delta, required), + (4, self.payment_params.payee.final_cltv_expiry_delta(), option), }); Ok(()) } @@ -453,11 +453,13 @@ impl Readable for RouteParameters { _init_and_read_tlv_fields!(reader, { (0, payment_params, (required: ReadableArgs, 0)), (2, final_value_msat, required), - (4, final_cltv_expiry_delta, required), + (4, final_cltv_delta, option), }); let mut payment_params: PaymentParameters = payment_params.0.unwrap(); - if payment_params.final_cltv_expiry_delta == 0 { - payment_params.final_cltv_expiry_delta = final_cltv_expiry_delta.0.unwrap(); + if let Payee::Clear { ref mut final_cltv_expiry_delta, .. } = payment_params.payee { + if final_cltv_expiry_delta == &0 { + *final_cltv_expiry_delta = final_cltv_delta.ok_or(DecodeError::InvalidValue)?; + } } Ok(Self { payment_params, @@ -526,9 +528,6 @@ 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, - - /// The minimum CLTV delta at the end of the route. This value must not be zero. - pub final_cltv_expiry_delta: u32, } impl Writeable for PaymentParameters { @@ -549,7 +548,7 @@ impl Writeable for PaymentParameters { (6, self.expiry_time, option), (7, self.previously_failed_channels, vec_type), (8, *blinded_hints, optional_vec), - (9, self.final_cltv_expiry_delta, required), + (9, self.payee.final_cltv_expiry_delta(), option), }); Ok(()) } @@ -582,6 +581,7 @@ impl ReadableArgs for PaymentParameters { route_hints: clear_route_hints, node_id: payee_pubkey.ok_or(DecodeError::InvalidValue)?, features: features.and_then(|f| f.bolt11()), + final_cltv_expiry_delta: final_cltv_expiry_delta.0.unwrap(), } }; Ok(Self { @@ -591,7 +591,6 @@ impl ReadableArgs for PaymentParameters { 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()), - final_cltv_expiry_delta: _init_tlv_based_struct_field!(final_cltv_expiry_delta, (default_value, unused)), }) } } @@ -604,13 +603,12 @@ impl PaymentParameters { /// provided. pub fn from_node_id(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self { Self { - payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![], features: None }, + payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![], features: None, final_cltv_expiry_delta }, expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, max_channel_saturation_power_of_half: 2, previously_failed_channels: Vec::new(), - final_cltv_expiry_delta, } } @@ -629,8 +627,12 @@ impl PaymentParameters { pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { route_hints, node_id, .. } => - Ok(Self { payee: Payee::Clear { route_hints, node_id, features: Some(features) }, ..self }) + Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } => + Ok(Self { + payee: Payee::Clear { + route_hints, node_id, features: Some(features), final_cltv_expiry_delta + }, ..self + }) } } @@ -641,8 +643,12 @@ impl PaymentParameters { pub fn with_route_hints(self, route_hints: Vec) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { node_id, features, .. } => - Ok(Self { payee: Payee::Clear { route_hints, node_id, features }, ..self }) + Payee::Clear { node_id, features, final_cltv_expiry_delta, .. } => + Ok(Self { + payee: Payee::Clear { + route_hints, node_id, features, final_cltv_expiry_delta, + }, ..self + }) } } @@ -704,6 +710,8 @@ pub enum Payee { /// /// [`for_keysend`]: PaymentParameters::for_keysend features: Option, + /// The minimum CLTV delta at the end of the route. This value must not be zero. + final_cltv_expiry_delta: u32, }, } @@ -732,6 +740,12 @@ impl Payee { Self::Blinded { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt12(f)), } } + fn final_cltv_expiry_delta(&self) -> Option { + match self { + Self::Clear { final_cltv_expiry_delta, .. } => Some(*final_cltv_expiry_delta), + _ => None, + } + } } enum FeaturesRef<'a> { @@ -1256,7 +1270,8 @@ where L::Target: Logger { _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}), } - if payment_params.max_total_cltv_expiry_delta <= payment_params.final_cltv_expiry_delta { + let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0); + if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta { return Err(LightningError{err: "Can't find a route where the maximum total CLTV expiry delta is below the final CLTV expiry.".to_owned(), action: ErrorAction::IgnoreError}); } @@ -1487,9 +1502,9 @@ where L::Target: Logger { // In order to already account for some of the privacy enhancing random CLTV // expiry delta offset we add on top later, we subtract a rough estimate // (2*MEDIAN_HOP_CLTV_EXPIRY_DELTA) here. - let max_total_cltv_expiry_delta = (payment_params.max_total_cltv_expiry_delta - payment_params.final_cltv_expiry_delta) + let max_total_cltv_expiry_delta = (payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta) .checked_sub(2*MEDIAN_HOP_CLTV_EXPIRY_DELTA) - .unwrap_or(payment_params.max_total_cltv_expiry_delta - payment_params.final_cltv_expiry_delta); + .unwrap_or(payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta); let hop_total_cltv_delta = ($next_hops_cltv_delta as u32) .saturating_add($candidate.cltv_expiry_delta()); let exceeds_cltv_delta_limit = hop_total_cltv_delta > max_total_cltv_expiry_delta; @@ -2179,7 +2194,7 @@ where L::Target: Logger { }).collect::>(); // Propagate the cltv_expiry_delta one hop backwards since the delta from the current hop is // applicable for the previous hop. - path.iter_mut().rev().fold(payment_params.final_cltv_expiry_delta, |prev_cltv_expiry_delta, hop| { + path.iter_mut().rev().fold(final_cltv_expiry_delta, |prev_cltv_expiry_delta, hop| { core::mem::replace(&mut hop.as_mut().unwrap().cltv_expiry_delta, prev_cltv_expiry_delta) }); selected_paths.push(path);