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::msgs::{ChannelMessageHandler, Init};
|
||||||
use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
|
use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
|
||||||
use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
|
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::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp, LockableScore};
|
||||||
|
use lightning::routing::router::{DefaultRouter, Path, RouteHop, CandidateRouteHop};
|
||||||
use lightning::util::config::UserConfig;
|
use lightning::util::config::UserConfig;
|
||||||
use lightning::util::ser::Writeable;
|
use lightning::util::ser::Writeable;
|
||||||
use lightning::util::test_utils;
|
use lightning::util::test_utils;
|
||||||
|
@ -1071,7 +1071,7 @@ mod tests {
|
||||||
impl ScoreLookUp for TestScorer {
|
impl ScoreLookUp for TestScorer {
|
||||||
type ScoreParams = ();
|
type ScoreParams = ();
|
||||||
fn channel_penalty_msat(
|
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!(); }
|
) -> u64 { unimplemented!(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -870,31 +870,31 @@ impl ChannelInfo {
|
||||||
/// Returns a [`DirectedChannelInfo`] for the channel directed to the given `target` from a
|
/// 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.
|
/// 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)> {
|
pub fn as_directed_to(&self, target: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> {
|
||||||
let (direction, source) = {
|
let (direction, source, outbound) = {
|
||||||
if target == &self.node_one {
|
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 {
|
} 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 {
|
} else {
|
||||||
return None;
|
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
|
/// 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.
|
/// 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)> {
|
pub fn as_directed_from(&self, source: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> {
|
||||||
let (direction, target) = {
|
let (direction, target, outbound) = {
|
||||||
if source == &self.node_one {
|
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 {
|
} 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 {
|
} else {
|
||||||
return None;
|
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.
|
/// Returns a [`ChannelUpdateInfo`] based on the direction implied by the channel_flag.
|
||||||
|
@ -992,24 +992,32 @@ pub struct DirectedChannelInfo<'a> {
|
||||||
direction: &'a ChannelUpdateInfo,
|
direction: &'a ChannelUpdateInfo,
|
||||||
htlc_maximum_msat: u64,
|
htlc_maximum_msat: u64,
|
||||||
effective_capacity: EffectiveCapacity,
|
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> {
|
impl<'a> DirectedChannelInfo<'a> {
|
||||||
#[inline]
|
#[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 mut htlc_maximum_msat = direction.htlc_maximum_msat;
|
||||||
let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000);
|
let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000);
|
||||||
|
|
||||||
let effective_capacity = match capacity_msat {
|
let effective_capacity = match capacity_msat {
|
||||||
Some(capacity_msat) => {
|
Some(capacity_msat) => {
|
||||||
htlc_maximum_msat = cmp::min(htlc_maximum_msat, 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 },
|
None => EffectiveCapacity::AdvertisedMaxHTLC { amount_msat: htlc_maximum_msat },
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
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.
|
/// Returns information for the direction.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn direction(&self) -> &'a ChannelUpdateInfo { self.direction }
|
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> {
|
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 {
|
impl<'a, S: Deref> ScoreLookUp for ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: ScoreLookUp {
|
||||||
type ScoreParams = <S::Target as ScoreLookUp>::ScoreParams;
|
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(
|
if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat(
|
||||||
source, target, short_channel_id
|
&source, &target, short_channel_id
|
||||||
) {
|
) {
|
||||||
let usage = ChannelUsage {
|
let usage = ChannelUsage {
|
||||||
inflight_htlc_msat: usage.inflight_htlc_msat.saturating_add(used_liquidity),
|
inflight_htlc_msat: usage.inflight_htlc_msat.saturating_add(used_liquidity),
|
||||||
..usage
|
..usage
|
||||||
};
|
};
|
||||||
|
|
||||||
self.scorer.channel_penalty_msat(short_channel_id, source, target, usage, score_params)
|
self.scorer.channel_penalty_msat(candidate, usage, score_params)
|
||||||
} else {
|
} 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.
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
enum CandidateRouteHop<'a> {
|
pub enum CandidateRouteHop<'a> {
|
||||||
/// A hop from the payer, where the outbound liquidity is known.
|
/// A hop from the payer, where the outbound liquidity is known.
|
||||||
FirstHop {
|
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,
|
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 {
|
PublicHop {
|
||||||
|
/// channel info of the hop.
|
||||||
info: DirectedChannelInfo<'a>,
|
info: DirectedChannelInfo<'a>,
|
||||||
|
/// short_channel_id of the channel.
|
||||||
short_channel_id: u64,
|
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.
|
/// channel.
|
||||||
PrivateHop {
|
PrivateHop {
|
||||||
|
/// Hint provides information about a private hop,
|
||||||
|
/// needed while routing through a private
|
||||||
|
/// channel.
|
||||||
hint: &'a RouteHintHop,
|
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 {
|
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),
|
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,
|
hint_idx: usize,
|
||||||
},
|
},
|
||||||
/// Similar to [`Self::Blinded`], but the path here has 1 blinded hop. `BlindedPayInfo` provided
|
/// Similar to [`Self::Blinded`], but the path here
|
||||||
/// for 1-hop blinded paths is ignored because it is meant to apply to the hops *between* the
|
/// has 1 blinded hop. `BlindedPayInfo` provided
|
||||||
/// introduction node and the destination. Useful for tracking that we need to include a blinded
|
/// 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`].
|
/// path at the end of our [`Route`].
|
||||||
OneHopBlinded {
|
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),
|
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,
|
hint_idx: usize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CandidateRouteHop<'a> {
|
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 {
|
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::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::Blinded { .. } => None,
|
||||||
CandidateRouteHop::OneHopBlinded { .. } => 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.
|
// NOTE: This may alloc memory so avoid calling it in a hot code path.
|
||||||
fn features(&self) -> ChannelFeatures {
|
fn features(&self) -> ChannelFeatures {
|
||||||
match self {
|
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::PublicHop { info, .. } => info.channel().features.clone(),
|
||||||
CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(),
|
CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(),
|
||||||
CandidateRouteHop::Blinded { .. } => 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 {
|
match self {
|
||||||
CandidateRouteHop::FirstHop { .. } => 0,
|
CandidateRouteHop::FirstHop { .. } => 0,
|
||||||
CandidateRouteHop::PublicHop { info, .. } => info.direction().cltv_expiry_delta as u32,
|
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::Blinded { hint, .. } => hint.0.cltv_expiry_delta as u32,
|
||||||
CandidateRouteHop::OneHopBlinded { .. } => 0,
|
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 {
|
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::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::Blinded { hint, .. } => hint.0.htlc_minimum_msat,
|
||||||
CandidateRouteHop::OneHopBlinded { .. } => 0,
|
CandidateRouteHop::OneHopBlinded { .. } => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fees(&self) -> RoutingFees {
|
/// Returns the fees for this hop.
|
||||||
|
pub fn fees(&self) -> RoutingFees {
|
||||||
match self {
|
match self {
|
||||||
CandidateRouteHop::FirstHop { .. } => RoutingFees {
|
CandidateRouteHop::FirstHop { .. } => RoutingFees {
|
||||||
base_msat: 0, proportional_millionths: 0,
|
base_msat: 0, proportional_millionths: 0,
|
||||||
},
|
},
|
||||||
CandidateRouteHop::PublicHop { info, .. } => info.direction().fees,
|
CandidateRouteHop::PublicHop { info, .. } => info.direction().fees,
|
||||||
CandidateRouteHop::PrivateHop { hint } => hint.fees,
|
CandidateRouteHop::PrivateHop { hint, .. } => hint.fees,
|
||||||
CandidateRouteHop::Blinded { hint, .. } => {
|
CandidateRouteHop::Blinded { hint, .. } => {
|
||||||
RoutingFees {
|
RoutingFees {
|
||||||
base_msat: hint.0.fee_base_msat,
|
base_msat: hint.0.fee_base_msat,
|
||||||
|
@ -1087,13 +1139,13 @@ impl<'a> CandidateRouteHop<'a> {
|
||||||
|
|
||||||
fn effective_capacity(&self) -> EffectiveCapacity {
|
fn effective_capacity(&self) -> EffectiveCapacity {
|
||||||
match self {
|
match self {
|
||||||
CandidateRouteHop::FirstHop { details } => EffectiveCapacity::ExactLiquidity {
|
CandidateRouteHop::FirstHop { details, .. } => EffectiveCapacity::ExactLiquidity {
|
||||||
liquidity_msat: details.next_outbound_htlc_limit_msat,
|
liquidity_msat: details.next_outbound_htlc_limit_msat,
|
||||||
},
|
},
|
||||||
CandidateRouteHop::PublicHop { info, .. } => info.effective_capacity(),
|
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 },
|
EffectiveCapacity::HintMaxHTLC { amount_msat: *max },
|
||||||
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }} =>
|
CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. } =>
|
||||||
EffectiveCapacity::Infinite,
|
EffectiveCapacity::Infinite,
|
||||||
CandidateRouteHop::Blinded { hint, .. } =>
|
CandidateRouteHop::Blinded { hint, .. } =>
|
||||||
EffectiveCapacity::HintMaxHTLC { amount_msat: hint.0.htlc_maximum_msat },
|
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 {
|
match self {
|
||||||
CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
|
CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
|
||||||
CandidateRouteHop::OneHopBlinded { 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> {
|
fn blinded_path(&self) -> Option<&'a BlindedPath> {
|
||||||
|
@ -1116,10 +1171,42 @@ impl<'a> CandidateRouteHop<'a> {
|
||||||
_ => None,
|
_ => 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)]
|
#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
enum CandidateHopId {
|
pub enum CandidateHopId {
|
||||||
/// Contains (scid, src_node_id < target_node_id)
|
/// Contains (scid, src_node_id < target_node_id)
|
||||||
Clear((u64, bool)),
|
Clear((u64, bool)),
|
||||||
/// Index of the blinded route hint in [`Payee::Blinded::route_hints`].
|
/// 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".
|
/// These fee values are useful to choose hops as we traverse the graph "payee-to-payer".
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct PathBuildingHop<'a> {
|
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>,
|
candidate: CandidateRouteHop<'a>,
|
||||||
fee_msat: u64,
|
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> {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||||
let mut debug_struct = f.debug_struct("PathBuildingHop");
|
let mut debug_struct = f.debug_struct("PathBuildingHop");
|
||||||
debug_struct
|
debug_struct
|
||||||
.field("node_id", &self.node_id)
|
.field("node_id", &self.candidate.target())
|
||||||
.field("short_channel_id", &self.candidate.short_channel_id())
|
.field("short_channel_id", &self.candidate.short_channel_id())
|
||||||
.field("total_fee_msat", &self.total_fee_msat)
|
.field("total_fee_msat", &self.total_fee_msat)
|
||||||
.field("next_hops_fee_msat", &self.next_hops_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;
|
let mut num_ignored_htlc_minimum_msat_limit: u32 = 0;
|
||||||
|
|
||||||
macro_rules! add_entry {
|
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,
|
// $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.
|
// since that value has to be transferred over this channel.
|
||||||
// Returns the contribution amount of $candidate if the channel caused an update to `targets`.
|
// 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_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 ) => { {
|
$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
|
// 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:
|
// practice these cases should be caught earlier:
|
||||||
// - for regular channels at channel announcement (TODO)
|
// - for regular channels at channel announcement (TODO)
|
||||||
// - for first and last hops early in get_route
|
// - 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 scid_opt = $candidate.short_channel_id();
|
||||||
let effective_capacity = $candidate.effective_capacity();
|
let effective_capacity = $candidate.effective_capacity();
|
||||||
let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
|
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.
|
// 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) {
|
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
|
||||||
let used_liquidity_msat = used_liquidities
|
let used_liquidity_msat = used_liquidities
|
||||||
.get(&$candidate.id($src_node_id < $dest_node_id))
|
.get(&$candidate.id())
|
||||||
.map_or(0, |used_liquidity_msat| {
|
.map_or(0, |used_liquidity_msat| {
|
||||||
available_value_contribution_msat = available_value_contribution_msat
|
available_value_contribution_msat = available_value_contribution_msat
|
||||||
.saturating_sub(*used_liquidity_msat);
|
.saturating_sub(*used_liquidity_msat);
|
||||||
|
@ -1796,7 +1881,7 @@ where L::Target: Logger {
|
||||||
|scid| payment_params.previously_failed_channels.contains(&scid));
|
|scid| payment_params.previously_failed_channels.contains(&scid));
|
||||||
|
|
||||||
let (should_log_candidate, first_hop_details) = match $candidate {
|
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::PrivateHop { .. } => (true, None),
|
||||||
CandidateRouteHop::Blinded { .. } => (true, None),
|
CandidateRouteHop::Blinded { .. } => (true, None),
|
||||||
CandidateRouteHop::OneHopBlinded { .. } => (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())
|
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
|
||||||
.saturating_add(curr_min);
|
.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(|| {
|
let old_entry = hm_entry.or_insert_with(|| {
|
||||||
// If there was previously no known way to access the source node
|
// 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
|
// (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.
|
// semi-dummy record just to compute the fees to reach the source node.
|
||||||
// This will affect our decision on selecting short_channel_id
|
// 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 {
|
PathBuildingHop {
|
||||||
node_id: $dest_node_id.clone(),
|
|
||||||
candidate: $candidate.clone(),
|
candidate: $candidate.clone(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
next_hops_fee_msat: u64::max_value(),
|
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
|
// Ignore hop_use_fee_msat for channel-from-us as we assume all channels-from-us
|
||||||
// will have the same effective-fee
|
// 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
|
// Note that `u64::max_value` means we'll always fail the
|
||||||
// `old_entry.total_fee_msat > total_fee_msat` check below
|
// `old_entry.total_fee_msat > total_fee_msat` check below
|
||||||
hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, $candidate.fees());
|
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,
|
inflight_htlc_msat: used_liquidity_msat,
|
||||||
effective_capacity,
|
effective_capacity,
|
||||||
};
|
};
|
||||||
let channel_penalty_msat = scid_opt.map_or(0,
|
let channel_penalty_msat =
|
||||||
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
|
scorer.channel_penalty_msat($candidate,
|
||||||
channel_usage, score_params));
|
channel_usage,
|
||||||
|
score_params);
|
||||||
let path_penalty_msat = $next_hops_path_penalty_msat
|
let path_penalty_msat = $next_hops_path_penalty_msat
|
||||||
.saturating_add(channel_penalty_msat);
|
.saturating_add(channel_penalty_msat);
|
||||||
let new_graph_node = RouteGraphNode {
|
let new_graph_node = RouteGraphNode {
|
||||||
node_id: $src_node_id,
|
node_id: src_node_id,
|
||||||
lowest_fee_to_node: total_fee_msat,
|
lowest_fee_to_node: total_fee_msat,
|
||||||
total_cltv_delta: hop_total_cltv_delta,
|
total_cltv_delta: hop_total_cltv_delta,
|
||||||
value_contribution_msat,
|
value_contribution_msat,
|
||||||
|
@ -1946,7 +2031,8 @@ where L::Target: Logger {
|
||||||
path_length_to_node,
|
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
|
// if this way is cheaper than the already known
|
||||||
// (considering the cost to "reach" this channel from the route destination,
|
// (considering the cost to "reach" this channel from the route destination,
|
||||||
// the cost of using this channel,
|
// 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.next_hops_fee_msat = $next_hops_fee_msat;
|
||||||
old_entry.hop_use_fee_msat = hop_use_fee_msat;
|
old_entry.hop_use_fee_msat = hop_use_fee_msat;
|
||||||
old_entry.total_fee_msat = total_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.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.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;
|
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
|
||||||
|
@ -2060,8 +2145,8 @@ where L::Target: Logger {
|
||||||
if !skip_node {
|
if !skip_node {
|
||||||
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
|
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
|
||||||
for details in first_channels {
|
for details in first_channels {
|
||||||
let candidate = CandidateRouteHop::FirstHop { details };
|
let candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id };
|
||||||
add_entry!(candidate, our_node_id, $node_id, $fee_to_target_msat,
|
add_entry!(&candidate, $fee_to_target_msat,
|
||||||
$next_hops_value_contribution,
|
$next_hops_value_contribution,
|
||||||
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
|
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
|
||||||
$next_hops_cltv_delta, $next_hops_path_length);
|
$next_hops_cltv_delta, $next_hops_path_length);
|
||||||
|
@ -2085,7 +2170,7 @@ where L::Target: Logger {
|
||||||
info: directed_channel,
|
info: directed_channel,
|
||||||
short_channel_id: *chan_id,
|
short_channel_id: *chan_id,
|
||||||
};
|
};
|
||||||
add_entry!(candidate, *source, $node_id,
|
add_entry!(&candidate,
|
||||||
$fee_to_target_msat,
|
$fee_to_target_msat,
|
||||||
$next_hops_value_contribution,
|
$next_hops_value_contribution,
|
||||||
$next_hops_path_htlc_minimum_msat,
|
$next_hops_path_htlc_minimum_msat,
|
||||||
|
@ -2115,8 +2200,8 @@ where L::Target: Logger {
|
||||||
// place where it could be added.
|
// place where it could be added.
|
||||||
payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|first_channels| {
|
payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|first_channels| {
|
||||||
for details in first_channels {
|
for details in first_channels {
|
||||||
let candidate = CandidateRouteHop::FirstHop { details };
|
let candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id };
|
||||||
let added = add_entry!(candidate, our_node_id, payee, 0, path_value_msat,
|
let added = add_entry!(&candidate, 0, path_value_msat,
|
||||||
0, 0u64, 0, 0).is_some();
|
0, 0u64, 0, 0).is_some();
|
||||||
log_trace!(logger, "{} direct route to payee via {}",
|
log_trace!(logger, "{} direct route to payee via {}",
|
||||||
if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
|
if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
|
||||||
|
@ -2153,7 +2238,7 @@ where L::Target: Logger {
|
||||||
CandidateRouteHop::OneHopBlinded { hint, hint_idx }
|
CandidateRouteHop::OneHopBlinded { hint, hint_idx }
|
||||||
} else { CandidateRouteHop::Blinded { hint, hint_idx } };
|
} else { CandidateRouteHop::Blinded { hint, hint_idx } };
|
||||||
let mut path_contribution_msat = path_value_msat;
|
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)
|
0, path_contribution_msat, 0, 0_u64, 0, 0)
|
||||||
{
|
{
|
||||||
path_contribution_msat = hop_used_msat;
|
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,
|
sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat,
|
||||||
our_node_pubkey);
|
our_node_pubkey);
|
||||||
for details in first_channels {
|
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()) {
|
let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) {
|
||||||
Some(fee) => fee,
|
Some(fee) => fee,
|
||||||
None => continue
|
None => continue
|
||||||
};
|
};
|
||||||
let path_min = candidate.htlc_minimum_msat().saturating_add(
|
let path_min = candidate.htlc_minimum_msat().saturating_add(
|
||||||
compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
|
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(),
|
path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(),
|
||||||
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
|
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
|
||||||
}
|
}
|
||||||
|
@ -2219,9 +2304,9 @@ where L::Target: Logger {
|
||||||
info,
|
info,
|
||||||
short_channel_id: hop.short_channel_id,
|
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_fee_msat, aggregate_path_contribution_msat,
|
||||||
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
|
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
|
||||||
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
|
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
|
||||||
|
@ -2235,7 +2320,7 @@ where L::Target: Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
let used_liquidity_msat = used_liquidities
|
let used_liquidity_msat = used_liquidities
|
||||||
.get(&candidate.id(source < target)).copied()
|
.get(&candidate.id()).copied()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let channel_usage = ChannelUsage {
|
let channel_usage = ChannelUsage {
|
||||||
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
|
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
|
||||||
|
@ -2243,7 +2328,7 @@ where L::Target: Logger {
|
||||||
effective_capacity: candidate.effective_capacity(),
|
effective_capacity: candidate.effective_capacity(),
|
||||||
};
|
};
|
||||||
let channel_penalty_msat = scorer.channel_penalty_msat(
|
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
|
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
|
||||||
.saturating_add(channel_penalty_msat);
|
.saturating_add(channel_penalty_msat);
|
||||||
|
@ -2259,8 +2344,8 @@ where L::Target: Logger {
|
||||||
sort_first_hop_channels(first_channels, &used_liquidities,
|
sort_first_hop_channels(first_channels, &used_liquidities,
|
||||||
recommended_value_msat, our_node_pubkey);
|
recommended_value_msat, our_node_pubkey);
|
||||||
for details in first_channels {
|
for details in first_channels {
|
||||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
let first_hop_candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id};
|
||||||
add_entry!(first_hop_candidate, our_node_id, target,
|
add_entry!(&first_hop_candidate,
|
||||||
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
|
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_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
|
||||||
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length);
|
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,
|
sort_first_hop_channels(first_channels, &used_liquidities,
|
||||||
recommended_value_msat, our_node_pubkey);
|
recommended_value_msat, our_node_pubkey);
|
||||||
for details in first_channels {
|
for details in first_channels {
|
||||||
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
|
let first_hop_candidate = CandidateRouteHop::FirstHop { details, node_id: our_node_id};
|
||||||
add_entry!(first_hop_candidate, our_node_id,
|
add_entry!(&first_hop_candidate,
|
||||||
NodeId::from_pubkey(&hop.src_node_id),
|
|
||||||
aggregate_next_hops_fee_msat,
|
aggregate_next_hops_fee_msat,
|
||||||
aggregate_path_contribution_msat,
|
aggregate_path_contribution_msat,
|
||||||
aggregate_next_hops_path_htlc_minimum_msat,
|
aggregate_next_hops_path_htlc_minimum_msat,
|
||||||
|
@ -2345,7 +2429,8 @@ where L::Target: Logger {
|
||||||
|
|
||||||
'path_walk: loop {
|
'path_walk: loop {
|
||||||
let mut features_set = false;
|
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 {
|
for details in first_channels {
|
||||||
if let Some(scid) = ordered_hops.last().unwrap().0.candidate.short_channel_id() {
|
if let Some(scid) = ordered_hops.last().unwrap().0.candidate.short_channel_id() {
|
||||||
if details.get_outbound_payment_scid().unwrap() == scid {
|
if details.get_outbound_payment_scid().unwrap() == scid {
|
||||||
|
@ -2357,7 +2442,7 @@ where L::Target: Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !features_set {
|
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() {
|
if let Some(node_info) = node.announcement_info.as_ref() {
|
||||||
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
|
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
|
||||||
} else {
|
} else {
|
||||||
|
@ -2374,11 +2459,11 @@ where L::Target: Logger {
|
||||||
// save this path for the payment route. Also, update the liquidity
|
// save this path for the payment route. Also, update the liquidity
|
||||||
// remaining on the used hops, so that we take them into account
|
// remaining on the used hops, so that we take them into account
|
||||||
// while looking for more paths.
|
// 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;
|
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,
|
Some(payment_hop) => payment_hop,
|
||||||
// We can't arrive at None because, if we ever add an entry to targets,
|
// 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!).
|
// 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
|
// Remember that we used these channels so that we don't rely
|
||||||
// on the same liquidity in future paths.
|
// on the same liquidity in future paths.
|
||||||
let mut prevented_redundant_path_selection = false;
|
let mut prevented_redundant_path_selection = false;
|
||||||
let prev_hop_iter = core::iter::once(&our_node_id)
|
for (hop, _) in payment_path.hops.iter() {
|
||||||
.chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id));
|
|
||||||
for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) {
|
|
||||||
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
|
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
|
||||||
let used_liquidity_msat = used_liquidities
|
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)
|
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
|
||||||
.or_insert(spent_on_hop_msat);
|
.or_insert(spent_on_hop_msat);
|
||||||
let hop_capacity = hop.candidate.effective_capacity();
|
let hop_capacity = hop.candidate.effective_capacity();
|
||||||
|
@ -2443,8 +2526,10 @@ where L::Target: Logger {
|
||||||
log_trace!(logger,
|
log_trace!(logger,
|
||||||
"Disabling route candidate {} for future path building iterations to avoid duplicates.",
|
"Disabling route candidate {} for future path building iterations to avoid duplicates.",
|
||||||
LoggedCandidateHop(victim_candidate));
|
LoggedCandidateHop(victim_candidate));
|
||||||
*used_liquidities.entry(victim_candidate.id(false)).or_default() = exhausted;
|
if let Some(scid) = victim_candidate.short_channel_id() {
|
||||||
*used_liquidities.entry(victim_candidate.id(true)).or_default() = exhausted;
|
*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
|
// 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| {
|
selected_route.sort_unstable_by_key(|path| {
|
||||||
let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
|
let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
|
||||||
debug_assert!(path.hops.len() <= key.len());
|
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 = scid;
|
||||||
}
|
}
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
for idx in 0..(selected_route.len() - 1) {
|
for idx in 0..(selected_route.len() - 1) {
|
||||||
if idx + 1 >= selected_route.len() { break; }
|
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)),
|
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(true), h.0.node_id))) {
|
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();
|
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[idx].update_value_and_recompute_fees(new_value);
|
||||||
selected_route.remove(idx + 1);
|
selected_route.remove(idx + 1);
|
||||||
|
@ -2608,14 +2693,14 @@ where L::Target: Logger {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
for payment_path in selected_route {
|
for payment_path in selected_route {
|
||||||
let mut hops = Vec::with_capacity(payment_path.hops.len());
|
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()
|
for (hop, node_features) in payment_path.hops.iter()
|
||||||
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
|
.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 {
|
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.
|
// If we sourced the hop from the graph we're sure the target node is announced.
|
||||||
true
|
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.
|
// If this is a first hop we also know if it's announced.
|
||||||
details.is_public
|
details.is_public
|
||||||
} else {
|
} else {
|
||||||
|
@ -2623,14 +2708,14 @@ where L::Target: Logger {
|
||||||
// there are announced channels between the endpoints. If so, the hop might be
|
// 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
|
// 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.
|
// 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)
|
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 {
|
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(),
|
node_features: node_features.clone(),
|
||||||
short_channel_id: hop.candidate.short_channel_id().unwrap(),
|
short_channel_id: hop.candidate.short_channel_id().unwrap(),
|
||||||
channel_features: hop.candidate.features(),
|
channel_features: hop.candidate.features(),
|
||||||
|
@ -2638,8 +2723,6 @@ where L::Target: Logger {
|
||||||
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
|
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
|
||||||
maybe_announced_channel,
|
maybe_announced_channel,
|
||||||
});
|
});
|
||||||
|
|
||||||
prev_hop_node_id = hop.node_id;
|
|
||||||
}
|
}
|
||||||
let mut final_cltv_delta = final_cltv_expiry_delta;
|
let mut final_cltv_delta = final_cltv_expiry_delta;
|
||||||
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
|
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 {
|
impl ScoreLookUp for HopScorer {
|
||||||
type ScoreParams = ();
|
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
|
_usage: ChannelUsage, _score_params: &Self::ScoreParams) -> u64
|
||||||
{
|
{
|
||||||
let mut cur_id = self.our_node_id;
|
let mut cur_id = self.our_node_id;
|
||||||
for i in 0..self.hop_ids.len() {
|
for i in 0..self.hop_ids.len() {
|
||||||
if let Some(next_id) = self.hop_ids[i] {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
cur_id = next_id;
|
cur_id = next_id;
|
||||||
|
@ -2849,7 +2932,7 @@ mod tests {
|
||||||
use crate::routing::utxo::UtxoResult;
|
use crate::routing::utxo::UtxoResult;
|
||||||
use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
|
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,
|
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::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::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::transaction::OutPoint;
|
||||||
|
@ -6154,8 +6237,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
impl ScoreLookUp for BadChannelScorer {
|
impl ScoreLookUp for BadChannelScorer {
|
||||||
type ScoreParams = ();
|
type ScoreParams = ();
|
||||||
fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||||
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
|
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 {
|
impl ScoreLookUp for BadNodeScorer {
|
||||||
type ScoreParams = ();
|
type ScoreParams = ();
|
||||||
fn channel_penalty_msat(&self, _: u64, _: &NodeId, target: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
|
||||||
if *target == self.node_id { u64::max_value() } else { 0 }
|
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[3]), 123);
|
||||||
scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456);
|
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
|
// Then check we can get a normal route
|
||||||
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
|
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
|
||||||
let route_params = RouteParameters::from_payment_params_and_value(
|
let route_params = RouteParameters::from_payment_params_and_value(
|
||||||
payment_params, 100);
|
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);
|
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
||||||
assert!(route.is_ok());
|
assert!(route.is_ok());
|
||||||
|
|
||||||
// Then check that we can't get a route if we ban an intermediate node.
|
// Then check that we can't get a route if we ban an intermediate node.
|
||||||
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
|
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
|
||||||
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);
|
||||||
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
|
||||||
assert!(route.is_err());
|
assert!(route.is_err());
|
||||||
|
|
||||||
// Finally make sure we can route again, when we remove the ban.
|
// Finally make sure we can route again, when we remove the ban.
|
||||||
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
|
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
|
||||||
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);
|
||||||
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
|
|
||||||
assert!(route.is_ok());
|
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;
|
||||||
use crate::chain::channelmonitor::MonitorEvent;
|
use crate::chain::channelmonitor::MonitorEvent;
|
||||||
use crate::chain::transaction::OutPoint;
|
use crate::chain::transaction::OutPoint;
|
||||||
|
use crate::routing::router::CandidateRouteHop;
|
||||||
use crate::sign;
|
use crate::sign;
|
||||||
use crate::events;
|
use crate::events;
|
||||||
use crate::events::bump_transaction::{WalletSource, Utxo};
|
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
|
// Since the path is reversed, the last element in our iteration is the first
|
||||||
// hop.
|
// hop.
|
||||||
if idx == path.hops.len() - 1 {
|
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 {
|
} else {
|
||||||
let curr_hop_path_idx = path.hops.len() - 1 - idx;
|
let network_graph = self.network_graph.read_only();
|
||||||
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 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 {
|
impl ScoreLookUp for TestScorer {
|
||||||
type ScoreParams = ();
|
type ScoreParams = ();
|
||||||
fn channel_penalty_msat(
|
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 {
|
) -> 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() {
|
if let Some(scorer_expectations) = self.scorer_expectations.borrow_mut().as_mut() {
|
||||||
match scorer_expectations.pop_front() {
|
match scorer_expectations.pop_front() {
|
||||||
Some((scid, expectation)) => {
|
Some((scid, expectation)) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue