X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Frouter.rs;h=8543956ac657de2da7a1ac163478aa1fd5ba22b2;hb=bde841e928da56354d267cb329eea2ee9359e3e3;hp=170ac9c991c43b3f4b079a026bd388801a6ca12d;hpb=31042ab7d524d75a1e9eb34080c1bdc666c9101e;p=rust-lightning diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 170ac9c9..8543956a 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -13,22 +13,230 @@ //! interrogate it to get routes for your own payments. use bitcoin::secp256k1::PublicKey; - -use ln::channelmanager::ChannelDetails; -use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures}; -use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; -use routing::gossip::{DirectedChannelInfoWithUpdate, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; -use routing::scoring::{ChannelUsage, Score}; -use util::ser::{Writeable, Readable, Writer}; -use util::logger::{Level, Logger}; -use util::chacha20::ChaCha20; - -use io; -use prelude::*; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256::Hash as Sha256; + +use crate::ln::PaymentHash; +use crate::ln::channelmanager::{ChannelDetails, PaymentId}; +use crate::ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures}; +use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; +use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; +use crate::routing::scoring::{ChannelUsage, LockableScore, Score}; +use crate::util::ser::{Writeable, Readable, Writer}; +use crate::util::logger::{Level, Logger}; +use crate::util::chacha20::ChaCha20; + +use crate::io; +use crate::prelude::*; +use crate::sync::Mutex; use alloc::collections::BinaryHeap; use core::cmp; use core::ops::Deref; +/// A [`Router`] implemented using [`find_route`]. +pub struct DefaultRouter>, L: Deref, S: Deref> where + L::Target: Logger, + S::Target: for <'a> LockableScore<'a>, +{ + network_graph: G, + logger: L, + random_seed_bytes: Mutex<[u8; 32]>, + scorer: S +} + +impl>, L: Deref, S: Deref> DefaultRouter where + L::Target: Logger, + S::Target: for <'a> LockableScore<'a>, +{ + /// Creates a new router. + pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S) -> Self { + let random_seed_bytes = Mutex::new(random_seed_bytes); + Self { network_graph, logger, random_seed_bytes, scorer } + } +} + +impl>, L: Deref, S: Deref> Router for DefaultRouter where + L::Target: Logger, + S::Target: for <'a> LockableScore<'a>, +{ + fn find_route( + &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>, + inflight_htlcs: &InFlightHtlcs + ) -> Result { + let random_seed_bytes = { + let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap(); + *locked_random_seed_bytes = Sha256::hash(&*locked_random_seed_bytes).into_inner(); + *locked_random_seed_bytes + }; + + find_route( + payer, params, &self.network_graph, first_hops, &*self.logger, + &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock(), inflight_htlcs), + &random_seed_bytes + ) + } + + fn notify_payment_path_failed(&self, path: &[&RouteHop], short_channel_id: u64) { + self.scorer.lock().payment_path_failed(path, short_channel_id); + } + + fn notify_payment_path_successful(&self, path: &[&RouteHop]) { + self.scorer.lock().payment_path_successful(path); + } + + fn notify_payment_probe_successful(&self, path: &[&RouteHop]) { + self.scorer.lock().probe_successful(path); + } + + fn notify_payment_probe_failed(&self, path: &[&RouteHop], short_channel_id: u64) { + self.scorer.lock().probe_failed(path, short_channel_id); + } +} + +/// A trait defining behavior for routing a payment. +pub trait Router { + /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. + fn find_route( + &self, payer: &PublicKey, route_params: &RouteParameters, + first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs + ) -> Result; + /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. Includes + /// `PaymentHash` and `PaymentId` to be able to correlate the request with a specific payment. + fn find_route_with_id( + &self, payer: &PublicKey, route_params: &RouteParameters, + first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs, + _payment_hash: PaymentHash, _payment_id: PaymentId + ) -> Result { + self.find_route(payer, route_params, first_hops, inflight_htlcs) + } + /// Lets the router know that payment through a specific path has failed. + fn notify_payment_path_failed(&self, path: &[&RouteHop], short_channel_id: u64); + /// Lets the router know that payment through a specific path was successful. + fn notify_payment_path_successful(&self, path: &[&RouteHop]); + /// Lets the router know that a payment probe was successful. + fn notify_payment_probe_successful(&self, path: &[&RouteHop]); + /// Lets the router know that a payment probe failed. + fn notify_payment_probe_failed(&self, path: &[&RouteHop], short_channel_id: u64); +} + +/// [`Score`] implementation that factors in in-flight HTLC liquidity. +/// +/// Useful for custom [`Router`] implementations to wrap their [`Score`] on-the-fly when calling +/// [`find_route`]. +/// +/// [`Score`]: crate::routing::scoring::Score +pub struct ScorerAccountingForInFlightHtlcs<'a, S: Score> { + scorer: S, + // Maps a channel's short channel id and its direction to the liquidity used up. + inflight_htlcs: &'a InFlightHtlcs, +} + +impl<'a, S: Score> ScorerAccountingForInFlightHtlcs<'a, S> { + /// Initialize a new `ScorerAccountingForInFlightHtlcs`. + pub fn new(scorer: S, inflight_htlcs: &'a InFlightHtlcs) -> Self { + ScorerAccountingForInFlightHtlcs { + scorer, + inflight_htlcs + } + } +} + +#[cfg(c_bindings)] +impl<'a, S: Score> Writeable for ScorerAccountingForInFlightHtlcs<'a, S> { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { self.scorer.write(writer) } +} + +impl<'a, S: Score> Score for ScorerAccountingForInFlightHtlcs<'a, S> { + fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage) -> u64 { + if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat( + source, target, short_channel_id + ) { + let usage = ChannelUsage { + inflight_htlc_msat: usage.inflight_htlc_msat + used_liquidity, + ..usage + }; + + self.scorer.channel_penalty_msat(short_channel_id, source, target, usage) + } else { + self.scorer.channel_penalty_msat(short_channel_id, source, target, usage) + } + } + + fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) { + self.scorer.payment_path_failed(path, short_channel_id) + } + + fn payment_path_successful(&mut self, path: &[&RouteHop]) { + self.scorer.payment_path_successful(path) + } + + fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) { + self.scorer.probe_failed(path, short_channel_id) + } + + fn probe_successful(&mut self, path: &[&RouteHop]) { + self.scorer.probe_successful(path) + } +} + +/// A data structure for tracking in-flight HTLCs. May be used during pathfinding to account for +/// in-use channel liquidity. +#[derive(Clone)] +pub struct InFlightHtlcs( + // A map with liquidity value (in msat) keyed by a short channel id and the direction the HTLC + // is traveling in. The direction boolean is determined by checking if the HTLC source's public + // key is less than its destination. See `InFlightHtlcs::used_liquidity_msat` for more + // details. + HashMap<(u64, bool), u64> +); + +impl InFlightHtlcs { + /// Constructs an empty `InFlightHtlcs`. + pub fn new() -> Self { InFlightHtlcs(HashMap::new()) } + + /// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`. + pub fn process_path(&mut self, path: &[RouteHop], payer_node_id: PublicKey) { + if path.is_empty() { return }; + // total_inflight_map needs to be direction-sensitive when keeping track of the HTLC value + // that is held up. However, the `hops` array, which is a path returned by `find_route` in + // the router excludes the payer node. In the following lines, the payer's information is + // hardcoded with an inflight value of 0 so that we can correctly represent the first hop + // in our sliding window of two. + let reversed_hops_with_payer = path.iter().rev().skip(1) + .map(|hop| hop.pubkey) + .chain(core::iter::once(payer_node_id)); + let mut cumulative_msat = 0; + + // Taking the reversed vector from above, we zip it with just the reversed hops list to + // work "backwards" of the given path, since the last hop's `fee_msat` actually represents + // the total amount sent. + for (next_hop, prev_hop) in path.iter().rev().zip(reversed_hops_with_payer) { + cumulative_msat += next_hop.fee_msat; + self.0 + .entry((next_hop.short_channel_id, NodeId::from_pubkey(&prev_hop) < NodeId::from_pubkey(&next_hop.pubkey))) + .and_modify(|used_liquidity_msat| *used_liquidity_msat += cumulative_msat) + .or_insert(cumulative_msat); + } + } + + /// Returns liquidity in msat given the public key of the HTLC source, target, and short channel + /// id. + pub fn used_liquidity_msat(&self, source: &NodeId, target: &NodeId, channel_scid: u64) -> Option { + self.0.get(&(channel_scid, source < target)).map(|v| *v) + } +} + +impl Writeable for InFlightHtlcs { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { self.0.write(writer) } +} + +impl Readable for InFlightHtlcs { + fn read(reader: &mut R) -> Result { + let infight_map: HashMap<(u64, bool), u64> = Readable::read(reader)?; + Ok(Self(infight_map)) + } +} + /// A hop in a route #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RouteHop { @@ -112,7 +320,7 @@ const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; impl Writeable for Route { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); (self.paths.len() as u64).write(writer)?; for hops in self.paths.iter() { @@ -185,8 +393,7 @@ pub const DEFAULT_MAX_PATH_COUNT: u8 = 10; const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40; // During routing, we only consider paths shorter than our maximum length estimate. -// In the legacy onion format, the maximum number of hops used to be a fixed value of 20. -// However, in the TLV onion format, there is no fixed maximum length, but the `hop_payloads` +// In the TLV onion format, there is no fixed maximum length, but the `hop_payloads` // field is always 1300 bytes. As the `tlv_payload` for each hop may vary in length, we have to // estimate how many hops the route may have so that it actually fits the `hop_payloads` field. // @@ -326,7 +533,7 @@ impl PaymentParameters { pub struct RouteHint(pub Vec); impl Writeable for RouteHint { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { (self.0.len() as u64).write(writer)?; for hop in self.0.iter() { hop.write(writer)?; @@ -375,7 +582,6 @@ impl_writeable_tlv_based!(RouteHintHop, { #[derive(Eq, PartialEq)] struct RouteGraphNode { node_id: NodeId, - lowest_fee_to_peer_through_node: u64, lowest_fee_to_node: u64, total_cltv_delta: u32, // The maximum value a yet-to-be-constructed payment path might flow through this node. @@ -396,9 +602,9 @@ struct RouteGraphNode { impl cmp::Ord for RouteGraphNode { fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering { - let other_score = cmp::max(other.lowest_fee_to_peer_through_node, other.path_htlc_minimum_msat) + let other_score = cmp::max(other.lowest_fee_to_node, other.path_htlc_minimum_msat) .saturating_add(other.path_penalty_msat); - let self_score = cmp::max(self.lowest_fee_to_peer_through_node, self.path_htlc_minimum_msat) + let self_score = cmp::max(self.lowest_fee_to_node, self.path_htlc_minimum_msat) .saturating_add(self.path_penalty_msat); other_score.cmp(&self_score).then_with(|| other.node_id.cmp(&self.node_id)) } @@ -421,7 +627,7 @@ enum CandidateRouteHop<'a> { }, /// A hop found in the [`ReadOnlyNetworkGraph`], where the channel capacity may be unknown. PublicHop { - info: DirectedChannelInfoWithUpdate<'a>, + info: DirectedChannelInfo<'a>, short_channel_id: u64, }, /// A hop to the payee found in the payment invoice, though not necessarily a direct channel. @@ -494,10 +700,8 @@ fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_po EffectiveCapacity::Unknown => EffectiveCapacity::Unknown.as_msat(), EffectiveCapacity::MaximumHTLC { amount_msat } => amount_msat.checked_shr(saturation_shift).unwrap_or(0), - EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: None } => - capacity_msat.checked_shr(saturation_shift).unwrap_or(0), - EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: Some(htlc_max) } => - cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_max), + EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat } => + cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_maximum_msat), } } @@ -524,8 +728,6 @@ struct PathBuildingHop<'a> { candidate: CandidateRouteHop<'a>, fee_msat: u64, - /// Minimal fees required to route to the source node of the current hop via any of its inbound channels. - src_lowest_inbound_fees: RoutingFees, /// All the fees paid *after* this channel on the way to the destination next_hops_fee_msat: u64, /// Fee paid for the use of the current channel (see candidate.fees()). @@ -683,18 +885,20 @@ impl<'a> PaymentPath<'a> { } } +#[inline(always)] +/// Calculate the fees required to route the given amount over a channel with the given fees. fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { - let proportional_fee_millions = - amount_msat.checked_mul(channel_fees.proportional_millionths as u64); - if let Some(new_fee) = proportional_fee_millions.and_then(|part| { - (channel_fees.base_msat as u64).checked_add(part / 1_000_000) }) { + amount_msat.checked_mul(channel_fees.proportional_millionths as u64) + .and_then(|part| (channel_fees.base_msat as u64).checked_add(part / 1_000_000)) +} - Some(new_fee) - } else { - // This function may be (indirectly) called without any verification, - // with channel_fees provided by a caller. We should handle it gracefully. - None - } +#[inline(always)] +/// Calculate the fees required to route the given amount over a channel with the given fees, +/// saturating to [`u64::max_value`]. +fn compute_fees_saturating(amount_msat: u64, channel_fees: RoutingFees) -> u64 { + amount_msat.checked_mul(channel_fees.proportional_millionths as u64) + .map(|prop| prop / 1_000_000).unwrap_or(u64::max_value()) + .saturating_add(channel_fees.base_msat as u64) } /// The default `features` we assume for a node in a route, when no `features` are known about that @@ -802,9 +1006,8 @@ where L::Target: Logger { // 8. If our maximum channel saturation limit caused us to pick two identical paths, combine // them so that we're not sending two HTLCs along the same path. - // As for the actual search algorithm, - // we do a payee-to-payer pseudo-Dijkstra's sorting by each node's distance from the payee - // plus the minimum per-HTLC fee to get from it to another node (aka "shitty pseudo-A*"). + // As for the actual search algorithm, we do a payee-to-payer Dijkstra's sorting by each node's + // distance from the payee // // We are not a faithful Dijkstra's implementation because we can change values which impact // earlier nodes while processing later nodes. Specifically, if we reach a channel with a lower @@ -839,10 +1042,6 @@ where L::Target: Logger { // runtime for little gain. Specifically, the current algorithm rather efficiently explores the // graph for candidate paths, calculating the maximum value which can realistically be sent at // the same time, remaining generic across different payment values. - // - // TODO: There are a few tweaks we could do, including possibly pre-calculating more stuff - // to use as the A* heuristic beyond just the cost to get one node further than the current - // one. let network_channels = network_graph.channels(); let network_nodes = network_graph.nodes(); @@ -892,7 +1091,7 @@ where L::Target: Logger { } } - // The main heap containing all candidate next-hops sorted by their score (max(A* fee, + // The main heap containing all candidate next-hops sorted by their score (max(fee, // htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of // adding duplicate entries when we find a better path to a given node. let mut targets: BinaryHeap = BinaryHeap::new(); @@ -1057,10 +1256,10 @@ where L::Target: Logger { // might violate htlc_minimum_msat on the hops which are next along the // payment path (upstream to the payee). To avoid that, we recompute // path fees knowing the final path contribution after constructing it. - let path_htlc_minimum_msat = compute_fees($next_hops_path_htlc_minimum_msat, $candidate.fees()) - .and_then(|fee_msat| fee_msat.checked_add($next_hops_path_htlc_minimum_msat)) - .map(|fee_msat| cmp::max(fee_msat, $candidate.htlc_minimum_msat())) - .unwrap_or_else(|| u64::max_value()); + let path_htlc_minimum_msat = cmp::max( + compute_fees_saturating($next_hops_path_htlc_minimum_msat, $candidate.fees()) + .saturating_add($next_hops_path_htlc_minimum_msat), + $candidate.htlc_minimum_msat()); let hm_entry = dist.entry($src_node_id); let old_entry = hm_entry.or_insert_with(|| { // If there was previously no known way to access the source node @@ -1068,20 +1267,10 @@ where L::Target: Logger { // semi-dummy record just to compute the fees to reach the source node. // This will affect our decision on selecting short_channel_id // as a way to reach the $dest_node_id. - let mut fee_base_msat = 0; - let mut fee_proportional_millionths = 0; - if let Some(Some(fees)) = network_nodes.get(&$src_node_id).map(|node| node.lowest_inbound_channel_fees) { - fee_base_msat = fees.base_msat; - fee_proportional_millionths = fees.proportional_millionths; - } PathBuildingHop { node_id: $dest_node_id.clone(), candidate: $candidate.clone(), fee_msat: 0, - src_lowest_inbound_fees: RoutingFees { - base_msat: fee_base_msat, - proportional_millionths: fee_proportional_millionths, - }, next_hops_fee_msat: u64::max_value(), hop_use_fee_msat: u64::max_value(), total_fee_msat: u64::max_value(), @@ -1104,38 +1293,15 @@ where L::Target: Logger { if should_process { let mut hop_use_fee_msat = 0; - let mut total_fee_msat = $next_hops_fee_msat; + let mut total_fee_msat: u64 = $next_hops_fee_msat; // Ignore hop_use_fee_msat for channel-from-us as we assume all channels-from-us // will have the same effective-fee if $src_node_id != our_node_id { - match compute_fees(amount_to_transfer_over_msat, $candidate.fees()) { - // max_value means we'll always fail - // the old_entry.total_fee_msat > total_fee_msat check - None => total_fee_msat = u64::max_value(), - Some(fee_msat) => { - hop_use_fee_msat = fee_msat; - total_fee_msat += hop_use_fee_msat; - // When calculating the lowest inbound fees to a node, we - // calculate fees here not based on the actual value we think - // will flow over this channel, but on the minimum value that - // we'll accept flowing over it. The minimum accepted value - // is a constant through each path collection run, ensuring - // consistent basis. Otherwise we may later find a - // different path to the source node that is more expensive, - // but which we consider to be cheaper because we are capacity - // constrained and the relative fee becomes lower. - match compute_fees(minimal_value_contribution_msat, old_entry.src_lowest_inbound_fees) - .map(|a| a.checked_add(total_fee_msat)) { - Some(Some(v)) => { - total_fee_msat = v; - }, - _ => { - total_fee_msat = u64::max_value(); - } - }; - } - } + // Note that `u64::max_value` means we'll always fail the + // `old_entry.total_fee_msat > total_fee_msat` check below + hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, $candidate.fees()); + total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat); } let channel_usage = ChannelUsage { @@ -1150,8 +1316,7 @@ where L::Target: Logger { .saturating_add(channel_penalty_msat); let new_graph_node = RouteGraphNode { node_id: $src_node_id, - lowest_fee_to_peer_through_node: total_fee_msat, - lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat, + lowest_fee_to_node: total_fee_msat, total_cltv_delta: hop_total_cltv_delta, value_contribution_msat, path_htlc_minimum_msat, @@ -1276,13 +1441,11 @@ where L::Target: Logger { for chan_id in $node.channels.iter() { let chan = network_channels.get(chan_id).unwrap(); if !chan.features.requires_unknown_bits() { - let (directed_channel, source) = - chan.as_directed_to(&$node_id).expect("inconsistent NetworkGraph"); - if first_hops.is_none() || *source != our_node_id { - if let Some(direction) = directed_channel.direction() { - if direction.enabled { + if let Some((directed_channel, source)) = chan.as_directed_to(&$node_id) { + if first_hops.is_none() || *source != our_node_id { + if directed_channel.direction().enabled { let candidate = CandidateRouteHop::PublicHop { - info: directed_channel.with_update().unwrap(), + info: directed_channel, short_channel_id: *chan_id, }; add_entry!(candidate, *source, $node_id, @@ -1367,8 +1530,7 @@ where L::Target: Logger { let candidate = network_channels .get(&hop.short_channel_id) .and_then(|channel| channel.as_directed_to(&target)) - .and_then(|(channel, _)| channel.with_update()) - .map(|info| CandidateRouteHop::PublicHop { + .map(|(info, _)| CandidateRouteHop::PublicHop { info, short_channel_id: hop.short_channel_id, }) @@ -1816,10 +1978,8 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, random_channel.as_directed_from(&cur_node_id).map(|(dir_info, next_id)| { if !nodes_to_avoid.iter().any(|x| x == next_id) { nodes_to_avoid[random_hop] = *next_id; - dir_info.direction().map(|channel_update_info| { - random_hop_offset = channel_update_info.cltv_expiry_delta.into(); - cur_hop = Some(*next_id); - }); + random_hop_offset = dir_info.direction().cltv_expiry_delta.into(); + cur_hop = Some(*next_id); } }); } @@ -1932,21 +2092,22 @@ fn build_route_from_hops_internal( #[cfg(test)] mod tests { - use routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; - use routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features, + use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; + use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE}; - use routing::scoring::{ChannelUsage, Score, ProbabilisticScorer, ProbabilisticScoringParameters}; - use routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel}; - use chain::transaction::OutPoint; - use chain::keysinterface::KeysInterface; - use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; - use ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; - use ln::channelmanager; - use util::test_utils as ln_test_utils; - use util::chacha20::ChaCha20; + use crate::routing::scoring::{ChannelUsage, Score, ProbabilisticScorer, ProbabilisticScoringParameters}; + use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel}; + use crate::chain::transaction::OutPoint; + use crate::chain::keysinterface::EntropySource; + use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; + use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; + use crate::ln::channelmanager; + use crate::util::config::UserConfig; + use crate::util::test_utils as ln_test_utils; + use crate::util::chacha20::ChaCha20; #[cfg(c_bindings)] - use util::ser::{Writeable, Writer}; + use crate::util::ser::{Writeable, Writer}; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; @@ -1960,8 +2121,8 @@ mod tests { use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::Secp256k1; - use prelude::*; - use sync::Arc; + use crate::prelude::*; + use crate::sync::Arc; use core::convert::TryInto; @@ -1990,6 +2151,7 @@ mod tests { inbound_capacity_msat: 42, unspendable_punishment_reserve: None, confirmations_required: None, + confirmations: None, force_close_spend_delay: None, is_outbound: true, is_channel_ready: true, is_usable: true, is_public: true, @@ -2185,7 +2347,8 @@ mod tests { fn htlc_minimum_overpay_test() { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features(&config)); let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); @@ -3158,7 +3321,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features(&config)); // We will use a simple single-path route from // our node to node2 via node0: channels {1, 3}. @@ -3432,7 +3596,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features(&config)); // 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. @@ -3605,8 +3770,9 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); 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]) - .with_features(channelmanager::provided_invoice_features()); + .with_features(channelmanager::provided_invoice_features(&config)); // We need a route consisting of 3 paths: // From our node to node2 via node0, node7, node1 (three paths one hop each). @@ -3764,7 +3930,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features(&config)); // We need a route consisting of 3 paths: // From our node to node3 via {node0, node2}, {node7, node2, node4} and {node7, node2}. @@ -3928,7 +4095,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features(&config)); // 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, @@ -4097,7 +4265,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(channelmanager::provided_invoice_features(&config)); // We need a route consisting of 2 paths: // From our node to node3 via {node0, node2} and {node7, node2, node4}. @@ -4278,7 +4447,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap()).with_features(channelmanager::provided_invoice_features()) + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap()).with_features(channelmanager::provided_invoice_features(&config)) .with_route_hints(vec![RouteHint(vec![RouteHintHop { src_node_id: nodes[2], short_channel_id: 42, @@ -4369,7 +4539,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features()) + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features(&config)) .with_max_channel_saturation_power_of_half(0); // We need a route consisting of 3 paths: @@ -4725,7 +4896,8 @@ mod tests { let scorer = ln_test_utils::TestScorer::with_penalty(0); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); - let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features(&config)); // 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 @@ -4774,7 +4946,7 @@ mod tests { assert_eq!(route.paths[0][1].short_channel_id, 13); assert_eq!(route.paths[0][1].fee_msat, 90_000); assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][1].node_features.le_flags(), channelmanager::provided_invoice_features().le_flags()); + assert_eq!(route.paths[0][1].node_features.le_flags(), channelmanager::provided_invoice_features(&config).le_flags()); assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(13)); } } @@ -4793,14 +4965,15 @@ mod tests { let logger = Arc::new(ln_test_utils::TestLogger::new()); let network_graph = NetworkGraph::new(genesis_hash, Arc::clone(&logger)); let scorer = ln_test_utils::TestScorer::with_penalty(0); - let payment_params = PaymentParameters::from_node_id(nodes[0]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[0]).with_features(channelmanager::provided_invoice_features(&config)); 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(), Some(&[ - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(), 200_000), - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(), 10_000), + &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 200_000), + &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000), ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].len(), 1); @@ -4811,8 +4984,8 @@ mod tests { } { let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[ - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(), 50_000), + &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000), ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths.len(), 2); assert_eq!(route.paths[0].len(), 1); @@ -4837,14 +5010,14 @@ mod tests { // smallest of them, avoiding further fragmenting our available outbound balance to // this node. let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[ - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(6), nodes[0], channelmanager::provided_init_features(), 300_000), - &get_channel_details(Some(7), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(), 50_000), - &get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(), 1_000_000), + &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(6), nodes[0], channelmanager::provided_init_features(&config), 300_000), + &get_channel_details(Some(7), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(&config), 50_000), + &get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000), ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].len(), 1); @@ -4895,7 +5068,7 @@ mod tests { #[cfg(c_bindings)] impl Writeable for BadChannelScorer { - fn write(&self, _w: &mut W) -> Result<(), ::io::Error> { unimplemented!() } + fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() } } impl Score for BadChannelScorer { fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage) -> u64 { @@ -4914,7 +5087,7 @@ mod tests { #[cfg(c_bindings)] impl Writeable for BadNodeScorer { - fn write(&self, _w: &mut W) -> Result<(), ::io::Error> { unimplemented!() } + fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() } } impl Score for BadNodeScorer { @@ -5214,14 +5387,12 @@ mod tests { for channel_id in &cur_node.channels { if let Some(channel_info) = network_channels.get(&channel_id) { if let Some((dir_info, next_id)) = channel_info.as_directed_from(&cur_node_id) { - if let Some(channel_update_info) = dir_info.direction() { - let next_cltv_expiry_delta = channel_update_info.cltv_expiry_delta as u32; - if cur_path_cltv_deltas.iter().sum::() - .saturating_add(next_cltv_expiry_delta) <= observed_cltv_expiry_delta { - let mut new_path_cltv_deltas = cur_path_cltv_deltas.clone(); - new_path_cltv_deltas.push(next_cltv_expiry_delta); - candidates.push_back((*next_id, new_path_cltv_deltas)); - } + let next_cltv_expiry_delta = dir_info.direction().cltv_expiry_delta as u32; + if cur_path_cltv_deltas.iter().sum::() + .saturating_add(next_cltv_expiry_delta) <= observed_cltv_expiry_delta { + let mut new_path_cltv_deltas = cur_path_cltv_deltas.clone(); + new_path_cltv_deltas.push(next_cltv_expiry_delta); + candidates.push_back((*next_id, new_path_cltv_deltas)); } } } @@ -5288,7 +5459,8 @@ mod tests { excess_data: Vec::new() }); - let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features()); + let config = UserConfig::default(); + let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(channelmanager::provided_invoice_features(&config)); 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. @@ -5307,12 +5479,12 @@ mod tests { seed } #[cfg(not(feature = "no-std"))] - use util::ser::ReadableArgs; + use crate::util::ser::ReadableArgs; #[test] #[cfg(not(feature = "no-std"))] fn generate_routes() { - use routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; + use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; let mut d = match super::bench_utils::get_route_file() { Ok(f) => f, @@ -5332,9 +5504,9 @@ mod tests { 'load_endpoints: for _ in 0..10 { loop { seed = seed.overflowing_mul(0xdeadbeef).0; - let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + 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.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + 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); let amt = seed as u64 % 200_000_000; let params = ProbabilisticScoringParameters::default(); @@ -5349,7 +5521,7 @@ mod tests { #[test] #[cfg(not(feature = "no-std"))] fn generate_routes_mpp() { - use routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; + use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; let mut d = match super::bench_utils::get_route_file() { Ok(f) => f, @@ -5362,6 +5534,7 @@ mod tests { let graph = NetworkGraph::read(&mut d, &logger).unwrap(); 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(); // First, get 100 (source, destination) pairs for which route-getting actually succeeds... let mut seed = random_init_seed() as usize; @@ -5369,10 +5542,10 @@ mod tests { 'load_endpoints: for _ in 0..10 { loop { seed = seed.overflowing_mul(0xdeadbeef).0; - let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + 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.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); - let payment_params = PaymentParameters::from_node_id(dst).with_features(channelmanager::provided_invoice_features()); + 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).with_features(channelmanager::provided_invoice_features(&config)); let amt = seed as u64 % 200_000_000; let params = ProbabilisticScoringParameters::default(); let scorer = ProbabilisticScorer::new(params, &graph, &logger); @@ -5398,7 +5571,7 @@ mod tests { let usage = ChannelUsage { amount_msat: 0, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: Some(1_000) }, + effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 }, }; scorer.set_manual_penalty(&NodeId::from_pubkey(&nodes[3]), 123); scorer.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456); @@ -5426,8 +5599,8 @@ pub(crate) mod bench_utils { use std::fs::File; /// Tries to open a network graph file, or panics with a URL to fetch it. pub(crate) fn get_route_file() -> Result { - let res = File::open("net_graph-2021-05-31.bin") // By default we're run in RL/lightning - .or_else(|_| File::open("lightning/net_graph-2021-05-31.bin")) // We may be run manually in RL/ + let res = File::open("net_graph-2023-01-18.bin") // By default we're run in RL/lightning + .or_else(|_| File::open("lightning/net_graph-2023-01-18.bin")) // We may be run manually in RL/ .or_else(|_| { // Fall back to guessing based on the binary location // path is likely something like .../rust-lightning/target/debug/deps/lightning-... let mut path = std::env::current_exe().unwrap(); @@ -5436,11 +5609,11 @@ pub(crate) mod bench_utils { path.pop(); // debug path.pop(); // target path.push("lightning"); - path.push("net_graph-2021-05-31.bin"); + path.push("net_graph-2023-01-18.bin"); eprintln!("{}", path.to_str().unwrap()); File::open(path) }) - .map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.15-2021-05-31.bin and place it at lightning/net_graph-2021-05-31.bin"); + .map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.113-2023-01-18.bin and place it at lightning/net_graph-2023-01-18.bin"); #[cfg(require_route_graph_test)] return Ok(res.unwrap()); #[cfg(not(require_route_graph_test))] @@ -5453,14 +5626,15 @@ mod benches { use super::*; use bitcoin::hashes::Hash; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; - use chain::transaction::OutPoint; - use chain::keysinterface::{KeysManager,KeysInterface}; - use ln::channelmanager::{self, ChannelCounterparty, ChannelDetails}; - use ln::features::{InitFeatures, InvoiceFeatures}; - use routing::gossip::NetworkGraph; - use routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters}; - use util::logger::{Logger, Record}; - use util::ser::ReadableArgs; + use crate::chain::transaction::OutPoint; + use crate::chain::keysinterface::{EntropySource, KeysManager}; + use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails}; + use crate::ln::features::InvoiceFeatures; + use crate::routing::gossip::NetworkGraph; + use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters}; + use crate::util::config::UserConfig; + use crate::util::logger::{Logger, Record}; + use crate::util::ser::ReadableArgs; use test::Bencher; @@ -5484,7 +5658,7 @@ mod benches { ChannelDetails { channel_id: [0; 32], counterparty: ChannelCounterparty { - features: channelmanager::provided_init_features(), + features: channelmanager::provided_init_features(&UserConfig::default()), node_id, unspendable_punishment_reserve: 0, forwarding_info: None, @@ -5506,6 +5680,7 @@ mod benches { inbound_capacity_msat: 0, unspendable_punishment_reserve: None, confirmations_required: None, + confirmations: None, force_close_spend_delay: None, is_outbound: true, is_channel_ready: true, @@ -5530,7 +5705,7 @@ mod benches { let logger = DummyLogger {}; let network_graph = read_network_graph(&logger); let scorer = FixedPenaltyScorer::with_penalty(0); - generate_routes(bench, &network_graph, scorer, channelmanager::provided_invoice_features()); + generate_routes(bench, &network_graph, scorer, channelmanager::provided_invoice_features(&UserConfig::default())); } #[bench] @@ -5548,7 +5723,7 @@ mod benches { let network_graph = read_network_graph(&logger); let params = ProbabilisticScoringParameters::default(); let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); - generate_routes(bench, &network_graph, scorer, channelmanager::provided_invoice_features()); + generate_routes(bench, &network_graph, scorer, channelmanager::provided_invoice_features(&UserConfig::default())); } fn generate_routes( @@ -5567,9 +5742,9 @@ mod benches { 'load_endpoints: for _ in 0..150 { loop { seed *= 0xdeadbeef; - let src = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + 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.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); let params = PaymentParameters::from_node_id(dst).with_features(features.clone()); let first_hop = first_hop(src); let amt = seed as u64 % 1_000_000;