diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index e265c6322ec..d6ebfef4586 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -707,37 +707,45 @@ pub struct ChannelInfo { /// (which we can probably assume we are - no-std environments probably won't have a full /// network graph in memory!). announcement_received_time: u64, + /// Lowest fees to enter the first direction, based on the cheapest channel to the source node. + /// The two fields (flat and proportional fee) are independent, + /// meaning they don't have to refer to the same channel. + pub lowest_inbound_channel_fees_to_one: Option, + /// Lowest fees to enter the second direction, based on the cheapest channel to the source node. + /// The two fields (flat and proportional fee) are independent, + /// meaning they don't have to refer to the same channel. + pub lowest_inbound_channel_fees_to_two: Option, } 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, lowest_inbound_channel_fees) = { if target == &self.node_one { - (self.two_to_one.as_ref(), &self.node_two) + (self.two_to_one.as_ref(), &self.node_two, self.lowest_inbound_channel_fees_to_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, self.lowest_inbound_channel_fees_to_one) } else { return None; } }; - Some((DirectedChannelInfo::new(self, direction), source)) + Some((DirectedChannelInfo::new(self, direction, lowest_inbound_channel_fees), 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, lowest_inbound_channel_fees) = { if source == &self.node_one { - (self.one_to_two.as_ref(), &self.node_two) + (self.one_to_two.as_ref(), &self.node_two, self.lowest_inbound_channel_fees_to_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, self.lowest_inbound_channel_fees_to_one) } else { return None; } }; - Some((DirectedChannelInfo::new(self, direction), target)) + Some((DirectedChannelInfo::new(self, direction, lowest_inbound_channel_fees), target)) } /// Returns a [`ChannelUpdateInfo`] based on the direction implied by the channel_flag. @@ -770,6 +778,8 @@ impl Writeable for ChannelInfo { (8, self.two_to_one, required), (10, self.capacity_sats, required), (12, self.announcement_message, required), + (14, self.lowest_inbound_channel_fees_to_one, option), + (16, self.lowest_inbound_channel_fees_to_two, option), }); Ok(()) } @@ -803,6 +813,8 @@ impl Readable for ChannelInfo { let mut two_to_one_wrap: Option = None; init_tlv_field_var!(capacity_sats, required); init_tlv_field_var!(announcement_message, required); + let mut lowest_inbound_channel_fees_to_one = None; + let mut lowest_inbound_channel_fees_to_two = None; read_tlv_fields!(reader, { (0, features, required), (1, announcement_received_time, (default_value, 0)), @@ -812,6 +824,8 @@ impl Readable for ChannelInfo { (8, two_to_one_wrap, ignorable), (10, capacity_sats, required), (12, announcement_message, required), + (14, lowest_inbound_channel_fees_to_one, option), + (16, lowest_inbound_channel_fees_to_two, option), }); Ok(ChannelInfo { @@ -823,6 +837,8 @@ impl Readable for ChannelInfo { capacity_sats: init_tlv_based_struct_field!(capacity_sats, required), announcement_message: init_tlv_based_struct_field!(announcement_message, required), announcement_received_time: init_tlv_based_struct_field!(announcement_received_time, (default_value, 0)), + lowest_inbound_channel_fees_to_one: init_tlv_based_struct_field!(lowest_inbound_channel_fees_to_one, option), + lowest_inbound_channel_fees_to_two: init_tlv_based_struct_field!(lowest_inbound_channel_fees_to_two, option), }) } } @@ -835,11 +851,12 @@ pub struct DirectedChannelInfo<'a> { direction: Option<&'a ChannelUpdateInfo>, htlc_maximum_msat: u64, effective_capacity: EffectiveCapacity, + lowest_inbound_channel_fees: Option, } impl<'a> DirectedChannelInfo<'a> { #[inline] - fn new(channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>) -> Self { + fn new(channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>, lowest_inbound_channel_fees: Option) -> Self { let htlc_maximum_msat = direction.map(|direction| direction.htlc_maximum_msat); let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000); @@ -858,7 +875,7 @@ impl<'a> DirectedChannelInfo<'a> { }; Self { - channel, direction, htlc_maximum_msat, effective_capacity + channel, direction, htlc_maximum_msat, effective_capacity, lowest_inbound_channel_fees, } } @@ -882,6 +899,13 @@ impl<'a> DirectedChannelInfo<'a> { self.effective_capacity } + /// Returns the [`Option`] to reach the channel in the direction. + /// + /// This is based on the known and enabled channels to the entry node. + pub fn lowest_inbound_channel_fees(&self) -> Option { + self.lowest_inbound_channel_fees + } + /// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction. pub(super) fn with_update(self) -> Option> { match self.direction { @@ -917,6 +941,10 @@ impl<'a> DirectedChannelInfoWithUpdate<'a> { /// Returns the [`EffectiveCapacity`] of the channel in the direction. #[inline] pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() } + + #[inline] + pub(super) fn lowest_inbound_channel_fees(&self) -> Option { self.inner.lowest_inbound_channel_fees() } + } impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { @@ -1382,6 +1410,8 @@ impl NetworkGraph where L::Target: Logger { capacity_sats: None, announcement_message: None, announcement_received_time: timestamp, + lowest_inbound_channel_fees_to_one: None, + lowest_inbound_channel_fees_to_two: None, }; self.add_channel_between_nodes(short_channel_id, channel_info, None) @@ -1408,7 +1438,7 @@ impl NetworkGraph where L::Target: Logger { // b) we don't track UTXOs of channels we know about and remove them if they // get reorg'd out. // c) it's unclear how to do so without exposing ourselves to massive DoS risk. - Self::remove_channel_in_nodes(&mut nodes, &entry.get(), short_channel_id); + self.remove_channel_in_nodes(&mut nodes, &entry.get(), short_channel_id); *entry.get_mut() = channel_info; } else { return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); @@ -1524,6 +1554,8 @@ impl NetworkGraph where L::Target: Logger { announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY { full_msg.cloned() } else { None }, announcement_received_time, + lowest_inbound_channel_fees_to_one: None, + lowest_inbound_channel_fees_to_two: None, }; self.add_channel_between_nodes(msg.short_channel_id, chan_info, utxo_value) @@ -1538,7 +1570,7 @@ impl NetworkGraph where L::Target: Logger { if is_permanent { if let Some(chan) = channels.remove(&short_channel_id) { let mut nodes = self.nodes.write().unwrap(); - Self::remove_channel_in_nodes(&mut nodes, &chan, short_channel_id); + self.remove_channel_in_nodes(&mut nodes, &chan, short_channel_id); } } else { if let Some(chan) = channels.get_mut(&short_channel_id) { @@ -1619,7 +1651,7 @@ impl NetworkGraph where L::Target: Logger { let mut nodes = self.nodes.write().unwrap(); for scid in scids_to_remove { let info = channels.remove(&scid).expect("We just accessed this scid, it should be present"); - Self::remove_channel_in_nodes(&mut nodes, &info, scid); + self.remove_channel_in_nodes(&mut nodes, &info, scid); } } } @@ -1752,47 +1784,25 @@ impl NetworkGraph where L::Target: Logger { } let mut nodes = self.nodes.write().unwrap(); + let node = nodes.get_mut(&dest_node_id).unwrap(); if chan_enabled { - let node = nodes.get_mut(&dest_node_id).unwrap(); let mut base_msat = msg.fee_base_msat; let mut proportional_millionths = msg.fee_proportional_millionths; if let Some(fees) = node.lowest_inbound_channel_fees { base_msat = cmp::min(base_msat, fees.base_msat); proportional_millionths = cmp::min(proportional_millionths, fees.proportional_millionths); } - node.lowest_inbound_channel_fees = Some(RoutingFees { - base_msat, - proportional_millionths - }); + self.update_lowest_inbound_channel_fees(dest_node_id, node, &mut channels, Some(RoutingFees { + base_msat, proportional_millionths + })); } else if chan_was_enabled { - let node = nodes.get_mut(&dest_node_id).unwrap(); - let mut lowest_inbound_channel_fees = None; - - for chan_id in node.channels.iter() { - let chan = channels.get(chan_id).unwrap(); - let chan_info_opt; - if chan.node_one == dest_node_id { - chan_info_opt = chan.two_to_one.as_ref(); - } else { - chan_info_opt = chan.one_to_two.as_ref(); - } - if let Some(chan_info) = chan_info_opt { - if chan_info.enabled { - let fees = lowest_inbound_channel_fees.get_or_insert(RoutingFees { - base_msat: u32::max_value(), proportional_millionths: u32::max_value() }); - fees.base_msat = cmp::min(fees.base_msat, chan_info.fees.base_msat); - fees.proportional_millionths = cmp::min(fees.proportional_millionths, chan_info.fees.proportional_millionths); - } - } - } - - node.lowest_inbound_channel_fees = lowest_inbound_channel_fees; + self.recompute_and_update_lowest_inbound_channel_fees(dest_node_id, node, &mut channels); } Ok(()) } - fn remove_channel_in_nodes(nodes: &mut BTreeMap, chan: &ChannelInfo, short_channel_id: u64) { + fn remove_channel_in_nodes(&self, nodes: &mut BTreeMap, chan: &ChannelInfo, short_channel_id: u64) { macro_rules! remove_from_node { ($node_id: expr) => { if let BtreeEntry::Occupied(mut entry) = nodes.entry($node_id) { @@ -1805,12 +1815,51 @@ impl NetworkGraph where L::Target: Logger { } else { panic!("Had channel that pointed to unknown node (ie inconsistent network map)!"); } + if let Some(node) = nodes.get_mut(&$node_id) { + self.recompute_and_update_lowest_inbound_channel_fees($node_id, node, &mut self.channels.write().unwrap()); + } } } remove_from_node!(chan.node_one); remove_from_node!(chan.node_two); } + + fn recompute_and_update_lowest_inbound_channel_fees(&self, node_id: NodeId, node: &mut NodeInfo, channels: &mut BTreeMap) { + let mut updated_lowest_inbound_channel_fee = None; + for chan_id in node.channels.iter() { + let chan = channels.get(chan_id).unwrap(); + let chan_info_opt; + if chan.node_one == node_id { + chan_info_opt = chan.two_to_one.as_ref(); + } else { + chan_info_opt = chan.one_to_two.as_ref(); + } + if let Some(chan_info) = chan_info_opt { + if chan_info.enabled { + let fees = updated_lowest_inbound_channel_fee.get_or_insert(RoutingFees { + base_msat: u32::max_value(), proportional_millionths: u32::max_value() }); + fees.base_msat = cmp::min(fees.base_msat, chan_info.fees.base_msat); + fees.proportional_millionths = cmp::min(fees.proportional_millionths, chan_info.fees.proportional_millionths); + } + } + } + self.update_lowest_inbound_channel_fees(node_id, node, channels, updated_lowest_inbound_channel_fee); + } + + fn update_lowest_inbound_channel_fees(&self, node_id: NodeId, node: &mut NodeInfo, channels: &mut BTreeMap, updated_fees: Option) { + node.lowest_inbound_channel_fees = updated_fees; + for (_, chan) in channels.iter_mut() { + if chan.node_one == node_id { + chan.lowest_inbound_channel_fees_to_two = updated_fees; + } + + if chan.node_two == node_id { + chan.lowest_inbound_channel_fees_to_one = updated_fees; + } + } + } + } impl ReadOnlyNetworkGraph<'_> { @@ -3019,6 +3068,8 @@ mod tests { capacity_sats: None, announcement_message: None, announcement_received_time: 87654, + lowest_inbound_channel_fees_to_one: None, + lowest_inbound_channel_fees_to_two: None, }; let mut encoded_chan_info: Vec = Vec::new(); @@ -3037,6 +3088,8 @@ mod tests { capacity_sats: None, announcement_message: None, announcement_received_time: 87654, + lowest_inbound_channel_fees_to_one: None, + lowest_inbound_channel_fees_to_two: None, }; let mut encoded_chan_info: Vec = Vec::new(); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 4c00dc5fcf9..8eae2cfee07 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -483,6 +483,16 @@ impl<'a> CandidateRouteHop<'a> { CandidateRouteHop::PrivateHop { .. } => EffectiveCapacity::Infinite, } } + + fn lowest_inbound_channel_fees(&self) -> Option { + match self { + CandidateRouteHop::FirstHop { .. } => Some(RoutingFees { + base_msat: 0, proportional_millionths: 0, + }), + CandidateRouteHop::PublicHop { info, .. } => info.lowest_inbound_channel_fees(), + CandidateRouteHop::PrivateHop { .. } => None, + } + } } #[inline] @@ -1070,7 +1080,7 @@ where L::Target: Logger { // as a way to reach the $dest_node_id. let mut fee_base_msat = 0; let mut fee_proportional_millionths = 0; - if let Some(Some(fees)) = network_nodes.get(&$src_node_id).map(|node| node.lowest_inbound_channel_fees) { + if let Some(fees) = $candidate.lowest_inbound_channel_fees() { fee_base_msat = fees.base_msat; fee_proportional_millionths = fees.proportional_millionths; }