use crate::ln::channelmanager::ChannelDetails;
use crate::ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
-use crate::routing::gossip::{DirectedChannelInfoWithUpdate, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
+use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
use crate::routing::scoring::{ChannelUsage, Score};
use crate::util::ser::{Writeable, Readable, Writer};
use crate::util::logger::{Level, Logger};
use core::cmp;
use core::ops::Deref;
+/// 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<Route, LightningError>;
+}
+
+/// A data structure for tracking in-flight HTLCs. May be used during pathfinding to account for
+/// in-use channel liquidity.
+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<u64> {
+ self.0.get(&(channel_scid, source < target)).map(|v| *v)
+ }
+}
+
+impl Writeable for InFlightHtlcs {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> { self.0.write(writer) }
+}
+
+impl Readable for InFlightHtlcs {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ 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 {
},
/// 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.
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),
}
}
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,
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,
})
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);
}
});
}
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::<u32>()
- .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::<u32>()
+ .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 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);