use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode};
-use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
-use crate::ln::types::PaymentHash;
-use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA};
+use crate::blinded_path::message;
+use crate::blinded_path::payment::{ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs, self};
+use crate::ln::{PaymentHash, PaymentPreimage};
+use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA, RecipientOnionFields};
use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
+use crate::ln::onion_utils;
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta,
htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0),
};
- Some(ForwardNode {
+ Some(payment::ForwardNode {
tlvs: ForwardTlvs {
short_channel_id,
payment_relay,
fn create_blinded_paths<
T: secp256k1::Signing + secp256k1::Verification
> (
- &self, recipient: PublicKey, peers: Vec<PublicKey>, secp_ctx: &Secp256k1<T>,
+ &self, recipient: PublicKey, peers: Vec<message::ForwardNode>, secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedPath>, ()> {
self.message_router.create_blinded_paths(recipient, peers, secp_ctx)
}
pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
Self { payment_params, final_value_msat, max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000) }
}
+
+ /// Sets the maximum number of hops that can be included in a payment path, based on the provided
+ /// [`RecipientOnionFields`] and blinded paths.
+ pub fn set_max_path_length(
+ &mut self, recipient_onion: &RecipientOnionFields, is_keysend: bool, best_block_height: u32
+ ) -> Result<(), ()> {
+ let keysend_preimage_opt = is_keysend.then(|| PaymentPreimage([42; 32]));
+ onion_utils::set_max_path_length(
+ self, recipient_onion, keysend_preimage_opt, best_block_height
+ )
+ }
}
impl Writeable for RouteParameters {
/// 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
(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(())
}
(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 {
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)),
})
}
}
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(),
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(),
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,
let max_total_routing_fee_msat = route_params.max_total_routing_fee_msat.unwrap_or(u64::max_value());
- log_trace!(logger, "Searching for a route from payer {} to {} {} MPP and {} first hops {}overriding the network graph with a fee limit of {} msat",
+ log_trace!(logger, "Searching for a route from payer {} to {} {} MPP and {} first hops {}overriding the network graph of {} nodes and {} channels with a fee limit of {} msat",
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 " },
+ network_graph.nodes().len(), network_graph.channels().len(),
max_total_routing_fee_msat);
// Step (1).
// 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
};
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);
}
}
}
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();
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);
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]
});
// 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) {
// 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::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
} 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();
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 {
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);
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::<u8>::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]
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 5,
+ timestamp: 2,
+ flags: 3, // disable direction 1
+ cltv_expiry_delta: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: 200_000,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
// Add 100 sats to the capacities of {12, 13}, because these channels
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 5,
+ timestamp: 2,
+ flags: 3, // disable direction 1
+ cltv_expiry_delta: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: 200_000,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
// Add 100 sats to the capacities of {12, 13}, because these channels
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 5,
+ timestamp: 2,
+ flags: 3, // Disable direction 1
+ cltv_expiry_delta: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: 100_000,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
// All channels should be 100 sats capacity. But for the fee experiment,
let payment_params = PaymentParameters::from_node_id(nodes[6], 42);
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
- update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 6,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (6 << 4) | 0,
- htlc_minimum_msat: 0,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 0,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&our_privkey, 0), (&privkeys[1], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 6,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (6 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
- update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 5,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (5 << 4) | 0,
- htlc_minimum_msat: 0,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 100,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&privkeys[1], 0), (&privkeys[4], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 5,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (5 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 100,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], NodeFeatures::from_le_bytes(id_to_feature_flags(4)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4);
- update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 4,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (4 << 4) | 0,
- htlc_minimum_msat: 0,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 0,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&privkeys[4], 0), (&privkeys[3], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 4,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (4 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], NodeFeatures::from_le_bytes(id_to_feature_flags(3)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[3], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3);
- update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 3,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (3 << 4) | 0,
- htlc_minimum_msat: 0,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 0,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&privkeys[3], 0), (&privkeys[2], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 3,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (3 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], NodeFeatures::from_le_bytes(id_to_feature_flags(2)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2);
- update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 2,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (2 << 4) | 0,
- htlc_minimum_msat: 0,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 0,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&privkeys[2], 0), (&privkeys[4], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 2,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (2 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
- update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: ChainHash::using_genesis_block(Network::Testnet),
- short_channel_id: 1,
- timestamp: 1,
- flags: 0,
- cltv_expiry_delta: (1 << 4) | 0,
- htlc_minimum_msat: 100,
- htlc_maximum_msat: MAX_VALUE_MSAT,
- fee_base_msat: 0,
- fee_proportional_millionths: 0,
- excess_data: Vec::new()
- });
+ for (key, flags) in [(&privkeys[4], 0), (&privkeys[6], 3)] {
+ update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 1,
+ timestamp: 1,
+ flags,
+ cltv_expiry_delta: (1 << 4) | 0,
+ htlc_minimum_msat: 100,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ }
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[6], NodeFeatures::from_le_bytes(id_to_feature_flags(6)), 0);
{