mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-10 13:35:38 +01:00
Merge pull request #2551 from jbesraa/expose-CandidateRouteHop-to-channel_penalty_msat
Add CandidateRouteHop to channel_penalty_msat inputs
This commit is contained in:
commit
becdf6fb27
5 changed files with 576 additions and 280 deletions
|
@ -863,8 +863,8 @@ mod tests {
|
|||
use lightning::ln::msgs::{ChannelMessageHandler, Init};
|
||||
use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
|
||||
use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
|
||||
use lightning::routing::router::{DefaultRouter, Path, RouteHop};
|
||||
use lightning::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp, LockableScore};
|
||||
use lightning::routing::router::{DefaultRouter, Path, RouteHop, CandidateRouteHop};
|
||||
use lightning::util::config::UserConfig;
|
||||
use lightning::util::ser::Writeable;
|
||||
use lightning::util::test_utils;
|
||||
|
@ -1071,7 +1071,7 @@ mod tests {
|
|||
impl ScoreLookUp for TestScorer {
|
||||
type ScoreParams = ();
|
||||
fn channel_penalty_msat(
|
||||
&self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage, _score_params: &Self::ScoreParams
|
||||
&self, _candidate: &CandidateRouteHop, _usage: ChannelUsage, _score_params: &Self::ScoreParams
|
||||
) -> u64 { unimplemented!(); }
|
||||
}
|
||||
|
||||
|
|
|
@ -870,31 +870,31 @@ impl ChannelInfo {
|
|||
/// Returns a [`DirectedChannelInfo`] for the channel directed to the given `target` from a
|
||||
/// returned `source`, or `None` if `target` is not one of the channel's counterparties.
|
||||
pub fn as_directed_to(&self, target: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> {
|
||||
let (direction, source) = {
|
||||
let (direction, source, outbound) = {
|
||||
if target == &self.node_one {
|
||||
(self.two_to_one.as_ref(), &self.node_two)
|
||||
(self.two_to_one.as_ref(), &self.node_two, false)
|
||||
} else if target == &self.node_two {
|
||||
(self.one_to_two.as_ref(), &self.node_one)
|
||||
(self.one_to_two.as_ref(), &self.node_one, true)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
direction.map(|dir| (DirectedChannelInfo::new(self, dir), source))
|
||||
direction.map(|dir| (DirectedChannelInfo::new(self, dir, outbound), source))
|
||||
}
|
||||
|
||||
/// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a
|
||||
/// returned `target`, or `None` if `source` is not one of the channel's counterparties.
|
||||
pub fn as_directed_from(&self, source: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> {
|
||||
let (direction, target) = {
|
||||
let (direction, target, outbound) = {
|
||||
if source == &self.node_one {
|
||||
(self.one_to_two.as_ref(), &self.node_two)
|
||||
(self.one_to_two.as_ref(), &self.node_two, true)
|
||||
} else if source == &self.node_two {
|
||||
(self.two_to_one.as_ref(), &self.node_one)
|
||||
(self.two_to_one.as_ref(), &self.node_one, false)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
direction.map(|dir| (DirectedChannelInfo::new(self, dir), target))
|
||||
direction.map(|dir| (DirectedChannelInfo::new(self, dir, outbound), target))
|
||||
}
|
||||
|
||||
/// Returns a [`ChannelUpdateInfo`] based on the direction implied by the channel_flag.
|
||||
|
@ -992,24 +992,32 @@ pub struct DirectedChannelInfo<'a> {
|
|||
direction: &'a ChannelUpdateInfo,
|
||||
htlc_maximum_msat: u64,
|
||||
effective_capacity: EffectiveCapacity,
|
||||
/// Outbound from the perspective of `node_one`.
|
||||
///
|
||||
/// If true, the channel is considered to be outbound from `node_one` perspective.
|
||||
/// If false, the channel is considered to be outbound from `node_two` perspective.
|
||||
///
|
||||
/// [`ChannelInfo::node_one`]
|
||||
/// [`ChannelInfo::node_two`]
|
||||
outbound: bool,
|
||||
}
|
||||
|
||||
impl<'a> DirectedChannelInfo<'a> {
|
||||
#[inline]
|
||||
fn new(channel: &'a ChannelInfo, direction: &'a ChannelUpdateInfo) -> Self {
|
||||
fn new(channel: &'a ChannelInfo, direction: &'a ChannelUpdateInfo, outbound: bool) -> Self {
|
||||
let mut htlc_maximum_msat = direction.htlc_maximum_msat;
|
||||
let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000);
|
||||
|
||||
let effective_capacity = match capacity_msat {
|
||||
Some(capacity_msat) => {
|
||||
htlc_maximum_msat = cmp::min(htlc_maximum_msat, capacity_msat);
|
||||
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: htlc_maximum_msat }
|
||||
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat }
|
||||
},
|
||||
None => EffectiveCapacity::AdvertisedMaxHTLC { amount_msat: htlc_maximum_msat },
|
||||
};
|
||||
|
||||
Self {
|
||||
channel, direction, htlc_maximum_msat, effective_capacity
|
||||
channel, direction, htlc_maximum_msat, effective_capacity, outbound
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1035,6 +1043,16 @@ impl<'a> DirectedChannelInfo<'a> {
|
|||
/// Returns information for the direction.
|
||||
#[inline]
|
||||
pub(super) fn direction(&self) -> &'a ChannelUpdateInfo { self.direction }
|
||||
|
||||
/// Returns the `node_id` of the source hop.
|
||||
///
|
||||
/// Refers to the `node_id` forwarding the payment to the next hop.
|
||||
pub(super) fn source(&self) -> &'a NodeId { if self.outbound { &self.channel.node_one } else { &self.channel.node_two } }
|
||||
|
||||
/// Returns the `node_id` of the target hop.
|
||||
///
|
||||
/// Refers to the `node_id` receiving the payment from the previous hop.
|
||||
pub(super) fn target(&self) -> &'a NodeId { if self.outbound { &self.channel.node_two } else { &self.channel.node_one } }
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for DirectedChannelInfo<'a> {
|
||||
|
|
|
@ -130,18 +130,27 @@ impl<'a, S: Deref> ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: Scor
|
|||
|
||||
impl<'a, S: Deref> ScoreLookUp for ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: ScoreLookUp {
|
||||
type ScoreParams = <S::Target as ScoreLookUp>::ScoreParams;
|
||||
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
|
||||
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
|
||||
let target = match candidate.target() {
|
||||
Some(target) => target,
|
||||
None => return self.scorer.channel_penalty_msat(candidate, usage, score_params),
|
||||
};
|
||||
let short_channel_id = match candidate.short_channel_id() {
|
||||
Some(short_channel_id) => short_channel_id,
|
||||
None => return self.scorer.channel_penalty_msat(candidate, usage, score_params),
|
||||
};
|
||||
let source = candidate.source();
|
||||
if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat(
|
||||
source, target, short_channel_id
|
||||
&source, &target, short_channel_id
|
||||
) {
|
||||
let usage = ChannelUsage {
|
||||
inflight_htlc_msat: usage.inflight_htlc_msat.saturating_add(used_liquidity),
|
||||
..usage
|
||||
};
|
||||
|
||||
self.scorer.channel_penalty_msat(short_channel_id, source, target, usage, score_params)
|
||||
self.scorer.channel_penalty_msat(candidate, usage, score_params)
|
||||
} else {
|
||||
self.scorer.channel_penalty_msat(short_channel_id, source, target, usage, score_params)
|
||||
self.scorer.channel_penalty_msat(candidate, usage, score_params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -993,44 +1002,84 @@ impl cmp::PartialOrd for RouteGraphNode {
|
|||
|
||||
/// A wrapper around the various hop representations.
|
||||
///
|
||||
/// Used to construct a [`PathBuildingHop`] and to estimate [`EffectiveCapacity`].
|
||||
/// Can be used to examine the properties of a hop,
|
||||
/// potentially to decide whether to include it in a route.
|
||||
#[derive(Clone, Debug)]
|
||||
enum CandidateRouteHop<'a> {
|
||||
pub enum CandidateRouteHop<'a> {
|
||||
/// A hop from the payer, where the outbound liquidity is known.
|
||||
FirstHop {
|
||||
/// Channel details of the first hop
|
||||
/// [`ChannelDetails::get_outbound_payment_scid`] is assumed
|
||||
/// to always return `Some(scid)`
|
||||
/// this assumption is checked in [`find_route`] method.
|
||||
details: &'a ChannelDetails,
|
||||
/// The node id of the payer.
|
||||
///
|
||||
/// Can be accessed via `source` method.
|
||||
node_id: NodeId
|
||||
},
|
||||
/// A hop found in the [`ReadOnlyNetworkGraph`], where the channel capacity may be unknown.
|
||||
/// A hop found in the [`ReadOnlyNetworkGraph`],
|
||||
/// where the channel capacity may be unknown.
|
||||
PublicHop {
|
||||
/// channel info of the hop.
|
||||
info: DirectedChannelInfo<'a>,
|
||||
/// short_channel_id of the channel.
|
||||
short_channel_id: u64,
|
||||
},
|
||||
/// A hop to the payee found in the BOLT 11 payment invoice, though not necessarily a direct
|
||||
/// A hop to the payee found in the BOLT 11 payment invoice,
|
||||
/// though not necessarily a direct
|
||||
/// channel.
|
||||
PrivateHop {
|
||||
/// Hint provides information about a private hop,
|
||||
/// needed while routing through a private
|
||||
/// channel.
|
||||
hint: &'a RouteHintHop,
|
||||
/// Node id of the next hop in route.
|
||||
target_node_id: NodeId
|
||||
},
|
||||
/// The payee's identity is concealed behind blinded paths provided in a BOLT 12 invoice.
|
||||
/// The payee's identity is concealed behind
|
||||
/// blinded paths provided in a BOLT 12 invoice.
|
||||
Blinded {
|
||||
/// Hint provides information about a blinded hop,
|
||||
/// needed while routing through a blinded path.
|
||||
/// `BlindedPayInfo` provides information needed about the
|
||||
/// payment while routing through a blinded path.
|
||||
/// `BlindedPath` is the blinded path to the destination.
|
||||
hint: &'a (BlindedPayInfo, BlindedPath),
|
||||
/// Index of the hint in the original list of blinded hints.
|
||||
/// Provided to uniquely identify a hop as we are
|
||||
/// route building.
|
||||
hint_idx: usize,
|
||||
},
|
||||
/// Similar to [`Self::Blinded`], but the path here has 1 blinded hop. `BlindedPayInfo` provided
|
||||
/// for 1-hop blinded paths is ignored because it is meant to apply to the hops *between* the
|
||||
/// introduction node and the destination. Useful for tracking that we need to include a blinded
|
||||
/// Similar to [`Self::Blinded`], but the path here
|
||||
/// has 1 blinded hop. `BlindedPayInfo` provided
|
||||
/// for 1-hop blinded paths is ignored
|
||||
/// because it is meant to apply to the hops *between* the
|
||||
/// introduction node and the destination.
|
||||
/// Useful for tracking that we need to include a blinded
|
||||
/// path at the end of our [`Route`].
|
||||
OneHopBlinded {
|
||||
/// Hint provides information about a single blinded hop,
|
||||
/// needed while routing through a one hop blinded path.
|
||||
/// `BlindedPayInfo` is ignored here.
|
||||
/// `BlindedPath` is the blinded path to the destination.
|
||||
hint: &'a (BlindedPayInfo, BlindedPath),
|
||||
/// Index of the hint in the original list of blinded hints.
|
||||
/// Provided to uniquely identify a hop as we are route building.
|
||||
hint_idx: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> CandidateRouteHop<'a> {
|
||||
fn short_channel_id(&self) -> Option<u64> {
|
||||
/// Returns short_channel_id if known.
|
||||
/// For `FirstHop` we assume [`ChannelDetails::get_outbound_payment_scid`] is always set, this assumption is checked in
|
||||
/// [`find_route`] method.
|
||||
/// For `Blinded` and `OneHopBlinded` we return `None` because next hop is not known.
|
||||
pub fn short_channel_id(&self) -> Option<u64> {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { details } => Some(details.get_outbound_payment_scid().unwrap()),
|
||||
CandidateRouteHop::FirstHop { details, .. } => details.get_outbound_payment_scid(),
|
||||
CandidateRouteHop::PublicHop { short_channel_id, .. } => Some(*short_channel_id),
|
||||
CandidateRouteHop::PrivateHop { hint } => Some(hint.short_channel_id),
|
||||
CandidateRouteHop::PrivateHop { hint, .. } => Some(hint.short_channel_id),
|
||||
CandidateRouteHop::Blinded { .. } => None,
|
||||
CandidateRouteHop::OneHopBlinded { .. } => None,
|
||||
}
|
||||
|
@ -1039,7 +1088,7 @@ impl<'a> CandidateRouteHop<'a> {
|
|||
// NOTE: This may alloc memory so avoid calling it in a hot code path.
|
||||
fn features(&self) -> ChannelFeatures {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { details } => details.counterparty.features.to_context(),
|
||||
CandidateRouteHop::FirstHop { details, .. } => details.counterparty.features.to_context(),
|
||||
CandidateRouteHop::PublicHop { info, .. } => info.channel().features.clone(),
|
||||
CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(),
|
||||
CandidateRouteHop::Blinded { .. } => ChannelFeatures::empty(),
|
||||
|
@ -1047,33 +1096,36 @@ impl<'a> CandidateRouteHop<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn cltv_expiry_delta(&self) -> u32 {
|
||||
/// Returns cltv_expiry_delta for this hop.
|
||||
pub fn cltv_expiry_delta(&self) -> u32 {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { .. } => 0,
|
||||
CandidateRouteHop::PublicHop { info, .. } => info.direction().cltv_expiry_delta as u32,
|
||||
CandidateRouteHop::PrivateHop { hint } => hint.cltv_expiry_delta as u32,
|
||||
CandidateRouteHop::PrivateHop { hint, .. } => hint.cltv_expiry_delta as u32,
|
||||
CandidateRouteHop::Blinded { hint, .. } => hint.0.cltv_expiry_delta as u32,
|
||||
CandidateRouteHop::OneHopBlinded { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn htlc_minimum_msat(&self) -> u64 {
|
||||
/// Returns the htlc_minimum_msat for this hop.
|
||||
pub fn htlc_minimum_msat(&self) -> u64 {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_minimum_msat,
|
||||
CandidateRouteHop::FirstHop { details, .. } => details.next_outbound_htlc_minimum_msat,
|
||||
CandidateRouteHop::PublicHop { info, .. } => info.direction().htlc_minimum_msat,
|
||||
CandidateRouteHop::PrivateHop { hint } => hint.htlc_minimum_msat.unwrap_or(0),
|
||||
CandidateRouteHop::PrivateHop { hint, .. } => hint.htlc_minimum_msat.unwrap_or(0),
|
||||
CandidateRouteHop::Blinded { hint, .. } => hint.0.htlc_minimum_msat,
|
||||
CandidateRouteHop::OneHopBlinded { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn fees(&self) -> RoutingFees {
|
||||
/// Returns the fees for this hop.
|
||||
pub fn fees(&self) -> RoutingFees {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { .. } => RoutingFees {
|
||||
base_msat: 0, proportional_millionths: 0,
|
||||
},
|
||||
CandidateRouteHop::PublicHop { info, .. } => info.direction().fees,
|
||||
CandidateRouteHop::PrivateHop { hint } => hint.fees,
|
||||
CandidateRouteHop::PrivateHop { hint, .. } => hint.fees,
|
||||
CandidateRouteHop::Blinded { hint, .. } => {
|
||||
RoutingFees {
|
||||
base_msat: hint.0.fee_base_msat,
|
||||
|
@ -1087,13 +1139,13 @@ impl<'a> CandidateRouteHop<'a> {
|
|||
|
||||
fn effective_capacity(&self) -> EffectiveCapacity {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { details } => EffectiveCapacity::ExactLiquidity {
|
||||
CandidateRouteHop::FirstHop { details, .. } => EffectiveCapacity::ExactLiquidity {
|
||||
liquidity_msat: details.next_outbound_htlc_limit_msat,
|
||||
},
|
||||
CandidateRouteHop::PublicHop { info, .. } => info.effective_capacity(),
|
||||
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }} =>
|
||||
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, .. } =>
|
||||
EffectiveCapacity::HintMaxHTLC { amount_msat: *max },
|
||||
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }} =>
|
||||
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. } =>
|
||||
EffectiveCapacity::Infinite,
|
||||
CandidateRouteHop::Blinded { hint, .. } =>
|
||||
EffectiveCapacity::HintMaxHTLC { amount_msat: hint.0.htlc_maximum_msat },
|
||||
|
@ -1101,11 +1153,14 @@ impl<'a> CandidateRouteHop<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn id(&self, channel_direction: bool /* src_node_id < target_node_id */) -> CandidateHopId {
|
||||
/// Returns the id of this hop.
|
||||
/// For `Blinded` and `OneHopBlinded` we return `CandidateHopId::Blinded` with `hint_idx` because we don't know the channel id.
|
||||
/// For any other option we return `CandidateHopId::Clear` because we know the channel id and the direction.
|
||||
pub fn id(&self) -> CandidateHopId {
|
||||
match self {
|
||||
CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
|
||||
CandidateRouteHop::OneHopBlinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
|
||||
_ => CandidateHopId::Clear((self.short_channel_id().unwrap(), channel_direction)),
|
||||
_ => CandidateHopId::Clear((self.short_channel_id().unwrap(), self.source() < self.target().unwrap())),
|
||||
}
|
||||
}
|
||||
fn blinded_path(&self) -> Option<&'a BlindedPath> {
|
||||
|
@ -1116,10 +1171,42 @@ impl<'a> CandidateRouteHop<'a> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Returns the source node id of current hop.
|
||||
///
|
||||
/// Source node id refers to the hop forwarding the payment.
|
||||
///
|
||||
/// For `FirstHop` we return payer's node id.
|
||||
pub fn source(&self) -> NodeId {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { node_id, .. } => *node_id,
|
||||
CandidateRouteHop::PublicHop { info, .. } => *info.source(),
|
||||
CandidateRouteHop::PrivateHop { hint, .. } => hint.src_node_id.into(),
|
||||
CandidateRouteHop::Blinded { hint, .. } => hint.1.introduction_node_id.into(),
|
||||
CandidateRouteHop::OneHopBlinded { hint, .. } => hint.1.introduction_node_id.into(),
|
||||
}
|
||||
}
|
||||
/// Returns the target node id of this hop, if known.
|
||||
///
|
||||
/// Target node id refers to the hop receiving the payment.
|
||||
///
|
||||
/// For `Blinded` and `OneHopBlinded` we return `None` because next hop is blinded.
|
||||
pub fn target(&self) -> Option<NodeId> {
|
||||
match self {
|
||||
CandidateRouteHop::FirstHop { details, .. } => Some(details.counterparty.node_id.into()),
|
||||
CandidateRouteHop::PublicHop { info, .. } => Some(*info.target()),
|
||||
CandidateRouteHop::PrivateHop { target_node_id, .. } => Some(*target_node_id),
|
||||
CandidateRouteHop::Blinded { .. } => None,
|
||||
CandidateRouteHop::OneHopBlinded { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around the various hop id representations.
|
||||
///
|
||||
/// `CandidateHopId::Clear` is used to identify a hop with a known short channel id and direction.
|
||||
/// `CandidateHopId::Blinded` is used to identify a blinded hop `hint_idx`.
|
||||
#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
enum CandidateHopId {
|
||||
pub enum CandidateHopId {
|
||||
/// Contains (scid, src_node_id < target_node_id)
|
||||
Clear((u64, bool)),
|
||||
/// Index of the blinded route hint in [`Payee::Blinded::route_hints`].
|
||||
|
@ -1160,9 +1247,6 @@ fn iter_equal<I1: Iterator, I2: Iterator>(mut iter_a: I1, mut iter_b: I2)
|
|||
/// These fee values are useful to choose hops as we traverse the graph "payee-to-payer".
|
||||
#[derive(Clone)]
|
||||
struct PathBuildingHop<'a> {
|
||||
// Note that this should be dropped in favor of loading it from CandidateRouteHop, but doing so
|
||||
// is a larger refactor and will require careful performance analysis.
|
||||
node_id: NodeId,
|
||||
candidate: CandidateRouteHop<'a>,
|
||||
fee_msat: u64,
|
||||
|
||||
|
@ -1200,7 +1284,7 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
|
|||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
let mut debug_struct = f.debug_struct("PathBuildingHop");
|
||||
debug_struct
|
||||
.field("node_id", &self.node_id)
|
||||
.field("node_id", &self.candidate.target())
|
||||
.field("short_channel_id", &self.candidate.short_channel_id())
|
||||
.field("total_fee_msat", &self.total_fee_msat)
|
||||
.field("next_hops_fee_msat", &self.next_hops_fee_msat)
|
||||
|
@ -1722,11 +1806,11 @@ where L::Target: Logger {
|
|||
let mut num_ignored_htlc_minimum_msat_limit: u32 = 0;
|
||||
|
||||
macro_rules! add_entry {
|
||||
// Adds entry which goes from $src_node_id to $dest_node_id over the $candidate hop.
|
||||
// Adds entry which goes from $candidate.source() to $candidate.target() over the $candidate hop.
|
||||
// $next_hops_fee_msat represents the fees paid for using all the channels *after* this one,
|
||||
// since that value has to be transferred over this channel.
|
||||
// Returns the contribution amount of $candidate if the channel caused an update to `targets`.
|
||||
( $candidate: expr, $src_node_id: expr, $dest_node_id: expr, $next_hops_fee_msat: expr,
|
||||
( $candidate: expr, $next_hops_fee_msat: expr,
|
||||
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
|
||||
$next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => { {
|
||||
// We "return" whether we updated the path at the end, and how much we can route via
|
||||
|
@ -1736,7 +1820,8 @@ where L::Target: Logger {
|
|||
// practice these cases should be caught earlier:
|
||||
// - for regular channels at channel announcement (TODO)
|
||||
// - for first and last hops early in get_route
|
||||
if $src_node_id != $dest_node_id {
|
||||
let src_node_id = $candidate.source();
|
||||
if Some(src_node_id) != $candidate.target() {
|
||||
let scid_opt = $candidate.short_channel_id();
|
||||
let effective_capacity = $candidate.effective_capacity();
|
||||
let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
|
||||
|
@ -1750,7 +1835,7 @@ where L::Target: Logger {
|
|||
// We do this for now, but this is a subject for removal.
|
||||
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
|
||||
let used_liquidity_msat = used_liquidities
|
||||
.get(&$candidate.id($src_node_id < $dest_node_id))
|
||||
.get(&$candidate.id())
|
||||
.map_or(0, |used_liquidity_msat| {
|
||||
available_value_contribution_msat = available_value_contribution_msat
|
||||
.saturating_sub(*used_liquidity_msat);
|
||||
|
@ -1796,7 +1881,7 @@ where L::Target: Logger {
|
|||
|scid| payment_params.previously_failed_channels.contains(&scid));
|
||||
|
||||
let (should_log_candidate, first_hop_details) = match $candidate {
|
||||
CandidateRouteHop::FirstHop { details } => (true, Some(details)),
|
||||
CandidateRouteHop::FirstHop { details, .. } => (true, Some(details)),
|
||||
CandidateRouteHop::PrivateHop { .. } => (true, None),
|
||||
CandidateRouteHop::Blinded { .. } => (true, None),
|
||||
CandidateRouteHop::OneHopBlinded { .. } => (true, None),
|
||||
|
@ -1867,15 +1952,14 @@ where L::Target: Logger {
|
|||
);
|
||||
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
|
||||
.saturating_add(curr_min);
|
||||
let hm_entry = dist.entry($src_node_id);
|
||||
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
|
||||
// (recall it goes payee-to-payer) of short_channel_id, first add a
|
||||
// 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.
|
||||
// as a way to reach the $candidate.target() node.
|
||||
PathBuildingHop {
|
||||
node_id: $dest_node_id.clone(),
|
||||
candidate: $candidate.clone(),
|
||||
fee_msat: 0,
|
||||
next_hops_fee_msat: u64::max_value(),
|
||||
|
@ -1904,7 +1988,7 @@ where L::Target: Logger {
|
|||
|
||||
// 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 {
|
||||
if src_node_id != our_node_id {
|
||||
// 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());
|
||||
|
@ -1931,13 +2015,14 @@ where L::Target: Logger {
|
|||
inflight_htlc_msat: used_liquidity_msat,
|
||||
effective_capacity,
|
||||
};
|
||||
let channel_penalty_msat = scid_opt.map_or(0,
|
||||
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
|
||||
channel_usage, score_params));
|
||||
let channel_penalty_msat =
|
||||
scorer.channel_penalty_msat($candidate,
|
||||
channel_usage,
|
||||
score_params);
|
||||
let path_penalty_msat = $next_hops_path_penalty_msat
|
||||
.saturating_add(channel_penalty_msat);
|
||||
let new_graph_node = RouteGraphNode {
|
||||
node_id: $src_node_id,
|
||||
node_id: src_node_id,
|
||||
lowest_fee_to_node: total_fee_msat,
|
||||
total_cltv_delta: hop_total_cltv_delta,
|
||||
value_contribution_msat,
|
||||
|
@ -1946,7 +2031,8 @@ where L::Target: Logger {
|
|||
path_length_to_node,
|
||||
};
|
||||
|
||||
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
|
||||
// Update the way of reaching $candidate.source()
|
||||
// with the given short_channel_id (from $candidate.target()),
|
||||
// if this way is cheaper than the already known
|
||||
// (considering the cost to "reach" this channel from the route destination,
|
||||
// the cost of using this channel,
|
||||
|
@ -1972,7 +2058,6 @@ where L::Target: Logger {
|
|||
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
|
||||
old_entry.hop_use_fee_msat = hop_use_fee_msat;
|
||||
old_entry.total_fee_msat = total_fee_msat;
|
||||
old_entry.node_id = $dest_node_id.clone();
|
||||
old_entry.candidate = $candidate.clone();
|
||||
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
|
||||
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
|
||||
|
@ -2060,8 +2145,8 @@ where L::Target: Logger {
|
|||
if !skip_node {
|
||||
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
|
||||
for details in first_channels {
|
||||
let candidate = CandidateRouteHop::FirstHop { details };
|
||||
add_entry!(candidate, our_node_id, $node_id, $fee_to_target_msat,
|
||||
let candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id };
|
||||
add_entry!(&candidate, $fee_to_target_msat,
|
||||
$next_hops_value_contribution,
|
||||
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
|
||||
$next_hops_cltv_delta, $next_hops_path_length);
|
||||
|
@ -2085,7 +2170,7 @@ where L::Target: Logger {
|
|||
info: directed_channel,
|
||||
short_channel_id: *chan_id,
|
||||
};
|
||||
add_entry!(candidate, *source, $node_id,
|
||||
add_entry!(&candidate,
|
||||
$fee_to_target_msat,
|
||||
$next_hops_value_contribution,
|
||||
$next_hops_path_htlc_minimum_msat,
|
||||
|
@ -2115,8 +2200,8 @@ where L::Target: Logger {
|
|||
// place where it could be added.
|
||||
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, 0, path_value_msat,
|
||||
let candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id };
|
||||
let added = add_entry!(&candidate, 0, path_value_msat,
|
||||
0, 0u64, 0, 0).is_some();
|
||||
log_trace!(logger, "{} direct route to payee via {}",
|
||||
if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
|
||||
|
@ -2153,7 +2238,7 @@ where L::Target: Logger {
|
|||
CandidateRouteHop::OneHopBlinded { hint, hint_idx }
|
||||
} else { CandidateRouteHop::Blinded { hint, hint_idx } };
|
||||
let mut path_contribution_msat = path_value_msat;
|
||||
if let Some(hop_used_msat) = add_entry!(candidate, intro_node_id, maybe_dummy_payee_node_id,
|
||||
if let Some(hop_used_msat) = add_entry!(&candidate,
|
||||
0, path_contribution_msat, 0, 0_u64, 0, 0)
|
||||
{
|
||||
path_contribution_msat = hop_used_msat;
|
||||
|
@ -2162,14 +2247,14 @@ where L::Target: Logger {
|
|||
sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat,
|
||||
our_node_pubkey);
|
||||
for details in first_channels {
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id};
|
||||
let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) {
|
||||
Some(fee) => fee,
|
||||
None => continue
|
||||
};
|
||||
let path_min = candidate.htlc_minimum_msat().saturating_add(
|
||||
compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
|
||||
add_entry!(first_hop_candidate, our_node_id, intro_node_id, blinded_path_fee,
|
||||
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));
|
||||
}
|
||||
|
@ -2219,9 +2304,9 @@ where L::Target: Logger {
|
|||
info,
|
||||
short_channel_id: hop.short_channel_id,
|
||||
})
|
||||
.unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop });
|
||||
.unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop, target_node_id: target });
|
||||
|
||||
if let Some(hop_used_msat) = add_entry!(candidate, source, target,
|
||||
if let Some(hop_used_msat) = add_entry!(&candidate,
|
||||
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
|
||||
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
|
||||
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
|
||||
|
@ -2235,7 +2320,7 @@ where L::Target: Logger {
|
|||
}
|
||||
|
||||
let used_liquidity_msat = used_liquidities
|
||||
.get(&candidate.id(source < target)).copied()
|
||||
.get(&candidate.id()).copied()
|
||||
.unwrap_or(0);
|
||||
let channel_usage = ChannelUsage {
|
||||
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
|
||||
|
@ -2243,7 +2328,7 @@ where L::Target: Logger {
|
|||
effective_capacity: candidate.effective_capacity(),
|
||||
};
|
||||
let channel_penalty_msat = scorer.channel_penalty_msat(
|
||||
hop.short_channel_id, &source, &target, channel_usage, score_params
|
||||
&candidate, channel_usage, score_params
|
||||
);
|
||||
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
|
||||
.saturating_add(channel_penalty_msat);
|
||||
|
@ -2259,8 +2344,8 @@ where L::Target: Logger {
|
|||
sort_first_hop_channels(first_channels, &used_liquidities,
|
||||
recommended_value_msat, our_node_pubkey);
|
||||
for details in first_channels {
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
||||
add_entry!(first_hop_candidate, our_node_id, target,
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id};
|
||||
add_entry!(&first_hop_candidate,
|
||||
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
|
||||
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
|
||||
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length);
|
||||
|
@ -2304,9 +2389,8 @@ where L::Target: Logger {
|
|||
sort_first_hop_channels(first_channels, &used_liquidities,
|
||||
recommended_value_msat, our_node_pubkey);
|
||||
for details in first_channels {
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
||||
add_entry!(first_hop_candidate, our_node_id,
|
||||
NodeId::from_pubkey(&hop.src_node_id),
|
||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id};
|
||||
add_entry!(&first_hop_candidate,
|
||||
aggregate_next_hops_fee_msat,
|
||||
aggregate_path_contribution_msat,
|
||||
aggregate_next_hops_path_htlc_minimum_msat,
|
||||
|
@ -2345,7 +2429,8 @@ where L::Target: Logger {
|
|||
|
||||
'path_walk: loop {
|
||||
let mut features_set = false;
|
||||
if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.node_id) {
|
||||
let target = ordered_hops.last().unwrap().0.candidate.target().unwrap_or(maybe_dummy_payee_node_id);
|
||||
if let Some(first_channels) = first_hop_targets.get(&target) {
|
||||
for details in first_channels {
|
||||
if let Some(scid) = ordered_hops.last().unwrap().0.candidate.short_channel_id() {
|
||||
if details.get_outbound_payment_scid().unwrap() == scid {
|
||||
|
@ -2357,7 +2442,7 @@ where L::Target: Logger {
|
|||
}
|
||||
}
|
||||
if !features_set {
|
||||
if let Some(node) = network_nodes.get(&ordered_hops.last().unwrap().0.node_id) {
|
||||
if let Some(node) = network_nodes.get(&target) {
|
||||
if let Some(node_info) = node.announcement_info.as_ref() {
|
||||
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
|
||||
} else {
|
||||
|
@ -2374,11 +2459,11 @@ 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 == maybe_dummy_payee_node_id {
|
||||
if target == maybe_dummy_payee_node_id {
|
||||
break 'path_walk;
|
||||
}
|
||||
|
||||
new_entry = match dist.remove(&ordered_hops.last().unwrap().0.node_id) {
|
||||
new_entry = match dist.remove(&target) {
|
||||
Some(payment_hop) => payment_hop,
|
||||
// We can't arrive at None because, if we ever add an entry to targets,
|
||||
// we also fill in the entry in dist (see add_entry!).
|
||||
|
@ -2417,12 +2502,10 @@ where L::Target: Logger {
|
|||
// Remember that we used these channels so that we don't rely
|
||||
// on the same liquidity in future paths.
|
||||
let mut prevented_redundant_path_selection = false;
|
||||
let prev_hop_iter = core::iter::once(&our_node_id)
|
||||
.chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id));
|
||||
for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) {
|
||||
for (hop, _) in payment_path.hops.iter() {
|
||||
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
|
||||
let used_liquidity_msat = used_liquidities
|
||||
.entry(hop.candidate.id(*prev_hop < hop.node_id))
|
||||
.entry(hop.candidate.id())
|
||||
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
|
||||
.or_insert(spent_on_hop_msat);
|
||||
let hop_capacity = hop.candidate.effective_capacity();
|
||||
|
@ -2443,8 +2526,10 @@ where L::Target: Logger {
|
|||
log_trace!(logger,
|
||||
"Disabling route candidate {} for future path building iterations to avoid duplicates.",
|
||||
LoggedCandidateHop(victim_candidate));
|
||||
*used_liquidities.entry(victim_candidate.id(false)).or_default() = exhausted;
|
||||
*used_liquidities.entry(victim_candidate.id(true)).or_default() = exhausted;
|
||||
if let Some(scid) = victim_candidate.short_channel_id() {
|
||||
*used_liquidities.entry(CandidateHopId::Clear((scid, false))).or_default() = exhausted;
|
||||
*used_liquidities.entry(CandidateHopId::Clear((scid, true))).or_default() = exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
// Track the total amount all our collected paths allow to send so that we know
|
||||
|
@ -2590,15 +2675,15 @@ where L::Target: Logger {
|
|||
selected_route.sort_unstable_by_key(|path| {
|
||||
let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
|
||||
debug_assert!(path.hops.len() <= key.len());
|
||||
for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id(true)).zip(key.iter_mut()) {
|
||||
for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id()).zip(key.iter_mut()) {
|
||||
*key = scid;
|
||||
}
|
||||
key
|
||||
});
|
||||
for idx in 0..(selected_route.len() - 1) {
|
||||
if idx + 1 >= selected_route.len() { break; }
|
||||
if iter_equal(selected_route[idx ].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id)),
|
||||
selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id))) {
|
||||
if iter_equal(selected_route[idx].hops.iter().map(|h| (h.0.candidate.id(), h.0.candidate.target())),
|
||||
selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.id(), h.0.candidate.target()))) {
|
||||
let new_value = selected_route[idx].get_value_msat() + selected_route[idx + 1].get_value_msat();
|
||||
selected_route[idx].update_value_and_recompute_fees(new_value);
|
||||
selected_route.remove(idx + 1);
|
||||
|
@ -2608,14 +2693,14 @@ where L::Target: Logger {
|
|||
let mut paths = Vec::new();
|
||||
for payment_path in selected_route {
|
||||
let mut hops = Vec::with_capacity(payment_path.hops.len());
|
||||
let mut prev_hop_node_id = our_node_id;
|
||||
for (hop, node_features) in payment_path.hops.iter()
|
||||
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
|
||||
{
|
||||
let target = hop.candidate.target().expect("target is defined when short_channel_id is defined");
|
||||
let maybe_announced_channel = if let CandidateRouteHop::PublicHop { .. } = hop.candidate {
|
||||
// If we sourced the hop from the graph we're sure the target node is announced.
|
||||
true
|
||||
} else if let CandidateRouteHop::FirstHop { details } = hop.candidate {
|
||||
} else if let CandidateRouteHop::FirstHop { details, .. } = hop.candidate {
|
||||
// If this is a first hop we also know if it's announced.
|
||||
details.is_public
|
||||
} else {
|
||||
|
@ -2623,14 +2708,14 @@ where L::Target: Logger {
|
|||
// there are announced channels between the endpoints. If so, the hop might be
|
||||
// referring to any of the announced channels, as its `short_channel_id` might be
|
||||
// an alias, in which case we don't take any chances here.
|
||||
network_graph.node(&hop.node_id).map_or(false, |hop_node|
|
||||
network_graph.node(&target).map_or(false, |hop_node|
|
||||
hop_node.channels.iter().any(|scid| network_graph.channel(*scid)
|
||||
.map_or(false, |c| c.as_directed_from(&prev_hop_node_id).is_some()))
|
||||
.map_or(false, |c| c.as_directed_from(&hop.candidate.source()).is_some()))
|
||||
)
|
||||
};
|
||||
|
||||
hops.push(RouteHop {
|
||||
pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
|
||||
pubkey: PublicKey::from_slice(target.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &target), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
|
||||
node_features: node_features.clone(),
|
||||
short_channel_id: hop.candidate.short_channel_id().unwrap(),
|
||||
channel_features: hop.candidate.features(),
|
||||
|
@ -2638,8 +2723,6 @@ where L::Target: Logger {
|
|||
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
|
||||
maybe_announced_channel,
|
||||
});
|
||||
|
||||
prev_hop_node_id = hop.node_id;
|
||||
}
|
||||
let mut final_cltv_delta = final_cltv_expiry_delta;
|
||||
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
|
||||
|
@ -2802,13 +2885,13 @@ fn build_route_from_hops_internal<L: Deref>(
|
|||
|
||||
impl ScoreLookUp for HopScorer {
|
||||
type ScoreParams = ();
|
||||
fn channel_penalty_msat(&self, _short_channel_id: u64, source: &NodeId, target: &NodeId,
|
||||
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop,
|
||||
_usage: ChannelUsage, _score_params: &Self::ScoreParams) -> u64
|
||||
{
|
||||
let mut cur_id = self.our_node_id;
|
||||
for i in 0..self.hop_ids.len() {
|
||||
if let Some(next_id) = self.hop_ids[i] {
|
||||
if cur_id == *source && next_id == *target {
|
||||
if cur_id == candidate.source() && Some(next_id) == candidate.target() {
|
||||
return 0;
|
||||
}
|
||||
cur_id = next_id;
|
||||
|
@ -2849,7 +2932,7 @@ mod tests {
|
|||
use crate::routing::utxo::UtxoResult;
|
||||
use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
|
||||
BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
|
||||
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters};
|
||||
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters, CandidateRouteHop};
|
||||
use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, ScoreLookUp, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
|
||||
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;
|
||||
|
@ -6154,8 +6237,8 @@ mod tests {
|
|||
}
|
||||
impl ScoreLookUp for BadChannelScorer {
|
||||
type ScoreParams = ();
|
||||
fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
|
||||
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||
if candidate.short_channel_id() == Some(self.short_channel_id) { u64::max_value() } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6170,8 +6253,8 @@ mod tests {
|
|||
|
||||
impl ScoreLookUp for BadNodeScorer {
|
||||
type ScoreParams = ();
|
||||
fn channel_penalty_msat(&self, _: u64, _: &NodeId, target: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||
if *target == self.node_id { u64::max_value() } else { 0 }
|
||||
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||
if candidate.target() == Some(self.node_id) { u64::max_value() } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6659,26 +6742,32 @@ mod tests {
|
|||
};
|
||||
scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[3]), 123);
|
||||
scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456);
|
||||
assert_eq!(scorer.channel_penalty_msat(42, &NodeId::from_pubkey(&nodes[3]), &NodeId::from_pubkey(&nodes[4]), usage, &scorer_params), 456);
|
||||
let network_graph = network_graph.read_only();
|
||||
let channels = network_graph.channels();
|
||||
let channel = channels.get(&5).unwrap();
|
||||
let info = channel.as_directed_from(&NodeId::from_pubkey(&nodes[3])).unwrap();
|
||||
let candidate: CandidateRouteHop = CandidateRouteHop::PublicHop {
|
||||
info: info.0,
|
||||
short_channel_id: 5,
|
||||
};
|
||||
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &scorer_params), 456);
|
||||
|
||||
// Then check we can get a normal route
|
||||
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
|
||||
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,
|
||||
let route = get_route(&our_id, &route_params, &network_graph, None,
|
||||
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
||||
assert!(route.is_ok());
|
||||
|
||||
// Then check that we can't get a route if we ban an intermediate node.
|
||||
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
|
||||
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
|
||||
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
||||
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
|
||||
assert!(route.is_err());
|
||||
|
||||
// Finally make sure we can route again, when we remove the ban.
|
||||
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
|
||||
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
|
||||
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
||||
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
|
||||
assert!(route.is_ok());
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@ use crate::chain::chainmonitor::{MonitorUpdateId, UpdateOrigin};
|
|||
use crate::chain::channelmonitor;
|
||||
use crate::chain::channelmonitor::MonitorEvent;
|
||||
use crate::chain::transaction::OutPoint;
|
||||
use crate::routing::router::CandidateRouteHop;
|
||||
use crate::sign;
|
||||
use crate::events;
|
||||
use crate::events::bump_transaction::{WalletSource, Utxo};
|
||||
|
@ -139,10 +140,34 @@ impl<'a> Router for TestRouter<'a> {
|
|||
// Since the path is reversed, the last element in our iteration is the first
|
||||
// hop.
|
||||
if idx == path.hops.len() - 1 {
|
||||
scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(payer), &NodeId::from_pubkey(&hop.pubkey), usage, &Default::default());
|
||||
let first_hops = match first_hops {
|
||||
Some(hops) => hops,
|
||||
None => continue,
|
||||
};
|
||||
if first_hops.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
let idx = if first_hops.len() > 1 { route.paths.iter().position(|p| p == path).unwrap_or(0) } else { 0 };
|
||||
let candidate = CandidateRouteHop::FirstHop {
|
||||
details: first_hops[idx],
|
||||
node_id: NodeId::from_pubkey(payer)
|
||||
};
|
||||
scorer.channel_penalty_msat(&candidate, usage, &());
|
||||
} else {
|
||||
let curr_hop_path_idx = path.hops.len() - 1 - idx;
|
||||
scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(&path.hops[curr_hop_path_idx - 1].pubkey), &NodeId::from_pubkey(&hop.pubkey), usage, &Default::default());
|
||||
let network_graph = self.network_graph.read_only();
|
||||
let channel = match network_graph.channel(hop.short_channel_id) {
|
||||
Some(channel) => channel,
|
||||
None => continue,
|
||||
};
|
||||
let channel = match channel.as_directed_to(&NodeId::from_pubkey(&hop.pubkey)) {
|
||||
Some(channel) => channel,
|
||||
None => panic!("Channel directed to {} was not found", hop.pubkey),
|
||||
};
|
||||
let candidate = CandidateRouteHop::PublicHop {
|
||||
info: channel.0,
|
||||
short_channel_id: hop.short_channel_id,
|
||||
};
|
||||
scorer.channel_penalty_msat(&candidate, usage, &());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1297,8 +1322,12 @@ impl crate::util::ser::Writeable for TestScorer {
|
|||
impl ScoreLookUp for TestScorer {
|
||||
type ScoreParams = ();
|
||||
fn channel_penalty_msat(
|
||||
&self, short_channel_id: u64, _source: &NodeId, _target: &NodeId, usage: ChannelUsage, _score_params: &Self::ScoreParams
|
||||
&self, candidate: &CandidateRouteHop, usage: ChannelUsage, _score_params: &Self::ScoreParams
|
||||
) -> u64 {
|
||||
let short_channel_id = match candidate.short_channel_id() {
|
||||
Some(scid) => scid,
|
||||
None => return 0,
|
||||
};
|
||||
if let Some(scorer_expectations) = self.scorer_expectations.borrow_mut().as_mut() {
|
||||
match scorer_expectations.pop_front() {
|
||||
Some((scid, expectation)) => {
|
||||
|
|
Loading…
Add table
Reference in a new issue