diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 6cb4d4d644f..805b501970f 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -24,9 +24,16 @@ use bitcoin::network::Network; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::msgs; -use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, GossipTimestampFilter, NodeAnnouncement}; -use crate::ln::msgs::{DecodeError, ErrorAction, Init, LightningError, RoutingMessageHandler, SocketAddress, MAX_VALUE_MSAT}; -use crate::ln::msgs::{QueryChannelRange, QueryShortChannelIds, ReplyChannelRange, ReplyShortChannelIdsEnd}; +use crate::ln::msgs::{ + ChannelAnnouncement, ChannelUpdate, GossipTimestampFilter, NodeAnnouncement, +}; +use crate::ln::msgs::{ + DecodeError, ErrorAction, Init, LightningError, RoutingMessageHandler, SocketAddress, + MAX_VALUE_MSAT, +}; +use crate::ln::msgs::{ + QueryChannelRange, QueryShortChannelIds, ReplyChannelRange, ReplyShortChannelIdsEnd, +}; use crate::ln::types::ChannelId; use crate::routing::utxo::{self, UtxoLookup, UtxoResolver}; use crate::util::indexed_map::{Entry as IndexedMapEntry, IndexedMap}; @@ -169,7 +176,10 @@ impl FromStr for NodeId { } /// Represents the network as nodes and channels between them -pub struct NetworkGraph where L::Target: Logger { +pub struct NetworkGraph +where + L::Target: Logger, +{ secp_ctx: Secp256k1, last_rapid_gossip_sync_timestamp: Mutex>, chain_hash: ChainHash, @@ -232,7 +242,7 @@ pub enum NetworkUpdate { /// Whether the node should be permanently removed from consideration or can be restored /// when a new `channel_update` message is received. is_permanent: bool, - } + }, } impl Writeable for NetworkUpdate { @@ -251,7 +261,7 @@ impl Writeable for NetworkUpdate { (0, node_id, required), (2, is_permanent, required), }); - } + }, } Ok(()) } @@ -270,7 +280,7 @@ impl MaybeReadable for NetworkUpdate { }); Ok(Some(Self::ChannelFailure { short_channel_id: msg.0.unwrap().contents.short_channel_id, - is_permanent: false + is_permanent: false, })) }, 2 => { @@ -292,7 +302,7 @@ impl MaybeReadable for NetworkUpdate { node_id: node_id.0.unwrap(), is_permanent: is_permanent.0.unwrap(), })) - } + }, t if t % 2 == 0 => Err(DecodeError::UnknownRequiredFeature), _ => Ok(None), } @@ -304,8 +314,10 @@ impl MaybeReadable for NetworkUpdate { /// This network graph is then used for routing payments. /// Provides interface to help with initial routing sync by /// serving historical announcements. -pub struct P2PGossipSync>, U: Deref, L: Deref> -where U::Target: UtxoLookup, L::Target: Logger +pub struct P2PGossipSync>, U: Deref, L: Deref> +where + U::Target: UtxoLookup, + L::Target: Logger, { network_graph: G, utxo_lookup: RwLock>, @@ -314,8 +326,10 @@ where U::Target: UtxoLookup, L::Target: Logger logger: L, } -impl>, U: Deref, L: Deref> P2PGossipSync -where U::Target: UtxoLookup, L::Target: Logger +impl>, U: Deref, L: Deref> P2PGossipSync +where + U::Target: UtxoLookup, + L::Target: Logger, { /// Creates a new tracker of the actual state of the network of channels and nodes, /// assuming an existing [`NetworkGraph`]. @@ -364,20 +378,25 @@ where U::Target: UtxoLookup, L::Target: Logger pub(super) fn forward_gossip_msg(&self, mut ev: MessageSendEvent) { match &mut ev { MessageSendEvent::BroadcastChannelAnnouncement { msg, ref mut update_msg } => { - if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY { return; } - if update_msg.as_ref() - .map(|msg| msg.contents.excess_data.len()).unwrap_or(0) > MAX_EXCESS_BYTES_FOR_RELAY + if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY { + return; + } + if update_msg.as_ref().map(|msg| msg.contents.excess_data.len()).unwrap_or(0) + > MAX_EXCESS_BYTES_FOR_RELAY { *update_msg = None; } }, MessageSendEvent::BroadcastChannelUpdate { msg } => { - if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY { return; } + if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY { + return; + } }, MessageSendEvent::BroadcastNodeAnnouncement { msg } => { - if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY || - msg.contents.excess_address_data.len() > MAX_EXCESS_BYTES_FOR_RELAY || - msg.contents.excess_data.len() + msg.contents.excess_address_data.len() > MAX_EXCESS_BYTES_FOR_RELAY + if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY + || msg.contents.excess_address_data.len() > MAX_EXCESS_BYTES_FOR_RELAY + || msg.contents.excess_data.len() + msg.contents.excess_address_data.len() + > MAX_EXCESS_BYTES_FOR_RELAY { return; } @@ -388,7 +407,10 @@ where U::Target: UtxoLookup, L::Target: Logger } } -impl NetworkGraph where L::Target: Logger { +impl NetworkGraph +where + L::Target: Logger, +{ /// Handles any network updates originating from [`Event`]s. /// /// [`Event`]: crate::events::Event @@ -396,14 +418,21 @@ impl NetworkGraph where L::Target: Logger { match *network_update { NetworkUpdate::ChannelFailure { short_channel_id, is_permanent } => { if is_permanent { - log_debug!(self.logger, "Removing channel graph entry for {} due to a payment failure.", short_channel_id); + log_debug!( + self.logger, + "Removing channel graph entry for {} due to a payment failure.", + short_channel_id + ); self.channel_failed_permanent(short_channel_id); } }, NetworkUpdate::NodeFailure { ref node_id, is_permanent } => { if is_permanent { - log_debug!(self.logger, - "Removed node graph entry for {} due to a payment failure.", log_pubkey!(node_id)); + log_debug!( + self.logger, + "Removed node graph entry for {} due to a payment failure.", + log_pubkey!(node_id) + ); self.node_failed_permanent(node_id); }; }, @@ -438,18 +467,17 @@ macro_rules! secp_verify_sig { macro_rules! get_pubkey_from_node_id { ( $node_id: expr, $msg_type: expr ) => { - PublicKey::from_slice($node_id.as_slice()) - .map_err(|_| LightningError { - err: format!("Invalid public key on {} message", $msg_type), - action: ErrorAction::SendWarningMessage { - msg: msgs::WarningMessage { - channel_id: ChannelId::new_zero(), - data: format!("Invalid public key on {} message", $msg_type), - }, - log_level: Level::Trace - } - })? - } + PublicKey::from_slice($node_id.as_slice()).map_err(|_| LightningError { + err: format!("Invalid public key on {} message", $msg_type), + action: ErrorAction::SendWarningMessage { + msg: msgs::WarningMessage { + channel_id: ChannelId::new_zero(), + data: format!("Invalid public key on {} message", $msg_type), + }, + log_level: Level::Trace, + }, + })? + }; } fn message_sha256d_hash(msg: &M) -> Sha256dHash { @@ -461,9 +489,17 @@ fn message_sha256d_hash(msg: &M) -> Sha256dHash { /// Verifies the signature of a [`NodeAnnouncement`]. /// /// Returns an error if it is invalid. -pub fn verify_node_announcement(msg: &NodeAnnouncement, secp_ctx: &Secp256k1) -> Result<(), LightningError> { +pub fn verify_node_announcement( + msg: &NodeAnnouncement, secp_ctx: &Secp256k1, +) -> Result<(), LightningError> { let msg_hash = hash_to_message!(&message_sha256d_hash(&msg.contents)[..]); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.signature, &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), "node_announcement"); + secp_verify_sig!( + secp_ctx, + &msg_hash, + &msg.signature, + &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), + "node_announcement" + ); Ok(()) } @@ -471,37 +507,76 @@ pub fn verify_node_announcement(msg: &NodeAnnouncement, secp_ct /// Verifies all signatures included in a [`ChannelAnnouncement`]. /// /// Returns an error if one of the signatures is invalid. -pub fn verify_channel_announcement(msg: &ChannelAnnouncement, secp_ctx: &Secp256k1) -> Result<(), LightningError> { +pub fn verify_channel_announcement( + msg: &ChannelAnnouncement, secp_ctx: &Secp256k1, +) -> Result<(), LightningError> { let msg_hash = hash_to_message!(&message_sha256d_hash(&msg.contents)[..]); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_1, &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), "channel_announcement"); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_2, &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), "channel_announcement"); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), "channel_announcement"); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), "channel_announcement"); + secp_verify_sig!( + secp_ctx, + &msg_hash, + &msg.node_signature_1, + &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), + "channel_announcement" + ); + secp_verify_sig!( + secp_ctx, + &msg_hash, + &msg.node_signature_2, + &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), + "channel_announcement" + ); + secp_verify_sig!( + secp_ctx, + &msg_hash, + &msg.bitcoin_signature_1, + &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), + "channel_announcement" + ); + secp_verify_sig!( + secp_ctx, + &msg_hash, + &msg.bitcoin_signature_2, + &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), + "channel_announcement" + ); Ok(()) } -impl>, U: Deref, L: Deref> RoutingMessageHandler for P2PGossipSync -where U::Target: UtxoLookup, L::Target: Logger +impl>, U: Deref, L: Deref> RoutingMessageHandler + for P2PGossipSync +where + U::Target: UtxoLookup, + L::Target: Logger, { - fn handle_node_announcement(&self, _their_node_id: Option, msg: &msgs::NodeAnnouncement) -> Result { + fn handle_node_announcement( + &self, _their_node_id: Option, msg: &msgs::NodeAnnouncement, + ) -> Result { self.network_graph.update_node_from_announcement(msg)?; - Ok(msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY && - msg.contents.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY && - msg.contents.excess_data.len() + msg.contents.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY) + Ok(msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY + && msg.contents.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY + && msg.contents.excess_data.len() + msg.contents.excess_address_data.len() + <= MAX_EXCESS_BYTES_FOR_RELAY) } - fn handle_channel_announcement(&self, _their_node_id: Option, msg: &msgs::ChannelAnnouncement) -> Result { - self.network_graph.update_channel_from_announcement(msg, &*self.utxo_lookup.read().unwrap())?; + fn handle_channel_announcement( + &self, _their_node_id: Option, msg: &msgs::ChannelAnnouncement, + ) -> Result { + self.network_graph + .update_channel_from_announcement(msg, &*self.utxo_lookup.read().unwrap())?; Ok(msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY) } - fn handle_channel_update(&self, _their_node_id: Option, msg: &msgs::ChannelUpdate) -> Result { + fn handle_channel_update( + &self, _their_node_id: Option, msg: &msgs::ChannelUpdate, + ) -> Result { self.network_graph.update_channel(msg)?; Ok(msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY) } - fn get_next_channel_announcement(&self, starting_point: u64) -> Option<(ChannelAnnouncement, Option, Option)> { + fn get_next_channel_announcement( + &self, starting_point: u64, + ) -> Option<(ChannelAnnouncement, Option, Option)> { let mut channels = self.network_graph.channels.write().unwrap(); for (_, ref chan) in channels.range(starting_point..) { if chan.announcement_message.is_some() { @@ -523,13 +598,15 @@ where U::Target: UtxoLookup, L::Target: Logger None } - fn get_next_node_announcement(&self, starting_point: Option<&NodeId>) -> Option { + fn get_next_node_announcement( + &self, starting_point: Option<&NodeId>, + ) -> Option { let mut nodes = self.network_graph.nodes.write().unwrap(); let iter = if let Some(node_id) = starting_point { - nodes.range((Bound::Excluded(node_id), Bound::Unbounded)) - } else { - nodes.range(..) - }; + nodes.range((Bound::Excluded(node_id), Bound::Unbounded)) + } else { + nodes.range(..) + }; for (_, ref node) in iter { if let Some(node_info) = node.announcement_info.as_ref() { if let NodeAnnouncementInfo::Relayed(announcement) = node_info { @@ -555,7 +632,9 @@ where U::Target: UtxoLookup, L::Target: Logger /// [`query_channel_range`]: msgs::QueryChannelRange /// [`query_scid`]: msgs::QueryShortChannelIds /// [`reply_scids_end`]: msgs::ReplyShortChannelIdsEnd - fn peer_connected(&self, their_node_id: PublicKey, init_msg: &Init, _inbound: bool) -> Result<(), ()> { + fn peer_connected( + &self, their_node_id: PublicKey, init_msg: &Init, _inbound: bool, + ) -> Result<(), ()> { // We will only perform a sync with peers that support gossip_queries. if !init_msg.features.supports_gossip_queries() { // Don't disconnect peers for not supporting gossip queries. We may wish to have @@ -619,7 +698,10 @@ where U::Target: UtxoLookup, L::Target: Logger let should_sync = self.should_request_full_sync(); #[cfg(feature = "std")] { - gossip_start_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + gossip_start_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); if should_sync { gossip_start_time -= 60 * 60 * 24 * 7 * 2; // 2 weeks ago } else { @@ -639,14 +721,18 @@ where U::Target: UtxoLookup, L::Target: Logger Ok(()) } - fn handle_reply_channel_range(&self, _their_node_id: PublicKey, _msg: ReplyChannelRange) -> Result<(), LightningError> { + fn handle_reply_channel_range( + &self, _their_node_id: PublicKey, _msg: ReplyChannelRange, + ) -> Result<(), LightningError> { // We don't make queries, so should never receive replies. If, in the future, the set // reconciliation extensions to gossip queries become broadly supported, we should revert // this code to its state pre-0.0.106. Ok(()) } - fn handle_reply_short_channel_ids_end(&self, _their_node_id: PublicKey, _msg: ReplyShortChannelIdsEnd) -> Result<(), LightningError> { + fn handle_reply_short_channel_ids_end( + &self, _their_node_id: PublicKey, _msg: ReplyShortChannelIdsEnd, + ) -> Result<(), LightningError> { // We don't make queries, so should never receive replies. If, in the future, the set // reconciliation extensions to gossip queries become broadly supported, we should revert // this code to its state pre-0.0.106. @@ -660,17 +746,30 @@ where U::Target: UtxoLookup, L::Target: Logger /// sync of the public routing table with 128k channels will generated 16 messages and allocate ~1MB. /// Logic can be changed to reduce allocation if/when a full sync of the routing table impacts /// memory constrained systems. - fn handle_query_channel_range(&self, their_node_id: PublicKey, msg: QueryChannelRange) -> Result<(), LightningError> { - log_debug!(self.logger, "Handling query_channel_range peer={}, first_blocknum={}, number_of_blocks={}", log_pubkey!(their_node_id), msg.first_blocknum, msg.number_of_blocks); + fn handle_query_channel_range( + &self, their_node_id: PublicKey, msg: QueryChannelRange, + ) -> Result<(), LightningError> { + log_debug!( + self.logger, + "Handling query_channel_range peer={}, first_blocknum={}, number_of_blocks={}", + log_pubkey!(their_node_id), + msg.first_blocknum, + msg.number_of_blocks + ); let inclusive_start_scid = scid_from_parts(msg.first_blocknum as u64, 0, 0); // We might receive valid queries with end_blocknum that would overflow SCID conversion. // If so, we manually cap the ending block to avoid this overflow. - let exclusive_end_scid = scid_from_parts(cmp::min(msg.end_blocknum() as u64, MAX_SCID_BLOCK), 0, 0); + let exclusive_end_scid = + scid_from_parts(cmp::min(msg.end_blocknum() as u64, MAX_SCID_BLOCK), 0, 0); // Per spec, we must reply to a query. Send an empty message when things are invalid. - if msg.chain_hash != self.network_graph.chain_hash || inclusive_start_scid.is_err() || exclusive_end_scid.is_err() || msg.number_of_blocks == 0 { + if msg.chain_hash != self.network_graph.chain_hash + || inclusive_start_scid.is_err() + || exclusive_end_scid.is_err() + || msg.number_of_blocks == 0 + { let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push(MessageSendEvent::SendReplyChannelRange { node_id: their_node_id.clone(), @@ -680,7 +779,7 @@ where U::Target: UtxoLookup, L::Target: Logger number_of_blocks: msg.number_of_blocks, sync_complete: true, short_channel_ids: vec![], - } + }, }); return Err(LightningError { err: String::from("query_channel_range could not be processed"), @@ -693,7 +792,9 @@ where U::Target: UtxoLookup, L::Target: Logger // exists even if its not yet routable. let mut batches: Vec> = vec![Vec::with_capacity(MAX_SCIDS_PER_REPLY)]; let mut channels = self.network_graph.channels.write().unwrap(); - for (_, ref chan) in channels.range(inclusive_start_scid.unwrap()..exclusive_end_scid.unwrap()) { + for (_, ref chan) in + channels.range(inclusive_start_scid.unwrap()..exclusive_end_scid.unwrap()) + { if let Some(chan_announcement) = &chan.announcement_message { // Construct a new batch if last one is full if batches.last().unwrap().len() == batches.last().unwrap().capacity() { @@ -731,7 +832,7 @@ where U::Target: UtxoLookup, L::Target: Logger // // Overflow safe since end_blocknum=msg.first_block_num+msg.number_of_blocks and // first_blocknum will be either msg.first_blocknum or a higher block height. - let (sync_complete, number_of_blocks) = if batch_index == batch_count-1 { + let (sync_complete, number_of_blocks) = if batch_index == batch_count - 1 { (true, msg.end_blocknum() - first_blocknum) } // Prior replies should use the number of blocks that fit into the reply. Overflow @@ -750,14 +851,16 @@ where U::Target: UtxoLookup, L::Target: Logger number_of_blocks, sync_complete, short_channel_ids: batch, - } + }, }); } Ok(()) } - fn handle_query_short_channel_ids(&self, _their_node_id: PublicKey, _msg: QueryShortChannelIds) -> Result<(), LightningError> { + fn handle_query_short_channel_ids( + &self, _their_node_id: PublicKey, _msg: QueryShortChannelIds, + ) -> Result<(), LightningError> { // TODO Err(LightningError { err: String::from("Not implemented"), @@ -782,7 +885,8 @@ where U::Target: UtxoLookup, L::Target: Logger } } -impl>, U: Deref, L: Deref> MessageSendEventsProvider for P2PGossipSync +impl>, U: Deref, L: Deref> MessageSendEventsProvider + for P2PGossipSync where U::Target: UtxoLookup, L::Target: Logger, @@ -830,7 +934,15 @@ pub struct ChannelUpdateInfo { impl fmt::Display for ChannelUpdateInfo { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "last_update {}, enabled {}, cltv_expiry_delta {}, htlc_minimum_msat {}, fees {:?}", self.last_update, self.enabled, self.cltv_expiry_delta, self.htlc_minimum_msat, self.fees)?; + write!( + f, + "last_update {}, enabled {}, cltv_expiry_delta {}, htlc_minimum_msat {}, fees {:?}", + self.last_update, + self.enabled, + self.cltv_expiry_delta, + self.htlc_minimum_msat, + self.fees + )?; Ok(()) } } @@ -941,14 +1053,14 @@ pub struct ChannelInfo { impl PartialEq for ChannelInfo { fn eq(&self, o: &ChannelInfo) -> bool { - self.features == o.features && - self.node_one == o.node_one && - self.one_to_two == o.one_to_two && - self.node_two == o.node_two && - self.two_to_one == o.two_to_one && - self.capacity_sats == o.capacity_sats && - self.announcement_message == o.announcement_message && - self.announcement_received_time == o.announcement_received_time + self.features == o.features + && self.node_one == o.node_one + && self.one_to_two == o.one_to_two + && self.node_two == o.node_two + && self.two_to_one == o.two_to_one + && self.capacity_sats == o.capacity_sats + && self.announcement_message == o.announcement_message + && self.announcement_received_time == o.announcement_received_time } } @@ -956,7 +1068,9 @@ 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)> { - if self.one_to_two.is_none() || self.two_to_one.is_none() { return None; } + if self.one_to_two.is_none() || self.two_to_one.is_none() { + return None; + } let (direction, source, outbound) = { if target == &self.node_one { (self.two_to_one.as_ref(), &self.node_two, false) @@ -973,7 +1087,9 @@ impl ChannelInfo { /// 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)> { - if self.one_to_two.is_none() || self.two_to_one.is_none() { return None; } + if self.one_to_two.is_none() || self.two_to_one.is_none() { + return None; + } let (direction, target, outbound) = { if source == &self.node_one { (self.one_to_two.as_ref(), &self.node_two, true) @@ -1000,8 +1116,15 @@ impl ChannelInfo { impl fmt::Display for ChannelInfo { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "features: {}, node_one: {}, one_to_two: {:?}, node_two: {}, two_to_one: {:?}", - log_bytes!(self.features.encode()), &self.node_one, self.one_to_two, &self.node_two, self.two_to_one)?; + write!( + f, + "features: {}, node_one: {}, one_to_two: {:?}, node_two: {}, two_to_one: {:?}", + log_bytes!(self.features.encode()), + &self.node_one, + self.one_to_two, + &self.node_two, + self.two_to_one + )?; Ok(()) } } @@ -1069,7 +1192,10 @@ impl Readable for ChannelInfo { two_to_one: two_to_one_wrap.map(|w| w.0).unwrap_or(None), 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)), + announcement_received_time: _init_tlv_based_struct_field!( + announcement_received_time, + (default_value, 0) + ), node_one_counter: u32::max_value(), node_two_counter: u32::max_value(), }) @@ -1091,7 +1217,9 @@ pub struct DirectedChannelInfo<'a> { impl<'a> DirectedChannelInfo<'a> { #[inline] - fn new(channel: &'a ChannelInfo, direction: &'a ChannelUpdateInfo, from_node_one: bool) -> Self { + fn new( + channel: &'a ChannelInfo, direction: &'a ChannelUpdateInfo, from_node_one: bool, + ) -> Self { let (source_counter, target_counter) = if from_node_one { (channel.node_one_counter, channel.node_two_counter) } else { @@ -1102,7 +1230,9 @@ impl<'a> DirectedChannelInfo<'a> { /// Returns information for the channel. #[inline] - pub fn channel(&self) -> &'a ChannelInfo { self.channel } + pub fn channel(&self) -> &'a ChannelInfo { + self.channel + } /// Returns the [`EffectiveCapacity`] of the channel in the direction. /// @@ -1125,34 +1255,50 @@ impl<'a> DirectedChannelInfo<'a> { /// Returns information for the direction. #[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. #[inline] - pub fn source(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_one } else { &self.channel.node_two } } + pub fn source(&self) -> &'a NodeId { + if self.from_node_one { + &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. #[inline] - pub fn target(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_two } else { &self.channel.node_one } } + pub fn target(&self) -> &'a NodeId { + if self.from_node_one { + &self.channel.node_two + } else { + &self.channel.node_one + } + } /// Returns the source node's counter #[inline(always)] - pub(super) fn source_counter(&self) -> u32 { self.source_counter } + pub(super) fn source_counter(&self) -> u32 { + self.source_counter + } /// Returns the target node's counter #[inline(always)] - pub(super) fn target_counter(&self) -> u32 { self.target_counter } + pub(super) fn target_counter(&self) -> u32 { + self.target_counter + } } impl<'a> fmt::Debug for DirectedChannelInfo<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.debug_struct("DirectedChannelInfo") - .field("channel", &self.channel) - .finish() + f.debug_struct("DirectedChannelInfo").field("channel", &self.channel).finish() } } @@ -1179,7 +1325,7 @@ pub enum EffectiveCapacity { /// The funding amount denominated in millisatoshi. capacity_msat: u64, /// The maximum HTLC amount denominated in millisatoshi. - htlc_maximum_msat: u64 + htlc_maximum_msat: u64, }, /// A capacity sufficient to route any payment, typically used for private channels provided by /// an invoice. @@ -1252,16 +1398,11 @@ pub enum NodeAnnouncementInfo { } impl NodeAnnouncementInfo { - /// Protocol features the node announced support for pub fn features(&self) -> &NodeFeatures { match self { - NodeAnnouncementInfo::Relayed(relayed) => { - &relayed.contents.features - } - NodeAnnouncementInfo::Local(local) => { - &local.features - } + NodeAnnouncementInfo::Relayed(relayed) => &relayed.contents.features, + NodeAnnouncementInfo::Local(local) => &local.features, } } @@ -1270,24 +1411,16 @@ impl NodeAnnouncementInfo { /// Value may or may not be a timestamp, depending on the policy of the origin node. pub fn last_update(&self) -> u32 { match self { - NodeAnnouncementInfo::Relayed(relayed) => { - relayed.contents.timestamp - } - NodeAnnouncementInfo::Local(local) => { - local.last_update - } + NodeAnnouncementInfo::Relayed(relayed) => relayed.contents.timestamp, + NodeAnnouncementInfo::Local(local) => local.last_update, } } /// Color assigned to the node pub fn rgb(&self) -> [u8; 3] { match self { - NodeAnnouncementInfo::Relayed(relayed) => { - relayed.contents.rgb - } - NodeAnnouncementInfo::Local(local) => { - local.rgb - } + NodeAnnouncementInfo::Relayed(relayed) => relayed.contents.rgb, + NodeAnnouncementInfo::Local(local) => local.rgb, } } @@ -1296,24 +1429,16 @@ impl NodeAnnouncementInfo { /// May be invalid or malicious (eg control chars), should not be exposed to the user. pub fn alias(&self) -> &NodeAlias { match self { - NodeAnnouncementInfo::Relayed(relayed) => { - &relayed.contents.alias - } - NodeAnnouncementInfo::Local(local) => { - &local.alias - } + NodeAnnouncementInfo::Relayed(relayed) => &relayed.contents.alias, + NodeAnnouncementInfo::Local(local) => &local.alias, } } /// Internet-level addresses via which one can connect to the node pub fn addresses(&self) -> &[SocketAddress] { match self { - NodeAnnouncementInfo::Relayed(relayed) => { - &relayed.contents.addresses - } - NodeAnnouncementInfo::Local(local) => { - &local.addresses - } + NodeAnnouncementInfo::Relayed(relayed) => &relayed.contents.addresses, + NodeAnnouncementInfo::Local(local) => &local.addresses, } } @@ -1322,12 +1447,8 @@ impl NodeAnnouncementInfo { /// Not stored if contains excess data to prevent DoS. pub fn announcement_message(&self) -> Option<&NodeAnnouncement> { match self { - NodeAnnouncementInfo::Relayed(announcement) => { - Some(announcement) - } - NodeAnnouncementInfo::Local(_) => { - None - } + NodeAnnouncementInfo::Relayed(announcement) => Some(announcement), + NodeAnnouncementInfo::Local(_) => None, } } } @@ -1453,8 +1574,12 @@ impl NodeInfo { impl fmt::Display for NodeInfo { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, " channels: {:?}, announcement_info: {:?}", - &self.channels[..], self.announcement_info)?; + write!( + f, + " channels: {:?}, announcement_info: {:?}", + &self.channels[..], + self.announcement_info + )?; Ok(()) } } @@ -1482,7 +1607,7 @@ impl MaybeReadable for NodeAnnouncementInfoDeserWrapper { Ok(node_announcement_info) => return Ok(Some(Self(node_announcement_info))), Err(_) => { copy(reader, &mut sink()).unwrap(); - return Ok(None) + return Ok(None); }, }; } @@ -1501,7 +1626,8 @@ impl Readable for NodeInfo { (4, channels, required_vec), }); let _: Option = _lowest_inbound_channel_fees; - let announcement_info_wrap: Option = announcement_info_wrap; + let announcement_info_wrap: Option = + announcement_info_wrap; Ok(NodeInfo { announcement_info: announcement_info_wrap.map(|w| w.0), @@ -1514,7 +1640,10 @@ impl Readable for NodeInfo { const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; -impl Writeable for NetworkGraph where L::Target: Logger { +impl Writeable for NetworkGraph +where + L::Target: Logger, +{ fn write(&self, writer: &mut W) -> Result<(), io::Error> { self.test_node_counter_consistency(); @@ -1542,7 +1671,10 @@ impl Writeable for NetworkGraph where L::Target: Logger { } } -impl ReadableArgs for NetworkGraph where L::Target: Logger { +impl ReadableArgs for NetworkGraph +where + L::Target: Logger, +{ fn read(reader: &mut R, logger: L) -> Result, DecodeError> { let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); @@ -1558,7 +1690,9 @@ impl ReadableArgs for NetworkGraph where L::Target: Logger { let nodes_count: u64 = Readable::read(reader)?; // There shouldn't be anywhere near `u32::MAX` nodes, and we need some headroom to insert // new nodes during sync, so reject any graphs claiming more than `u32::MAX / 2` nodes. - if nodes_count > u32::max_value() as u64 / 2 { return Err(DecodeError::InvalidValue); } + if nodes_count > u32::max_value() as u64 / 2 { + return Err(DecodeError::InvalidValue); + } // In Nov, 2023 there were about 69K channels; we cap allocations to 1.5x that. let mut nodes = IndexedMap::with_capacity(cmp::min(nodes_count as usize, 103500)); for i in 0..nodes_count { @@ -1596,7 +1730,10 @@ impl ReadableArgs for NetworkGraph where L::Target: Logger { } } -impl fmt::Display for NetworkGraph where L::Target: Logger { +impl fmt::Display for NetworkGraph +where + L::Target: Logger, +{ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { writeln!(f, "Network map\n[Channels]")?; for (key, val) in self.channels.read().unwrap().unordered_iter() { @@ -1611,20 +1748,32 @@ impl fmt::Display for NetworkGraph where L::Target: Logger { } impl Eq for NetworkGraph where L::Target: Logger {} -impl PartialEq for NetworkGraph where L::Target: Logger { +impl PartialEq for NetworkGraph +where + L::Target: Logger, +{ fn eq(&self, other: &Self) -> bool { // For a total lockorder, sort by position in memory and take the inner locks in that order. // (Assumes that we can't move within memory while a lock is held). let ord = ((self as *const _) as usize) < ((other as *const _) as usize); let a = if ord { (&self.channels, &self.nodes) } else { (&other.channels, &other.nodes) }; let b = if ord { (&other.channels, &other.nodes) } else { (&self.channels, &self.nodes) }; - let (channels_a, channels_b) = (a.0.unsafe_well_ordered_double_lock_self(), b.0.unsafe_well_ordered_double_lock_self()); - let (nodes_a, nodes_b) = (a.1.unsafe_well_ordered_double_lock_self(), b.1.unsafe_well_ordered_double_lock_self()); + let (channels_a, channels_b) = ( + a.0.unsafe_well_ordered_double_lock_self(), + b.0.unsafe_well_ordered_double_lock_self(), + ); + let (nodes_a, nodes_b) = ( + a.1.unsafe_well_ordered_double_lock_self(), + b.1.unsafe_well_ordered_double_lock_self(), + ); self.chain_hash.eq(&other.chain_hash) && channels_a.eq(&channels_b) && nodes_a.eq(&nodes_b) } } -impl NetworkGraph where L::Target: Logger { +impl NetworkGraph +where + L::Target: Logger, +{ /// Creates a new, empty, network graph. pub fn new(network: Network, logger: L) -> NetworkGraph { Self { @@ -1643,7 +1792,8 @@ impl NetworkGraph where L::Target: Logger { } fn test_node_counter_consistency(&self) { - #[cfg(debug_assertions)] { + #[cfg(debug_assertions)] + { let channels = self.channels.read().unwrap(); let nodes = self.nodes.read().unwrap(); let removed_node_counters = self.removed_node_counters.lock().unwrap(); @@ -1688,7 +1838,8 @@ impl NetworkGraph where L::Target: Logger { ReadOnlyNetworkGraph { channels, nodes, - max_node_counter: (self.next_node_counter.load(Ordering::Acquire) as u32).saturating_sub(1), + max_node_counter: (self.next_node_counter.load(Ordering::Acquire) as u32) + .saturating_sub(1), } } @@ -1701,7 +1852,10 @@ impl NetworkGraph where L::Target: Logger { /// Update the unix timestamp provided by the most recent rapid gossip sync. /// This should be done automatically by the rapid sync process after every sync completion. pub fn set_last_rapid_gossip_sync_timestamp(&self, last_rapid_gossip_sync_timestamp: u32) { - self.last_rapid_gossip_sync_timestamp.lock().unwrap().replace(last_rapid_gossip_sync_timestamp); + self.last_rapid_gossip_sync_timestamp + .lock() + .unwrap() + .replace(last_rapid_gossip_sync_timestamp); } /// Clears the `NodeAnnouncementInfo` field for all nodes in the `NetworkGraph` for testing @@ -1719,13 +1873,18 @@ impl NetworkGraph where L::Target: Logger { /// You probably don't want to call this directly, instead relying on a P2PGossipSync's /// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept /// routing messages from a source using a protocol other than the lightning P2P protocol. - pub fn update_node_from_announcement(&self, msg: &msgs::NodeAnnouncement) -> Result<(), LightningError> { + pub fn update_node_from_announcement( + &self, msg: &msgs::NodeAnnouncement, + ) -> Result<(), LightningError> { // First check if we have the announcement already to avoid the CPU cost of validating a // redundant announcement. if let Some(node) = self.nodes.read().unwrap().get(&msg.contents.node_id) { if let Some(node_info) = node.announcement_info.as_ref() { - if node_info.last_update() == msg.contents.timestamp { - return Err(LightningError{err: "Update had the same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); + if node_info.last_update() == msg.contents.timestamp { + return Err(LightningError { + err: "Update had the same timestamp as last processed update".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); } } } @@ -1737,49 +1896,64 @@ impl NetworkGraph where L::Target: Logger { /// given node announcement without verifying the associated signatures. Because we aren't /// given the associated signatures here we cannot relay the node announcement to any of our /// peers. - pub fn update_node_from_unsigned_announcement(&self, msg: &msgs::UnsignedNodeAnnouncement) -> Result<(), LightningError> { + pub fn update_node_from_unsigned_announcement( + &self, msg: &msgs::UnsignedNodeAnnouncement, + ) -> Result<(), LightningError> { self.update_node_from_announcement_intern(msg, None) } - fn update_node_from_announcement_intern(&self, msg: &msgs::UnsignedNodeAnnouncement, full_msg: Option<&msgs::NodeAnnouncement>) -> Result<(), LightningError> { + fn update_node_from_announcement_intern( + &self, msg: &msgs::UnsignedNodeAnnouncement, full_msg: Option<&msgs::NodeAnnouncement>, + ) -> Result<(), LightningError> { let mut nodes = self.nodes.write().unwrap(); match nodes.get_mut(&msg.node_id) { None => { core::mem::drop(nodes); self.pending_checks.check_hold_pending_node_announcement(msg, full_msg)?; - Err(LightningError{err: "No existing channels for node_announcement".to_owned(), action: ErrorAction::IgnoreError}) + Err(LightningError { + err: "No existing channels for node_announcement".to_owned(), + action: ErrorAction::IgnoreError, + }) }, Some(node) => { if let Some(node_info) = node.announcement_info.as_ref() { // The timestamp field is somewhat of a misnomer - the BOLTs use it to order // updates to ensure you always have the latest one, only vaguely suggesting // that it be at least the current time. - if node_info.last_update() > msg.timestamp { - return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); - } else if node_info.last_update() == msg.timestamp { - return Err(LightningError{err: "Update had the same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); + if node_info.last_update() > msg.timestamp { + return Err(LightningError { + err: "Update older than last processed update".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); + } else if node_info.last_update() == msg.timestamp { + return Err(LightningError { + err: "Update had the same timestamp as last processed update" + .to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); } } - let should_relay = - msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY && - msg.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY && - msg.excess_data.len() + msg.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY; + let should_relay = msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY + && msg.excess_address_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY + && msg.excess_data.len() + msg.excess_address_data.len() + <= MAX_EXCESS_BYTES_FOR_RELAY; - node.announcement_info = if let (Some(signed_announcement), true) = (full_msg, should_relay) { - Some(NodeAnnouncementInfo::Relayed(signed_announcement.clone())) - } else { - Some(NodeAnnouncementInfo::Local(NodeAnnouncementDetails { - features: msg.features.clone(), - last_update: msg.timestamp, - rgb: msg.rgb, - alias: msg.alias, - addresses: msg.addresses.clone(), - })) - }; + node.announcement_info = + if let (Some(signed_announcement), true) = (full_msg, should_relay) { + Some(NodeAnnouncementInfo::Relayed(signed_announcement.clone())) + } else { + Some(NodeAnnouncementInfo::Local(NodeAnnouncementDetails { + features: msg.features.clone(), + last_update: msg.timestamp, + rgb: msg.rgb, + alias: msg.alias, + addresses: msg.addresses.clone(), + })) + }; Ok(()) - } + }, } } @@ -1810,7 +1984,7 @@ impl NetworkGraph where L::Target: Logger { /// /// This will skip verification of if the channel is actually on-chain. pub fn update_channel_from_announcement_no_lookup( - &self, msg: &ChannelAnnouncement + &self, msg: &ChannelAnnouncement, ) -> Result<(), LightningError> { self.update_channel_from_announcement::<&UtxoResolver>(msg, &None) } @@ -1822,7 +1996,7 @@ impl NetworkGraph where L::Target: Logger { /// If a [`UtxoLookup`] object is provided via `utxo_lookup`, it will be called to verify /// the corresponding UTXO exists on chain and is correctly-formatted. pub fn update_channel_from_unsigned_announcement( - &self, msg: &msgs::UnsignedChannelAnnouncement, utxo_lookup: &Option + &self, msg: &msgs::UnsignedChannelAnnouncement, utxo_lookup: &Option, ) -> Result<(), LightningError> where U::Target: UtxoLookup, @@ -1837,9 +2011,15 @@ impl NetworkGraph where L::Target: Logger { /// rapid gossip sync server) /// /// All other parameters as used in [`msgs::UnsignedChannelAnnouncement`] fields. - pub fn add_channel_from_partial_announcement(&self, short_channel_id: u64, timestamp: u64, features: ChannelFeatures, node_id_1: PublicKey, node_id_2: PublicKey) -> Result<(), LightningError> { + pub fn add_channel_from_partial_announcement( + &self, short_channel_id: u64, timestamp: u64, features: ChannelFeatures, + node_id_1: PublicKey, node_id_2: PublicKey, + ) -> Result<(), LightningError> { if node_id_1 == node_id_2 { - return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Channel announcement node had a channel with itself".to_owned(), + action: ErrorAction::IgnoreError, + }); }; let node_1 = NodeId::from_pubkey(&node_id_1); @@ -1860,14 +2040,22 @@ impl NetworkGraph where L::Target: Logger { self.add_channel_between_nodes(short_channel_id, channel_info, None) } - fn add_channel_between_nodes(&self, short_channel_id: u64, channel_info: ChannelInfo, utxo_value: Option) -> Result<(), LightningError> { + fn add_channel_between_nodes( + &self, short_channel_id: u64, channel_info: ChannelInfo, utxo_value: Option, + ) -> Result<(), LightningError> { let mut channels = self.channels.write().unwrap(); let mut nodes = self.nodes.write().unwrap(); let node_id_a = channel_info.node_one.clone(); let node_id_b = channel_info.node_two.clone(); - log_gossip!(self.logger, "Adding channel {} between nodes {} and {}", short_channel_id, node_id_a, node_id_b); + log_gossip!( + self.logger, + "Adding channel {} between nodes {} and {}", + short_channel_id, + node_id_a, + node_id_b + ); let channel_info = match channels.entry(short_channel_id) { IndexedMapEntry::Occupied(mut entry) => { @@ -1887,17 +2075,18 @@ impl NetworkGraph where L::Target: Logger { *entry.get_mut() = channel_info; entry.into_mut() } else { - return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); + return Err(LightningError { + err: "Already have knowledge of channel".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); } }, - IndexedMapEntry::Vacant(entry) => { - entry.insert(channel_info) - } + IndexedMapEntry::Vacant(entry) => entry.insert(channel_info), }; let mut node_counter_id = [ (&mut channel_info.node_one_counter, node_id_a), - (&mut channel_info.node_two_counter, node_id_b) + (&mut channel_info.node_two_counter, node_id_b), ]; for (chan_info_node_counter, current_node_id) in node_counter_id.iter_mut() { match nodes.entry(current_node_id.clone()) { @@ -1908,16 +2097,17 @@ impl NetworkGraph where L::Target: Logger { }, IndexedMapEntry::Vacant(node_entry) => { let mut removed_node_counters = self.removed_node_counters.lock().unwrap(); - **chan_info_node_counter = removed_node_counters.pop() + **chan_info_node_counter = removed_node_counters + .pop() .unwrap_or(self.next_node_counter.fetch_add(1, Ordering::Relaxed) as u32); node_entry.insert(NodeInfo { - channels: vec!(short_channel_id), + channels: vec![short_channel_id], announcement_info: None, node_counter: **chan_info_node_counter, }); - } + }, }; - }; + } Ok(()) } @@ -1929,7 +2119,10 @@ impl NetworkGraph where L::Target: Logger { /// return an `Ok(())`. fn pre_channel_announcement_validation_check( &self, msg: &msgs::UnsignedChannelAnnouncement, utxo_lookup: &Option, - ) -> Result<(), LightningError> where U::Target: UtxoLookup { + ) -> Result<(), LightningError> + where + U::Target: UtxoLookup, + { let channels = self.channels.read().unwrap(); if let Some(chan) = channels.get(&msg.short_channel_id) { @@ -1948,7 +2141,7 @@ impl NetworkGraph where L::Target: Logger { if msg.node_id_1 == chan.node_one && msg.node_id_2 == chan.node_two { return Err(LightningError { err: "Already have chain-validated channel".to_owned(), - action: ErrorAction::IgnoreDuplicateGossip + action: ErrorAction::IgnoreDuplicateGossip, }); } } else if utxo_lookup.is_none() { @@ -1956,7 +2149,7 @@ impl NetworkGraph where L::Target: Logger { // duplicate announcement without bothering to take the channels write lock. return Err(LightningError { err: "Already have non-chain-validated channel".to_owned(), - action: ErrorAction::IgnoreDuplicateGossip + action: ErrorAction::IgnoreDuplicateGossip, }); } } @@ -1969,13 +2162,17 @@ impl NetworkGraph where L::Target: Logger { /// Generally [`Self::pre_channel_announcement_validation_check`] should have been called /// first. fn update_channel_from_unsigned_announcement_intern( - &self, msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, utxo_lookup: &Option + &self, msg: &msgs::UnsignedChannelAnnouncement, + full_msg: Option<&msgs::ChannelAnnouncement>, utxo_lookup: &Option, ) -> Result<(), LightningError> where U::Target: UtxoLookup, { if msg.node_id_1 == msg.node_id_2 || msg.bitcoin_key_1 == msg.bitcoin_key_2 { - return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Channel announcement node had a channel with itself".to_owned(), + action: ErrorAction::IgnoreError, + }); } if msg.chain_hash != self.chain_hash { @@ -1988,23 +2185,27 @@ impl NetworkGraph where L::Target: Logger { { let removed_channels = self.removed_channels.lock().unwrap(); let removed_nodes = self.removed_nodes.lock().unwrap(); - if removed_channels.contains_key(&msg.short_channel_id) || - removed_nodes.contains_key(&msg.node_id_1) || - removed_nodes.contains_key(&msg.node_id_2) { + if removed_channels.contains_key(&msg.short_channel_id) + || removed_nodes.contains_key(&msg.node_id_1) + || removed_nodes.contains_key(&msg.node_id_2) + { return Err(LightningError{ err: format!("Channel with SCID {} or one of its nodes was removed from our network graph recently", &msg.short_channel_id), action: ErrorAction::IgnoreAndLog(Level::Gossip)}); } } - let utxo_value = self.pending_checks.check_channel_announcement( - utxo_lookup, msg, full_msg)?; + let utxo_value = + self.pending_checks.check_channel_announcement(utxo_lookup, msg, full_msg)?; #[allow(unused_mut, unused_assignments)] let mut announcement_received_time = 0; #[cfg(feature = "std")] { - announcement_received_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + announcement_received_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); } let chan_info = ChannelInfo { @@ -2014,8 +2215,11 @@ impl NetworkGraph where L::Target: Logger { node_two: msg.node_id_2, two_to_one: None, capacity_sats: utxo_value.map(|a| a.to_sat()), - announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY - { full_msg.cloned() } else { None }, + announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY { + full_msg.cloned() + } else { + None + }, announcement_received_time, node_one_counter: u32::max_value(), node_two_counter: u32::max_value(), @@ -2023,7 +2227,12 @@ impl NetworkGraph where L::Target: Logger { self.add_channel_between_nodes(msg.short_channel_id, chan_info, utxo_value)?; - log_gossip!(self.logger, "Added channel_announcement for {}{}", msg.short_channel_id, if !msg.excess_data.is_empty() { " with excess uninterpreted data!" } else { "" }); + log_gossip!( + self.logger, + "Added channel_announcement for {}{}", + msg.short_channel_id, + if !msg.excess_data.is_empty() { " with excess uninterpreted data!" } else { "" } + ); Ok(()) } @@ -2032,7 +2241,9 @@ impl NetworkGraph where L::Target: Logger { /// The channel and any node for which this was their last channel are removed from the graph. pub fn channel_failed_permanent(&self, short_channel_id: u64) { #[cfg(feature = "std")] - let current_time_unix = Some(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs()); + let current_time_unix = Some( + SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(), + ); #[cfg(not(feature = "std"))] let current_time_unix = None; @@ -2042,7 +2253,9 @@ impl NetworkGraph where L::Target: Logger { /// Marks a channel in the graph as failed permanently. /// /// The channel and any node for which this was their last channel are removed from the graph. - fn channel_failed_permanent_with_time(&self, short_channel_id: u64, current_time_unix: Option) { + fn channel_failed_permanent_with_time( + &self, short_channel_id: u64, current_time_unix: Option, + ) { let mut channels = self.channels.write().unwrap(); if let Some(chan) = channels.remove(&short_channel_id) { let mut nodes = self.nodes.write().unwrap(); @@ -2055,7 +2268,9 @@ impl NetworkGraph where L::Target: Logger { /// from local storage. pub fn node_failed_permanent(&self, node_id: &PublicKey) { #[cfg(feature = "std")] - let current_time_unix = Some(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs()); + let current_time_unix = Some( + SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(), + ); #[cfg(not(feature = "std"))] let current_time_unix = None; @@ -2069,11 +2284,15 @@ impl NetworkGraph where L::Target: Logger { let mut removed_node_counters = self.removed_node_counters.lock().unwrap(); for scid in node.channels.iter() { if let Some(chan_info) = channels.remove(scid) { - let other_node_id = if node_id == chan_info.node_one { chan_info.node_two } else { chan_info.node_one }; - if let IndexedMapEntry::Occupied(mut other_node_entry) = nodes.entry(other_node_id) { - other_node_entry.get_mut().channels.retain(|chan_id| { - *scid != *chan_id - }); + let other_node_id = if node_id == chan_info.node_one { + chan_info.node_two + } else { + chan_info.node_one + }; + if let IndexedMapEntry::Occupied(mut other_node_entry) = + nodes.entry(other_node_id) + { + other_node_entry.get_mut().channels.retain(|chan_id| *scid != *chan_id); if other_node_entry.get().channels.is_empty() { removed_node_counters.push(other_node_entry.get().node_counter); other_node_entry.remove_entry(); @@ -2107,7 +2326,8 @@ impl NetworkGraph where L::Target: Logger { /// This method is only available with the `std` feature. See /// [`NetworkGraph::remove_stale_channels_and_tracking_with_time`] for non-`std` use. pub fn remove_stale_channels_and_tracking(&self) { - let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + let time = + SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); self.remove_stale_channels_and_tracking_with_time(time); } @@ -2122,24 +2342,38 @@ impl NetworkGraph where L::Target: Logger { /// This method will also cause us to stop tracking removed nodes and channels if they have been /// in the map for a while so that these can be resynced from gossip in the future. #[cfg_attr(feature = "std", doc = "")] - #[cfg_attr(feature = "std", doc = "This function takes the current unix time as an argument. For users with the `std` feature")] - #[cfg_attr(feature = "std", doc = "enabled, [`NetworkGraph::remove_stale_channels_and_tracking`] may be preferable.")] + #[cfg_attr( + feature = "std", + doc = "This function takes the current unix time as an argument. For users with the `std` feature" + )] + #[cfg_attr( + feature = "std", + doc = "enabled, [`NetworkGraph::remove_stale_channels_and_tracking`] may be preferable." + )] pub fn remove_stale_channels_and_tracking_with_time(&self, current_time_unix: u64) { let mut channels = self.channels.write().unwrap(); // Time out if we haven't received an update in at least 14 days. - if current_time_unix > u32::max_value() as u64 { return; } // Remove by 2106 - if current_time_unix < STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS { return; } + if current_time_unix > u32::max_value() as u64 { + return; + } // Remove by 2106 + if current_time_unix < STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS { + return; + } let min_time_unix: u32 = (current_time_unix - STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS) as u32; // Sadly BTreeMap::retain was only stabilized in 1.53 so we can't switch to it for some // time. let mut scids_to_remove = Vec::new(); for (scid, info) in channels.unordered_iter_mut() { - if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix { + if info.one_to_two.is_some() + && info.one_to_two.as_ref().unwrap().last_update < min_time_unix + { log_gossip!(self.logger, "Removing directional update one_to_two (0) for channel {} due to its timestamp {} being below {}", scid, info.one_to_two.as_ref().unwrap().last_update, min_time_unix); info.one_to_two = None; } - if info.two_to_one.is_some() && info.two_to_one.as_ref().unwrap().last_update < min_time_unix { + if info.two_to_one.is_some() + && info.two_to_one.as_ref().unwrap().last_update < min_time_unix + { log_gossip!(self.logger, "Removing directional update two_to_one (1) for channel {} due to its timestamp {} being below {}", scid, info.two_to_one.as_ref().unwrap().last_update, min_time_unix); info.two_to_one = None; @@ -2159,7 +2393,9 @@ impl NetworkGraph where L::Target: Logger { if !scids_to_remove.is_empty() { 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"); + 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.removed_channels.lock().unwrap().insert(scid, Some(current_time_unix)); } @@ -2180,7 +2416,8 @@ impl NetworkGraph where L::Target: Logger { } #[allow(unreachable_code)] false - }}; + } + }; self.removed_channels.lock().unwrap().retain(|_, time| should_keep_tracking(time)); self.removed_nodes.lock().unwrap().retain(|_, time| should_keep_tracking(time)); @@ -2205,7 +2442,9 @@ impl NetworkGraph where L::Target: Logger { /// /// If not built with `std`, any updates with a timestamp more than two weeks in the past or /// materially in the future will be rejected. - pub fn update_channel_unsigned(&self, msg: &msgs::UnsignedChannelUpdate) -> Result<(), LightningError> { + pub fn update_channel_unsigned( + &self, msg: &msgs::UnsignedChannelUpdate, + ) -> Result<(), LightningError> { self.update_channel_internal(msg, None, None, false) } @@ -2219,10 +2458,10 @@ impl NetworkGraph where L::Target: Logger { self.update_channel_internal(&msg.contents, Some(&msg), Some(&msg.signature), true) } - fn update_channel_internal(&self, msg: &msgs::UnsignedChannelUpdate, - full_msg: Option<&msgs::ChannelUpdate>, sig: Option<&secp256k1::ecdsa::Signature>, - only_verify: bool) -> Result<(), LightningError> - { + fn update_channel_internal( + &self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, + sig: Option<&secp256k1::ecdsa::Signature>, only_verify: bool, + ) -> Result<(), LightningError> { let chan_enabled = msg.channel_flags & (1 << 1) != (1 << 1); if msg.chain_hash != self.chain_hash { @@ -2236,12 +2475,21 @@ impl NetworkGraph where L::Target: Logger { { // Note that many tests rely on being able to set arbitrarily old timestamps, thus we // disable this check during tests! - let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + let time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); if (msg.timestamp as u64) < time - STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS { - return Err(LightningError{err: "channel_update is older than two weeks old".to_owned(), action: ErrorAction::IgnoreAndLog(Level::Gossip)}); + return Err(LightningError { + err: "channel_update is older than two weeks old".to_owned(), + action: ErrorAction::IgnoreAndLog(Level::Gossip), + }); } if msg.timestamp as u64 > time + 60 * 60 * 24 { - return Err(LightningError{err: "channel_update has a timestamp more than a day in the future".to_owned(), action: ErrorAction::IgnoreAndLog(Level::Gossip)}); + return Err(LightningError { + err: "channel_update has a timestamp more than a day in the future".to_owned(), + action: ErrorAction::IgnoreAndLog(Level::Gossip), + }); } } @@ -2254,45 +2502,56 @@ impl NetworkGraph where L::Target: Logger { ); if msg.htlc_maximum_msat > MAX_VALUE_MSAT { - return Err(LightningError{err: - "htlc_maximum_msat is larger than maximum possible msats".to_owned(), - action: ErrorAction::IgnoreError}); - } - - let check_update_latest = |target: &Option| -> Result<(), LightningError> { - if let Some(existing_chan_info) = target { - // The timestamp field is somewhat of a misnomer - the BOLTs use it to - // order updates to ensure you always have the latest one, only - // suggesting that it be at least the current time. For - // channel_updates specifically, the BOLTs discuss the possibility of - // pruning based on the timestamp field being more than two weeks old, - // but only in the non-normative section. - if existing_chan_info.last_update > msg.timestamp { - return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); - } else if existing_chan_info.last_update == msg.timestamp { - return Err(LightningError{err: "Update had same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); + return Err(LightningError { + err: "htlc_maximum_msat is larger than maximum possible msats".to_owned(), + action: ErrorAction::IgnoreError, + }); + } + + let check_update_latest = + |target: &Option| -> Result<(), LightningError> { + if let Some(existing_chan_info) = target { + // The timestamp field is somewhat of a misnomer - the BOLTs use it to + // order updates to ensure you always have the latest one, only + // suggesting that it be at least the current time. For + // channel_updates specifically, the BOLTs discuss the possibility of + // pruning based on the timestamp field being more than two weeks old, + // but only in the non-normative section. + if existing_chan_info.last_update > msg.timestamp { + return Err(LightningError { + err: "Update older than last processed update".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); + } else if existing_chan_info.last_update == msg.timestamp { + return Err(LightningError { + err: "Update had same timestamp as last processed update".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip, + }); + } } - } - Ok(()) - }; + Ok(()) + }; - let check_msg_sanity = |channel: &ChannelInfo| -> Result<(), LightningError> { - if let Some(capacity_sats) = channel.capacity_sats { - // It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None). - // Don't query UTXO set here to reduce DoS risks. - if capacity_sats > MAX_VALUE_MSAT / 1000 || msg.htlc_maximum_msat > capacity_sats * 1000 { - return Err(LightningError{err: + let check_msg_sanity = + |channel: &ChannelInfo| -> Result<(), LightningError> { + if let Some(capacity_sats) = channel.capacity_sats { + // It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None). + // Don't query UTXO set here to reduce DoS risks. + if capacity_sats > MAX_VALUE_MSAT / 1000 + || msg.htlc_maximum_msat > capacity_sats * 1000 + { + return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity or capacity is bogus".to_owned(), action: ErrorAction::IgnoreError}); + } } - } - if msg.channel_flags & 1 == 1 { - check_update_latest(&channel.two_to_one) - } else { - check_update_latest(&channel.one_to_two) - } - }; + if msg.channel_flags & 1 == 1 { + check_update_latest(&channel.two_to_one) + } else { + check_update_latest(&channel.one_to_two) + } + }; let node_pubkey; { @@ -2313,11 +2572,10 @@ impl NetworkGraph where L::Target: Logger { } else { channel.node_one.as_slice() }; - node_pubkey = PublicKey::from_slice(node_id) - .map_err(|_| LightningError{ - err: "Couldn't parse source node pubkey".to_owned(), - action: ErrorAction::IgnoreAndLog(Level::Debug) - })?; + node_pubkey = PublicKey::from_slice(node_id).map_err(|_| LightningError { + err: "Couldn't parse source node pubkey".to_owned(), + action: ErrorAction::IgnoreAndLog(Level::Debug), + })?; }, } } @@ -2327,14 +2585,19 @@ impl NetworkGraph where L::Target: Logger { secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &node_pubkey, "channel_update"); } - if only_verify { return Ok(()); } + if only_verify { + return Ok(()); + } let mut channels = self.channels.write().unwrap(); if let Some(channel) = channels.get_mut(&msg.short_channel_id) { check_msg_sanity(channel)?; - let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY - { full_msg.cloned() } else { None }; + let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY { + full_msg.cloned() + } else { + None + }; let new_channel_info = Some(ChannelUpdateInfo { enabled: chan_enabled, @@ -2346,7 +2609,7 @@ impl NetworkGraph where L::Target: Logger { base_msat: msg.fee_base_msat, proportional_millionths: msg.fee_proportional_millionths, }, - last_update_message + last_update_message, }); if msg.channel_flags & 1 == 1 { @@ -2359,21 +2622,23 @@ impl NetworkGraph where L::Target: Logger { Ok(()) } - fn remove_channel_in_nodes(&self, nodes: &mut IndexedMap, chan: &ChannelInfo, short_channel_id: u64) { + fn remove_channel_in_nodes( + &self, nodes: &mut IndexedMap, chan: &ChannelInfo, short_channel_id: u64, + ) { macro_rules! remove_from_node { ($node_id: expr) => { if let IndexedMapEntry::Occupied(mut entry) = nodes.entry($node_id) { - entry.get_mut().channels.retain(|chan_id| { - short_channel_id != *chan_id - }); + entry.get_mut().channels.retain(|chan_id| short_channel_id != *chan_id); if entry.get().channels.is_empty() { self.removed_node_counters.lock().unwrap().push(entry.get().node_counter); entry.remove_entry(); } } else { - panic!("Had channel that pointed to unknown node (ie inconsistent network map)!"); + panic!( + "Had channel that pointed to unknown node (ie inconsistent network map)!" + ); } - } + }; } remove_from_node!(chan.node_one); @@ -2422,7 +2687,8 @@ impl ReadOnlyNetworkGraph<'_> { /// Returns None if the requested node is completely unknown, /// or if node announcement for the node was never received. pub fn get_addresses(&self, pubkey: &PublicKey) -> Option> { - self.nodes.get(&NodeId::from_pubkey(&pubkey)) + self.nodes + .get(&NodeId::from_pubkey(&pubkey)) .and_then(|node| node.announcement_info.as_ref().map(|ann| ann.addresses().to_vec())) } @@ -2435,48 +2701,61 @@ impl ReadOnlyNetworkGraph<'_> { #[cfg(test)] pub(crate) mod tests { use crate::events::{MessageSendEvent, MessageSendEventsProvider}; - use crate::ln::channelmanager; use crate::ln::chan_utils::make_funding_redeemscript; + use crate::ln::channelmanager; #[cfg(feature = "std")] use crate::ln::features::InitFeatures; use crate::ln::msgs::SocketAddress; - use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY, NodeId, RoutingFees, ChannelUpdateInfo, ChannelInfo, NodeAnnouncementInfo, NodeInfo}; + use crate::ln::msgs::{ + ChannelAnnouncement, ChannelUpdate, NodeAnnouncement, QueryChannelRange, + QueryShortChannelIds, ReplyChannelRange, RoutingMessageHandler, + UnsignedChannelAnnouncement, UnsignedChannelUpdate, UnsignedNodeAnnouncement, + MAX_VALUE_MSAT, + }; + use crate::routing::gossip::{ + ChannelInfo, ChannelUpdateInfo, NetworkGraph, NetworkUpdate, NodeAlias, + NodeAnnouncementInfo, NodeId, NodeInfo, P2PGossipSync, RoutingFees, + MAX_EXCESS_BYTES_FOR_RELAY, + }; use crate::routing::utxo::{UtxoLookupError, UtxoResult}; - use crate::ln::msgs::{RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, - UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, - ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT}; use crate::util::config::UserConfig; - use crate::util::test_utils; - use crate::util::ser::{Hostname, ReadableArgs, Readable, Writeable}; use crate::util::scid_utils::scid_from_parts; + use crate::util::ser::{Hostname, Readable, ReadableArgs, Writeable}; + use crate::util::test_utils; - use crate::routing::gossip::REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS; use super::STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS; + use crate::routing::gossip::REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS; + use bitcoin::amount::Amount; + use bitcoin::constants::ChainHash; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash; use bitcoin::hex::FromHex; use bitcoin::network::Network; - use bitcoin::amount::Amount; - use bitcoin::constants::ChainHash; use bitcoin::script::ScriptBuf; - use bitcoin::transaction::TxOut; - use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::secp256k1::{All, Secp256k1}; + use bitcoin::secp256k1::{PublicKey, SecretKey}; + use bitcoin::transaction::TxOut; use crate::io; - use bitcoin::secp256k1; use crate::prelude::*; use crate::sync::Arc; + use bitcoin::secp256k1; fn create_network_graph() -> NetworkGraph> { let logger = Arc::new(test_utils::TestLogger::new()); NetworkGraph::new(Network::Testnet, logger) } - fn create_gossip_sync(network_graph: &NetworkGraph>) -> ( - Secp256k1, P2PGossipSync<&NetworkGraph>, - Arc, Arc> + fn create_gossip_sync( + network_graph: &NetworkGraph>, + ) -> ( + Secp256k1, + P2PGossipSync< + &NetworkGraph>, + Arc, + Arc, + >, ) { let secp_ctx = Secp256k1::new(); let logger = Arc::new(test_utils::TestLogger::new()); @@ -2497,7 +2776,9 @@ pub(crate) mod tests { assert!(!gossip_sync.should_request_full_sync()); } - pub(crate) fn get_signed_node_announcement(f: F, node_key: &SecretKey, secp_ctx: &Secp256k1) -> NodeAnnouncement { + pub(crate) fn get_signed_node_announcement( + f: F, node_key: &SecretKey, secp_ctx: &Secp256k1, + ) -> NodeAnnouncement { let node_id = NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_key)); let mut unsigned_announcement = UnsignedNodeAnnouncement { features: channelmanager::provided_node_features(&UserConfig::default()), @@ -2513,11 +2794,13 @@ pub(crate) mod tests { let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); NodeAnnouncement { signature: secp_ctx.sign_ecdsa(&msghash, node_key), - contents: unsigned_announcement + contents: unsigned_announcement, } } - pub(crate) fn get_signed_channel_announcement(f: F, node_1_key: &SecretKey, node_2_key: &SecretKey, secp_ctx: &Secp256k1) -> ChannelAnnouncement { + pub(crate) fn get_signed_channel_announcement( + f: F, node_1_key: &SecretKey, node_2_key: &SecretKey, secp_ctx: &Secp256k1, + ) -> ChannelAnnouncement { let node_id_1 = PublicKey::from_secret_key(&secp_ctx, node_1_key); let node_id_2 = PublicKey::from_secret_key(&secp_ctx, node_2_key); let node_1_btckey = &SecretKey::from_slice(&[40; 32]).unwrap(); @@ -2529,8 +2812,14 @@ pub(crate) mod tests { short_channel_id: 0, node_id_1: NodeId::from_pubkey(&node_id_1), node_id_2: NodeId::from_pubkey(&node_id_2), - bitcoin_key_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_1_btckey)), - bitcoin_key_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_2_btckey)), + bitcoin_key_1: NodeId::from_pubkey(&PublicKey::from_secret_key( + &secp_ctx, + node_1_btckey, + )), + bitcoin_key_2: NodeId::from_pubkey(&PublicKey::from_secret_key( + &secp_ctx, + node_2_btckey, + )), excess_data: Vec::new(), }; f(&mut unsigned_announcement); @@ -2547,11 +2836,16 @@ pub(crate) mod tests { pub(crate) fn get_channel_script(secp_ctx: &Secp256k1) -> ScriptBuf { let node_1_btckey = SecretKey::from_slice(&[40; 32]).unwrap(); let node_2_btckey = SecretKey::from_slice(&[39; 32]).unwrap(); - make_funding_redeemscript(&PublicKey::from_secret_key(secp_ctx, &node_1_btckey), - &PublicKey::from_secret_key(secp_ctx, &node_2_btckey)).to_p2wsh() + make_funding_redeemscript( + &PublicKey::from_secret_key(secp_ctx, &node_1_btckey), + &PublicKey::from_secret_key(secp_ctx, &node_2_btckey), + ) + .to_p2wsh() } - pub(crate) fn get_signed_channel_update(f: F, node_key: &SecretKey, secp_ctx: &Secp256k1) -> ChannelUpdate { + pub(crate) fn get_signed_channel_update( + f: F, node_key: &SecretKey, secp_ctx: &Secp256k1, + ) -> ChannelUpdate { let mut unsigned_channel_update = UnsignedChannelUpdate { chain_hash: ChainHash::using_genesis_block(Network::Testnet), short_channel_id: 0, @@ -2563,13 +2857,14 @@ pub(crate) mod tests { htlc_maximum_msat: 1_000_000, fee_base_msat: 10_000, fee_proportional_millionths: 20, - excess_data: Vec::new() + excess_data: Vec::new(), }; f(&mut unsigned_channel_update); - let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); + let msghash = + hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); ChannelUpdate { signature: secp_ctx.sign_ecdsa(&msghash, node_key), - contents: unsigned_channel_update + contents: unsigned_channel_update, } } @@ -2586,15 +2881,17 @@ pub(crate) mod tests { let valid_announcement = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!("No existing channels for node_announcement", e.err) + Err(e) => assert_eq!("No existing channels for node_announcement", e.err), }; { // Announce a channel to add a corresponding node. - let valid_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) + { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), }; } @@ -2603,35 +2900,44 @@ pub(crate) mod tests { Some(node_1_pubkey), &NodeAnnouncement { signature: secp_ctx.sign_ecdsa(&fake_msghash, node_1_privkey), - contents: valid_announcement.contents.clone() - }) { + contents: valid_announcement.contents.clone(), + }, + ) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature on node_announcement message") + Err(e) => assert_eq!(e.err, "Invalid signature on node_announcement message"), }; match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(res), - Err(_) => panic!() + Err(_) => panic!(), }; - let announcement_with_data = get_signed_node_announcement(|unsigned_announcement| { - unsigned_announcement.timestamp += 1000; - unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); - }, node_1_privkey, &secp_ctx); + let announcement_with_data = get_signed_node_announcement( + |unsigned_announcement| { + unsigned_announcement.timestamp += 1000; + unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); + }, + node_1_privkey, + &secp_ctx, + ); // Return false because contains excess data. match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement_with_data) { Ok(res) => assert!(!res), - Err(_) => panic!() + Err(_) => panic!(), }; // Even though previous announcement was not relayed further, we still accepted it, // so we now won't accept announcements before the previous one. - let outdated_announcement = get_signed_node_announcement(|unsigned_announcement| { - unsigned_announcement.timestamp += 1000 - 10; - }, node_1_privkey, &secp_ctx); + let outdated_announcement = get_signed_node_announcement( + |unsigned_announcement| { + unsigned_announcement.timestamp += 1000 - 10; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &outdated_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Update older than last processed update") + Err(e) => assert_eq!(e.err, "Update older than last processed update"), }; } @@ -2645,20 +2951,25 @@ pub(crate) mod tests { let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); let good_script = get_channel_script(&secp_ctx); - let valid_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); // Test if the UTXO lookups were not supported let network_graph = NetworkGraph::new(Network::Testnet, &logger); let mut gossip_sync = P2PGossipSync::new(&network_graph, None, &logger); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), }; { - match network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id) { + match network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + { None => panic!(), - Some(_) => () + Some(_) => (), }; } @@ -2666,7 +2977,7 @@ pub(crate) mod tests { // drop new one on the floor, since we can't see any changes. match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Already have non-chain-validated channel") + Err(e) => assert_eq!(e.err, "Already have non-chain-validated channel"), }; // Test if an associated transaction were not on-chain (or not confirmed). @@ -2675,29 +2986,43 @@ pub(crate) mod tests { let network_graph = NetworkGraph::new(Network::Testnet, &logger); gossip_sync = P2PGossipSync::new(&network_graph, Some(&chain_source), &logger); - let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.short_channel_id += 1; - }, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.short_channel_id += 1; + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry") + Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry"), }; // Now test if the transaction is found in the UTXO set and the script is correct. *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Sync(Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script.clone() })); - let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.short_channel_id += 2; - }, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.short_channel_id += 2; + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), }; { - match network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id) { + match network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + { None => panic!(), - Some(_) => () + Some(_) => (), }; } @@ -2707,67 +3032,101 @@ pub(crate) mod tests { UtxoResult::Sync(Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script })); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Already have chain-validated channel") + Err(e) => assert_eq!(e.err, "Already have chain-validated channel"), }; #[cfg(feature = "std")] { use std::time::{SystemTime, UNIX_EPOCH}; - let tracking_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + let tracking_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); // Mark a node as permanently failed so it's tracked as removed. - gossip_sync.network_graph().node_failed_permanent(&PublicKey::from_secret_key(&secp_ctx, node_1_privkey)); + gossip_sync + .network_graph() + .node_failed_permanent(&PublicKey::from_secret_key(&secp_ctx, node_1_privkey)); // Return error and ignore valid channel announcement if one of the nodes has been tracked as removed. - let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.short_channel_id += 3; - }, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.short_channel_id += 3; + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => panic!(), Err(e) => assert_eq!(e.err, "Channel with SCID 3 or one of its nodes was removed from our network graph recently") } - gossip_sync.network_graph().remove_stale_channels_and_tracking_with_time(tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS); + gossip_sync.network_graph().remove_stale_channels_and_tracking_with_time( + tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS, + ); // The above channel announcement should be handled as per normal now. - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { + match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) + { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), } } - let valid_excess_data_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.short_channel_id += 4; - unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); - }, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_excess_data_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.short_channel_id += 4; + unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); let mut invalid_sig_announcement = valid_excess_data_announcement.clone(); invalid_sig_announcement.contents.excess_data = Vec::new(); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &invalid_sig_announcement) { + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &invalid_sig_announcement) + { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature on channel_announcement message") + Err(e) => assert_eq!(e.err, "Invalid signature on channel_announcement message"), }; // Don't relay valid channels with excess data - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_excess_data_announcement) { + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &valid_excess_data_announcement) + { Ok(res) => assert!(!res), - _ => panic!() + _ => panic!(), }; - let channel_to_itself_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_1_privkey, &secp_ctx); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &channel_to_itself_announcement) { + let channel_to_itself_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_1_privkey, &secp_ctx); + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &channel_to_itself_announcement) + { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Channel announcement node had a channel with itself") + Err(e) => assert_eq!(e.err, "Channel announcement node had a channel with itself"), }; // Test that channel announcements with the wrong chain hash are ignored (network graph is testnet, // announcement is mainnet). - let incorrect_chain_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.chain_hash = ChainHash::using_genesis_block(Network::Bitcoin); - }, node_1_privkey, node_2_privkey, &secp_ctx); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &incorrect_chain_announcement) { + let incorrect_chain_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.chain_hash = ChainHash::using_genesis_block(Network::Bitcoin); + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &incorrect_chain_announcement) + { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Channel announcement chain hash does not match genesis hash") + Err(e) => { + assert_eq!(e.err, "Channel announcement chain hash does not match genesis hash") + }, }; } @@ -2789,16 +3148,20 @@ pub(crate) mod tests { { // Announce a channel we will update let good_script = get_channel_script(&secp_ctx); - *chain_source.utxo_ret.lock().unwrap() = - UtxoResult::Sync(Ok(TxOut { value: amount_sats, script_pubkey: good_script.clone() })); + *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Sync(Ok(TxOut { + value: amount_sats, + script_pubkey: good_script.clone(), + })); - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); short_channel_id = valid_channel_announcement.contents.short_channel_id; - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) { + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) + { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; - } let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx); @@ -2814,77 +3177,109 @@ pub(crate) mod tests { Some(channel_info) => { assert_eq!(channel_info.one_to_two.as_ref().unwrap().cltv_expiry_delta, 144); assert!(channel_info.two_to_one.is_none()); - } + }, }; } - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp += 100; - unsigned_channel_update.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp += 100; + unsigned_channel_update.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0); + }, + node_1_privkey, + &secp_ctx, + ); // Return false because contains excess data match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(res) => assert!(!res), - _ => panic!() + _ => panic!(), }; - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp += 110; - unsigned_channel_update.short_channel_id += 1; - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp += 110; + unsigned_channel_update.short_channel_id += 1; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Couldn't find channel for update") + Err(e) => assert_eq!(e.err, "Couldn't find channel for update"), }; - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.htlc_maximum_msat = MAX_VALUE_MSAT + 1; - unsigned_channel_update.timestamp += 110; - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.htlc_maximum_msat = MAX_VALUE_MSAT + 1; + unsigned_channel_update.timestamp += 110; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than maximum possible msats") + Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than maximum possible msats"), }; - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.htlc_maximum_msat = amount_sats.to_sat() * 1000 + 1; - unsigned_channel_update.timestamp += 110; - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.htlc_maximum_msat = amount_sats.to_sat() * 1000 + 1; + unsigned_channel_update.timestamp += 110; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity or capacity is bogus") + Err(e) => assert_eq!( + e.err, + "htlc_maximum_msat is larger than channel capacity or capacity is bogus" + ), }; // Even though previous update was not relayed further, we still accepted it, // so we now won't accept update before the previous one. - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp += 100; - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp += 100; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Update had same timestamp as last processed update") + Err(e) => assert_eq!(e.err, "Update had same timestamp as last processed update"), }; - let mut invalid_sig_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp += 500; - }, node_1_privkey, &secp_ctx); + let mut invalid_sig_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp += 500; + }, + node_1_privkey, + &secp_ctx, + ); let zero_hash = Sha256dHash::hash(&[0; 32]); let fake_msghash = hash_to_message!(zero_hash.as_byte_array()); invalid_sig_channel_update.signature = secp_ctx.sign_ecdsa(&fake_msghash, node_1_privkey); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &invalid_sig_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature on channel_update message") + Err(e) => assert_eq!(e.err, "Invalid signature on channel_update message"), }; // Test that channel updates with the wrong chain hash are ignored (network graph is testnet, channel // update is mainet). - let incorrect_chain_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.chain_hash = ChainHash::using_genesis_block(Network::Bitcoin); - }, node_1_privkey, &secp_ctx); + let incorrect_chain_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.chain_hash = + ChainHash::using_genesis_block(Network::Bitcoin); + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &incorrect_chain_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Channel update chain hash does not match genesis hash") + Err(e) => assert_eq!(e.err, "Channel update chain hash does not match genesis hash"), }; } @@ -2906,17 +3301,32 @@ pub(crate) mod tests { let short_channel_id; { // Check that we can manually apply a channel update. - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); short_channel_id = valid_channel_announcement.contents.short_channel_id; let chain_source: Option<&test_utils::TestChainSource> = None; - assert!(network_graph.update_channel_from_announcement(&valid_channel_announcement, &chain_source).is_ok()); + assert!(network_graph + .update_channel_from_announcement(&valid_channel_announcement, &chain_source) + .is_ok()); assert!(network_graph.read_only().channels().get(&short_channel_id).is_some()); let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx); - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none()); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .one_to_two + .is_none()); network_graph.update_channel(&valid_channel_update).unwrap(); - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some()); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .one_to_two + .is_some()); } // Non-permanent failure doesn't touch the channel at all @@ -2925,7 +3335,7 @@ pub(crate) mod tests { None => panic!(), Some(channel_info) => { assert!(channel_info.one_to_two.as_ref().unwrap().enabled); - } + }, }; network_graph.handle_network_update(&NetworkUpdate::ChannelFailure { @@ -2937,7 +3347,7 @@ pub(crate) mod tests { None => panic!(), Some(channel_info) => { assert!(channel_info.one_to_two.as_ref().unwrap().enabled); - } + }, }; } @@ -2956,10 +3366,13 @@ pub(crate) mod tests { let network_graph = NetworkGraph::new(Network::Testnet, &logger); // Announce a channel to test permanent node failure - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); let short_channel_id = valid_channel_announcement.contents.short_channel_id; let chain_source: Option<&test_utils::TestChainSource> = None; - assert!(network_graph.update_channel_from_announcement(&valid_channel_announcement, &chain_source).is_ok()); + assert!(network_graph + .update_channel_from_announcement(&valid_channel_announcement, &chain_source) + .is_ok()); assert!(network_graph.read_only().channels().get(&short_channel_id).is_some()); // Non-permanent node failure does not delete any nodes or channels @@ -2969,7 +3382,11 @@ pub(crate) mod tests { }); assert!(network_graph.read_only().channels().get(&short_channel_id).is_some()); - assert!(network_graph.read_only().nodes().get(&NodeId::from_pubkey(&node_2_id)).is_some()); + assert!(network_graph + .read_only() + .nodes() + .get(&NodeId::from_pubkey(&node_2_id)) + .is_some()); // Permanent node failure deletes node and its channels network_graph.handle_network_update(&NetworkUpdate::NodeFailure { @@ -2996,32 +3413,61 @@ pub(crate) mod tests { let node_1_pubkey = PublicKey::from_secret_key(&secp_ctx, node_1_privkey); let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); let short_channel_id = valid_channel_announcement.contents.short_channel_id; let chain_source: Option<&test_utils::TestChainSource> = None; - assert!(network_graph.update_channel_from_announcement(&valid_channel_announcement, &chain_source).is_ok()); + assert!(network_graph + .update_channel_from_announcement(&valid_channel_announcement, &chain_source) + .is_ok()); assert!(network_graph.read_only().channels().get(&short_channel_id).is_some()); // Submit two channel updates for each channel direction (update.flags bit). let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx); - assert!(gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update).is_ok()); - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some()); - - let valid_channel_update_2 = get_signed_channel_update(|update| {update.channel_flags |=1;}, node_2_privkey, &secp_ctx); + assert!(gossip_sync + .handle_channel_update(Some(node_1_pubkey), &valid_channel_update) + .is_ok()); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .one_to_two + .is_some()); + + let valid_channel_update_2 = get_signed_channel_update( + |update| { + update.channel_flags |= 1; + }, + node_2_privkey, + &secp_ctx, + ); gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update_2).unwrap(); - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().two_to_one.is_some()); - - network_graph.remove_stale_channels_and_tracking_with_time(100 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .two_to_one + .is_some()); + + network_graph.remove_stale_channels_and_tracking_with_time( + 100 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS, + ); assert_eq!(network_graph.read_only().channels().len(), 1); assert_eq!(network_graph.read_only().nodes().len(), 2); - network_graph.remove_stale_channels_and_tracking_with_time(101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS); - #[cfg(not(feature = "std"))] { + network_graph.remove_stale_channels_and_tracking_with_time( + 101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS, + ); + #[cfg(not(feature = "std"))] + { // Make sure removed channels are tracked. assert_eq!(network_graph.removed_channels.lock().unwrap().len(), 1); } - network_graph.remove_stale_channels_and_tracking_with_time(101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS + - REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS); + network_graph.remove_stale_channels_and_tracking_with_time( + 101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS, + ); #[cfg(feature = "std")] { @@ -3034,20 +3480,47 @@ pub(crate) mod tests { // Note that the directional channel information will have been removed already.. // We want to check that this will work even if *one* of the channel updates is recent, // so we should add it with a recent timestamp. - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none()); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .one_to_two + .is_none()); use std::time::{SystemTime, UNIX_EPOCH}; - let announcement_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp = (announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS) as u32; - }, node_1_privkey, &secp_ctx); - assert!(gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update).is_ok()); - assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some()); - network_graph.remove_stale_channels_and_tracking_with_time(announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS); + let announcement_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp = + (announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS) as u32; + }, + node_1_privkey, + &secp_ctx, + ); + assert!(gossip_sync + .handle_channel_update(Some(node_1_pubkey), &valid_channel_update) + .is_ok()); + assert!(network_graph + .read_only() + .channels() + .get(&short_channel_id) + .unwrap() + .one_to_two + .is_some()); + network_graph.remove_stale_channels_and_tracking_with_time( + announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS, + ); // Make sure removed channels are tracked. assert_eq!(network_graph.removed_channels.lock().unwrap().len(), 1); // Provide a later time so that sufficient time has passed - network_graph.remove_stale_channels_and_tracking_with_time(announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS + - REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS); + network_graph.remove_stale_channels_and_tracking_with_time( + announcement_time + + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS + + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS, + ); } assert_eq!(network_graph.read_only().channels().len(), 0); @@ -3058,7 +3531,10 @@ pub(crate) mod tests { { use std::time::{SystemTime, UNIX_EPOCH}; - let tracking_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); + let tracking_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); // Clear tracked nodes and channels for clean slate network_graph.removed_channels.lock().unwrap().clear(); @@ -3066,8 +3542,9 @@ pub(crate) mod tests { // Add a channel and nodes from channel announcement. So our network graph will // now only consist of two nodes and one channel between them. - assert!(network_graph.update_channel_from_announcement( - &valid_channel_announcement, &chain_source).is_ok()); + assert!(network_graph + .update_channel_from_announcement(&valid_channel_announcement, &chain_source) + .is_ok()); // Mark the channel as permanently failed. This will also remove the two nodes // and all of the entries will be tracked as removed. @@ -3075,14 +3552,29 @@ pub(crate) mod tests { // Should not remove from tracking if insufficient time has passed network_graph.remove_stale_channels_and_tracking_with_time( - tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS - 1); - assert_eq!(network_graph.removed_channels.lock().unwrap().len(), 1, "Removed channel count ≠ 1 with tracking_time {}", tracking_time); + tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS - 1, + ); + assert_eq!( + network_graph.removed_channels.lock().unwrap().len(), + 1, + "Removed channel count ≠ 1 with tracking_time {}", + tracking_time + ); // Provide a later time so that sufficient time has passed network_graph.remove_stale_channels_and_tracking_with_time( - tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS); - assert!(network_graph.removed_channels.lock().unwrap().is_empty(), "Unexpectedly removed channels with tracking_time {}", tracking_time); - assert!(network_graph.removed_nodes.lock().unwrap().is_empty(), "Unexpectedly removed nodes with tracking_time {}", tracking_time); + tracking_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS, + ); + assert!( + network_graph.removed_channels.lock().unwrap().is_empty(), + "Unexpectedly removed channels with tracking_time {}", + tracking_time + ); + assert!( + network_graph.removed_nodes.lock().unwrap().is_empty(), + "Unexpectedly removed nodes with tracking_time {}", + tracking_time + ); } #[cfg(not(feature = "std"))] @@ -3099,8 +3591,9 @@ pub(crate) mod tests { // Add a channel and nodes from channel announcement. So our network graph will // now only consist of two nodes and one channel between them. - assert!(network_graph.update_channel_from_announcement( - &valid_channel_announcement, &chain_source).is_ok()); + assert!(network_graph + .update_channel_from_announcement(&valid_channel_announcement, &chain_source) + .is_ok()); // Mark the channel as permanently failed. This will also remove the two nodes // and all of the entries will be tracked as removed. @@ -3112,7 +3605,8 @@ pub(crate) mod tests { // Provide a later time so that sufficient time has passed network_graph.remove_stale_channels_and_tracking_with_time( - removal_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS); + removal_time + REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS, + ); assert!(network_graph.removed_channels.lock().unwrap().is_empty()); assert!(network_graph.removed_nodes.lock().unwrap().is_empty()); } @@ -3133,16 +3627,20 @@ pub(crate) mod tests { let short_channel_id; { // Announce a channel we will update - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); short_channel_id = valid_channel_announcement.contents.short_channel_id; - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) { + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) + { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } // Contains initial channel announcement now. - let channels_with_announcements = gossip_sync.get_next_channel_announcement(short_channel_id); + let channels_with_announcements = + gossip_sync.get_next_channel_announcement(short_channel_id); if let Some(channel_announcements) = channels_with_announcements { let (_, ref update_1, ref update_2) = channel_announcements; assert_eq!(update_1, &None); @@ -3153,17 +3651,22 @@ pub(crate) mod tests { { // Valid channel update - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp = 101; - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp = 101; + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } // Now contains an initial announcement and an update. - let channels_with_announcements = gossip_sync.get_next_channel_announcement(short_channel_id); + let channels_with_announcements = + gossip_sync.get_next_channel_announcement(short_channel_id); if let Some(channel_announcements) = channels_with_announcements { let (_, ref update_1, ref update_2) = channel_announcements; assert_ne!(update_1, &None); @@ -3174,18 +3677,24 @@ pub(crate) mod tests { { // Channel update with excess data. - let valid_channel_update = get_signed_channel_update(|unsigned_channel_update| { - unsigned_channel_update.timestamp = 102; - unsigned_channel_update.excess_data = [1; MAX_EXCESS_BYTES_FOR_RELAY + 1].to_vec(); - }, node_1_privkey, &secp_ctx); + let valid_channel_update = get_signed_channel_update( + |unsigned_channel_update| { + unsigned_channel_update.timestamp = 102; + unsigned_channel_update.excess_data = + [1; MAX_EXCESS_BYTES_FOR_RELAY + 1].to_vec(); + }, + node_1_privkey, + &secp_ctx, + ); match gossip_sync.handle_channel_update(Some(node_1_pubkey), &valid_channel_update) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } // Test that announcements with excess data won't be returned - let channels_with_announcements = gossip_sync.get_next_channel_announcement(short_channel_id); + let channels_with_announcements = + gossip_sync.get_next_channel_announcement(short_channel_id); if let Some(channel_announcements) = channels_with_announcements { let (_, ref update_1, ref update_2) = channel_announcements; assert_eq!(update_1, &None); @@ -3195,7 +3704,8 @@ pub(crate) mod tests { } // Further starting point have no channels after it - let channels_with_announcements = gossip_sync.get_next_channel_announcement(short_channel_id + 1000); + let channels_with_announcements = + gossip_sync.get_next_channel_announcement(short_channel_id + 1000); assert!(channels_with_announcements.is_none()); } @@ -3214,10 +3724,13 @@ pub(crate) mod tests { { // Announce a channel to add 2 nodes - let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) { + let valid_channel_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + match gossip_sync + .handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement) + { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } @@ -3226,16 +3739,18 @@ pub(crate) mod tests { assert!(next_announcements.is_none()); { - let valid_announcement = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); + let valid_announcement = + get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; - let valid_announcement = get_signed_node_announcement(|_| {}, node_2_privkey, &secp_ctx); + let valid_announcement = + get_signed_node_announcement(|_| {}, node_2_privkey, &secp_ctx); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } @@ -3248,13 +3763,18 @@ pub(crate) mod tests { { // Later announcement which should not be relayed (excess data) prevent us from sharing a node - let valid_announcement = get_signed_node_announcement(|unsigned_announcement| { - unsigned_announcement.timestamp += 10; - unsigned_announcement.excess_data = [1; MAX_EXCESS_BYTES_FOR_RELAY + 1].to_vec(); - }, node_2_privkey, &secp_ctx); + let valid_announcement = get_signed_node_announcement( + |unsigned_announcement| { + unsigned_announcement.timestamp += 10; + unsigned_announcement.excess_data = + [1; MAX_EXCESS_BYTES_FOR_RELAY + 1].to_vec(); + }, + node_2_privkey, + &secp_ctx, + ); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(!res), - Err(_) => panic!() + Err(_) => panic!(), }; } @@ -3272,16 +3792,17 @@ pub(crate) mod tests { let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); // Announce a channel to add a corresponding node. - let valid_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), }; let valid_announcement = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); match gossip_sync.handle_node_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; let mut w = test_utils::TestVecWriter(Vec::new()); @@ -3290,7 +3811,9 @@ pub(crate) mod tests { network_graph.write(&mut w).unwrap(); let logger = Arc::new(test_utils::TestLogger::new()); - assert!(>::read(&mut io::Cursor::new(&w.0), logger).unwrap() == network_graph); + assert!( + >::read(&mut io::Cursor::new(&w.0), logger).unwrap() == network_graph + ); } #[test] @@ -3302,7 +3825,8 @@ pub(crate) mod tests { network_graph.write(&mut w).unwrap(); let logger = Arc::new(test_utils::TestLogger::new()); - let reassembled_network_graph: NetworkGraph<_> = ReadableArgs::read(&mut io::Cursor::new(&w.0), logger).unwrap(); + let reassembled_network_graph: NetworkGraph<_> = + ReadableArgs::read(&mut io::Cursor::new(&w.0), logger).unwrap(); assert!(reassembled_network_graph == network_graph); assert_eq!(reassembled_network_graph.get_last_rapid_gossip_sync_timestamp().unwrap(), 42); } @@ -3310,8 +3834,8 @@ pub(crate) mod tests { #[test] #[cfg(feature = "std")] fn calling_sync_routing_table() { - use std::time::{SystemTime, UNIX_EPOCH}; use crate::ln::msgs::Init; + use std::time::{SystemTime, UNIX_EPOCH}; let network_graph = create_network_graph(); let (secp_ctx, gossip_sync) = create_gossip_sync(&network_graph); @@ -3322,7 +3846,11 @@ pub(crate) mod tests { // It should ignore if gossip_queries feature is not enabled { - let init_msg = Init { features: InitFeatures::empty(), networks: None, remote_network_address: None }; + let init_msg = Init { + features: InitFeatures::empty(), + networks: None, + remote_network_address: None, + }; gossip_sync.peer_connected(node_id_1, &init_msg, true).unwrap(); let events = gossip_sync.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 0); @@ -3337,15 +3865,23 @@ pub(crate) mod tests { let events = gossip_sync.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); match &events[0] { - MessageSendEvent::SendGossipTimestampFilter{ node_id, msg } => { + MessageSendEvent::SendGossipTimestampFilter { node_id, msg } => { assert_eq!(node_id, &node_id_1); assert_eq!(msg.chain_hash, chain_hash); - let expected_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(); - assert!((msg.first_timestamp as u64) >= expected_timestamp - 60*60*24*7*2); - assert!((msg.first_timestamp as u64) < expected_timestamp - 60*60*24*7*2 + 10); + let expected_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(); + assert!( + (msg.first_timestamp as u64) >= expected_timestamp - 60 * 60 * 24 * 7 * 2 + ); + assert!( + (msg.first_timestamp as u64) + < expected_timestamp - 60 * 60 * 24 * 7 * 2 + 10 + ); assert_eq!(msg.timestamp_range, u32::max_value()); }, - _ => panic!("Expected MessageSendEvent::SendChannelRangeQuery") + _ => panic!("Expected MessageSendEvent::SendChannelRangeQuery"), }; } } @@ -3375,12 +3911,18 @@ pub(crate) mod tests { scids.push(scid_from_parts(108001, 1, 0).unwrap()); for scid in scids { - let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.short_channel_id = scid; - }, node_1_privkey, node_2_privkey, &secp_ctx); - match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { + let valid_announcement = get_signed_channel_announcement( + |unsigned_announcement| { + unsigned_announcement.short_channel_id = scid; + }, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); + match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) + { Ok(_) => (), - _ => panic!() + _ => panic!(), }; } @@ -3399,8 +3941,8 @@ pub(crate) mod tests { first_blocknum: 0, number_of_blocks: 0, sync_complete: true, - short_channel_ids: vec![] - }] + short_channel_ids: vec![], + }], ); // Error when wrong chain @@ -3419,7 +3961,7 @@ pub(crate) mod tests { number_of_blocks: 0xffff_ffff, sync_complete: true, short_channel_ids: vec![], - }] + }], ); // Error when first_blocknum > 0xffffff @@ -3437,8 +3979,8 @@ pub(crate) mod tests { first_blocknum: 0x01000000, number_of_blocks: 0xffff_ffff, sync_complete: true, - short_channel_ids: vec![] - }] + short_channel_ids: vec![], + }], ); // Empty reply when max valid SCID block num @@ -3451,15 +3993,13 @@ pub(crate) mod tests { number_of_blocks: 1, }, true, - vec![ - ReplyChannelRange { - chain_hash: chain_hash.clone(), - first_blocknum: 0xffffff, - number_of_blocks: 1, - sync_complete: true, - short_channel_ids: vec![] - }, - ] + vec![ReplyChannelRange { + chain_hash: chain_hash.clone(), + first_blocknum: 0xffffff, + number_of_blocks: 1, + sync_complete: true, + short_channel_ids: vec![], + }], ); // No results in valid query range @@ -3472,15 +4012,13 @@ pub(crate) mod tests { number_of_blocks: 1000, }, true, - vec![ - ReplyChannelRange { - chain_hash: chain_hash.clone(), - first_blocknum: 1000, - number_of_blocks: 1000, - sync_complete: true, - short_channel_ids: vec![], - } - ] + vec![ReplyChannelRange { + chain_hash: chain_hash.clone(), + first_blocknum: 1000, + number_of_blocks: 1000, + sync_complete: true, + short_channel_ids: vec![], + }], ); // Overflow first_blocknum + number_of_blocks @@ -3493,17 +4031,15 @@ pub(crate) mod tests { number_of_blocks: 0xffffffff, }, true, - vec![ - ReplyChannelRange { - chain_hash: chain_hash.clone(), - first_blocknum: 0xfe0000, - number_of_blocks: 0xffffffff - 0xfe0000, - sync_complete: true, - short_channel_ids: vec![ - 0xfffffe_ffffff_ffff, // max - ] - } - ] + vec![ReplyChannelRange { + chain_hash: chain_hash.clone(), + first_blocknum: 0xfe0000, + number_of_blocks: 0xffffffff - 0xfe0000, + sync_complete: true, + short_channel_ids: vec![ + 0xfffffe_ffffff_ffff, // max + ], + }], ); // Single block exactly full @@ -3516,17 +4052,15 @@ pub(crate) mod tests { number_of_blocks: 8000, }, true, - vec![ - ReplyChannelRange { - chain_hash: chain_hash.clone(), - first_blocknum: 100000, - number_of_blocks: 8000, - sync_complete: true, - short_channel_ids: (100000..=107999) - .map(|block| scid_from_parts(block, 0, 0).unwrap()) - .collect(), - }, - ] + vec![ReplyChannelRange { + chain_hash: chain_hash.clone(), + first_blocknum: 100000, + number_of_blocks: 8000, + sync_complete: true, + short_channel_ids: (100000..=107999) + .map(|block| scid_from_parts(block, 0, 0).unwrap()) + .collect(), + }], ); // Multiple split on new block @@ -3554,11 +4088,9 @@ pub(crate) mod tests { first_blocknum: 107999, number_of_blocks: 2, sync_complete: true, - short_channel_ids: vec![ - scid_from_parts(108000, 0, 0).unwrap(), - ], - } - ] + short_channel_ids: vec![scid_from_parts(108000, 0, 0).unwrap()], + }, + ], ); // Multiple split on same block @@ -3586,20 +4118,20 @@ pub(crate) mod tests { first_blocknum: 108001, number_of_blocks: 1, sync_complete: true, - short_channel_ids: vec![ - scid_from_parts(108001, 1, 0).unwrap(), - ], - } - ] + short_channel_ids: vec![scid_from_parts(108001, 1, 0).unwrap()], + }, + ], ); } fn do_handling_query_channel_range( - gossip_sync: &P2PGossipSync<&NetworkGraph>, Arc, Arc>, - test_node_id: &PublicKey, - msg: QueryChannelRange, - expected_ok: bool, - expected_replies: Vec + gossip_sync: &P2PGossipSync< + &NetworkGraph>, + Arc, + Arc, + >, + test_node_id: &PublicKey, msg: QueryChannelRange, expected_ok: bool, + expected_replies: Vec, ) { let mut max_firstblocknum = msg.first_blocknum.saturating_sub(1); let mut c_lightning_0_9_prev_end_blocknum = max_firstblocknum; @@ -3627,14 +4159,22 @@ pub(crate) mod tests { assert_eq!(msg.short_channel_ids, expected_reply.short_channel_ids); // Enforce exactly the sequencing requirements present on c-lightning v0.9.3 - assert!(msg.first_blocknum == c_lightning_0_9_prev_end_blocknum || msg.first_blocknum == c_lightning_0_9_prev_end_blocknum.saturating_add(1)); + assert!( + msg.first_blocknum == c_lightning_0_9_prev_end_blocknum + || msg.first_blocknum + == c_lightning_0_9_prev_end_blocknum.saturating_add(1) + ); assert!(msg.first_blocknum >= max_firstblocknum); max_firstblocknum = msg.first_blocknum; - c_lightning_0_9_prev_end_blocknum = msg.first_blocknum.saturating_add(msg.number_of_blocks); + c_lightning_0_9_prev_end_blocknum = + msg.first_blocknum.saturating_add(msg.number_of_blocks); // Check that the last block count is >= the query's end_blocknum if i == events.len() - 1 { - assert!(msg.first_blocknum.saturating_add(msg.number_of_blocks) >= query_end_blocknum); + assert!( + msg.first_blocknum.saturating_add(msg.number_of_blocks) + >= query_end_blocknum + ); } }, _ => panic!("expected MessageSendEvent::SendReplyChannelRange"), @@ -3651,10 +4191,10 @@ pub(crate) mod tests { let chain_hash = ChainHash::using_genesis_block(Network::Testnet); - let result = gossip_sync.handle_query_short_channel_ids(node_id, QueryShortChannelIds { - chain_hash, - short_channel_ids: vec![0x0003e8_000000_0000], - }); + let result = gossip_sync.handle_query_short_channel_ids( + node_id, + QueryShortChannelIds { chain_hash, short_channel_ids: vec![0x0003e8_000000_0000] }, + ); assert!(result.is_err()); } @@ -3685,7 +4225,11 @@ pub(crate) mod tests { fn channel_info_is_readable() { let chanmon_cfgs = crate::ln::functional_test_utils::create_chanmon_cfgs(2); let node_cfgs = crate::ln::functional_test_utils::create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = crate::ln::functional_test_utils::create_node_chanmgrs(2, &node_cfgs, &[None, None, None, None]); + let node_chanmgrs = crate::ln::functional_test_utils::create_node_chanmgrs( + 2, + &node_cfgs, + &[None, None, None, None], + ); let nodes = crate::ln::functional_test_utils::create_network(2, &node_cfgs, &node_chanmgrs); let config = crate::ln::functional_test_utils::test_default_channel_config(); @@ -3704,7 +4248,8 @@ pub(crate) mod tests { assert!(chan_update_info.write(&mut encoded_chan_update_info).is_ok()); // First make sure we can read ChannelUpdateInfos we just wrote - let read_chan_update_info: ChannelUpdateInfo = crate::util::ser::Readable::read(&mut encoded_chan_update_info.as_slice()).unwrap(); + let read_chan_update_info: ChannelUpdateInfo = + crate::util::ser::Readable::read(&mut encoded_chan_update_info.as_slice()).unwrap(); assert_eq!(chan_update_info, read_chan_update_info); // Check the serialization hasn't changed. @@ -3714,11 +4259,15 @@ pub(crate) mod tests { // Check we fail if htlc_maximum_msat is not present in either the ChannelUpdateInfo itself // or the ChannelUpdate enclosed with `last_update_message`. let legacy_chan_update_info_with_some_and_fail_update: Vec = >::from_hex("b40004000000170201010402002a060800000000000004d2080909000000000000162e0a0d0c00040000000902040000000a0c8181d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f00083a840000034d013413a70000009000000000000f42400000271000000014").unwrap(); - let read_chan_update_info_res: Result = crate::util::ser::Readable::read(&mut legacy_chan_update_info_with_some_and_fail_update.as_slice()); + let read_chan_update_info_res: Result = + crate::util::ser::Readable::read( + &mut legacy_chan_update_info_with_some_and_fail_update.as_slice(), + ); assert!(read_chan_update_info_res.is_err()); let legacy_chan_update_info_with_none: Vec = >::from_hex("2c0004000000170201010402002a060800000000000004d20801000a0d0c00040000000902040000000a0c0100").unwrap(); - let read_chan_update_info_res: Result = crate::util::ser::Readable::read(&mut legacy_chan_update_info_with_none.as_slice()); + let read_chan_update_info_res: Result = + crate::util::ser::Readable::read(&mut legacy_chan_update_info_with_none.as_slice()); assert!(read_chan_update_info_res.is_err()); // 2. Test encoding/decoding of ChannelInfo @@ -3739,7 +4288,8 @@ pub(crate) mod tests { let mut encoded_chan_info: Vec = Vec::new(); assert!(chan_info_none_updates.write(&mut encoded_chan_info).is_ok()); - let read_chan_info: ChannelInfo = crate::util::ser::Readable::read(&mut encoded_chan_info.as_slice()).unwrap(); + let read_chan_info: ChannelInfo = + crate::util::ser::Readable::read(&mut encoded_chan_info.as_slice()).unwrap(); assert_eq!(chan_info_none_updates, read_chan_info); // Check we can encode/decode ChannelInfo with ChannelUpdateInfo fields present. @@ -3759,7 +4309,8 @@ pub(crate) mod tests { let mut encoded_chan_info: Vec = Vec::new(); assert!(chan_info_some_updates.write(&mut encoded_chan_info).is_ok()); - let read_chan_info: ChannelInfo = crate::util::ser::Readable::read(&mut encoded_chan_info.as_slice()).unwrap(); + let read_chan_info: ChannelInfo = + crate::util::ser::Readable::read(&mut encoded_chan_info.as_slice()).unwrap(); assert_eq!(chan_info_some_updates, read_chan_info); // Check the serialization hasn't changed. @@ -3769,13 +4320,17 @@ pub(crate) mod tests { // Check we can decode legacy ChannelInfo, even if the `two_to_one` / `one_to_two` / // `last_update_message` fields fail to decode due to missing htlc_maximum_msat. let legacy_chan_info_with_some_and_fail_update = >::from_hex("fd01ca00020000010800000000000156660221027f921585f2ac0c7c70e36110adecfd8fd14b8a99bfb3d000a283fcac358fce8804b6b6b40004000000170201010402002a060800000000000004d2080909000000000000162e0a0d0c00040000000902040000000a0c8181d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f00083a840000034d013413a70000009000000000000f4240000027100000001406210355f8d2238a322d16b602bd0ceaad5b01019fb055971eaadcc9b29226a4da6c2308b6b6b40004000000170201010402002a060800000000000004d2080909000000000000162e0a0d0c00040000000902040000000a0c8181d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f00083a840000034d013413a70000009000000000000f424000002710000000140a01000c0100").unwrap(); - let read_chan_info: ChannelInfo = crate::util::ser::Readable::read(&mut legacy_chan_info_with_some_and_fail_update.as_slice()).unwrap(); + let read_chan_info: ChannelInfo = crate::util::ser::Readable::read( + &mut legacy_chan_info_with_some_and_fail_update.as_slice(), + ) + .unwrap(); assert_eq!(read_chan_info.announcement_received_time, 87654); assert_eq!(read_chan_info.one_to_two, None); assert_eq!(read_chan_info.two_to_one, None); let legacy_chan_info_with_none: Vec = >::from_hex("ba00020000010800000000000156660221027f921585f2ac0c7c70e36110adecfd8fd14b8a99bfb3d000a283fcac358fce88042e2e2c0004000000170201010402002a060800000000000004d20801000a0d0c00040000000902040000000a0c010006210355f8d2238a322d16b602bd0ceaad5b01019fb055971eaadcc9b29226a4da6c23082e2e2c0004000000170201010402002a060800000000000004d20801000a0d0c00040000000902040000000a0c01000a01000c0100").unwrap(); - let read_chan_info: ChannelInfo = crate::util::ser::Readable::read(&mut legacy_chan_info_with_none.as_slice()).unwrap(); + let read_chan_info: ChannelInfo = + crate::util::ser::Readable::read(&mut legacy_chan_info_with_none.as_slice()).unwrap(); assert_eq!(read_chan_info.announcement_received_time, 87654); assert_eq!(read_chan_info.one_to_two, None); assert_eq!(read_chan_info.two_to_one, None); @@ -3785,17 +4340,20 @@ pub(crate) mod tests { fn node_info_is_readable() { // 1. Check we can read a valid NodeAnnouncementInfo and fail on an invalid one let announcement_message = >::from_hex("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a000122013413a7031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2020201010101010101010101010101010101010101010101010101010101010101010000701fffefdfc2607").unwrap(); - let announcement_message = NodeAnnouncement::read(&mut announcement_message.as_slice()).unwrap(); + let announcement_message = + NodeAnnouncement::read(&mut announcement_message.as_slice()).unwrap(); let valid_node_ann_info = NodeAnnouncementInfo::Relayed(announcement_message); let mut encoded_valid_node_ann_info = Vec::new(); assert!(valid_node_ann_info.write(&mut encoded_valid_node_ann_info).is_ok()); - let read_valid_node_ann_info = NodeAnnouncementInfo::read(&mut encoded_valid_node_ann_info.as_slice()).unwrap(); + let read_valid_node_ann_info = + NodeAnnouncementInfo::read(&mut encoded_valid_node_ann_info.as_slice()).unwrap(); assert_eq!(read_valid_node_ann_info, valid_node_ann_info); assert_eq!(read_valid_node_ann_info.addresses().len(), 1); let encoded_invalid_node_ann_info = >::from_hex("3f0009000788a000080a51a20204000000000403000000062000000000000000000000000000000000000000000000000000000000000000000a0505014004d2").unwrap(); - let read_invalid_node_ann_info_res = NodeAnnouncementInfo::read(&mut encoded_invalid_node_ann_info.as_slice()); + let read_invalid_node_ann_info_res = + NodeAnnouncementInfo::read(&mut encoded_invalid_node_ann_info.as_slice()); assert!(read_invalid_node_ann_info_res.is_err()); // 2. Check we can read a NodeInfo anyways, but set the NodeAnnouncementInfo to None if invalid @@ -3811,14 +4369,16 @@ pub(crate) mod tests { assert_eq!(read_valid_node_info, valid_node_info); let encoded_invalid_node_info_hex = >::from_hex("4402403f0009000788a000080a51a20204000000000403000000062000000000000000000000000000000000000000000000000000000000000000000a0505014004d20400").unwrap(); - let read_invalid_node_info = NodeInfo::read(&mut encoded_invalid_node_info_hex.as_slice()).unwrap(); + let read_invalid_node_info = + NodeInfo::read(&mut encoded_invalid_node_info_hex.as_slice()).unwrap(); assert_eq!(read_invalid_node_info.announcement_info, None); } #[test] fn test_node_info_keeps_compatibility() { let old_ann_info_with_addresses = >::from_hex("3f0009000708a000080a51220204000000000403000000062000000000000000000000000000000000000000000000000000000000000000000a0505014104d2").unwrap(); - let ann_info_with_addresses = NodeAnnouncementInfo::read(&mut old_ann_info_with_addresses.as_slice()) + let ann_info_with_addresses = + NodeAnnouncementInfo::read(&mut old_ann_info_with_addresses.as_slice()) .expect("to be able to read an old NodeAnnouncementInfo with addresses"); // This serialized info has no announcement_message but its address field should still be considered assert!(!ann_info_with_addresses.addresses().is_empty()); @@ -3827,7 +4387,10 @@ pub(crate) mod tests { #[test] fn test_node_id_display() { let node_id = NodeId([42; 33]); - assert_eq!(format!("{}", &node_id), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"); + assert_eq!( + format!("{}", &node_id), + "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" + ); } #[test] @@ -3840,23 +4403,25 @@ pub(crate) mod tests { let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); let node_1_id = NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_1_privkey)); - let announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &announcement).unwrap(); - let tcp_ip_v4 = SocketAddress::TcpIpV4 { - addr: [255, 254, 253, 252], - port: 9735 - }; + let tcp_ip_v4 = SocketAddress::TcpIpV4 { addr: [255, 254, 253, 252], port: 9735 }; let tcp_ip_v6 = SocketAddress::TcpIpV6 { addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240], - port: 9735 + port: 9735, }; - let onion_v2 = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]); + let onion_v2 = + SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]); let onion_v3 = SocketAddress::OnionV3 { - ed25519_pubkey: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224], + ed25519_pubkey: [ + 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, + 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, + ], checksum: 32, version: 16, - port: 9735 + port: 9735, }; let hostname = SocketAddress::Hostname { hostname: Hostname::try_from(String::from("host")).unwrap(), @@ -3872,36 +4437,40 @@ pub(crate) mod tests { let announcement = get_signed_node_announcement( |announcement| { announcement.addresses = vec![ - tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone(), - hostname.clone() + tcp_ip_v4.clone(), + tcp_ip_v6.clone(), + onion_v2.clone(), + onion_v3.clone(), + hostname.clone(), ]; announcement.timestamp += 1000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); let announcement = get_signed_node_announcement( |announcement| { - announcement.addresses = vec![ - tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone() - ]; + announcement.addresses = + vec![tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone()]; announcement.timestamp += 2000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); let announcement = get_signed_node_announcement( |announcement| { - announcement.addresses = vec![ - tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone() - ]; + announcement.addresses = + vec![tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone()]; announcement.timestamp += 3000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); @@ -3911,7 +4480,8 @@ pub(crate) mod tests { announcement.addresses = vec![onion_v2.clone(), onion_v3.clone()]; announcement.timestamp += 4000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); @@ -3921,7 +4491,8 @@ pub(crate) mod tests { announcement.addresses = vec![onion_v2.clone()]; announcement.timestamp += 5000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); @@ -3931,7 +4502,8 @@ pub(crate) mod tests { announcement.addresses = vec![tcp_ip_v4.clone()]; announcement.timestamp += 6000; }, - node_1_privkey, &secp_ctx + node_1_privkey, + &secp_ctx, ); gossip_sync.handle_node_announcement(Some(node_1_pubkey), &announcement).unwrap(); assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); @@ -3941,17 +4513,19 @@ pub(crate) mod tests { #[cfg(ldk_bench)] pub mod benches { use super::*; - use std::io::Read; use criterion::{black_box, Criterion}; + use std::io::Read; pub fn read_network_graph(bench: &mut Criterion) { let logger = crate::util::test_utils::TestLogger::new(); let (mut d, _) = crate::routing::router::bench_utils::get_graph_scorer_file().unwrap(); let mut v = Vec::new(); d.read_to_end(&mut v).unwrap(); - bench.bench_function("read_network_graph", |b| b.iter(|| - NetworkGraph::read(&mut crate::io::Cursor::new(black_box(&v)), &logger).unwrap() - )); + bench.bench_function("read_network_graph", |b| { + b.iter(|| { + NetworkGraph::read(&mut crate::io::Cursor::new(black_box(&v)), &logger).unwrap() + }) + }); } pub fn write_network_graph(bench: &mut Criterion) { @@ -3960,8 +4534,6 @@ pub mod benches { let mut graph_buffer = Vec::new(); d.read_to_end(&mut graph_buffer).unwrap(); let net_graph = NetworkGraph::read(&mut &graph_buffer[..], &logger).unwrap(); - bench.bench_function("write_network_graph", |b| b.iter(|| - black_box(&net_graph).encode() - )); + bench.bench_function("write_network_graph", |b| b.iter(|| black_box(&net_graph).encode())); } } diff --git a/lightning/src/routing/mod.rs b/lightning/src/routing/mod.rs index ca2d2c8cc77..cb16461dd66 100644 --- a/lightning/src/routing/mod.rs +++ b/lightning/src/routing/mod.rs @@ -9,10 +9,10 @@ //! Structs and impls for receiving messages about the network and storing the topology live here. -pub mod utxo; pub mod gossip; +mod log_approx; pub mod router; pub mod scoring; -mod log_approx; #[cfg(test)] pub(crate) mod test_utils; +pub mod utxo; diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b0a3f34a676..aec5ae8a593 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -9,31 +9,38 @@ //! The router finds paths within a [`NetworkGraph`] for a payment. -use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; +use crate::blinded_path::payment::{ + BlindedPaymentPath, ForwardTlvs, PaymentConstraints, PaymentForwardNode, PaymentRelay, + ReceiveTlvs, +}; use crate::blinded_path::{BlindedHop, Direction, IntroductionNode}; -use crate::blinded_path::payment::{BlindedPaymentPath, ForwardTlvs, PaymentConstraints, PaymentForwardNode, PaymentRelay, ReceiveTlvs}; -use crate::ln::{PaymentHash, PaymentPreimage}; +use crate::crypto::chacha20::ChaCha20; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA, RecipientOnionFields}; -use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; +use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, MIN_FINAL_CLTV_EXPIRY_DELTA}; +use crate::ln::features::{ + BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures, +}; use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use crate::ln::onion_utils; +use crate::ln::{PaymentHash, PaymentPreimage}; +use crate::offers::invoice::Bolt12Invoice; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; -use crate::offers::invoice::Bolt12Invoice; -use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId}; +use crate::routing::gossip::{ + DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId, ReadOnlyNetworkGraph, +}; use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; use crate::sign::EntropySource; -use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer}; use crate::util::logger::{Level, Logger}; -use crate::crypto::chacha20::ChaCha20; +use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::io; use crate::prelude::*; use alloc::collections::BinaryHeap; -use core::{cmp, fmt}; use core::ops::Deref; +use core::{cmp, fmt}; use lightning_types::routing::RoutingFees; @@ -47,9 +54,16 @@ pub use lightning_types::routing::{RouteHint, RouteHintHop}; /// it will create a one-hop path using the recipient as the introduction node if it is a announced /// node. Otherwise, there is no way to find a path to the introduction node in order to send a /// payment, and thus an `Err` is returned. -pub struct DefaultRouter>, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> where +pub struct DefaultRouter< + G: Deref>, + L: Deref, + ES: Deref, + S: Deref, + SP: Sized, + Sc: ScoreLookUp, +> where L::Target: Logger, - S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + S::Target: for<'a> LockableScore<'a, ScoreLookUp = Sc>, ES::Target: EntropySource, { network_graph: G, @@ -59,43 +73,60 @@ pub struct DefaultRouter>, L: Deref, ES: Deref score_params: SP, } -impl>, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> DefaultRouter where +impl< + G: Deref>, + L: Deref, + ES: Deref, + S: Deref, + SP: Sized, + Sc: ScoreLookUp, + > DefaultRouter +where L::Target: Logger, - S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + S::Target: for<'a> LockableScore<'a, ScoreLookUp = Sc>, ES::Target: EntropySource, { /// Creates a new router. - pub fn new(network_graph: G, logger: L, entropy_source: ES, scorer: S, score_params: SP) -> Self { + pub fn new( + network_graph: G, logger: L, entropy_source: ES, scorer: S, score_params: SP, + ) -> Self { Self { network_graph, logger, entropy_source, scorer, score_params } } } -impl>, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> Router for DefaultRouter where +impl< + G: Deref>, + L: Deref, + ES: Deref, + S: Deref, + SP: Sized, + Sc: ScoreLookUp, + > Router for DefaultRouter +where L::Target: Logger, - S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + S::Target: for<'a> LockableScore<'a, ScoreLookUp = Sc>, ES::Target: EntropySource, { fn find_route( - &self, - payer: &PublicKey, - params: &RouteParameters, - first_hops: Option<&[&ChannelDetails]>, - inflight_htlcs: InFlightHtlcs + &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>, + inflight_htlcs: InFlightHtlcs, ) -> Result { let random_seed_bytes = self.entropy_source.get_secure_random_bytes(); find_route( - payer, params, &self.network_graph, first_hops, &*self.logger, + payer, + params, + &self.network_graph, + first_hops, + &*self.logger, &ScorerAccountingForInFlightHtlcs::new(self.scorer.read_lock(), &inflight_htlcs), &self.score_params, - &random_seed_bytes + &random_seed_bytes, ) } - fn create_blinded_payment_paths< - T: secp256k1::Signing + secp256k1::Verification - > ( + fn create_blinded_payment_paths( &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: u64, secp_ctx: &Secp256k1 + amount_msats: u64, secp_ctx: &Secp256k1, ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PAYMENT_PATHS: usize = 3; @@ -107,29 +138,29 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size let has_one_peer = first_hops .first() .map(|details| details.counterparty.node_id) - .map(|node_id| first_hops - .iter() - .skip(1) - .all(|details| details.counterparty.node_id == node_id) - ) + .map(|node_id| { + first_hops.iter().skip(1).all(|details| details.counterparty.node_id == node_id) + }) .unwrap_or(false); let network_graph = self.network_graph.deref().read_only(); let is_recipient_announced = network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); - let paths = first_hops.into_iter() + let paths = first_hops + .into_iter() .filter(|details| details.counterparty.features.supports_route_blinding()) .filter(|details| amount_msats <= details.inbound_capacity_msat) .filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0)) .filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX)) // Limit to peers with announced channels unless the recipient is unannounced. - .filter(|details| network_graph + .filter(|details| { + network_graph .node(&NodeId::from_pubkey(&details.counterparty.node_id)) .map(|node| !is_recipient_announced || node.channels.len() >= MIN_PEER_CHANNELS) // Allow payments directly with the only peer when unannounced. .unwrap_or(!is_recipient_announced && has_one_peer) - ) + }) .filter_map(|details| { let short_channel_id = match details.get_inbound_payment_scid() { Some(short_channel_id) => short_channel_id, @@ -162,8 +193,13 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size }) .map(|forward_node| { BlindedPaymentPath::new( - &[forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, - &*self.entropy_source, secp_ctx + &[forward_node], + recipient, + tlvs.clone(), + u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, + &*self.entropy_source, + secp_ctx, ) }) .take(MAX_PAYMENT_PATHS) @@ -174,9 +210,15 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size _ => { if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) { BlindedPaymentPath::new( - &[], recipient, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, - secp_ctx - ).map(|path| vec![path]) + &[], + recipient, + tlvs, + u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, + &*self.entropy_source, + secp_ctx, + ) + .map(|path| vec![path]) } else { Err(()) } @@ -193,7 +235,7 @@ pub trait Router { /// and [`RouteParameters::final_value_msat`], respectively. fn find_route( &self, payer: &PublicKey, route_params: &RouteParameters, - first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs + first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs, ) -> Result; /// Finds a [`Route`] for a payment between the given `payer` and a payee. @@ -206,7 +248,7 @@ pub trait Router { fn find_route_with_id( &self, payer: &PublicKey, route_params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs, - _payment_hash: PaymentHash, _payment_id: PaymentId + _payment_hash: PaymentHash, _payment_id: PaymentId, ) -> Result { self.find_route(payer, route_params, first_hops, inflight_htlcs) } @@ -214,11 +256,9 @@ pub trait Router { /// Creates [`BlindedPaymentPath`]s for payment to the `recipient` node. The channels in `first_hops` /// are assumed to be with the `recipient`'s peers. The payment secret and any constraints are /// given in `tlvs`. - fn create_blinded_payment_paths< - T: secp256k1::Signing + secp256k1::Verification - > ( + fn create_blinded_payment_paths( &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: u64, secp_ctx: &Secp256k1 + amount_msats: u64, secp_ctx: &Secp256k1, ) -> Result, ()>; } @@ -228,24 +268,32 @@ pub trait Router { /// [`find_route`]. /// /// [`ScoreLookUp`]: crate::routing::scoring::ScoreLookUp -pub struct ScorerAccountingForInFlightHtlcs<'a, S: Deref> where S::Target: ScoreLookUp { +pub struct ScorerAccountingForInFlightHtlcs<'a, S: Deref> +where + S::Target: ScoreLookUp, +{ scorer: S, // Maps a channel's short channel id and its direction to the liquidity used up. inflight_htlcs: &'a InFlightHtlcs, } -impl<'a, S: Deref> ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: ScoreLookUp { +impl<'a, S: Deref> ScorerAccountingForInFlightHtlcs<'a, S> +where + S::Target: ScoreLookUp, +{ /// Initialize a new `ScorerAccountingForInFlightHtlcs`. pub fn new(scorer: S, inflight_htlcs: &'a InFlightHtlcs) -> Self { - ScorerAccountingForInFlightHtlcs { - scorer, - inflight_htlcs - } + ScorerAccountingForInFlightHtlcs { scorer, inflight_htlcs } } } -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 = ::ScoreParams; - fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, 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), @@ -255,9 +303,9 @@ impl<'a, S: Deref> ScoreLookUp for ScorerAccountingForInFlightHtlcs<'a, S> where 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 - ) { + if let Some(used_liquidity) = + self.inflight_htlcs.used_liquidity_msat(&source, &target, short_channel_id) + { let usage = ChannelUsage { inflight_htlc_msat: usage.inflight_htlc_msat.saturating_add(used_liquidity), ..usage @@ -278,16 +326,20 @@ pub struct InFlightHtlcs( // is traveling in. The direction boolean is determined by checking if the HTLC source's public // key is less than its destination. See `InFlightHtlcs::used_liquidity_msat` for more // details. - HashMap<(u64, bool), u64> + HashMap<(u64, bool), u64>, ); impl InFlightHtlcs { /// Constructs an empty `InFlightHtlcs`. - pub fn new() -> Self { InFlightHtlcs(new_hash_map()) } + pub fn new() -> Self { + InFlightHtlcs(new_hash_map()) + } /// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`. pub fn process_path(&mut self, path: &Path, payer_node_id: PublicKey) { - if path.hops.is_empty() { return }; + if path.hops.is_empty() { + return; + }; let mut cumulative_msat = 0; if let Some(tail) = &path.blinded_tail { @@ -299,7 +351,11 @@ impl InFlightHtlcs { // the router excludes the payer node. In the following lines, the payer's information is // hardcoded with an inflight value of 0 so that we can correctly represent the first hop // in our sliding window of two. - let reversed_hops_with_payer = path.hops.iter().rev().skip(1) + let reversed_hops_with_payer = path + .hops + .iter() + .rev() + .skip(1) .map(|hop| hop.pubkey) .chain(core::iter::once(payer_node_id)); @@ -309,7 +365,10 @@ impl InFlightHtlcs { for (next_hop, prev_hop) in path.hops.iter().rev().zip(reversed_hops_with_payer) { cumulative_msat += next_hop.fee_msat; self.0 - .entry((next_hop.short_channel_id, NodeId::from_pubkey(&prev_hop) < NodeId::from_pubkey(&next_hop.pubkey))) + .entry(( + next_hop.short_channel_id, + NodeId::from_pubkey(&prev_hop) < NodeId::from_pubkey(&next_hop.pubkey), + )) .and_modify(|used_liquidity_msat| *used_liquidity_msat += cumulative_msat) .or_insert(cumulative_msat); } @@ -317,7 +376,9 @@ impl InFlightHtlcs { /// Adds a known HTLC given the public key of the HTLC source, target, and short channel /// id. - pub fn add_inflight_htlc(&mut self, source: &NodeId, target: &NodeId, channel_scid: u64, used_msat: u64){ + pub fn add_inflight_htlc( + &mut self, source: &NodeId, target: &NodeId, channel_scid: u64, used_msat: u64, + ) { self.0 .entry((channel_scid, source < target)) .and_modify(|used_liquidity_msat| *used_liquidity_msat += used_msat) @@ -326,13 +387,17 @@ impl InFlightHtlcs { /// Returns liquidity in msat given the public key of the HTLC source, target, and short channel /// id. - pub fn used_liquidity_msat(&self, source: &NodeId, target: &NodeId, channel_scid: u64) -> Option { + pub fn used_liquidity_msat( + &self, source: &NodeId, target: &NodeId, channel_scid: u64, + ) -> Option { self.0.get(&(channel_scid, source < target)).map(|v| *v) } } impl Writeable for InFlightHtlcs { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { self.0.write(writer) } + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + self.0.write(writer) + } } impl Readable for InFlightHtlcs { @@ -430,9 +495,10 @@ impl Path { Some(_) => self.hops.iter().map(|hop| hop.fee_msat).sum::(), None => { // Do not count last hop of each path since that's the full value of the payment - self.hops.split_last().map_or(0, - |(_, path_prefix)| path_prefix.iter().map(|hop| hop.fee_msat).sum()) - } + self.hops + .split_last() + .map_or(0, |(_, path_prefix)| path_prefix.iter().map(|hop| hop.fee_msat).sum()) + }, } } @@ -440,7 +506,7 @@ impl Path { pub fn final_value_msat(&self) -> u64 { match &self.blinded_tail { Some(blinded_tail) => blinded_tail.final_value_msat, - None => self.hops.last().map_or(0, |hop| hop.fee_msat) + None => self.hops.last().map_or(0, |hop| hop.fee_msat), } } @@ -448,7 +514,7 @@ impl Path { pub fn final_cltv_expiry_delta(&self) -> Option { match &self.blinded_tail { Some(_) => None, - None => self.hops.last().map(|hop| hop.cltv_expiry_delta) + None => self.hops.last().map(|hop| hop.cltv_expiry_delta), } } } @@ -478,7 +544,9 @@ impl Route { /// /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message pub fn get_total_fees(&self) -> u64 { - let overpaid_value_msat = self.route_params.as_ref() + let overpaid_value_msat = self + .route_params + .as_ref() .map_or(0, |p| self.get_total_amount().saturating_sub(p.final_value_msat)); overpaid_value_msat + self.paths.iter().map(|path| path.fee_msat()).sum::() } @@ -521,7 +589,9 @@ impl Writeable for Route { } } blinded_tails.push(Some(blinded_tail)); - } else if !blinded_tails.is_empty() { blinded_tails.push(None); } + } else if !blinded_tails.is_empty() { + blinded_tails.push(None); + } } write_tlv_fields!(writer, { // For compatibility with LDK versions prior to 0.0.117, we take the individual @@ -539,7 +609,9 @@ impl Readable for Route { fn read(reader: &mut R) -> Result { let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); let path_count: u64 = Readable::read(reader)?; - if path_count == 0 { return Err(DecodeError::InvalidValue); } + if path_count == 0 { + return Err(DecodeError::InvalidValue); + } let mut paths = Vec::with_capacity(cmp::min(path_count, 128) as usize); let mut min_final_cltv_expiry_delta = u32::max_value(); for _ in 0..path_count { @@ -548,7 +620,9 @@ impl Readable for Route { for _ in 0..hop_count { hops.push(Readable::read(reader)?); } - if hops.is_empty() { return Err(DecodeError::InvalidValue); } + if hops.is_empty() { + return Err(DecodeError::InvalidValue); + } min_final_cltv_expiry_delta = cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta); paths.push(Path { hops, blinded_tail: None }); @@ -561,7 +635,9 @@ impl Readable for Route { }); let blinded_tails = blinded_tails.unwrap_or(Vec::new()); if blinded_tails.len() != 0 { - if blinded_tails.len() != paths.len() { return Err(DecodeError::InvalidValue) } + if blinded_tails.len() != paths.len() { + return Err(DecodeError::InvalidValue); + } for (path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) { path.blinded_tail = blinded_tail_opt; } @@ -569,9 +645,11 @@ impl Readable for Route { // If we previously wrote the corresponding fields, reconstruct RouteParameters. let route_params = match (payment_params, final_value_msat) { - (Some(payment_params), Some(final_value_msat)) => { - Some(RouteParameters { payment_params, final_value_msat, max_total_routing_fee_msat }) - } + (Some(payment_params), Some(final_value_msat)) => Some(RouteParameters { + payment_params, + final_value_msat, + max_total_routing_fee_msat, + }), _ => None, }; @@ -603,18 +681,27 @@ impl RouteParameters { /// Constructs [`RouteParameters`] from the given [`PaymentParameters`] and a payment amount. /// /// [`Self::max_total_routing_fee_msat`] defaults to 1% of the payment amount + 50 sats - pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self { - Self { payment_params, final_value_msat, max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000) } + pub fn from_payment_params_and_value( + payment_params: PaymentParameters, final_value_msat: u64, + ) -> Self { + Self { + payment_params, + final_value_msat, + max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000), + } } /// Sets the maximum number of hops that can be included in a payment path, based on the provided /// [`RecipientOnionFields`] and blinded paths. pub fn set_max_path_length( - &mut self, recipient_onion: &RecipientOnionFields, is_keysend: bool, best_block_height: u32 + &mut self, recipient_onion: &RecipientOnionFields, is_keysend: bool, best_block_height: u32, ) -> Result<(), ()> { let keysend_preimage_opt = is_keysend.then(|| PaymentPreimage([42; 32])); onion_utils::set_max_path_length( - self, recipient_onion, keysend_preimage_opt, best_block_height + self, + recipient_onion, + keysend_preimage_opt, + best_block_height, ) } } @@ -737,9 +824,10 @@ impl Writeable for PaymentParameters { match &self.payee { Payee::Clear { route_hints, .. } => clear_hints = route_hints, Payee::Blinded { route_hints, .. } => { - let hints_iter = route_hints.iter().map(|path| (&path.payinfo, path.inner_blinded_path())); + let hints_iter = + route_hints.iter().map(|path| (&path.payinfo, path.inner_blinded_path())); blinded_hints = Some(crate::util::ser::IterableOwned(hints_iter)); - } + }, } write_tlv_fields!(writer, { (0, self.payee.node_id(), option), @@ -760,7 +848,9 @@ impl Writeable for PaymentParameters { } impl ReadableArgs for PaymentParameters { - fn read(reader: &mut R, default_final_cltv_expiry_delta: u32) -> Result { + fn read( + reader: &mut R, default_final_cltv_expiry_delta: u32, + ) -> Result { _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payee_pubkey, option), (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)), @@ -777,7 +867,9 @@ impl ReadableArgs for PaymentParameters { }); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { - if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) } + if clear_route_hints.len() != 0 || payee_pubkey.is_some() { + return Err(DecodeError::InvalidValue); + } Payee::Blinded { route_hints: blinded_route_hints .into_iter() @@ -794,19 +886,28 @@ impl ReadableArgs for PaymentParameters { } }; Ok(Self { - max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)), + max_total_cltv_expiry_delta: _init_tlv_based_struct_field!( + max_total_cltv_expiry_delta, + (default_value, unused) + ), max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)), payee, - max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)), + max_channel_saturation_power_of_half: _init_tlv_based_struct_field!( + max_channel_saturation_power_of_half, + (default_value, unused) + ), expiry_time, previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()), - previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()), - max_path_length: _init_tlv_based_struct_field!(max_path_length, (default_value, unused)), + previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs + .unwrap_or(Vec::new()), + max_path_length: _init_tlv_based_struct_field!( + max_path_length, + (default_value, unused) + ), }) } } - impl PaymentParameters { /// Creates a payee with the node id of the given `pubkey`. /// @@ -814,7 +915,12 @@ impl PaymentParameters { /// provided. pub fn from_node_id(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self { Self { - payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![], features: None, final_cltv_expiry_delta }, + payee: Payee::Clear { + node_id: payee_pubkey, + route_hints: vec![], + features: None, + final_cltv_expiry_delta, + }, expiry_time: None, max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, max_path_count: DEFAULT_MAX_PATH_COUNT, @@ -836,10 +942,14 @@ impl PaymentParameters { /// [`RecipientOnionFields::secret_only`]. /// /// [`RecipientOnionFields::secret_only`]: crate::ln::channelmanager::RecipientOnionFields::secret_only - pub fn for_keysend(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32, allow_mpp: bool) -> Self { + pub fn for_keysend( + payee_pubkey: PublicKey, final_cltv_expiry_delta: u32, allow_mpp: bool, + ) -> Self { Self::from_node_id(payee_pubkey, final_cltv_expiry_delta) .with_bolt11_features(Bolt11InvoiceFeatures::for_keysend(allow_mpp)) - .expect("PaymentParameters::from_node_id should always initialize the payee as unblinded") + .expect( + "PaymentParameters::from_node_id should always initialize the payee as unblinded", + ) } /// Creates parameters for paying to a blinded payee from the provided invoice. Sets @@ -847,8 +957,11 @@ impl PaymentParameters { /// [`PaymentParameters::expiry_time`]. pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self { Self::blinded(invoice.payment_paths().to_vec()) - .with_bolt12_features(invoice.invoice_features().clone()).unwrap() - .with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs())) + .with_bolt12_features(invoice.invoice_features().clone()) + .unwrap() + .with_expiry_time( + invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()), + ) } /// Creates parameters for paying to a blinded payee from the provided invoice. Sets @@ -857,8 +970,11 @@ impl PaymentParameters { #[cfg(async_payments)] pub fn from_static_invoice(invoice: &StaticInvoice) -> Self { Self::blinded(invoice.payment_paths().to_vec()) - .with_bolt12_features(invoice.invoice_features().clone()).unwrap() - .with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs())) + .with_bolt12_features(invoice.invoice_features().clone()) + .unwrap() + .with_expiry_time( + invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()), + ) } /// Creates parameters for paying to a blinded payee from the provided blinded route hints. @@ -882,8 +998,9 @@ impl PaymentParameters { pub fn with_bolt12_features(self, features: Bolt12InvoiceFeatures) -> Result { match self.payee { Payee::Clear { .. } => Err(()), - Payee::Blinded { route_hints, .. } => + Payee::Blinded { route_hints, .. } => { Ok(Self { payee: Payee::Blinded { route_hints, features: Some(features) }, ..self }) + }, } } @@ -894,12 +1011,15 @@ impl PaymentParameters { pub fn with_bolt11_features(self, features: Bolt11InvoiceFeatures) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } => - Ok(Self { - payee: Payee::Clear { - route_hints, node_id, features: Some(features), final_cltv_expiry_delta - }, ..self - }) + Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } => Ok(Self { + payee: Payee::Clear { + route_hints, + node_id, + features: Some(features), + final_cltv_expiry_delta, + }, + ..self + }), } } @@ -910,12 +1030,10 @@ impl PaymentParameters { pub fn with_route_hints(self, route_hints: Vec) -> Result { match self.payee { Payee::Blinded { .. } => Err(()), - Payee::Clear { node_id, features, final_cltv_expiry_delta, .. } => - Ok(Self { - payee: Payee::Clear { - route_hints, node_id, features, final_cltv_expiry_delta, - }, ..self - }) + Payee::Clear { node_id, features, final_cltv_expiry_delta, .. } => Ok(Self { + payee: Payee::Clear { route_hints, node_id, features, final_cltv_expiry_delta }, + ..self + }), } } @@ -944,15 +1062,19 @@ impl PaymentParameters { /// a power of 1/2. See [`PaymentParameters::max_channel_saturation_power_of_half`]. /// /// This is not exported to bindings users since bindings don't support move semantics - pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self { + pub fn with_max_channel_saturation_power_of_half( + self, max_channel_saturation_power_of_half: u8, + ) -> Self { Self { max_channel_saturation_power_of_half, ..self } } - pub(crate) fn insert_previously_failed_blinded_path(&mut self, failed_blinded_tail: &BlindedTail) { + pub(crate) fn insert_previously_failed_blinded_path( + &mut self, failed_blinded_tail: &BlindedTail, + ) { let mut found_blinded_tail = false; for (idx, path) in self.payee.blinded_route_hints().iter().enumerate() { - if &failed_blinded_tail.hops == path.blinded_hops() && - failed_blinded_tail.blinding_point == path.blinding_point() + if &failed_blinded_tail.hops == path.blinded_hops() + && failed_blinded_tail.blinding_point == path.blinding_point() { self.previously_failed_blinded_path_idxs.push(idx as u64); found_blinded_tail = true; @@ -1011,8 +1133,12 @@ impl Payee { } fn supports_basic_mpp(&self) -> bool { match self { - Self::Clear { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), - Self::Blinded { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), + Self::Clear { features, .. } => { + features.as_ref().map_or(false, |f| f.supports_basic_mpp()) + }, + Self::Blinded { features, .. } => { + features.as_ref().map_or(false, |f| f.supports_basic_mpp()) + }, } } fn features(&self) -> Option { @@ -1030,21 +1156,21 @@ impl Payee { pub(crate) fn blinded_route_hints(&self) -> &[BlindedPaymentPath] { match self { Self::Blinded { route_hints, .. } => &route_hints[..], - Self::Clear { .. } => &[] + Self::Clear { .. } => &[], } } pub(crate) fn blinded_route_hints_mut(&mut self) -> &mut [BlindedPaymentPath] { match self { Self::Blinded { route_hints, .. } => &mut route_hints[..], - Self::Clear { .. } => &mut [] + Self::Clear { .. } => &mut [], } } fn unblinded_route_hints(&self) -> &[RouteHint] { match self { Self::Blinded { .. } => &[], - Self::Clear { route_hints, .. } => &route_hints[..] + Self::Clear { route_hints, .. } => &route_hints[..], } } } @@ -1084,7 +1210,9 @@ impl<'a> Writeable for FeaturesRef<'a> { impl ReadableArgs for Features { fn read(reader: &mut R, bolt11: bool) -> Result { - if bolt11 { return Ok(Self::Bolt11(Readable::read(reader)?)) } + if bolt11 { + return Ok(Self::Bolt11(Readable::read(reader)?)); + } Ok(Self::Bolt12(Readable::read(reader)?)) } } @@ -1357,7 +1485,13 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn globally_unique_short_channel_id(&self) -> Option { match self { - CandidateRouteHop::FirstHop(hop) => if hop.details.is_announced { hop.details.short_channel_id } else { None }, + CandidateRouteHop::FirstHop(hop) => { + if hop.details.is_announced { + hop.details.short_channel_id + } else { + None + } + }, CandidateRouteHop::PublicHop(hop) => Some(hop.short_channel_id), CandidateRouteHop::PrivateHop(_) => None, CandidateRouteHop::Blinded(_) => None, @@ -1430,19 +1564,18 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn fees(&self) -> RoutingFees { match self { - CandidateRouteHop::FirstHop(_) => RoutingFees { - base_msat: 0, proportional_millionths: 0, + CandidateRouteHop::FirstHop(_) => { + RoutingFees { base_msat: 0, proportional_millionths: 0 } }, CandidateRouteHop::PublicHop(hop) => hop.info.direction().fees, CandidateRouteHop::PrivateHop(hop) => hop.hint.fees, - CandidateRouteHop::Blinded(hop) => { - RoutingFees { - base_msat: hop.hint.payinfo.fee_base_msat, - proportional_millionths: hop.hint.payinfo.fee_proportional_millionths - } + CandidateRouteHop::Blinded(hop) => RoutingFees { + base_msat: hop.hint.payinfo.fee_base_msat, + proportional_millionths: hop.hint.payinfo.fee_proportional_millionths, + }, + CandidateRouteHop::OneHopBlinded(_) => { + RoutingFees { base_msat: 0, proportional_millionths: 0 } }, - CandidateRouteHop::OneHopBlinded(_) => - RoutingFees { base_msat: 0, proportional_millionths: 0 }, } } @@ -1456,12 +1589,17 @@ impl<'a> CandidateRouteHop<'a> { liquidity_msat: hop.details.next_outbound_htlc_limit_msat, }, CandidateRouteHop::PublicHop(hop) => hop.info.effective_capacity(), - CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, .. }) => - EffectiveCapacity::HintMaxHTLC { amount_msat: *max }, - CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. }) => - EffectiveCapacity::Infinite, - CandidateRouteHop::Blinded(hop) => - EffectiveCapacity::HintMaxHTLC { amount_msat: hop.hint.payinfo.htlc_maximum_msat }, + CandidateRouteHop::PrivateHop(PrivateHopCandidate { + hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, + .. + }) => EffectiveCapacity::HintMaxHTLC { amount_msat: *max }, + CandidateRouteHop::PrivateHop(PrivateHopCandidate { + hint: RouteHintHop { htlc_maximum_msat: None, .. }, + .. + }) => EffectiveCapacity::Infinite, + CandidateRouteHop::Blinded(hop) => { + EffectiveCapacity::HintMaxHTLC { amount_msat: hop.hint.payinfo.htlc_maximum_msat } + }, CandidateRouteHop::OneHopBlinded(_) => EffectiveCapacity::Infinite, } } @@ -1474,23 +1612,23 @@ impl<'a> CandidateRouteHop<'a> { match self { CandidateRouteHop::Blinded(hop) => CandidateHopId::Blinded(hop.hint_idx), CandidateRouteHop::OneHopBlinded(hop) => CandidateHopId::Blinded(hop.hint_idx), - _ => CandidateHopId::Clear((self.short_channel_id().unwrap(), self.source() < self.target().unwrap())), + _ => CandidateHopId::Clear(( + self.short_channel_id().unwrap(), + self.source() < self.target().unwrap(), + )), } } fn blinded_path(&self) -> Option<&'a BlindedPaymentPath> { match self { - CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { - Some(&hint) - }, + CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) + | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => Some(&hint), _ => None, } } fn blinded_hint_idx(&self) -> Option { match self { - Self::Blinded(BlindedPathCandidate { hint_idx, .. }) | - Self::OneHopBlinded(OneHopBlindedPathCandidate { hint_idx, .. }) => { - Some(*hint_idx) - }, + Self::Blinded(BlindedPathCandidate { hint_idx, .. }) + | Self::OneHopBlinded(OneHopBlindedPathCandidate { hint_idx, .. }) => Some(*hint_idx), _ => None, } } @@ -1593,22 +1731,23 @@ impl<'a> NodeCountersBuilder<'a> { fn select_node_counter_for_id(&mut self, node_id: NodeId) -> u32 { // For any node_id, we first have to check if its in the existing network graph, and then // ensure that we always look up in our internal map first. - self.0.network_graph.nodes().get(&node_id) - .map(|node| node.node_counter) - .unwrap_or_else(|| { - let next_node_counter = self.0.network_graph.max_node_counter() + 1 + - self.0.private_node_id_to_node_counter.len() as u32; + self.0.network_graph.nodes().get(&node_id).map(|node| node.node_counter).unwrap_or_else( + || { + let next_node_counter = self.0.network_graph.max_node_counter() + + 1 + self.0.private_node_id_to_node_counter.len() as u32; *self.0.private_node_id_to_node_counter.entry(node_id).or_insert(next_node_counter) - }) + }, + ) } - fn build(self) -> NodeCounters<'a> { self.0 } + fn build(self) -> NodeCounters<'a> { + self.0 + } } impl<'a> NodeCounters<'a> { fn max_counter(&self) -> u32 { - self.network_graph.max_node_counter() + - self.private_node_id_to_node_counter.len() as u32 + self.network_graph.max_node_counter() + self.private_node_id_to_node_counter.len() as u32 } fn private_node_counter_from_pubkey(&self, pubkey: &PublicKey) -> Option<&(NodeId, u32)> { @@ -1616,11 +1755,14 @@ impl<'a> NodeCounters<'a> { } fn node_counter_from_id(&self, node_id: &NodeId) -> Option<(&NodeId, u32)> { - self.private_node_id_to_node_counter.get_key_value(node_id).map(|(a, b)| (a, *b)) - .or_else(|| { - self.network_graph.nodes().get_key_value(node_id) + self.private_node_id_to_node_counter.get_key_value(node_id).map(|(a, b)| (a, *b)).or_else( + || { + self.network_graph + .nodes() + .get_key_value(node_id) .map(|(node_id, node)| (node_id, node.node_counter)) - }) + }, + ) } } @@ -1631,8 +1773,13 @@ fn calculate_blinded_path_intro_points<'a, L: Deref>( network_graph: &ReadOnlyNetworkGraph, logger: &L, our_node_id: NodeId, first_hop_targets: &HashMap, u32)>, ) -> Result>, LightningError> -where L::Target: Logger { - let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter() +where + L::Target: Logger, +{ + let introduction_node_id_cache = payment_params + .payee + .blinded_route_hints() + .iter() .map(|path| { match path.introduction_node() { IntroductionNode::NodeId(pubkey) => { @@ -1640,18 +1787,20 @@ where L::Target: Logger { // us (i.e. a channel counterparty or in the network graph). node_counters.node_counter_from_id(&NodeId::from_pubkey(&pubkey)) }, - IntroductionNode::DirectedShortChannelId(direction, scid) => { - path.public_introduction_node_id(network_graph) - .map(|node_id_ref| *node_id_ref) - .or_else(|| { - first_hop_targets.iter().find(|(_, (channels, _))| - channels - .iter() - .any(|details| Some(*scid) == details.get_outbound_payment_scid()) - ).map(|(cp, _)| direction.select_node_id(our_node_id, *cp)) - }) - .and_then(|node_id| node_counters.node_counter_from_id(&node_id)) - }, + IntroductionNode::DirectedShortChannelId(direction, scid) => path + .public_introduction_node_id(network_graph) + .map(|node_id_ref| *node_id_ref) + .or_else(|| { + first_hop_targets + .iter() + .find(|(_, (channels, _))| { + channels.iter().any(|details| { + Some(*scid) == details.get_outbound_payment_scid() + }) + }) + .map(|(cp, _)| direction.select_node_id(our_node_id, *cp)) + }) + .and_then(|node_id| node_counters.node_counter_from_id(&node_id)), } }) .collect::>(); @@ -1662,66 +1811,95 @@ where L::Target: Logger { if hop.src_node_id == *node_id { return Err(LightningError { err: "Route hint cannot have the payee as the source.".to_owned(), - action: ErrorAction::IgnoreError + action: ErrorAction::IgnoreError, }); } } } }, Payee::Blinded { route_hints, .. } => { - if introduction_node_id_cache.iter().all(|info_opt| info_opt.map(|(a, _)| a) == Some(&our_node_id)) { + if introduction_node_id_cache + .iter() + .all(|info_opt| info_opt.map(|(a, _)| a) == Some(&our_node_id)) + { return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError}); } - for (blinded_path, info_opt) in route_hints.iter().zip(introduction_node_id_cache.iter()) { + for (blinded_path, info_opt) in + route_hints.iter().zip(introduction_node_id_cache.iter()) + { if blinded_path.blinded_hops().len() == 0 { - return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "0-hop blinded path provided".to_owned(), + action: ErrorAction::IgnoreError, + }); } let introduction_node_id = match info_opt { None => continue, Some(info) => info.0, }; if *introduction_node_id == our_node_id { - log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring"); - } else if blinded_path.blinded_hops().len() == 1 && - route_hints - .iter().zip(introduction_node_id_cache.iter()) + log_info!( + logger, + "Got blinded path with ourselves as the introduction node, ignoring" + ); + } else if blinded_path.blinded_hops().len() == 1 + && route_hints + .iter() + .zip(introduction_node_id_cache.iter()) .filter(|(p, _)| p.blinded_hops().len() == 1) - .any(|(_, iter_info_opt)| iter_info_opt.is_some() && iter_info_opt != info_opt) - { - return Err(LightningError{err: "1-hop blinded paths must all have matching introduction node ids".to_string(), action: ErrorAction::IgnoreError}); + .any(|(_, iter_info_opt)| { + iter_info_opt.is_some() && iter_info_opt != info_opt + }) { + return Err(LightningError { + err: "1-hop blinded paths must all have matching introduction node ids" + .to_string(), + action: ErrorAction::IgnoreError, + }); } } - } + }, } Ok(introduction_node_id_cache) } #[inline] -fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_power_of_half: u8) -> u64 { +fn max_htlc_from_capacity( + capacity: EffectiveCapacity, max_channel_saturation_power_of_half: u8, +) -> u64 { let saturation_shift: u32 = max_channel_saturation_power_of_half as u32; match capacity { EffectiveCapacity::ExactLiquidity { liquidity_msat } => liquidity_msat, EffectiveCapacity::Infinite => u64::max_value(), EffectiveCapacity::Unknown => EffectiveCapacity::Unknown.as_msat(), - EffectiveCapacity::AdvertisedMaxHTLC { amount_msat } => - amount_msat.checked_shr(saturation_shift).unwrap_or(0), + EffectiveCapacity::AdvertisedMaxHTLC { amount_msat } => { + amount_msat.checked_shr(saturation_shift).unwrap_or(0) + }, // Treat htlc_maximum_msat from a route hint as an exact liquidity amount, since the invoice is // expected to have been generated from up-to-date capacity information. EffectiveCapacity::HintMaxHTLC { amount_msat } => amount_msat, - EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat } => - cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_maximum_msat), + EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat } => { + cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_maximum_msat) + }, } } -fn iter_equal(mut iter_a: I1, mut iter_b: I2) --> bool where I1::Item: PartialEq { +fn iter_equal(mut iter_a: I1, mut iter_b: I2) -> bool +where + I1::Item: PartialEq, +{ loop { let a = iter_a.next(); let b = iter_b.next(); - if a.is_none() && b.is_none() { return true; } - if a.is_none() || b.is_none() { return false; } - if a.unwrap().ne(&b.unwrap()) { return false; } + if a.is_none() && b.is_none() { + return true; + } + if a.is_none() || b.is_none() { + return false; + } + if a.unwrap().ne(&b.unwrap()) { + return false; + } } } @@ -1776,7 +1954,8 @@ struct PathBuildingHop<'a> { } const _NODE_MAP_SIZE_TWO_CACHE_LINES: usize = 128 - core::mem::size_of::>(); -const _NODE_MAP_SIZE_EXACTLY_TWO_CACHE_LINES: usize = core::mem::size_of::>() - 128; +const _NODE_MAP_SIZE_EXACTLY_TWO_CACHE_LINES: usize = + core::mem::size_of::>() - 128; impl<'a> core::fmt::Debug for PathBuildingHop<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { @@ -1789,13 +1968,18 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> { .field("total_fee_msat", &self.total_fee_msat) .field("next_hops_fee_msat", &self.next_hops_fee_msat) .field("hop_use_fee_msat", &self.hop_use_fee_msat) - .field("total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", &(&self.total_fee_msat.saturating_sub(self.next_hops_fee_msat).saturating_sub(self.hop_use_fee_msat))) + .field( + "total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", + &(&self + .total_fee_msat + .saturating_sub(self.next_hops_fee_msat) + .saturating_sub(self.hop_use_fee_msat)), + ) .field("path_penalty_msat", &self.path_penalty_msat) .field("path_htlc_minimum_msat", &self.path_htlc_minimum_msat) .field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta()); #[cfg(all(not(ldk_bench), any(test, fuzzing)))] - let debug_struct = debug_struct - .field("value_contribution_msat", &self.value_contribution_msat); + let debug_struct = debug_struct.field("value_contribution_msat", &self.value_contribution_msat); debug_struct.finish() } } @@ -1870,7 +2054,9 @@ impl<'a> PaymentPath<'a> { // set it too high just to maliciously take more fees by exploiting this // match htlc_minimum_msat logic. let mut cur_hop_transferred_amount_msat = total_fee_paid_msat + value_msat; - if let Some(extra_fees_msat) = cur_hop.candidate.htlc_minimum_msat().checked_sub(cur_hop_transferred_amount_msat) { + if let Some(extra_fees_msat) = + cur_hop.candidate.htlc_minimum_msat().checked_sub(cur_hop_transferred_amount_msat) + { // Note that there is a risk that *previous hops* (those closer to us, as we go // payee->our_node here) would exceed their htlc_maximum_msat or available balance. // @@ -1905,7 +2091,9 @@ impl<'a> PaymentPath<'a> { // Irrelevant for the first hop, as it doesn't have the previous hop, and the use of // this channel is free for us. if i != 0 { - if let Some(new_fee) = compute_fees(cur_hop_transferred_amount_msat, cur_hop.candidate.fees()) { + if let Some(new_fee) = + compute_fees(cur_hop_transferred_amount_msat, cur_hop.candidate.fees()) + { cur_hop.hop_use_fee_msat = new_fee; total_fee_paid_msat += new_fee; } else { @@ -1923,7 +2111,8 @@ impl<'a> PaymentPath<'a> { #[inline(always)] /// Calculate the fees required to route the given amount over a channel with the given fees. fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { - amount_msat.checked_mul(channel_fees.proportional_millionths as u64) + amount_msat + .checked_mul(channel_fees.proportional_millionths as u64) .and_then(|part| (channel_fees.base_msat as u64).checked_add(part / 1_000_000)) } @@ -1931,8 +2120,10 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { /// Calculate the fees required to route the given amount over a channel with the given fees, /// saturating to [`u64::max_value`]. fn compute_fees_saturating(amount_msat: u64, channel_fees: RoutingFees) -> u64 { - amount_msat.checked_mul(channel_fees.proportional_millionths as u64) - .map(|prop| prop / 1_000_000).unwrap_or(u64::max_value()) + amount_msat + .checked_mul(channel_fees.proportional_millionths as u64) + .map(|prop| prop / 1_000_000) + .unwrap_or(u64::max_value()) .saturating_add(channel_fees.base_msat as u64) } @@ -1955,9 +2146,7 @@ impl fmt::Display for LoggedPayeePubkey { "payee node id ".fmt(f)?; pk.fmt(f) }, - None => { - "blinded payee".fmt(f) - }, + None => "blinded payee".fmt(f), } } } @@ -1966,20 +2155,19 @@ struct LoggedCandidateHop<'a>(&'a CandidateRouteHop<'a>); impl<'a> fmt::Display for LoggedCandidateHop<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { - CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { + CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) + | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { "blinded route hint with introduction node ".fmt(f)?; match hint.introduction_node() { IntroductionNode::NodeId(pubkey) => write!(f, "id {}", pubkey)?, - IntroductionNode::DirectedShortChannelId(direction, scid) => { - match direction { - Direction::NodeOne => { - write!(f, "one on channel with SCID {}", scid)?; - }, - Direction::NodeTwo => { - write!(f, "two on channel with SCID {}", scid)?; - }, - } - } + IntroductionNode::DirectedShortChannelId(direction, scid) => match direction { + Direction::NodeOne => { + write!(f, "one on channel with SCID {}", scid)?; + }, + Direction::NodeTwo => { + write!(f, "two on channel with SCID {}", scid)?; + }, + }, } " and blinding point ".fmt(f)?; hint.blinding_point().fmt(f) @@ -2003,7 +2191,7 @@ impl<'a> fmt::Display for LoggedCandidateHop<'a> { #[inline] fn sort_first_hop_channels( channels: &mut Vec<&ChannelDetails>, used_liquidities: &HashMap, - recommended_value_msat: u64, our_node_pubkey: &PublicKey + recommended_value_msat: u64, our_node_pubkey: &PublicKey, ) { // Sort the first_hops channels to the same node(s) in priority order of which channel we'd // most like to use. @@ -2019,13 +2207,25 @@ fn sort_first_hop_channels( // // Available outbound balances factor in liquidity already reserved for previously found paths. channels.sort_unstable_by(|chan_a, chan_b| { - let chan_a_outbound_limit_msat = chan_a.next_outbound_htlc_limit_msat - .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_a.get_outbound_payment_scid().unwrap(), - our_node_pubkey < &chan_a.counterparty.node_id))).unwrap_or(&0)); - let chan_b_outbound_limit_msat = chan_b.next_outbound_htlc_limit_msat - .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_b.get_outbound_payment_scid().unwrap(), - our_node_pubkey < &chan_b.counterparty.node_id))).unwrap_or(&0)); - if chan_b_outbound_limit_msat < recommended_value_msat || chan_a_outbound_limit_msat < recommended_value_msat { + let chan_a_outbound_limit_msat = chan_a.next_outbound_htlc_limit_msat.saturating_sub( + *used_liquidities + .get(&CandidateHopId::Clear(( + chan_a.get_outbound_payment_scid().unwrap(), + our_node_pubkey < &chan_a.counterparty.node_id, + ))) + .unwrap_or(&0), + ); + let chan_b_outbound_limit_msat = chan_b.next_outbound_htlc_limit_msat.saturating_sub( + *used_liquidities + .get(&CandidateHopId::Clear(( + chan_b.get_outbound_payment_scid().unwrap(), + our_node_pubkey < &chan_b.counterparty.node_id, + ))) + .unwrap_or(&0), + ); + if chan_b_outbound_limit_msat < recommended_value_msat + || chan_a_outbound_limit_msat < recommended_value_msat + { // Sort in descending order chan_b_outbound_limit_msat.cmp(&chan_a_outbound_limit_msat) } else { @@ -2061,25 +2261,42 @@ fn sort_first_hop_channels( /// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed /// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph pub fn find_route( - our_node_pubkey: &PublicKey, route_params: &RouteParameters, - network_graph: &NetworkGraph, first_hops: Option<&[&ChannelDetails]>, logger: L, - scorer: &S, score_params: &S::ScoreParams, random_seed_bytes: &[u8; 32] + our_node_pubkey: &PublicKey, route_params: &RouteParameters, network_graph: &NetworkGraph, + first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, score_params: &S::ScoreParams, + random_seed_bytes: &[u8; 32], ) -> Result -where L::Target: Logger, GL::Target: Logger { +where + L::Target: Logger, + GL::Target: Logger, +{ let graph_lock = network_graph.read_only(); - let mut route = get_route(our_node_pubkey, &route_params, &graph_lock, first_hops, logger, - scorer, score_params, random_seed_bytes)?; - add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes); + let mut route = get_route( + our_node_pubkey, + &route_params, + &graph_lock, + first_hops, + logger, + scorer, + score_params, + random_seed_bytes, + )?; + add_random_cltv_offset( + &mut route, + &route_params.payment_params, + &graph_lock, + random_seed_bytes, + ); Ok(route) } pub(crate) fn get_route( - our_node_pubkey: &PublicKey, route_params: &RouteParameters, network_graph: &ReadOnlyNetworkGraph, - first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, score_params: &S::ScoreParams, - _random_seed_bytes: &[u8; 32] + our_node_pubkey: &PublicKey, route_params: &RouteParameters, + network_graph: &ReadOnlyNetworkGraph, first_hops: Option<&[&ChannelDetails]>, logger: L, + scorer: &S, score_params: &S::ScoreParams, _random_seed_bytes: &[u8; 32], ) -> Result -where L::Target: Logger { - +where + L::Target: Logger, +{ let payment_params = &route_params.payment_params; let max_path_length = core::cmp::min(payment_params.max_path_length, MAX_PATH_LENGTH_ESTIMATE); let final_value_msat = route_params.final_value_msat; @@ -2088,23 +2305,38 @@ where L::Target: Logger { // so use a dummy id for this in the blinded case. let payee_node_id_opt = payment_params.payee.node_id().map(|pk| NodeId::from_pubkey(&pk)); const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [2; 33]; - let maybe_dummy_payee_pk = payment_params.payee.node_id().unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap()); + let maybe_dummy_payee_pk = payment_params + .payee + .node_id() + .unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap()); let maybe_dummy_payee_node_id = NodeId::from_pubkey(&maybe_dummy_payee_pk); let our_node_id = NodeId::from_pubkey(&our_node_pubkey); if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) { - return Err(LightningError{err: "Cannot generate a route to ourselves".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Cannot generate a route to ourselves".to_owned(), + action: ErrorAction::IgnoreError, + }); } if our_node_id == maybe_dummy_payee_node_id { - return Err(LightningError{err: "Invalid origin node id provided, use a different one".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Invalid origin node id provided, use a different one".to_owned(), + action: ErrorAction::IgnoreError, + }); } if final_value_msat > MAX_VALUE_MSAT { - return Err(LightningError{err: "Cannot generate a route of more value than all existing satoshis".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Cannot generate a route of more value than all existing satoshis".to_owned(), + action: ErrorAction::IgnoreError, + }); } if final_value_msat == 0 { - return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Cannot send a payment of 0 msat".to_owned(), + action: ErrorAction::IgnoreError, + }); } let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0); @@ -2172,7 +2404,10 @@ where L::Target: Logger { let network_nodes = network_graph.nodes(); if payment_params.max_path_count == 0 { - return Err(LightningError{err: "Can't find a route with no paths allowed.".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Can't find a route with no paths allowed.".to_owned(), + action: ErrorAction::IgnoreError, + }); } // Allow MPP only if we have a features set from somewhere that indicates the payee supports @@ -2183,11 +2418,17 @@ where L::Target: Logger { } else if payment_params.payee.supports_basic_mpp() { true } else if let Some(payee) = payee_node_id_opt { - network_nodes.get(&payee).map_or(false, |node| node.announcement_info.as_ref().map_or(false, - |info| info.features().supports_basic_mpp())) - } else { false }; + network_nodes.get(&payee).map_or(false, |node| { + node.announcement_info + .as_ref() + .map_or(false, |info| info.features().supports_basic_mpp()) + }) + } else { + false + }; - let max_total_routing_fee_msat = route_params.max_total_routing_fee_msat.unwrap_or(u64::max_value()); + let max_total_routing_fee_msat = + route_params.max_total_routing_fee_msat.unwrap_or(u64::max_value()); let first_hop_count = first_hops.map(|hops| hops.len()).unwrap_or(0); log_trace!(logger, "Searching for a route from payer {} to {} {} MPP and {} first hops {}overriding the network graph of {} nodes and {} channels with a fee limit of {} msat", @@ -2215,7 +2456,8 @@ where L::Target: Logger { let mut node_counter_builder = NodeCountersBuilder::new(&network_graph); let payer_node_counter = node_counter_builder.select_node_counter_for_pubkey(*our_node_pubkey); - let payee_node_counter = node_counter_builder.select_node_counter_for_pubkey(maybe_dummy_payee_pk); + let payee_node_counter = + node_counter_builder.select_node_counter_for_pubkey(maybe_dummy_payee_pk); for route in payment_params.payee.unblinded_route_hints().iter() { for hop in route.0.iter() { @@ -2228,34 +2470,51 @@ where L::Target: Logger { // First cache all our direct channels so that we can insert them in the heap at startup. // Then process any blinded routes, resolving their introduction node and caching it. let mut first_hop_targets: HashMap<_, (Vec<&ChannelDetails>, u32)> = - hash_map_with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 }); + hash_map_with_capacity(if first_hops.is_some() { + first_hops.as_ref().unwrap().len() + } else { + 0 + }); if let Some(hops) = first_hops { for chan in hops { if chan.get_outbound_payment_scid().is_none() { panic!("first_hops should be filled in with usable channels, not pending ones"); } if chan.counterparty.node_id == *our_node_pubkey { - return Err(LightningError{err: "First hop cannot have our_node_pubkey as a destination.".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "First hop cannot have our_node_pubkey as a destination.".to_owned(), + action: ErrorAction::IgnoreError, + }); } let counterparty_id = NodeId::from_pubkey(&chan.counterparty.node_id); first_hop_targets .entry(counterparty_id) .or_insert_with(|| { // Make sure there's a counter assigned for the counterparty - let node_counter = node_counter_builder.select_node_counter_for_id(counterparty_id); + let node_counter = + node_counter_builder.select_node_counter_for_id(counterparty_id); (Vec::new(), node_counter) }) - .0.push(chan); + .0 + .push(chan); } if first_hop_targets.is_empty() { - return Err(LightningError{err: "Cannot route when there are no outbound routes away from us".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Cannot route when there are no outbound routes away from us".to_owned(), + action: ErrorAction::IgnoreError, + }); } } let node_counters = node_counter_builder.build(); let introduction_node_id_cache = calculate_blinded_path_intro_points( - &payment_params, &node_counters, network_graph, &logger, our_node_id, &first_hop_targets, + &payment_params, + &node_counters, + network_graph, + &logger, + our_node_id, + &first_hop_targets, )?; // The main heap containing all candidate next-hops sorted by their score (max(fee, @@ -2291,7 +2550,8 @@ where L::Target: Logger { // This requirement is currently set to be 1/max_path_count of the payment // value to ensure we only ever return routes that do not violate this limit. let minimal_value_contribution_msat: u64 = if allow_mpp { - (final_value_msat + (payment_params.max_path_count as u64 - 1)) / payment_params.max_path_count as u64 + (final_value_msat + (payment_params.max_path_count as u64 - 1)) + / payment_params.max_path_count as u64 } else { final_value_msat }; @@ -2314,12 +2574,21 @@ where L::Target: Logger { let mut already_collected_value_msat = 0; for (_, (channels, _)) in first_hop_targets.iter_mut() { - sort_first_hop_channels(channels, &used_liquidities, recommended_value_msat, - our_node_pubkey); + sort_first_hop_channels( + channels, + &used_liquidities, + recommended_value_msat, + our_node_pubkey, + ); } - log_trace!(logger, "Building path from {} to payer {} for value {} msat.", - LoggedPayeePubkey(payment_params.payee.node_id()), our_node_pubkey, final_value_msat); + log_trace!( + logger, + "Building path from {} to payer {} for value {} msat.", + LoggedPayeePubkey(payment_params.payee.node_id()), + our_node_pubkey, + final_value_msat + ); // Remember how many candidates we ignored to allow for some logging afterwards. let mut num_ignored_value_contribution: u32 = 0; @@ -2684,17 +2953,26 @@ where L::Target: Logger { if !skip_node { if is_first_hop_target { - if let Some((first_channels, peer_node_counter)) = first_hop_targets.get(&$node_id) { + if let Some((first_channels, peer_node_counter)) = + first_hop_targets.get(&$node_id) + { for details in first_channels { debug_assert_eq!(*peer_node_counter, $node.node_counter); let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { - details, payer_node_id: &our_node_id, payer_node_counter, + details, + payer_node_id: &our_node_id, + payer_node_counter, target_node_counter: $node.node_counter, }); - add_entry!(&candidate, fee_to_target_msat, + 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); + next_hops_path_htlc_minimum_msat, + next_hops_path_penalty_msat, + $next_hops_cltv_delta, + $next_hops_path_length + ); } } } @@ -2709,19 +2987,24 @@ where L::Target: Logger { for chan_id in $node.channels.iter() { let chan = network_channels.get(chan_id).unwrap(); if !chan.features.requires_unknown_bits() { - if let Some((directed_channel, source)) = chan.as_directed_to(&$node_id) { + if let Some((directed_channel, source)) = chan.as_directed_to(&$node_id) + { if first_hops.is_none() || *source != our_node_id { if directed_channel.direction().enabled { - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info: directed_channel, - short_channel_id: *chan_id, - }); - add_entry!(&candidate, + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { + info: directed_channel, + short_channel_id: *chan_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); + $next_hops_cltv_delta, + $next_hops_path_length + ); } } } @@ -2772,19 +3055,26 @@ where L::Target: Logger { // If first hop is a private channel and the only way to reach the payee, this is the only // place where it could be added. - payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|(first_channels, peer_node_counter)| { - debug_assert_eq!(*peer_node_counter, payee_node_counter); - for details in first_channels { - let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { - details, payer_node_id: &our_node_id, payer_node_counter, - target_node_counter: payee_node_counter, - }); - 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)); - } - })); + payee_node_id_opt.map(|payee| { + first_hop_targets.get(&payee).map(|(first_channels, peer_node_counter)| { + debug_assert_eq!(*peer_node_counter, payee_node_counter); + for details in first_channels { + let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { + details, + payer_node_id: &our_node_id, + payer_node_counter, + target_node_counter: payee_node_counter, + }); + 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) + ); + } + }) + }); // Add the payee as a target, so that the payee-to-payer // search algorithm knows what to start with. @@ -2813,43 +3103,72 @@ where L::Target: Logger { // we have a direct channel to the first hop or the first hop is // in the regular network graph. let source_node_opt = introduction_node_id_cache[hint_idx]; - let (source_node_id, source_node_counter) = if let Some(v) = source_node_opt { v } else { continue }; - if our_node_id == *source_node_id { continue } + let (source_node_id, source_node_counter) = + if let Some(v) = source_node_opt { v } else { continue }; + if our_node_id == *source_node_id { + continue; + } let candidate = if hint.blinded_hops().len() == 1 { - CandidateRouteHop::OneHopBlinded( - OneHopBlindedPathCandidate { source_node_counter, source_node_id, hint, hint_idx } - ) + CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { + source_node_counter, + source_node_id, + hint, + hint_idx, + }) } else { - CandidateRouteHop::Blinded(BlindedPathCandidate { source_node_counter, source_node_id, hint, hint_idx }) + CandidateRouteHop::Blinded(BlindedPathCandidate { + source_node_counter, + source_node_id, + hint, + hint_idx, + }) }; let mut path_contribution_msat = path_value_msat; - if let Some(hop_used_msat) = add_entry!(&candidate, - 0, path_contribution_msat, 0, 0_u64, 0, 0) + if let Some(hop_used_msat) = + add_entry!(&candidate, 0, path_contribution_msat, 0, 0_u64, 0, 0) { path_contribution_msat = hop_used_msat; - } else { continue } - if let Some((first_channels, peer_node_counter)) = first_hop_targets.get_mut(source_node_id) { + } else { + continue; + } + if let Some((first_channels, peer_node_counter)) = + first_hop_targets.get_mut(source_node_id) + { sort_first_hop_channels( - first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + first_channels, + &used_liquidities, + recommended_value_msat, + our_node_pubkey, ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { - details, payer_node_id: &our_node_id, payer_node_counter, + details, + payer_node_id: &our_node_id, + payer_node_counter, target_node_counter: *peer_node_counter, }); - let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) { - Some(fee) => fee, - None => continue - }; + 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, blinded_path_fee, path_contribution_msat, path_min, - 0_u64, candidate.cltv_expiry_delta(), 0); + compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()), + ); + add_entry!( + &first_hop_candidate, + blinded_path_fee, + path_contribution_msat, + path_min, + 0_u64, + candidate.cltv_expiry_delta(), + 0 + ); } } } - for route in payment_params.payee.unblinded_route_hints().iter() - .filter(|route| !route.0.is_empty()) + for route in + payment_params.payee.unblinded_route_hints().iter().filter(|route| !route.0.is_empty()) { let first_hop_src_id = NodeId::from_pubkey(&route.0.first().unwrap().src_node_id); let first_hop_src_is_reachable = @@ -2863,8 +3182,8 @@ where L::Target: Logger { // We start building the path from reverse, i.e., from payee // to the first RouteHintHop in the path. let hop_iter = route.0.iter().rev(); - let prev_hop_iter = core::iter::once(&maybe_dummy_payee_pk).chain( - route.0.iter().skip(1).rev().map(|hop| &hop.src_node_id)); + let prev_hop_iter = core::iter::once(&maybe_dummy_payee_pk) + .chain(route.0.iter().skip(1).rev().map(|hop| &hop.src_node_id)); let mut hop_used = true; let mut aggregate_next_hops_fee_msat: u64 = 0; let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0; @@ -2882,7 +3201,10 @@ where L::Target: Logger { .expect("node_counter_from_pubkey is called on all unblinded_route_hints keys during setup, so is always Some here"); if let Some((first_channels, _)) = first_hop_targets.get(target) { - if first_channels.iter().any(|d| d.outbound_scid_alias == Some(hop.short_channel_id)) { + if first_channels + .iter() + .any(|d| d.outbound_scid_alias == Some(hop.short_channel_id)) + { log_trace!(logger, "Ignoring route hint with SCID {} (and any previous) due to it being a direct channel of ours.", hop.short_channel_id); break; @@ -2892,21 +3214,30 @@ where L::Target: Logger { let candidate = network_channels .get(&hop.short_channel_id) .and_then(|channel| channel.as_directed_to(target)) - .map(|(info, _)| CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: hop.short_channel_id, - })) - .unwrap_or_else(|| CandidateRouteHop::PrivateHop(PrivateHopCandidate { - hint: hop, target_node_id: target, - source_node_counter: *private_source_node_counter, - target_node_counter: *private_target_node_counter, - })); - - 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) - { + .map(|(info, _)| { + CandidateRouteHop::PublicHop(PublicHopCandidate { + info, + short_channel_id: hop.short_channel_id, + }) + }) + .unwrap_or_else(|| { + CandidateRouteHop::PrivateHop(PrivateHopCandidate { + hint: hop, + target_node_id: target, + source_node_counter: *private_source_node_counter, + target_node_counter: *private_target_node_counter, + }) + }); + + 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 + ) { aggregate_path_contribution_msat = hop_used_msat; } else { // If this hop was not used then there is no use checking the preceding @@ -2915,40 +3246,51 @@ where L::Target: Logger { hop_used = false; } - let used_liquidity_msat = used_liquidities - .get(&candidate.id()).copied() - .unwrap_or(0); + let used_liquidity_msat = + used_liquidities.get(&candidate.id()).copied().unwrap_or(0); let channel_usage = ChannelUsage { amount_msat: final_value_msat + aggregate_next_hops_fee_msat, inflight_htlc_msat: used_liquidity_msat, effective_capacity: candidate.effective_capacity(), }; - let channel_penalty_msat = scorer.channel_penalty_msat( - &candidate, channel_usage, score_params - ); - aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat - .saturating_add(channel_penalty_msat); + let channel_penalty_msat = + scorer.channel_penalty_msat(&candidate, channel_usage, score_params); + aggregate_next_hops_path_penalty_msat = + aggregate_next_hops_path_penalty_msat.saturating_add(channel_penalty_msat); - aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta - .saturating_add(hop.cltv_expiry_delta as u32); + aggregate_next_hops_cltv_delta = + aggregate_next_hops_cltv_delta.saturating_add(hop.cltv_expiry_delta as u32); - aggregate_next_hops_path_length = aggregate_next_hops_path_length - .saturating_add(1); + aggregate_next_hops_path_length = + aggregate_next_hops_path_length.saturating_add(1); // Searching for a direct channel between last checked hop and first_hop_targets - if let Some((first_channels, peer_node_counter)) = first_hop_targets.get_mut(target) { + if let Some((first_channels, peer_node_counter)) = + first_hop_targets.get_mut(target) + { sort_first_hop_channels( - first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + first_channels, + &used_liquidities, + recommended_value_msat, + our_node_pubkey, ); for details in first_channels { - let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { - details, payer_node_id: &our_node_id, payer_node_counter, - target_node_counter: *peer_node_counter, - }); - 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); + let first_hop_candidate = + CandidateRouteHop::FirstHop(FirstHopCandidate { + details, + payer_node_id: &our_node_id, + payer_node_counter, + target_node_counter: *peer_node_counter, + }); + 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 + ); } } @@ -2961,18 +3303,33 @@ where L::Target: Logger { // for the last node in the RouteHint. We need to just add the fees to // route through the current node so that the preceding node (next iteration) // can use it. - let hops_fee = compute_fees(aggregate_next_hops_fee_msat + final_value_msat, hop.fees) - .map_or(None, |inc| inc.checked_add(aggregate_next_hops_fee_msat)); - aggregate_next_hops_fee_msat = if let Some(val) = hops_fee { val } else { break; }; + let hops_fee = + compute_fees(aggregate_next_hops_fee_msat + final_value_msat, hop.fees) + .map_or(None, |inc| inc.checked_add(aggregate_next_hops_fee_msat)); + aggregate_next_hops_fee_msat = if let Some(val) = hops_fee { + val + } else { + break; + }; // The next channel will need to relay this channel's min_htlc *plus* the fees taken by // this route hint's source node to forward said min over this channel. aggregate_next_hops_path_htlc_minimum_msat = { let curr_htlc_min = cmp::max( - candidate.htlc_minimum_msat(), aggregate_next_hops_path_htlc_minimum_msat + candidate.htlc_minimum_msat(), + aggregate_next_hops_path_htlc_minimum_msat, ); - let curr_htlc_min_fee = if let Some(val) = compute_fees(curr_htlc_min, hop.fees) { val } else { break }; - if let Some(min) = curr_htlc_min.checked_add(curr_htlc_min_fee) { min } else { break } + let curr_htlc_min_fee = + if let Some(val) = compute_fees(curr_htlc_min, hop.fees) { + val + } else { + break; + }; + if let Some(min) = curr_htlc_min.checked_add(curr_htlc_min_fee) { + min + } else { + break; + } }; if idx == route.0.len() - 1 { @@ -2985,22 +3342,32 @@ where L::Target: Logger { // Note that we *must* check if the last hop was added as `add_entry` // always assumes that the third argument is a node to which we have a // path. - if let Some((first_channels, peer_node_counter)) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) { + if let Some((first_channels, peer_node_counter)) = + first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) + { sort_first_hop_channels( - first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + first_channels, + &used_liquidities, + recommended_value_msat, + our_node_pubkey, ); for details in first_channels { - let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { - details, payer_node_id: &our_node_id, payer_node_counter, - target_node_counter: *peer_node_counter, - }); - add_entry!(&first_hop_candidate, + let first_hop_candidate = + CandidateRouteHop::FirstHop(FirstHopCandidate { + details, + payer_node_id: &our_node_id, + payer_node_counter, + target_node_counter: *peer_node_counter, + }); + 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); + aggregate_next_hops_path_length + ); } } } @@ -3008,7 +3375,11 @@ where L::Target: Logger { } } - log_trace!(logger, "Starting main path collection loop with {} nodes pre-filled from first/last hops.", targets.len()); + log_trace!( + logger, + "Starting main path collection loop with {} nodes pre-filled from first/last hops.", + targets.len() + ); // At this point, targets are filled with the data from first and // last hops communicated by the caller, and the payment receiver. @@ -3023,13 +3394,20 @@ where L::Target: Logger { // Both these cases (and other cases except reaching recommended_value_msat) mean that // paths_collection will be stopped because found_new_path==false. // This is not necessarily a routing failure. - 'path_construction: while let Some(RouteGraphNode { node_id, total_cltv_delta, mut value_contribution_msat, path_length_to_node, .. }) = targets.pop() { - + 'path_construction: while let Some(RouteGraphNode { + node_id, + total_cltv_delta, + mut value_contribution_msat, + path_length_to_node, + .. + }) = targets.pop() + { // Since we're going payee-to-payer, hitting our node as a target means we should stop // traversing the graph and arrange the path out of what we found. if node_id == our_node_id { let mut new_entry = dist[payer_node_counter as usize].take().unwrap(); - let mut ordered_hops: Vec<(PathBuildingHop, NodeFeatures)> = vec!((new_entry.clone(), default_node_features.clone())); + let mut ordered_hops: Vec<(PathBuildingHop, NodeFeatures)> = + vec![(new_entry.clone(), default_node_features.clone())]; 'path_walk: loop { let mut features_set = false; @@ -3038,11 +3416,16 @@ where L::Target: Logger { let target_node_counter = candidate.target_node_counter(); if let Some((first_channels, _)) = first_hop_targets.get(&target) { for details in first_channels { - if let CandidateRouteHop::FirstHop(FirstHopCandidate { details: last_hop_details, .. }) - = candidate + if let CandidateRouteHop::FirstHop(FirstHopCandidate { + details: last_hop_details, + .. + }) = candidate { - if details.get_outbound_payment_scid() == last_hop_details.get_outbound_payment_scid() { - ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context(); + if details.get_outbound_payment_scid() + == last_hop_details.get_outbound_payment_scid() + { + ordered_hops.last_mut().unwrap().1 = + details.counterparty.features.to_context(); features_set = true; break; } @@ -3070,7 +3453,9 @@ where L::Target: Logger { if target_node_counter.is_none() { break 'path_walk; } - if target_node_counter == Some(payee_node_counter) { break 'path_walk; } + if target_node_counter == Some(payee_node_counter) { + break 'path_walk; + } new_entry = match dist[target_node_counter.unwrap() as usize].take() { Some(payment_hop) => payment_hop, @@ -3090,7 +3475,7 @@ where L::Target: Logger { log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: \n {:#?}", ordered_hops.len(), value_contribution_msat, ordered_hops.iter().map(|h| &(h.0)).collect::>()); - let mut payment_path = PaymentPath {hops: ordered_hops}; + let mut payment_path = PaymentPath { hops: ordered_hops }; // We could have possibly constructed a slightly inconsistent path: since we reduce // value being transferred along the way, we could have violated htlc_minimum_msat @@ -3098,8 +3483,10 @@ where L::Target: Logger { // recompute the fees again, so that if that's the case, we match the currently // underpaid htlc_minimum_msat with fees. debug_assert_eq!(payment_path.get_value_msat(), value_contribution_msat); - let desired_value_contribution = cmp::min(value_contribution_msat, final_value_msat); - value_contribution_msat = payment_path.update_value_and_recompute_fees(desired_value_contribution); + let desired_value_contribution = + cmp::min(value_contribution_msat, final_value_msat); + value_contribution_msat = + payment_path.update_value_and_recompute_fees(desired_value_contribution); // Since a path allows to transfer as much value as // the smallest channel it has ("bottleneck"), we should recompute @@ -3118,7 +3505,8 @@ where L::Target: Logger { .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(); - let hop_max_msat = max_htlc_from_capacity(hop_capacity, channel_saturation_pow_half); + let hop_max_msat = + max_htlc_from_capacity(hop_capacity, channel_saturation_pow_half); if *used_liquidity_msat == hop_max_msat { // If this path used all of this channel's available liquidity, we know // this path will not be selected again in the next loop iteration. @@ -3130,14 +3518,18 @@ where L::Target: Logger { // If we weren't capped by hitting a liquidity limit on a channel in the path, // we'll probably end up picking the same path again on the next iteration. // Decrease the available liquidity of a hop in the middle of the path. - let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate; + let victim_candidate = + &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate; let exhausted = u64::max_value(); log_trace!(logger, "Disabling route candidate {} for future path building iterations to avoid duplicates.", LoggedCandidateHop(victim_candidate)); 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; + *used_liquidities + .entry(CandidateHopId::Clear((scid, false))) + .or_default() = exhausted; + *used_liquidities.entry(CandidateHopId::Clear((scid, true))).or_default() = + exhausted; } } @@ -3153,7 +3545,9 @@ where L::Target: Logger { // If we found a path back to the payee, we shouldn't try to process it again. This is // the equivalent of the `elem.was_processed` check in // add_entries_to_cheapest_to_target_node!() (see comment there for more info). - if node_id == maybe_dummy_payee_node_id { continue 'path_construction; } + if node_id == maybe_dummy_payee_node_id { + continue 'path_construction; + } // Otherwise, since the current target node is not us, // keep "unrolling" the payment graph from payee to payer by @@ -3161,9 +3555,13 @@ where L::Target: Logger { match network_nodes.get(&node_id) { None => {}, Some(node) => { - add_entries_to_cheapest_to_target_node!(node, node_id, + add_entries_to_cheapest_to_target_node!( + node, + node_id, value_contribution_msat, - total_cltv_delta, path_length_to_node); + total_cltv_delta, + path_length_to_node + ); }, } } @@ -3184,14 +3582,21 @@ where L::Target: Logger { // because we deterministically terminated the search due to low liquidity. if !found_new_path && channel_saturation_pow_half != 0 { channel_saturation_pow_half = 0; - } else if !found_new_path && hit_minimum_limit && already_collected_value_msat < final_value_msat && path_value_msat != recommended_value_msat { + } else if !found_new_path + && hit_minimum_limit + && already_collected_value_msat < final_value_msat + && path_value_msat != recommended_value_msat + { log_trace!(logger, "Failed to collect enough value, but running again to collect extra paths with a potentially higher limit."); path_value_msat = recommended_value_msat; } else if already_collected_value_msat >= recommended_value_msat || !found_new_path { log_trace!(logger, "Have now collected {} msat (seeking {} msat) in paths. Last path loop {} a new path.", already_collected_value_msat, recommended_value_msat, if found_new_path { "found" } else { "did not find" }); break 'paths_collection; - } else if found_new_path && already_collected_value_msat == final_value_msat && payment_paths.len() == 1 { + } else if found_new_path + && already_collected_value_msat == final_value_msat + && payment_paths.len() == 1 + { // Further, if this was our first walk of the graph, and we weren't limited by an // htlc_minimum_msat, return immediately because this path should suffice. If we were // limited by an htlc_minimum_msat value, find another path with a higher value, @@ -3206,10 +3611,13 @@ where L::Target: Logger { } } - let num_ignored_total = num_ignored_value_contribution + num_ignored_path_length_limit + - num_ignored_cltv_delta_limit + num_ignored_previously_failed + - num_ignored_avoid_overpayment + num_ignored_htlc_minimum_msat_limit + - num_ignored_total_fee_limit; + let num_ignored_total = num_ignored_value_contribution + + num_ignored_path_length_limit + + num_ignored_cltv_delta_limit + + num_ignored_previously_failed + + num_ignored_avoid_overpayment + + num_ignored_htlc_minimum_msat_limit + + num_ignored_total_fee_limit; if num_ignored_total > 0 { log_trace!(logger, "Ignored {} candidate hops due to insufficient value contribution, {} due to path length limit, {} due to CLTV delta limit, {} due to previous payment failure, {} due to htlc_minimum_msat limit, {} to avoid overpaying, {} due to maximum total fee limit. Total: {} ignored candidates.", @@ -3221,32 +3629,41 @@ where L::Target: Logger { // Step (5). if payment_paths.len() == 0 { - return Err(LightningError{err: "Failed to find a path to the given destination".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Failed to find a path to the given destination".to_owned(), + action: ErrorAction::IgnoreError, + }); } if already_collected_value_msat < final_value_msat { - return Err(LightningError{err: "Failed to find a sufficient route to the given destination".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Failed to find a sufficient route to the given destination".to_owned(), + action: ErrorAction::IgnoreError, + }); } // Step (6). let mut selected_route = payment_paths; - debug_assert_eq!(selected_route.iter().map(|p| p.get_value_msat()).sum::(), already_collected_value_msat); + debug_assert_eq!( + selected_route.iter().map(|p| p.get_value_msat()).sum::(), + already_collected_value_msat + ); let mut overpaid_value_msat = already_collected_value_msat - final_value_msat; // First, sort by the cost-per-value of the path, dropping the paths that cost the most for // the value they contribute towards the payment amount. // We sort in descending order as we will remove from the front in `retain`, next. - selected_route.sort_unstable_by(|a, b| + selected_route.sort_unstable_by(|a, b| { (((b.get_cost_msat() as u128) << 64) / (b.get_value_msat() as u128)) .cmp(&(((a.get_cost_msat() as u128) << 64) / (a.get_value_msat() as u128))) - ); + }); // We should make sure that at least 1 path left. let mut paths_left = selected_route.len(); selected_route.retain(|path| { if paths_left == 1 { - return true + return true; } let path_value_msat = path.get_value_msat(); if path_value_msat <= overpaid_value_msat { @@ -3264,15 +3681,24 @@ where L::Target: Logger { // TODO: this could also be optimized by also sorting by feerate_per_sat_routed, // so that the sender pays less fees overall. And also htlc_minimum_msat. selected_route.sort_unstable_by(|a, b| { - let a_f = a.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::(); - let b_f = b.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::(); + let a_f = a + .hops + .iter() + .map(|hop| hop.0.candidate.fees().proportional_millionths as u64) + .sum::(); + let b_f = b + .hops + .iter() + .map(|hop| hop.0.candidate.fees().proportional_millionths as u64) + .sum::(); a_f.cmp(&b_f).then_with(|| b.get_cost_msat().cmp(&a.get_cost_msat())) }); let expensive_payment_path = selected_route.first_mut().unwrap(); // We already dropped all the paths with value below `overpaid_value_msat` above, thus this // can't go negative. - let expensive_path_new_value_msat = expensive_payment_path.get_value_msat() - overpaid_value_msat; + let expensive_path_new_value_msat = + expensive_payment_path.get_value_msat() - overpaid_value_msat; expensive_payment_path.update_value_and_recompute_fees(expensive_path_new_value_msat); } @@ -3282,18 +3708,26 @@ where L::Target: Logger { // compare both SCIDs and NodeIds as individual nodes may use random aliases causing collisions // across nodes. 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()); - for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id()).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(), 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(); + if idx + 1 >= selected_route.len() { + break; + } + 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); } @@ -3302,10 +3736,11 @@ 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()); - for (hop, node_features) in payment_path.hops.iter() - .filter(|(h, _)| h.candidate.short_channel_id().is_some()) + 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 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 @@ -3317,14 +3752,20 @@ 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(&target).map_or(false, |hop_node| - hop_node.channels.iter().any(|scid| network_graph.channel(*scid) - .map_or(false, |c| c.as_directed_from(&hop.candidate.source()).is_some())) - ) + 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(&hop.candidate.source()).is_some() + }) + }) + }) }; hops.push(RouteHop { - pubkey: PublicKey::from_slice(target.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &target), 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(), @@ -3343,7 +3784,9 @@ where L::Target: Logger { excess_final_cltv_expiry_delta: 0, final_value_msat: h.fee_msat, }) - } else { None } + } else { + None + } }); // Propagate the cltv_expiry_delta one hop backwards since the delta from the current hop is // applicable for the previous hop. @@ -3367,8 +3810,13 @@ where L::Target: Logger { // Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat. if let Some(max_total_routing_fee_msat) = route_params.max_total_routing_fee_msat { if route.get_total_fees() > max_total_routing_fee_msat { - return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat", - max_total_routing_fee_msat), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: format!( + "Failed to find route that adheres to the maximum total fee limit of {}msat", + max_total_routing_fee_msat + ), + action: ErrorAction::IgnoreError, + }); } } @@ -3380,8 +3828,9 @@ where L::Target: Logger { // destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network // graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the // payment path by adding a randomized 'shadow route' offset to the final hop. -fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, - network_graph: &ReadOnlyNetworkGraph, random_seed_bytes: &[u8; 32] +fn add_random_cltv_offset( + route: &mut Route, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph, + random_seed_bytes: &[u8; 32], ) { let network_channels = network_graph.channels(); let network_nodes = network_graph.nodes(); @@ -3391,17 +3840,23 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, // Remember the last three nodes of the random walk and avoid looping back on them. // Init with the last three nodes from the actual path, if possible. - let mut nodes_to_avoid: [NodeId; 3] = [NodeId::from_pubkey(&path.hops.last().unwrap().pubkey), + let mut nodes_to_avoid: [NodeId; 3] = [ + NodeId::from_pubkey(&path.hops.last().unwrap().pubkey), NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(2)).unwrap().pubkey), - NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(3)).unwrap().pubkey)]; + NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(3)).unwrap().pubkey), + ]; // Choose the last publicly known node as the starting point for the random walk. let mut cur_hop: Option = None; let mut path_nonce = [0u8; 12]; - if let Some(starting_hop) = path.hops.iter().rev() - .find(|h| network_nodes.contains_key(&NodeId::from_pubkey(&h.pubkey))) { - cur_hop = Some(NodeId::from_pubkey(&starting_hop.pubkey)); - path_nonce.copy_from_slice(&cur_hop.unwrap().as_slice()[..12]); + if let Some(starting_hop) = path + .hops + .iter() + .rev() + .find(|h| network_nodes.contains_key(&NodeId::from_pubkey(&h.pubkey))) + { + cur_hop = Some(NodeId::from_pubkey(&starting_hop.pubkey)); + path_nonce.copy_from_slice(&cur_hop.unwrap().as_slice()[..12]); } // Init PRNG with the path-dependant nonce, which is static for private paths. @@ -3410,7 +3865,8 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, // Pick a random path length in [1 .. 3] prng.process_in_place(&mut random_path_bytes); - let random_walk_length = usize::from_be_bytes(random_path_bytes).wrapping_rem(3).wrapping_add(1); + let random_walk_length = + usize::from_be_bytes(random_path_bytes).wrapping_rem(3).wrapping_add(1); for random_hop in 0..random_walk_length { // If we don't find a suitable offset in the public network graph, we default to @@ -3424,15 +3880,16 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, if let Some(random_channel) = usize::from_be_bytes(random_path_bytes) .checked_rem(cur_node.channels.len()) .and_then(|index| cur_node.channels.get(index)) - .and_then(|id| network_channels.get(id)) { - random_channel.as_directed_from(&cur_node_id).map(|(dir_info, next_id)| { - if !nodes_to_avoid.iter().any(|x| x == next_id) { - nodes_to_avoid[random_hop] = *next_id; - random_hop_offset = dir_info.direction().cltv_expiry_delta.into(); - cur_hop = Some(*next_id); - } - }); - } + .and_then(|id| network_channels.get(id)) + { + random_channel.as_directed_from(&cur_node_id).map(|(dir_info, next_id)| { + if !nodes_to_avoid.iter().any(|x| x == next_id) { + nodes_to_avoid[random_hop] = *next_id; + random_hop_offset = dir_info.direction().cltv_expiry_delta.into(); + cur_hop = Some(*next_id); + } + }); + } } } @@ -3442,26 +3899,34 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, } // Limit the total offset to reduce the worst-case locked liquidity timevalue - const MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET: u32 = 3*144; - shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET); + const MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET: u32 = 3 * 144; + shadow_ctlv_expiry_delta_offset = + cmp::min(shadow_ctlv_expiry_delta_offset, MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET); // Limit the offset so we never exceed the max_total_cltv_expiry_delta. To improve plausibility, // we choose the limit to be the largest possible multiple of MEDIAN_HOP_CLTV_EXPIRY_DELTA. let path_total_cltv_expiry_delta: u32 = path.hops.iter().map(|h| h.cltv_expiry_delta).sum(); - let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta; + let mut max_path_offset = + payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta; max_path_offset = cmp::max( max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA), - max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA); - shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset); + max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA, + ); + shadow_ctlv_expiry_delta_offset = + cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset); // Add 'shadow' CLTV offset to the final hop if let Some(tail) = path.blinded_tail.as_mut() { - tail.excess_final_cltv_expiry_delta = tail.excess_final_cltv_expiry_delta - .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(tail.excess_final_cltv_expiry_delta); + tail.excess_final_cltv_expiry_delta = tail + .excess_final_cltv_expiry_delta + .checked_add(shadow_ctlv_expiry_delta_offset) + .unwrap_or(tail.excess_final_cltv_expiry_delta); } if let Some(last_hop) = path.hops.last_mut() { - last_hop.cltv_expiry_delta = last_hop.cltv_expiry_delta - .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(last_hop.cltv_expiry_delta); + last_hop.cltv_expiry_delta = last_hop + .cltv_expiry_delta + .checked_add(shadow_ctlv_expiry_delta_offset) + .unwrap_or(last_hop.cltv_expiry_delta); } } } @@ -3472,21 +3937,37 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, /// Re-uses logic from `find_route`, so the restrictions described there also apply here. pub fn build_route_from_hops( our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters, - network_graph: &NetworkGraph, logger: L, random_seed_bytes: &[u8; 32] + network_graph: &NetworkGraph, logger: L, random_seed_bytes: &[u8; 32], ) -> Result -where L::Target: Logger, GL::Target: Logger { +where + L::Target: Logger, + GL::Target: Logger, +{ let graph_lock = network_graph.read_only(); - let mut route = build_route_from_hops_internal(our_node_pubkey, hops, &route_params, - &graph_lock, logger, random_seed_bytes)?; - add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes); + let mut route = build_route_from_hops_internal( + our_node_pubkey, + hops, + &route_params, + &graph_lock, + logger, + random_seed_bytes, + )?; + add_random_cltv_offset( + &mut route, + &route_params.payment_params, + &graph_lock, + random_seed_bytes, + ); Ok(route) } fn build_route_from_hops_internal( our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters, network_graph: &ReadOnlyNetworkGraph, logger: L, random_seed_bytes: &[u8; 32], -) -> Result where L::Target: Logger { - +) -> Result +where + L::Target: Logger, +{ struct HopScorer { our_node_id: NodeId, hop_ids: [Option; MAX_PATH_LENGTH_ESTIMATE as usize], @@ -3494,9 +3975,10 @@ fn build_route_from_hops_internal( impl ScoreLookUp for HopScorer { type ScoreParams = (); - fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, - _usage: ChannelUsage, _score_params: &Self::ScoreParams) -> u64 - { + 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] { @@ -3520,7 +4002,10 @@ fn build_route_from_hops_internal( } if hops.len() > MAX_PATH_LENGTH_ESTIMATE.into() { - return Err(LightningError{err: "Cannot build a route exceeding the maximum path length.".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Cannot build a route exceeding the maximum path length.".to_owned(), + action: ErrorAction::IgnoreError, + }); } let our_node_id = NodeId::from_pubkey(our_node_pubkey); @@ -3531,50 +4016,70 @@ fn build_route_from_hops_internal( let scorer = HopScorer { our_node_id, hop_ids }; - get_route(our_node_pubkey, route_params, network_graph, None, logger, &scorer, &Default::default(), random_seed_bytes) + get_route( + our_node_pubkey, + route_params, + network_graph, + None, + logger, + &scorer, + &Default::default(), + random_seed_bytes, + ) } #[cfg(test)] mod tests { - use crate::blinded_path::BlindedHop; use crate::blinded_path::payment::{BlindedPayInfo, BlindedPaymentPath}; - use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; - 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, CandidateRouteHop, PublicHopCandidate}; - 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::blinded_path::BlindedHop; use crate::chain::transaction::OutPoint; + use crate::crypto::chacha20::ChaCha20; use crate::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelShutdownState}; - use crate::ln::types::ChannelId; + use crate::ln::channelmanager; use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; - use crate::ln::channelmanager; + use crate::ln::types::ChannelId; + use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, P2PGossipSync}; + use crate::routing::router::{ + add_random_cltv_offset, build_route_from_hops_internal, default_node_features, get_route, + BlindedTail, CandidateRouteHop, InFlightHtlcs, Path, PaymentParameters, PublicHopCandidate, + Route, RouteHint, RouteHintHop, RouteHop, RouteParameters, RoutingFees, + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, + }; + use crate::routing::scoring::{ + ChannelUsage, FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringDecayParameters, + ProbabilisticScoringFeeParameters, ScoreLookUp, + }; + use crate::routing::test_utils::{ + add_channel, add_or_update_node, build_graph, build_line_graph, get_nodes, + id_to_feature_flags, update_channel, + }; + use crate::routing::utxo::UtxoResult; use crate::util::config::UserConfig; - use crate::util::test_utils as ln_test_utils; - use crate::crypto::chacha20::ChaCha20; - use crate::util::ser::{FixedLengthReader, Readable, ReadableArgs, Writeable}; #[cfg(c_bindings)] use crate::util::ser::Writer; + use crate::util::ser::{FixedLengthReader, Readable, ReadableArgs, Writeable}; + use crate::util::test_utils as ln_test_utils; use bitcoin::amount::Amount; + use bitcoin::constants::ChainHash; use bitcoin::hashes::Hash; + use bitcoin::hex::FromHex; use bitcoin::network::Network; - use bitcoin::constants::ChainHash; - use bitcoin::script::Builder; use bitcoin::opcodes; - use bitcoin::transaction::TxOut; - use bitcoin::hex::FromHex; - use bitcoin::secp256k1::{PublicKey,SecretKey}; + use bitcoin::script::Builder; use bitcoin::secp256k1::Secp256k1; + use bitcoin::secp256k1::{PublicKey, SecretKey}; + use bitcoin::transaction::TxOut; use crate::io::Cursor; use crate::prelude::*; use crate::sync::Arc; - fn get_channel_details(short_channel_id: Option, node_id: PublicKey, - features: InitFeatures, outbound_capacity_msat: u64) -> ChannelDetails { + fn get_channel_details( + short_channel_id: Option, node_id: PublicKey, features: InitFeatures, + outbound_capacity_msat: u64, + ) -> ChannelDetails { #[allow(deprecated)] // TODO: Remove once balance_msat is removed. ChannelDetails { channel_id: ChannelId::new_zero(), @@ -3586,7 +4091,10 @@ mod tests { outbound_htlc_minimum_msat: None, outbound_htlc_maximum_msat: None, }, - funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), + funding_txo: Some(OutPoint { + txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), + index: 0, + }), channel_type: None, short_channel_id, outbound_scid_alias: None, @@ -3601,8 +4109,10 @@ mod tests { confirmations_required: None, confirmations: None, force_close_spend_delay: None, - is_outbound: true, is_channel_ready: true, - is_usable: true, is_announced: true, + is_outbound: true, + is_channel_ready: true, + is_usable: true, + is_announced: true, inbound_htlc_minimum_msat: None, inbound_htlc_maximum_msat: None, config: None, @@ -3615,22 +4125,33 @@ mod tests { fn dummy_blinded_path(intro_node: PublicKey, payinfo: BlindedPayInfo) -> BlindedPaymentPath { BlindedPaymentPath::from_raw( - intro_node, ln_test_utils::pubkey(42), + intro_node, + ln_test_utils::pubkey(42), vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() } + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 as u8), + encrypted_payload: Vec::new(), + }, + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 as u8), + encrypted_payload: Vec::new(), + }, ], - payinfo + payinfo, ) } - fn dummy_one_hop_blinded_path(intro_node: PublicKey, payinfo: BlindedPayInfo) -> BlindedPaymentPath { + fn dummy_one_hop_blinded_path( + intro_node: PublicKey, payinfo: BlindedPayInfo, + ) -> BlindedPaymentPath { BlindedPaymentPath::from_raw( - intro_node, ln_test_utils::pubkey(42), - vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, - ], - payinfo + intro_node, + ln_test_utils::pubkey(42), + vec![BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 as u8), + encrypted_payload: Vec::new(), + }], + payinfo, ) } @@ -3644,18 +4165,36 @@ mod tests { // Simple route to 2 via 1 - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 0); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Cannot send a payment of 0 msat"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 0); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Cannot send a payment of 0 msat"); + } else { + panic!(); + } payment_params.max_path_length = 2; let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -3673,8 +4212,17 @@ mod tests { assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4)); route_params.payment_params.max_path_length = 1; - get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err(); + get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap_err(); } #[test] @@ -3687,17 +4235,40 @@ mod tests { // Simple route to 2 via 1 - let our_chans = vec![get_channel_details(Some(2), our_id, InitFeatures::from_le_bytes(vec![0b11]), 100000)]; + let our_chans = vec![get_channel_details( + Some(2), + our_id, + InitFeatures::from_le_bytes(vec![0b11]), + 100000, + )]; let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), Some(&our_chans.iter().collect::>()), - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "First hop cannot have our_node_pubkey as a destination."); - } else { panic!(); } - - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "First hop cannot have our_node_pubkey as a destination."); + } else { + panic!(); + } + + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); } @@ -3712,131 +4283,189 @@ mod tests { // Simple route to 2 via 1 // Disable other paths - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Check against amount_to_transfer_over_msat. // Set minimal HTLC of 200_000_000 msat. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 200_000_000, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 200_000_000, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Second hop only allows to forward 199_999_999 at most, thus not allowing the first hop to // be used. - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 199_999_999, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 199_999_999, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Not possible to send 199_999_999, because the minimum on channel=2 is 200_000_000. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 199_999_999); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 199_999_999); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { + panic!(); + } // Lift the restriction on the first hop. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 4, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 4, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // A payment above the minimum should pass - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); } @@ -3854,81 +4483,115 @@ mod tests { // A route to node#2 via two paths. // One path allows transferring 35-40 sats, another one also allows 35-40 sats. // Thus, they can't send 60 without overpaying. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 35_000, - htlc_maximum_msat: 40_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 35_000, - htlc_maximum_msat: 40_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 35_000, + htlc_maximum_msat: 40_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 35_000, + htlc_maximum_msat: 40_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Make 0 fee. - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Disable other paths - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 60_000); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 60_000); route_params.max_total_routing_fee_msat = Some(15_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); // Overpay fees to hit htlc_minimum_msat. let overpaid_fees = route.paths[0].hops[0].fee_msat + route.paths[1].hops[0].fee_msat; // TODO: this could be better balanced to overpay 10k and not 15k. @@ -3936,58 +4599,91 @@ mod tests { // Now, test that if there are 2 paths, a "cheaper" by fee path wouldn't be prioritized // while taking even more fee to match htlc_minimum_msat. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 4, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 65_000, - htlc_maximum_msat: 80_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 4, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 100_000, - excess_data: Vec::new() - }); - - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); - // Fine to overpay for htlc_minimum_msat if it allows us to save fee. - assert_eq!(route.paths.len(), 1); - assert_eq!(route.paths[0].hops[0].short_channel_id, 12); - let fees = route.paths[0].hops[0].fee_msat; - assert_eq!(fees, 5_000); - - let route_params = RouteParameters::from_payment_params_and_value(payment_params, 50_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); - // Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 4, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 65_000, + htlc_maximum_msat: 80_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 4, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 100_000, + excess_data: Vec::new(), + }, + ); + + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); + // Fine to overpay for htlc_minimum_msat if it allows us to save fee. + assert_eq!(route.paths.len(), 1); + assert_eq!(route.paths[0].hops[0].short_channel_id, 12); + let fees = route.paths[0].hops[0].fee_msat; + assert_eq!(fees, 5_000); + + let route_params = RouteParameters::from_payment_params_and_value(payment_params, 50_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); + // Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on // the other channel. assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops[0].short_channel_id, 2); @@ -4000,60 +4696,93 @@ mod tests { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx); let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[2], 42) + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap(); let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; // Route to node2 over a single path which requires overpaying the recipient themselves. // First disable all paths except the us -> node1 -> node2 path - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 3, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 0, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 3, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 0, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Set channel 4 to free but with a high htlc_minimum_msat - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 15_000, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 15_000, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Now check that we'll fail to find a path if we fail to find a path if the htlc_minimum // is overrun. Note that the fees are actually calculated on 3*payment amount as that's // what we try to find a route for, so this test only just happens to work out to exactly // the fee limit. - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 5_000); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 5_000); route_params.max_total_routing_fee_msat = Some(9_999); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find route that adheres to the maximum total fee limit of 9999msat"); - } else { panic!(); } - - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 5_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!( + err, + "Failed to find route that adheres to the maximum total fee limit of 9999msat" + ); + } else { + panic!(); + } + + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 5_000); route_params.max_total_routing_fee_msat = Some(10_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.get_total_fees(), 10_000); } @@ -4066,48 +4795,79 @@ mod tests { let random_seed_bytes = [42; 32]; // // Disable channels 4 and 12 by flags=2 - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // If all the channels require some features we don't understand, route should fail let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!(); } + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { + panic!(); + } // If we specify a channel to node7, that overrides our local channel view and that gets used - let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), - InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; + let our_chans = vec![get_channel_details( + Some(42), + nodes[7].clone(), + InitFeatures::from_le_bytes(vec![0b11]), + 250_000_000, + )]; route_params.payment_params.max_path_length = 2; - let route = get_route(&our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]); @@ -4142,18 +4902,39 @@ mod tests { // If all nodes require some features we don't understand, route should fail let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!(); } + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { + panic!(); + } // If we specify a channel to node7, that overrides our local channel view and that gets used - let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), - InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; - let route = get_route(&our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let our_chans = vec![get_channel_details( + Some(42), + nodes[7].clone(), + InitFeatures::from_le_bytes(vec![0b11]), + 250_000_000, + )]; + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]); @@ -4185,8 +4966,17 @@ mod tests { // Route to 1 via 2 and 3 because our channel to 1 is disabled let payment_params = PaymentParameters::from_node_id(nodes[0], 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, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 3); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4213,11 +5003,23 @@ mod tests { // If we specify a channel to node7, that overrides our local channel view and that gets used let payment_params = PaymentParameters::from_node_id(nodes[2], 42); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), - InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; - let route = get_route(&our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let our_chans = vec![get_channel_details( + Some(42), + nodes[7].clone(), + InitFeatures::from_le_bytes(vec![0b11]), + 250_000_000, + )]; + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]); @@ -4236,79 +5038,73 @@ mod tests { } fn last_hops(nodes: &Vec) -> Vec { - let zero_fees = RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }; - vec![RouteHint(vec![RouteHintHop { - src_node_id: nodes[3], - short_channel_id: 8, - fees: zero_fees, - cltv_expiry_delta: (8 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - } - ]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[4], - short_channel_id: 9, - fees: RoutingFees { - base_msat: 1001, - proportional_millionths: 0, - }, - cltv_expiry_delta: (9 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[5], - short_channel_id: 10, - fees: zero_fees, - cltv_expiry_delta: (10 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])] + let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0 }; + vec![ + RouteHint(vec![RouteHintHop { + src_node_id: nodes[3], + short_channel_id: 8, + fees: zero_fees, + cltv_expiry_delta: (8 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[4], + short_channel_id: 9, + fees: RoutingFees { base_msat: 1001, proportional_millionths: 0 }, + cltv_expiry_delta: (9 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[5], + short_channel_id: 10, + fees: zero_fees, + cltv_expiry_delta: (10 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + ] } fn last_hops_multi_private_channels(nodes: &Vec) -> Vec { - let zero_fees = RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }; - vec![RouteHint(vec![RouteHintHop { - src_node_id: nodes[2], - short_channel_id: 5, - fees: RoutingFees { - base_msat: 100, - proportional_millionths: 0, - }, - cltv_expiry_delta: (5 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }, RouteHintHop { - src_node_id: nodes[3], - short_channel_id: 8, - fees: zero_fees, - cltv_expiry_delta: (8 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - } - ]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[4], - short_channel_id: 9, - fees: RoutingFees { - base_msat: 1001, - proportional_millionths: 0, - }, - cltv_expiry_delta: (9 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[5], - short_channel_id: 10, - fees: zero_fees, - cltv_expiry_delta: (10 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])] + let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0 }; + vec![ + RouteHint(vec![ + RouteHintHop { + src_node_id: nodes[2], + short_channel_id: 5, + fees: RoutingFees { base_msat: 100, proportional_millionths: 0 }, + cltv_expiry_delta: (5 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + RouteHintHop { + src_node_id: nodes[3], + short_channel_id: 8, + fees: zero_fees, + cltv_expiry_delta: (8 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + ]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[4], + short_channel_id: 9, + fees: RoutingFees { base_msat: 1001, proportional_millionths: 0 }, + cltv_expiry_delta: (9 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[5], + short_channel_id: 10, + fees: zero_fees, + cltv_expiry_delta: (10 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + ] } #[test] @@ -4326,10 +5122,7 @@ mod tests { let invalid_last_hop = RouteHint(vec![RouteHintHop { src_node_id: nodes[6], short_channel_id: 8, - fees: RoutingFees { - base_msat: 1000, - proportional_millionths: 0, - }, + fees: RoutingFees { base_msat: 1000, proportional_millionths: 0 }, cltv_expiry_delta: (8 << 4) | 1, htlc_minimum_msat: None, htlc_maximum_msat: None, @@ -4339,21 +5132,41 @@ mod tests { invalid_last_hops.push(invalid_last_hop); { let payment_params = PaymentParameters::from_node_id(nodes[6], 42) - .with_route_hints(invalid_last_hops).unwrap(); + .with_route_hints(invalid_last_hops) + .unwrap(); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Route hint cannot have the payee as the source."); - } else { panic!(); } + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Route hint cannot have the payee as the source."); + } else { + panic!(); + } } let mut payment_params = PaymentParameters::from_node_id(nodes[6], 42) - .with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap(); + .with_route_hints(last_hops_multi_private_channels(&nodes)) + .unwrap(); payment_params.max_path_length = 5; 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, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 5); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4390,46 +5203,60 @@ mod tests { assert_eq!(route.paths[0].hops[4].short_channel_id, 8); assert_eq!(route.paths[0].hops[4].fee_msat, 100); assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet - assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + assert_eq!( + route.paths[0].hops[4].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); + // We can't learn any flags from invoices, sadly } fn empty_last_hop(nodes: &Vec) -> Vec { - let zero_fees = RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }; - vec![RouteHint(vec![RouteHintHop { - src_node_id: nodes[3], - short_channel_id: 8, - fees: zero_fees, - cltv_expiry_delta: (8 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]), RouteHint(vec![ - - ]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[5], - short_channel_id: 10, - fees: zero_fees, - cltv_expiry_delta: (10 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])] + let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0 }; + vec![ + RouteHint(vec![RouteHintHop { + src_node_id: nodes[3], + short_channel_id: 8, + fees: zero_fees, + cltv_expiry_delta: (8 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + RouteHint(vec![]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[5], + short_channel_id: 10, + fees: zero_fees, + cltv_expiry_delta: (10 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + ] } #[test] fn ignores_empty_last_hops_test() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(empty_last_hop(&nodes)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(empty_last_hop(&nodes)) + .unwrap(); let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; // Test handling of an empty RouteHint passed in Invoice. 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, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 5); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4466,35 +5293,36 @@ mod tests { assert_eq!(route.paths[0].hops[4].short_channel_id, 8); assert_eq!(route.paths[0].hops[4].fee_msat, 100); assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet - assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + assert_eq!( + route.paths[0].hops[4].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); + // We can't learn any flags from invoices, sadly } /// Builds a trivial last-hop hint that passes through the two nodes given, with channel 0xff00 /// and 0xff01. fn multi_hop_last_hops_hint(hint_hops: [PublicKey; 2]) -> Vec { - let zero_fees = RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }; - vec![RouteHint(vec![RouteHintHop { - src_node_id: hint_hops[0], - short_channel_id: 0xff00, - fees: RoutingFees { - base_msat: 100, - proportional_millionths: 0, - }, - cltv_expiry_delta: (5 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }, RouteHintHop { - src_node_id: hint_hops[1], - short_channel_id: 0xff01, - fees: zero_fees, - cltv_expiry_delta: (8 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])] + let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0 }; + vec![RouteHint(vec![ + RouteHintHop { + src_node_id: hint_hops[0], + short_channel_id: 0xff00, + fees: RoutingFees { base_msat: 100, proportional_millionths: 0 }, + cltv_expiry_delta: (5 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + RouteHintHop { + src_node_id: hint_hops[1], + short_channel_id: 0xff01, + fees: zero_fees, + cltv_expiry_delta: (8 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + ])] } #[test] @@ -4502,7 +5330,9 @@ mod tests { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx); let last_hops = multi_hop_last_hops_hint([nodes[2], nodes[3]]); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops.clone()) + .unwrap(); let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; @@ -4511,37 +5341,56 @@ mod tests { // max path length. // Disabling channels 6 & 7 by flags=2 - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); route_params.payment_params.max_path_length = 4; - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 4); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4569,11 +5418,23 @@ mod tests { assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id); assert_eq!(route.paths[0].hops[3].fee_msat, 100); assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet + assert_eq!( + route.paths[0].hops[3].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly route_params.payment_params.max_path_length = 3; - get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err(); + get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap_err(); } #[test] @@ -4581,46 +5442,70 @@ mod tests { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx); - let non_announced_privkey = SecretKey::from_slice(&>::from_hex(&format!("{:02x}", 0xf0).repeat(32)).unwrap()[..]).unwrap(); + let non_announced_privkey = SecretKey::from_slice( + &>::from_hex(&format!("{:02x}", 0xf0).repeat(32)).unwrap()[..], + ) + .unwrap(); let non_announced_pubkey = PublicKey::from_secret_key(&secp_ctx, &non_announced_privkey); let last_hops = multi_hop_last_hops_hint([nodes[2], non_announced_pubkey]); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops.clone()) + .unwrap(); let scorer = ln_test_utils::TestScorer::new(); // Test through channels 2, 3, 0xff00, 0xff01. // Test shows that multiple hop hints are considered. // Disabling channels 6 & 7 by flags=2 - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, // to disable - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, // to disable + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + 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, - Arc::clone(&logger), &scorer, &Default::default(), &[42u8; 32]).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &[42u8; 32], + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 4); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4641,61 +5526,71 @@ mod tests { assert_eq!(route.paths[0].hops[2].short_channel_id, last_hops[0].0[0].short_channel_id); assert_eq!(route.paths[0].hops[2].fee_msat, 0); assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 129); - assert_eq!(route.paths[0].hops[2].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet + assert_eq!( + route.paths[0].hops[2].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]); assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id); assert_eq!(route.paths[0].hops[3].fee_msat, 100); assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet - assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + assert_eq!( + route.paths[0].hops[3].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::::new()); + // We can't learn any flags from invoices, sadly } fn last_hops_with_public_channel(nodes: &Vec) -> Vec { - let zero_fees = RoutingFees { - base_msat: 0, - proportional_millionths: 0, - }; - vec![RouteHint(vec![RouteHintHop { - src_node_id: nodes[4], - short_channel_id: 11, - fees: zero_fees, - cltv_expiry_delta: (11 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }, RouteHintHop { - src_node_id: nodes[3], - short_channel_id: 8, - fees: zero_fees, - cltv_expiry_delta: (8 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[4], - short_channel_id: 9, - fees: RoutingFees { - base_msat: 1001, - proportional_millionths: 0, - }, - cltv_expiry_delta: (9 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]), RouteHint(vec![RouteHintHop { - src_node_id: nodes[5], - short_channel_id: 10, - fees: zero_fees, - cltv_expiry_delta: (10 << 4) | 1, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])] + let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0 }; + vec![ + RouteHint(vec![ + RouteHintHop { + src_node_id: nodes[4], + short_channel_id: 11, + fees: zero_fees, + cltv_expiry_delta: (11 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + RouteHintHop { + src_node_id: nodes[3], + short_channel_id: 8, + fees: zero_fees, + cltv_expiry_delta: (8 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }, + ]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[4], + short_channel_id: 9, + fees: RoutingFees { base_msat: 1001, proportional_millionths: 0 }, + cltv_expiry_delta: (9 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + RouteHint(vec![RouteHintHop { + src_node_id: nodes[5], + short_channel_id: 10, + fees: zero_fees, + cltv_expiry_delta: (10 << 4) | 1, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]), + ] } #[test] fn last_hops_with_public_channel_test() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_with_public_channel(&nodes)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops_with_public_channel(&nodes)) + .unwrap(); let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; @@ -4703,8 +5598,17 @@ mod tests { // which would be handled in the same manner. 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, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 5); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4741,8 +5645,12 @@ mod tests { assert_eq!(route.paths[0].hops[4].short_channel_id, 8); assert_eq!(route.paths[0].hops[4].fee_msat, 100); assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet - assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + assert_eq!( + route.paths[0].hops[4].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); + // We can't learn any flags from invoices, sadly } #[test] @@ -4753,14 +5661,28 @@ mod tests { let random_seed_bytes = [42; 32]; // Simple test with outbound channel to 4 to test that last_hops and first_hops connect - let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)]; + let our_chans = vec![get_channel_details( + Some(42), + nodes[3].clone(), + InitFeatures::from_le_bytes(vec![0b11]), + 250_000_000, + )]; let mut last_hops = last_hops(&nodes); let payment_params = PaymentParameters::from_node_id(nodes[6], 42) - .with_route_hints(last_hops.clone()).unwrap(); + .with_route_hints(last_hops.clone()) + .unwrap(); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[3]); @@ -4774,18 +5696,30 @@ mod tests { assert_eq!(route.paths[0].hops[1].short_channel_id, 8); assert_eq!(route.paths[0].hops[1].fee_msat, 100); assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet + assert_eq!( + route.paths[0].hops[1].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly last_hops[0].0[0].fees.base_msat = 1000; // Revert to via 6 as the fee on 8 goes up - let payment_params = PaymentParameters::from_node_id(nodes[6], 42) - .with_route_hints(last_hops).unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let payment_params = + PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 4); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4815,13 +5749,25 @@ mod tests { assert_eq!(route.paths[0].hops[3].short_channel_id, 10); assert_eq!(route.paths[0].hops[3].fee_msat, 100); assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet + assert_eq!( + route.paths[0].hops[3].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly // ...but still use 8 for larger payments as 6 has a variable feerate let route_params = RouteParameters::from_payment_params_and_value(payment_params, 2000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths[0].hops.len(), 5); assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]); @@ -4858,37 +5804,74 @@ mod tests { assert_eq!(route.paths[0].hops[4].short_channel_id, 8); assert_eq!(route.paths[0].hops[4].fee_msat, 2000); assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet - assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly + assert_eq!( + route.paths[0].hops[4].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::::new()); + // We can't learn any flags from invoices, sadly } - fn do_unannounced_path_test(last_hop_htlc_max: Option, last_hop_fee_prop: u32, outbound_capacity_msat: u64, route_val: u64) -> Result { - let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&>::from_hex(&format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap()); - let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap()); - let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap()); + fn do_unannounced_path_test( + last_hop_htlc_max: Option, last_hop_fee_prop: u32, outbound_capacity_msat: u64, + route_val: u64, + ) -> Result { + let source_node_id = PublicKey::from_secret_key( + &Secp256k1::new(), + &SecretKey::from_slice( + &>::from_hex(&format!("{:02}", 41).repeat(32)).unwrap()[..], + ) + .unwrap(), + ); + let middle_node_id = PublicKey::from_secret_key( + &Secp256k1::new(), + &SecretKey::from_slice( + &>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..], + ) + .unwrap(), + ); + let target_node_id = PublicKey::from_secret_key( + &Secp256k1::new(), + &SecretKey::from_slice( + &>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..], + ) + .unwrap(), + ); // If we specify a channel to a middle hop, that overrides our local channel view and that gets used let last_hops = RouteHint(vec![RouteHintHop { src_node_id: middle_node_id, short_channel_id: 8, - fees: RoutingFees { - base_msat: 1000, - proportional_millionths: last_hop_fee_prop, - }, + fees: RoutingFees { base_msat: 1000, proportional_millionths: last_hop_fee_prop }, cltv_expiry_delta: (8 << 4) | 1, htlc_minimum_msat: None, htlc_maximum_msat: last_hop_htlc_max, }]); - let payment_params = PaymentParameters::from_node_id(target_node_id, 42).with_route_hints(vec![last_hops]).unwrap(); - let our_chans = vec![get_channel_details(Some(42), middle_node_id, InitFeatures::from_le_bytes(vec![0b11]), outbound_capacity_msat)]; + let payment_params = PaymentParameters::from_node_id(target_node_id, 42) + .with_route_hints(vec![last_hops]) + .unwrap(); + let our_chans = vec![get_channel_details( + Some(42), + middle_node_id, + InitFeatures::from_le_bytes(vec![0b11]), + outbound_capacity_msat, + )]; let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; let logger = ln_test_utils::TestLogger::new(); let network_graph = NetworkGraph::new(Network::Testnet, &logger); - let route_params = RouteParameters::from_payment_params_and_value(payment_params, route_val); - let route = get_route(&source_node_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), &logger, &scorer, &Default::default(), - &random_seed_bytes); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, route_val); + let route = get_route( + &source_node_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + &logger, + &scorer, + &Default::default(), + &random_seed_bytes, + ); route } @@ -4899,8 +5882,20 @@ mod tests { // hints. let route = do_unannounced_path_test(None, 1, 2000000, 1000000).unwrap(); - let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap()); - let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap()); + let middle_node_id = PublicKey::from_secret_key( + &Secp256k1::new(), + &SecretKey::from_slice( + &>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..], + ) + .unwrap(), + ); + let target_node_id = PublicKey::from_secret_key( + &Secp256k1::new(), + &SecretKey::from_slice( + &>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..], + ) + .unwrap(), + ); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, middle_node_id); @@ -4914,7 +5909,10 @@ mod tests { assert_eq!(route.paths[0].hops[1].short_channel_id, 8); assert_eq!(route.paths[0].hops[1].fee_msat, 1000000); assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet + assert_eq!( + route.paths[0].hops[1].node_features.le_flags(), + default_node_features().le_flags() + ); // We dont pass flags in from invoices yet assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly } @@ -4926,14 +5924,26 @@ mod tests { // we'd built a path (as our node is in the "best candidate" set), when we had not. // In this test, we previously hit a subtraction underflow due to having less available // liquidity at the last hop than 0. - assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 0, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err()); + assert!(do_unannounced_path_test( + Some(21_000_000_0000_0000_000), + 0, + 21_000_000_0000_0000_000, + 21_000_000_0000_0000_000 + ) + .is_err()); } #[test] fn overflow_unannounced_path_test_feerate_overflow() { // This tests for the same case as above, except instead of hitting a subtraction // underflow, we hit a case where the fee charged at a hop overflowed. - assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 50000, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err()); + assert!(do_unannounced_path_test( + Some(21_000_000_0000_0000_000), + 50000, + 21_000_000_0000_0000_000, + 21_000_000_0000_0000_000 + ) + .is_err()); } #[test] @@ -4953,82 +5963,120 @@ mod tests { // our node to node2 via node0: channels {1, 3}. // First disable all other paths. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Make the first channel (#1) very permissive, // and we will be testing all limits on the second channel. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 1_000_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 1_000_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // First, let's see if routing works if we have absolutely no idea about the available amount. // In this case, it should be set to 250_000 sats. - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 250_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 250_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 250_000_001); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 250_000_001); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route an exact amount we have should be fine. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 250_000_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 250_000_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let path = route.paths.last().unwrap(); assert_eq!(path.hops.len(), 2); @@ -5038,42 +6086,68 @@ mod tests { // Check that setting next_outbound_htlc_limit_msat in first_hops limits the channels. // Disable channel #1 and use another first hop. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 1_000_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 1_000_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Now, limit the first_hop by the next_outbound_htlc_limit_msat of 200_000 sats. - let our_chans = vec![get_channel_details(Some(42), nodes[0].clone(), InitFeatures::from_le_bytes(vec![0b11]), 200_000_000)]; + let our_chans = vec![get_channel_details( + Some(42), + nodes[0].clone(), + InitFeatures::from_le_bytes(vec![0b11]), + 200_000_000, + )]; { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 200_000_001); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 200_000_001); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route an exact amount we have should be fine. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 200_000_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), - Some(&our_chans.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 200_000_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&our_chans.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let path = route.paths.last().unwrap(); assert_eq!(path.hops.len(), 2); @@ -5082,53 +6156,80 @@ mod tests { } // Enable channel #1 back. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 4, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 1_000_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 4, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 1_000_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Now let's see if routing works if we know only htlc_maximum_msat. - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 3, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 15_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 3, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 15_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 15_001); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 15_001); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route an exact amount we have should be fine. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 15_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 15_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let path = route.paths.last().unwrap(); assert_eq!(path.hops.len(), 2); @@ -5140,75 +6241,120 @@ mod tests { // We can't change UTXO capacity on the fly, so we'll disable // the existing channel and add another one with the capacity we need. - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 4, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - let good_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[0]).serialize()) - .push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[2]).serialize()) - .push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_p2wsh(); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 4, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - *chain_monitor.utxo_ret.lock().unwrap() = - UtxoResult::Sync(Ok(TxOut { value: Amount::from_sat(15), script_pubkey: good_script.clone() })); + let good_script = Builder::new() + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[0]).serialize()) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[2]).serialize()) + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_opcode(opcodes::all::OP_CHECKMULTISIG) + .into_script() + .to_p2wsh(); + + *chain_monitor.utxo_ret.lock().unwrap() = UtxoResult::Sync(Ok(TxOut { + value: Amount::from_sat(15), + script_pubkey: good_script.clone(), + })); gossip_sync.add_utxo_lookup(Some(chain_monitor)); - add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 333); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 333, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (3 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: 15_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 333, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (3 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: 15_000, - fee_base_msat: 100, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + &privkeys[2], + ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), + 333, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 333, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (3 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: 15_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 333, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (3 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: 15_000, + fee_base_msat: 100, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 15_001); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 15_001); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route an exact amount we have should be fine. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 15_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 15_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let path = route.paths.last().unwrap(); assert_eq!(path.hops.len(), 2); @@ -5217,37 +6363,60 @@ mod tests { } // Now let's see if routing chooses htlc_maximum_msat over UTXO capacity. - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 333, - timestamp: 6, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 10_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 333, + timestamp: 6, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 10_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 10_001); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 10_001); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route an exact amount we have should be fine. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 10_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 10_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let path = route.paths.last().unwrap(); assert_eq!(path.hops.len(), 2); @@ -5274,105 +6443,153 @@ mod tests { // Total capacity: 50 sats. // Disable other potential paths. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Limit capacities - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 50_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 50_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 60_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 60_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route 49 sats (just a bit below the capacity). - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 49_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 49_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -5385,10 +6602,19 @@ mod tests { { // Attempt to route an exact amount is also fine - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 50_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 50_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -5409,38 +6635,57 @@ mod tests { let payment_params = PaymentParameters::from_node_id(nodes[2], 42); // Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50). - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 1_000_000, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 50_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 1_000_000, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 50_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 50_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 50_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -5464,7 +6709,8 @@ mod tests { // MPP to a 1-hop blinded path for nodes[2] let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); - let blinded_payinfo = BlindedPayInfo { // These fields are ignored for 1-hop blinded paths + let blinded_payinfo = BlindedPayInfo { + // These fields are ignored for 1-hop blinded paths fee_base_msat: 0, fee_proportional_millionths: 0, htlc_minimum_msat: 0, @@ -5474,7 +6720,8 @@ mod tests { }; let blinded_path = dummy_one_hop_blinded_path(nodes[2], blinded_payinfo.clone()); let one_hop_blinded_payment_params = PaymentParameters::blinded(vec![blinded_path.clone()]) - .with_bolt12_features(bolt12_features.clone()).unwrap(); + .with_bolt12_features(bolt12_features.clone()) + .unwrap(); do_simple_mpp_route_test(one_hop_blinded_payment_params.clone()); // MPP to 3 2-hop blinded paths @@ -5490,13 +6737,16 @@ mod tests { node_1_payinfo.htlc_maximum_msat = 180_000; let blinded_path_node_1 = dummy_blinded_path(nodes[1], node_1_payinfo); - let two_hop_blinded_payment_params = PaymentParameters::blinded( - vec![blinded_path_node_0, blinded_path_node_7, blinded_path_node_1]) - .with_bolt12_features(bolt12_features).unwrap(); + let two_hop_blinded_payment_params = PaymentParameters::blinded(vec![ + blinded_path_node_0, + blinded_path_node_7, + blinded_path_node_1, + ]) + .with_bolt12_features(bolt12_features) + .unwrap(); do_simple_mpp_route_test(two_hop_blinded_payment_params); } - fn do_simple_mpp_route_test(payment_params: PaymentParameters) { let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph(); let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx); @@ -5512,112 +6762,160 @@ mod tests { // Their aggregate capacity will be 50 + 60 + 180 = 290 sats. // Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50). - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 50_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 50_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via node7 is channels {12, 13}. Limit them to 60 and 60 sats // (total limit 60). - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 60_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 60_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 60_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 60_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via node1 is channels {2, 4}. Limit them to 200 and 180 sats // (total capacity 180 sats). - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 180_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 180_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 300_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 300_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Attempt to route while setting max_path_count to 0 results in a failure. let zero_payment_params = payment_params.clone().with_max_path_count(0); - let route_params = RouteParameters::from_payment_params_and_value( - zero_payment_params, 100); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Can't find a route with no paths allowed."); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(zero_payment_params, 100); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Can't find a route with no paths allowed."); + } else { + panic!(); + } } { @@ -5625,22 +6923,40 @@ mod tests { // This is the case because the minimal_value_contribution_msat would require each path // to account for 1/3 of the total value, which is violated by 2 out of 3 paths. let fail_payment_params = payment_params.clone().with_max_path_count(3); - let route_params = RouteParameters::from_payment_params_and_value( - fail_payment_params, 250_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(fail_payment_params, 250_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route 250 sats (just a bit below the capacity). // Our algorithm should provide us with these 3 paths. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 250_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 250_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 3); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -5657,22 +6973,37 @@ mod tests { { // Attempt to route an exact amount is also fine - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 290_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 290_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 3); let mut total_amount_paid_msat = 0; for path in &route.paths { if payment_params.payee.blinded_route_hints().len() != 0 { - assert!(path.blinded_tail.is_some()) } else { assert!(path.blinded_tail.is_none()) } + assert!(path.blinded_tail.is_some()) + } else { + assert!(path.blinded_tail.is_none()) + } if let Some(bt) = &path.blinded_tail { assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2); if bt.hops.len() > 1 { let network_graph = network_graph.read_only(); assert_eq!( NodeId::from_pubkey(&path.hops.last().unwrap().pubkey), - payment_params.payee.blinded_route_hints().iter() + payment_params + .payee + .blinded_route_hints() + .iter() .find(|p| p.payinfo.htlc_maximum_msat == path.final_value_msat()) .and_then(|p| p.public_introduction_node_id(&network_graph)) .copied() @@ -5710,146 +7041,203 @@ mod tests { // are used twice will have 200 sats capacity. // Disable other potential paths. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node0, node2} is channels {1, 3, 5}. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - // Capacity of 200 sats because this channel will be used by 3rd path as well. - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 3, // disable direction 1 - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + // Capacity of 200 sats because this channel will be used by 3rd path as well. + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[3], + ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), + 5, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[3], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 3, // disable direction 1 + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node7, node2, node4} is channels {12, 13, 6, 11}. // Add 100 sats to the capacities of {12, 13}, because these channels // are also used for 3rd path. 100 sats for the rest. Total capacity: 100 sats. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node7, node2} is channels {12, 13, 5}. // We already limited them to 200 sats (they are used twice for 100 sats). @@ -5857,22 +7245,40 @@ mod tests { { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 350_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 350_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route 300 sats (exact amount we can route). // Our algorithm should provide us with these 3 paths, 100 sats each. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 300_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 300_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 3); let mut total_amount_paid_msat = 0; @@ -5882,7 +7288,6 @@ mod tests { } assert_eq!(total_amount_paid_msat, 300_000); } - } #[test] @@ -5908,146 +7313,203 @@ mod tests { // are used twice will have 200 sats capacity. // Disable other potential paths. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node0, node2} is channels {1, 3, 5}. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Capacity of 200 sats because this channel will be used by 3rd path as well. - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 3, // disable direction 1 - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[3], + ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), + 5, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[3], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 3, // disable direction 1 + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node7, node2, node4} is channels {12, 13, 6, 11}. // Add 100 sats to the capacities of {12, 13}, because these channels // are also used for 3rd path. 100 sats for the rest. Total capacity: 100 sats. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 200_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 200_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 1_000, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 1_000, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node7, node2} is channels {12, 13, 5}. // We already limited them to 200 sats (they are used twice for 100 sats). @@ -6056,10 +7518,19 @@ mod tests { { // Now, attempt to route 180 sats. // Our algorithm should provide us with these 2 paths. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 180_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 180_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); let mut total_value_transferred_msat = 0; @@ -6102,89 +7573,126 @@ mod tests { // It's fine to ignore this concern for now. // Disable other potential paths. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 2, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 2, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node0, node2} is channels {1, 3, 5}. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 3, // Disable direction 1 - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[3], + ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), + 5, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[3], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 3, // Disable direction 1 + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via {node7, node2, node4} is channels {12, 13, 6, 11}. // All channels should be 100 sats capacity. But for the fee experiment, @@ -6196,88 +7704,141 @@ mod tests { // - channel 12 capacity is 250 sats // - fee for channel 6 is 150 sats // Let's test this by enforcing these 2 conditions and removing other limits. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 250_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 250_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 150_000, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 150_000, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 210_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 210_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Attempt to route while setting max_total_routing_fee_msat to 149_999 results in a failure. - let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000, - max_total_routing_fee_msat: Some(149_999) }; - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = RouteParameters { + payment_params: payment_params.clone(), + final_value_msat: 200_000, + max_total_routing_fee_msat: Some(149_999), + }; + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route 200 sats (exact amount we can route). - let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000, - max_total_routing_fee_msat: Some(150_000) }; - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters { + payment_params: payment_params.clone(), + final_value_msat: 200_000, + max_total_routing_fee_msat: Some(150_000), + }; + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); let mut total_amount_paid_msat = 0; @@ -6310,81 +7871,113 @@ mod tests { let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; let config = UserConfig::default(); - let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42) - .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap() - .with_route_hints(vec![RouteHint(vec![RouteHintHop { - src_node_id: nodes[2], - short_channel_id: 42, - fees: RoutingFees { base_msat: 0, proportional_millionths: 0 }, - cltv_expiry_delta: 42, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }])]).unwrap().with_max_channel_saturation_power_of_half(0); + let payment_params = + PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42) + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap() + .with_route_hints(vec![RouteHint(vec![RouteHintHop { + src_node_id: nodes[2], + short_channel_id: 42, + fees: RoutingFees { base_msat: 0, proportional_millionths: 0 }, + cltv_expiry_delta: 42, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }])]) + .unwrap() + .with_max_channel_saturation_power_of_half(0); // Keep only two paths from us to nodes[2], both with a 99sat HTLC maximum, with one with // no fee and one with a 1msat fee. Previously, trying to route 100 sats to nodes[2] here // would first use the no-fee route and then fail to find a path along the second route as // we think we can only send up to 1 additional sat over the last-hop but refuse to as its // under 5% of our payment amount. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (5 << 4) | 5, - htlc_minimum_msat: 0, - htlc_maximum_msat: 99_000, - fee_base_msat: u32::max_value(), - fee_proportional_millionths: u32::max_value(), - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (5 << 4) | 3, - htlc_minimum_msat: 0, - htlc_maximum_msat: 99_000, - fee_base_msat: u32::max_value(), - fee_proportional_millionths: u32::max_value(), - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (4 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 1, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0|2, // Channel disabled - cltv_expiry_delta: (13 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 2000000, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (5 << 4) | 5, + htlc_minimum_msat: 0, + htlc_maximum_msat: 99_000, + fee_base_msat: u32::max_value(), + fee_proportional_millionths: u32::max_value(), + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (5 << 4) | 3, + htlc_minimum_msat: 0, + htlc_maximum_msat: 99_000, + fee_base_msat: u32::max_value(), + fee_proportional_millionths: u32::max_value(), + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (4 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 1, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0 | 2, // Channel disabled + cltv_expiry_delta: (13 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 2000000, + excess_data: Vec::new(), + }, + ); // Get a route for 100 sats and check that we found the MPP route no problem and didn't // overpay at all. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 100_000); - let mut route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100_000); + let mut route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); route.paths.sort_by_key(|path| path.hops[0].short_channel_id); // Paths are manually ordered ordered by SCID, so: @@ -6424,107 +8017,155 @@ mod tests { // Their aggregate capacity will be 50 + 60 + 20 = 130 sats. // Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 100_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 50_000, - fee_base_msat: 100, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 100_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 50_000, + fee_base_msat: 100, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via node7 is channels {12, 13}. Limit them to 60 and 60 sats (total limit 60); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 60_000, - fee_base_msat: 100, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 60_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 60_000, + fee_base_msat: 100, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 60_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); // Path via node1 is channels {2, 4}. Limit them to 20 and 20 sats (total capacity 20 sats). - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 20_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 20_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 20_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 20_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Attempt to route more than available results in a failure. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 150_000); - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route( - &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 150_000); + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a sufficient route to the given destination"); + } else { + panic!(); + } } { // Now, attempt to route 125 sats (just a bit below the capacity of 3 channels). // Our algorithm should provide us with these 3 paths. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 125_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 125_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 3); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -6537,10 +8178,19 @@ mod tests { { // Attempt to route without the last small cheap channel - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 90_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 90_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); let mut total_amount_paid_msat = 0; for path in &route.paths { @@ -6587,119 +8237,230 @@ mod tests { let random_seed_bytes = [42; 32]; let payment_params = PaymentParameters::from_node_id(nodes[6], 42); - add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6); + add_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + &privkeys[1], + ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), + 6, + ); for (key, channel_flags) in [(&our_privkey, 0), (&privkeys[1], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (6 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (6 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[1], + NodeFeatures::from_le_bytes(id_to_feature_flags(1)), + 0, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + &privkeys[4], + ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), + 5, + ); for (key, channel_flags) in [(&privkeys[1], 0), (&privkeys[4], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 5, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (5 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 100, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 5, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (5 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 100, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], NodeFeatures::from_le_bytes(id_to_feature_flags(4)), 0); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[4], + NodeFeatures::from_le_bytes(id_to_feature_flags(4)), + 0, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + &privkeys[3], + ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), + 4, + ); for (key, channel_flags) in [(&privkeys[4], 0), (&privkeys[3], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (4 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (4 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], NodeFeatures::from_le_bytes(id_to_feature_flags(3)), 0); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[3], + NodeFeatures::from_le_bytes(id_to_feature_flags(3)), + 0, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[3], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[3], + &privkeys[2], + ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), + 3, + ); for (key, channel_flags) in [(&privkeys[3], 0), (&privkeys[2], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (3 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (3 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], NodeFeatures::from_le_bytes(id_to_feature_flags(2)), 0); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[2], + NodeFeatures::from_le_bytes(id_to_feature_flags(2)), + 0, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2); - for (key, channel_flags) in [(&privkeys[2], 0), (&privkeys[4], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (2 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[4], + ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), + 2, + ); + for (key, channel_flags) in [(&privkeys[2], 0), (&privkeys[4], 3)] { + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (2 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + &privkeys[6], + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + 1, + ); for (key, channel_flags) in [(&privkeys[4], 0), (&privkeys[6], 3)] { - update_channel(&gossip_sync, &secp_ctx, key, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags, - cltv_expiry_delta: (1 << 4) | 0, - htlc_minimum_msat: 100, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + key, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags, + cltv_expiry_delta: (1 << 4) | 0, + htlc_minimum_msat: 100, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); } - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[6], NodeFeatures::from_le_bytes(id_to_feature_flags(6)), 0); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[6], + NodeFeatures::from_le_bytes(id_to_feature_flags(6)), + 0, + ); { // Now ensure the route flows simply over nodes 1 and 4 to 6. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 10_000); - let route = get_route(&our_id, &route_params, &network.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 10_000); + let route = get_route( + &our_id, + &route_params, + &network.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 3); @@ -6726,7 +8487,6 @@ mod tests { } } - #[test] fn exact_fee_liquidity_limit() { // Test that if, while walking the graph, we find a hop that has exactly enough liquidity @@ -6740,58 +8500,83 @@ mod tests { // We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to // send. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 85_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 85_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (4 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: 270_000, - fee_base_msat: 0, - fee_proportional_millionths: 1000000, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (4 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: 270_000, + fee_base_msat: 0, + fee_proportional_millionths: 1000000, + excess_data: Vec::new(), + }, + ); { // Now, attempt to route 90 sats, which is exactly 90 sats at the last hop, plus the // 200% fee charged channel 13 in the 1-to-2 direction. - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params, 90_000); - route_params.max_total_routing_fee_msat = Some(90_000*2); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, 90_000); + route_params.max_total_routing_fee_msat = Some(90_000 * 2); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]); assert_eq!(route.paths[0].hops[0].short_channel_id, 12); - assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2); + assert_eq!(route.paths[0].hops[0].fee_msat, 90_000 * 2); assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1); assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8)); - assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12)); + assert_eq!( + route.paths[0].hops[0].channel_features.le_flags(), + &id_to_feature_flags(12) + ); assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]); assert_eq!(route.paths[0].hops[1].short_channel_id, 13); assert_eq!(route.paths[0].hops[1].fee_msat, 90_000); assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42); assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3)); - assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13)); + assert_eq!( + route.paths[0].hops[1].channel_features.le_flags(), + &id_to_feature_flags(13) + ); } } @@ -6813,58 +8598,86 @@ mod tests { // We modify the graph to set the htlc_minimum of channel 2 and 4 as needed - channel 2 // gets an htlc_maximum_msat of 80_000 and channel 4 an htlc_minimum_msat of 90_000. We // then try to send 90_000. - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: 80_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (4 << 4) | 1, - htlc_minimum_msat: 90_000, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: 80_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (4 << 4) | 1, + htlc_minimum_msat: 90_000, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); { // Now, attempt to route 90 sats, hitting the htlc_minimum on channel 4, but // overshooting the htlc_maximum on channel 2. Thus, we should pick the (absurdly // expensive) channels 12-13 path. - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params, 90_000); - route_params.max_total_routing_fee_msat = Some(90_000*2); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, 90_000); + route_params.max_total_routing_fee_msat = Some(90_000 * 2); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 2); assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]); assert_eq!(route.paths[0].hops[0].short_channel_id, 12); - assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2); + assert_eq!(route.paths[0].hops[0].fee_msat, 90_000 * 2); assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1); assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8)); - assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12)); + assert_eq!( + route.paths[0].hops[0].channel_features.le_flags(), + &id_to_feature_flags(12) + ); assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]); assert_eq!(route.paths[0].hops[1].short_channel_id, 13); assert_eq!(route.paths[0].hops[1].fee_msat, 90_000); assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0].hops[1].node_features.le_flags(), channelmanager::provided_bolt11_invoice_features(&config).le_flags()); - assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13)); + assert_eq!( + route.paths[0].hops[1].node_features.le_flags(), + channelmanager::provided_bolt11_invoice_features(&config).le_flags() + ); + assert_eq!( + route.paths[0].hops[1].channel_features.le_flags(), + &id_to_feature_flags(13) + ); } } @@ -6888,12 +8701,32 @@ mod tests { let random_seed_bytes = [42; 32]; { - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[ - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 200_000), - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000), - ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&[ + &get_channel_details( + Some(3), + nodes[0], + channelmanager::provided_init_features(&config), + 200_000, + ), + &get_channel_details( + Some(2), + nodes[0], + channelmanager::provided_init_features(&config), + 10_000, + ), + ]), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 1); @@ -6902,18 +8735,42 @@ mod tests { assert_eq!(route.paths[0].hops[0].fee_msat, 100_000); } { - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[ - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000), - ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&[ + &get_channel_details( + Some(3), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(2), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + ]), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); assert_eq!(route.paths[0].hops.len(), 1); assert_eq!(route.paths[1].hops.len(), 1); - assert!((route.paths[0].hops[0].short_channel_id == 3 && route.paths[1].hops[0].short_channel_id == 2) || - (route.paths[0].hops[0].short_channel_id == 2 && route.paths[1].hops[0].short_channel_id == 3)); + assert!( + (route.paths[0].hops[0].short_channel_id == 3 + && route.paths[1].hops[0].short_channel_id == 2) + || (route.paths[0].hops[0].short_channel_id == 2 + && route.paths[1].hops[0].short_channel_id == 3) + ); assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]); assert_eq!(route.paths[0].hops[0].fee_msat, 50_000); @@ -6930,18 +8787,68 @@ mod tests { // If we have several options above the 3xpayment value threshold, we should pick the // smallest of them, avoiding further fragmenting our available outbound balance to // this node. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 100_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[ - &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(6), nodes[0], channelmanager::provided_init_features(&config), 300_000), - &get_channel_details(Some(7), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(&config), 50_000), - &get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000), - ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 100_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + Some(&[ + &get_channel_details( + Some(2), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(3), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(5), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(6), + nodes[0], + channelmanager::provided_init_features(&config), + 300_000, + ), + &get_channel_details( + Some(7), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(8), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(9), + nodes[0], + channelmanager::provided_init_features(&config), + 50_000, + ), + &get_channel_details( + Some(4), + nodes[0], + channelmanager::provided_init_features(&config), + 1_000_000, + ), + ]), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 1); @@ -6955,15 +8862,26 @@ mod tests { fn prefers_shorter_route_with_higher_fees() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops(&nodes)) + .unwrap(); // Without penalizing each hop 100 msats, a longer path with lower fees is chosen. let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - let route = get_route( &our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert_eq!(route.get_total_fees(), 100); @@ -6973,10 +8891,18 @@ mod tests { // Applying a 100 msat penalty to each hop results in taking channels 7 and 10 to nodes[6] // from nodes[2] rather than channel 6, 11, and 8, even though the longer path is cheaper. let scorer = FixedPenaltyScorer::with_penalty(100); - 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, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + 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, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert_eq!(route.get_total_fees(), 300); @@ -6990,12 +8916,21 @@ mod tests { #[cfg(c_bindings)] impl Writeable for BadChannelScorer { - fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() } + fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { + unimplemented!() + } } impl ScoreLookUp for BadChannelScorer { type ScoreParams = (); - 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 } + 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 + } } } @@ -7005,13 +8940,22 @@ mod tests { #[cfg(c_bindings)] impl Writeable for BadNodeScorer { - fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() } + fn write(&self, _w: &mut W) -> Result<(), crate::io::Error> { + unimplemented!() + } } impl ScoreLookUp for BadNodeScorer { type ScoreParams = (); - 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 } + 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 + } } } @@ -7019,16 +8963,26 @@ mod tests { fn avoids_routing_through_bad_channels_and_nodes() { let (secp_ctx, network, _, _, logger) = build_graph(); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops(&nodes)) + .unwrap(); let network_graph = network.read_only(); // A path to nodes[6] exists when no penalties are applied to any channel. let scorer = ln_test_utils::TestScorer::new(); let random_seed_bytes = [42; 32]; - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 100); - let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert_eq!(route.get_total_fees(), 100); @@ -7037,8 +8991,17 @@ mod tests { // A different path to nodes[6] exists if channel 6 cannot be routed over. let scorer = BadChannelScorer { short_channel_id: 6 }; - let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert_eq!(route.get_total_fees(), 300); @@ -7047,12 +9010,20 @@ mod tests { // A path to nodes[6] does not exist if nodes[2] cannot be routed through. let scorer = BadNodeScorer { node_id: NodeId::from_pubkey(&nodes[2]) }; - match get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) { - Err(LightningError { err, .. } ) => { - assert_eq!(err, "Failed to find a path to the given destination"); - }, - Ok(_) => panic!("Expected error"), + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + Err(LightningError { err, .. }) => { + assert_eq!(err, "Failed to find a path to the given destination"); + }, + Ok(_) => panic!("Expected error"), } } @@ -7137,26 +9108,45 @@ mod tests { // Make sure that generally there is at least one route available let feasible_max_total_cltv_delta = 1008; - let feasible_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() + let feasible_payment_params = PaymentParameters::from_node_id(nodes[6], 0) + .with_route_hints(last_hops(&nodes)) + .unwrap() .with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta); let random_seed_bytes = [42; 32]; - let route_params = RouteParameters::from_payment_params_and_value( - feasible_payment_params, 100); - let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(feasible_payment_params, 100); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert_ne!(path.len(), 0); // But not if we exclude all paths on the basis of their accumulated CLTV delta let fail_max_total_cltv_delta = 23; - let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() + let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0) + .with_route_hints(last_hops(&nodes)) + .unwrap() .with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta); - let route_params = RouteParameters::from_payment_params_and_value( - fail_payment_params, 100); - match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) - { - Err(LightningError { err, .. } ) => { + let route_params = RouteParameters::from_payment_params_and_value(fail_payment_params, 100); + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + Err(LightningError { err, .. }) => { assert_eq!(err, "Failed to find a path to the given destination"); }, Ok(_) => panic!("Expected error"), @@ -7172,29 +9162,53 @@ mod tests { let network_graph = network.read_only(); let scorer = ln_test_utils::TestScorer::new(); - let mut payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap() + let mut payment_params = PaymentParameters::from_node_id(nodes[6], 0) + .with_route_hints(last_hops(&nodes)) + .unwrap() .with_max_path_count(1); let random_seed_bytes = [42; 32]; // We should be able to find a route initially, and then after we fail a few random // channels eventually we won't be able to any longer. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - assert!(get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).is_ok()); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + assert!(get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes + ) + .is_ok()); loop { - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - if let Ok(route) = get_route(&our_id, &route_params, &network_graph, None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) - { + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + if let Ok(route) = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { for chan in route.paths[0].hops.iter() { - assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id)); + assert!(!payment_params + .previously_failed_channels + .contains(&chan.short_channel_id)); } - let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) as usize) - % route.paths[0].hops.len(); - payment_params.previously_failed_channels.push(route.paths[0].hops[victim].short_channel_id); - } else { break; } + let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) + as usize) % route.paths[0].hops.len(); + payment_params + .previously_failed_channels + .push(route.paths[0].hops[victim].short_channel_id); + } else { + break; + } } } @@ -7209,21 +9223,36 @@ mod tests { // First check we can actually create a long route on this graph. let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0); - let route_params = RouteParameters::from_payment_params_and_value( - feasible_payment_params, 100); - let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(feasible_payment_params, 100); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::>(); assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into()); // But we can't create a path surpassing the MAX_PATH_LENGTH_ESTIMATE limit. let fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0); - let route_params = RouteParameters::from_payment_params_and_value( - fail_payment_params, 100); - match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) - { - Err(LightningError { err, .. } ) => { + let route_params = RouteParameters::from_payment_params_and_value(fail_payment_params, 100); + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + Err(LightningError { err, .. }) => { assert_eq!(err, "Failed to find a path to the given destination"); }, Ok(_) => panic!("Expected error"), @@ -7237,30 +9266,58 @@ mod tests { let scorer = ln_test_utils::TestScorer::new(); - let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap(); + let payment_params = PaymentParameters::from_node_id(nodes[6], 42) + .with_route_hints(last_hops(&nodes)) + .unwrap(); let random_seed_bytes = [42; 32]; - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); - let cltv_expiry_deltas_before = route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); + let cltv_expiry_deltas_before = + route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); // Check whether the offset added to the last hop by default is in [1 .. DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA] let mut route_default = route.clone(); - add_random_cltv_offset(&mut route_default, &payment_params, &network_graph.read_only(), &random_seed_bytes); - let cltv_expiry_deltas_default = route_default.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); - assert_eq!(cltv_expiry_deltas_before.split_last().unwrap().1, cltv_expiry_deltas_default.split_last().unwrap().1); + add_random_cltv_offset( + &mut route_default, + &payment_params, + &network_graph.read_only(), + &random_seed_bytes, + ); + let cltv_expiry_deltas_default = + route_default.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); + assert_eq!( + cltv_expiry_deltas_before.split_last().unwrap().1, + cltv_expiry_deltas_default.split_last().unwrap().1 + ); assert!(cltv_expiry_deltas_default.last() > cltv_expiry_deltas_before.last()); assert!(cltv_expiry_deltas_default.last().unwrap() <= &DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA); // Check that no offset is added when we restrict the max_total_cltv_expiry_delta let mut route_limited = route.clone(); let limited_max_total_cltv_expiry_delta = cltv_expiry_deltas_before.iter().sum(); - let limited_payment_params = payment_params.with_max_total_cltv_expiry_delta(limited_max_total_cltv_expiry_delta); - add_random_cltv_offset(&mut route_limited, &limited_payment_params, &network_graph.read_only(), &random_seed_bytes); - let cltv_expiry_deltas_limited = route_limited.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); + let limited_payment_params = + payment_params.with_max_total_cltv_expiry_delta(limited_max_total_cltv_expiry_delta); + add_random_cltv_offset( + &mut route_limited, + &limited_payment_params, + &network_graph.read_only(), + &random_seed_bytes, + ); + let cltv_expiry_deltas_limited = + route_limited.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::>(); assert_eq!(cltv_expiry_deltas_before, cltv_expiry_deltas_limited); } @@ -7275,10 +9332,19 @@ mod tests { let payment_params = PaymentParameters::from_node_id(nodes[3], 0); let random_seed_bytes = [42; 32]; - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), 100); - let mut route = get_route(&our_id, &route_params, &network_graph, None, - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), 100); + let mut route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); add_random_cltv_offset(&mut route, &payment_params, &network_graph, &random_seed_bytes); let mut path_plausibility = vec![]; @@ -7290,10 +9356,12 @@ mod tests { prng.process_in_place(&mut random_bytes); let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.hops.len()); - let observation_point = NodeId::from_pubkey(&p.hops.get(random_path_index).unwrap().pubkey); + let observation_point = + NodeId::from_pubkey(&p.hops.get(random_path_index).unwrap().pubkey); // 2. Calculate what CLTV expiry delta we would observe there - let observed_cltv_expiry_delta: u32 = p.hops[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum(); + let observed_cltv_expiry_delta: u32 = + p.hops[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum(); // 3. Starting from the observation point, find candidate paths let mut candidates: VecDeque<(NodeId, Vec)> = VecDeque::new(); @@ -7301,9 +9369,16 @@ mod tests { let mut found_plausible_candidate = false; - 'candidate_loop: while let Some((cur_node_id, cur_path_cltv_deltas)) = candidates.pop_front() { - if let Some(remaining) = observed_cltv_expiry_delta.checked_sub(cur_path_cltv_deltas.iter().sum::()) { - if remaining == 0 || remaining.wrapping_rem(40) == 0 || remaining.wrapping_rem(144) == 0 { + 'candidate_loop: while let Some((cur_node_id, cur_path_cltv_deltas)) = + candidates.pop_front() + { + if let Some(remaining) = + observed_cltv_expiry_delta.checked_sub(cur_path_cltv_deltas.iter().sum::()) + { + if remaining == 0 + || remaining.wrapping_rem(40) == 0 + || remaining.wrapping_rem(144) == 0 + { found_plausible_candidate = true; break 'candidate_loop; } @@ -7312,10 +9387,17 @@ mod tests { if let Some(cur_node) = network_nodes.get(&cur_node_id) { for channel_id in &cur_node.channels { if let Some(channel_info) = network_channels.get(&channel_id) { - if let Some((dir_info, next_id)) = channel_info.as_directed_from(&cur_node_id) { - let next_cltv_expiry_delta = dir_info.direction().cltv_expiry_delta as u32; - if cur_path_cltv_deltas.iter().sum::() - .saturating_add(next_cltv_expiry_delta) <= observed_cltv_expiry_delta { + if let Some((dir_info, next_id)) = + channel_info.as_directed_from(&cur_node_id) + { + let next_cltv_expiry_delta = + dir_info.direction().cltv_expiry_delta as u32; + if cur_path_cltv_deltas + .iter() + .sum::() + .saturating_add(next_cltv_expiry_delta) + <= observed_cltv_expiry_delta + { let mut new_path_cltv_deltas = cur_path_cltv_deltas.clone(); new_path_cltv_deltas.push(next_cltv_expiry_delta); candidates.push_back((*next_id, new_path_cltv_deltas)); @@ -7341,9 +9423,17 @@ mod tests { let payment_params = PaymentParameters::from_node_id(nodes[3], 0); let hops = [nodes[1], nodes[2], nodes[4], nodes[3]]; let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); - let route = build_route_from_hops_internal(&our_id, &hops, &route_params, &network_graph, - Arc::clone(&logger), &random_seed_bytes).unwrap(); - let route_hop_pubkeys = route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::>(); + let route = build_route_from_hops_internal( + &our_id, + &hops, + &route_params, + &network_graph, + Arc::clone(&logger), + &random_seed_bytes, + ) + .unwrap(); + let route_hop_pubkeys = + route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::>(); assert_eq!(hops.len(), route.paths[0].hops.len()); for (idx, hop_pubkey) in hops.iter().enumerate() { assert!(*hop_pubkey == route_hop_pubkeys[idx]); @@ -7359,32 +9449,42 @@ mod tests { // Set the fee on channel 13 to 100% to match channel 4 giving us two equivalent paths (us // -> node 7 -> node2 and us -> node 1 -> node 2) which we should balance over. - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (4 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: 250_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (13 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: 250_000_000, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (4 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: 250_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (13 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: 250_000_000, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); let config = UserConfig::default(); let payment_params = PaymentParameters::from_node_id(nodes[2], 42) @@ -7393,13 +9493,26 @@ mod tests { let random_seed_bytes = [42; 32]; // 100,000 sats is less than the available liquidity on each channel, set above. - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 100_000_000); - let route = get_route(&our_id, &route_params, &network_graph.read_only(), None, - Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap(); + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, 100_000_000); + let route = get_route( + &our_id, + &route_params, + &network_graph.read_only(), + None, + Arc::clone(&logger), + &scorer, + &ProbabilisticScoringFeeParameters::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); - assert!((route.paths[0].hops[1].short_channel_id == 4 && route.paths[1].hops[1].short_channel_id == 13) || - (route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13)); + assert!( + (route.paths[0].hops[1].short_channel_id == 4 + && route.paths[1].hops[1].short_channel_id == 13) + || (route.paths[1].hops[1].short_channel_id == 4 + && route.paths[0].hops[1].short_channel_id == 13) + ); } pub(super) fn random_init_seed() -> u64 { @@ -7426,7 +9539,15 @@ mod tests { let params = ProbabilisticScoringFeeParameters::default(); let features = super::Bolt11InvoiceFeatures::empty(); - super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2); + super::bench_utils::generate_test_routes( + &graph, + &mut scorer, + ¶ms, + features, + random_init_seed(), + 0, + 2, + ); } #[test] @@ -7445,7 +9566,15 @@ mod tests { let params = ProbabilisticScoringFeeParameters::default(); let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default()); - super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2); + super::bench_utils::generate_test_routes( + &graph, + &mut scorer, + ¶ms, + features, + random_init_seed(), + 0, + 2, + ); } #[test] @@ -7464,7 +9593,15 @@ mod tests { let params = ProbabilisticScoringFeeParameters::default(); let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default()); - super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 1_000_000, 2); + super::bench_utils::generate_test_routes( + &graph, + &mut scorer, + ¶ms, + features, + random_init_seed(), + 1_000_000, + 2, + ); } #[test] @@ -7474,13 +9611,20 @@ mod tests { let random_seed_bytes = [42; 32]; let mut scorer_params = ProbabilisticScoringFeeParameters::default(); - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), Arc::clone(&network_graph), Arc::clone(&logger)); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + Arc::clone(&network_graph), + Arc::clone(&logger), + ); // First check set manual penalties are returned by the scorer. let usage = ChannelUsage { amount_msat: 0, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 1_000, + }, }; scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[3]), 123); scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456); @@ -7488,28 +9632,51 @@ mod tests { 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(PublicHopCandidate { - info: info.0, - short_channel_id: 5, - }); + let candidate: CandidateRouteHop = + CandidateRouteHop::PublicHop(PublicHopCandidate { 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, None, - Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100); + 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, 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, 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()); } @@ -7528,44 +9695,60 @@ mod tests { let route_hint_1 = RouteHint(vec![RouteHintHop { src_node_id: nodes[2], short_channel_id: 42, - fees: RoutingFees { - base_msat: 100, - proportional_millionths: 0, - }, + fees: RoutingFees { base_msat: 100, proportional_millionths: 0 }, cltv_expiry_delta: 10, htlc_minimum_msat: None, htlc_maximum_msat: Some(max_htlc_msat), }]); let dest_node_id = ln_test_utils::pubkey(42); let payment_params = PaymentParameters::from_node_id(dest_node_id, 42) - .with_route_hints(vec![route_hint_1.clone()]).unwrap() + .with_route_hints(vec![route_hint_1.clone()]) + .unwrap() .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) .unwrap(); // Make sure we'll error if our route hints don't have enough liquidity according to their // htlc_maximum_msat. - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params, max_htlc_msat + 1); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, max_htlc_msat + 1); route_params.max_total_routing_fee_msat = None; - if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, - &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), - &random_seed_bytes) - { + if let Err(LightningError { err, action: ErrorAction::IgnoreError }) = get_route( + &our_id, + &route_params, + &netgraph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { assert_eq!(err, "Failed to find a sufficient route to the given destination"); - } else { panic!(); } + } else { + panic!(); + } // Make sure we'll split an MPP payment across route hints if their htlc_maximum_msat warrants. let mut route_hint_2 = route_hint_1.clone(); route_hint_2.0[0].short_channel_id = 43; let payment_params = PaymentParameters::from_node_id(dest_node_id, 42) - .with_route_hints(vec![route_hint_1, route_hint_2]).unwrap() + .with_route_hints(vec![route_hint_1, route_hint_2]) + .unwrap() .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) .unwrap(); - let mut route_params = RouteParameters::from_payment_params_and_value( - payment_params, max_htlc_msat + 1); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, max_htlc_msat + 1); route_params.max_total_routing_fee_msat = Some(max_htlc_msat * 2); - let route = get_route(&our_id, &route_params, &netgraph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &netgraph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat); assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat); @@ -7583,46 +9766,56 @@ mod tests { let our_node_id = ln_test_utils::pubkey(42); let intermed_node_id = ln_test_utils::pubkey(43); - let first_hop = vec![get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)]; + let first_hop = vec![get_channel_details( + Some(42), + intermed_node_id, + InitFeatures::from_le_bytes(vec![0b11]), + 10_000_000, + )]; let amt_msat = 900_000; let max_htlc_msat = 500_000; - let route_hint_1 = RouteHint(vec![RouteHintHop { - src_node_id: intermed_node_id, - short_channel_id: 44, - fees: RoutingFees { - base_msat: 100, - proportional_millionths: 0, + let route_hint_1 = RouteHint(vec![ + RouteHintHop { + src_node_id: intermed_node_id, + short_channel_id: 44, + fees: RoutingFees { base_msat: 100, proportional_millionths: 0 }, + cltv_expiry_delta: 10, + htlc_minimum_msat: None, + htlc_maximum_msat: Some(max_htlc_msat), }, - cltv_expiry_delta: 10, - htlc_minimum_msat: None, - htlc_maximum_msat: Some(max_htlc_msat), - }, RouteHintHop { - src_node_id: intermed_node_id, - short_channel_id: 45, - fees: RoutingFees { - base_msat: 100, - proportional_millionths: 0, + RouteHintHop { + src_node_id: intermed_node_id, + short_channel_id: 45, + fees: RoutingFees { base_msat: 100, proportional_millionths: 0 }, + cltv_expiry_delta: 10, + htlc_minimum_msat: None, + // Check that later route hint max htlcs don't override earlier ones + htlc_maximum_msat: Some(max_htlc_msat - 50), }, - cltv_expiry_delta: 10, - htlc_minimum_msat: None, - // Check that later route hint max htlcs don't override earlier ones - htlc_maximum_msat: Some(max_htlc_msat - 50), - }]); + ]); let mut route_hint_2 = route_hint_1.clone(); route_hint_2.0[0].short_channel_id = 46; route_hint_2.0[1].short_channel_id = 47; let dest_node_id = ln_test_utils::pubkey(44); let payment_params = PaymentParameters::from_node_id(dest_node_id, 42) - .with_route_hints(vec![route_hint_1, route_hint_2]).unwrap() + .with_route_hints(vec![route_hint_1, route_hint_2]) + .unwrap() .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) .unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); - let route = get_route(&our_node_id, &route_params, &network_graph.read_only(), - Some(&first_hop.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); + let route = get_route( + &our_node_id, + &route_params, + &network_graph.read_only(), + Some(&first_hop.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat); assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat); @@ -7631,12 +9824,30 @@ mod tests { // Re-run but with two first hop channels connected to the same route hint peers that must be // split between. let first_hops = vec![ - get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10), - get_channel_details(Some(43), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10), + get_channel_details( + Some(42), + intermed_node_id, + InitFeatures::from_le_bytes(vec![0b11]), + amt_msat - 10, + ), + get_channel_details( + Some(43), + intermed_node_id, + InitFeatures::from_le_bytes(vec![0b11]), + amt_msat - 10, + ), ]; - let route = get_route(&our_node_id, &route_params, &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_node_id, + &route_params, + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat); assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat); @@ -7653,14 +9864,22 @@ mod tests { }; let blinded_path = dummy_blinded_path(intermed_node_id, blinded_payinfo); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); - let payment_params = PaymentParameters::blinded(vec![ - blinded_path.clone(), blinded_path.clone() - ]).with_bolt12_features(bolt12_features).unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); - let route = get_route(&our_node_id, &route_params, &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let payment_params = + PaymentParameters::blinded(vec![blinded_path.clone(), blinded_path.clone()]) + .with_bolt12_features(bolt12_features) + .unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); + let route = get_route( + &our_node_id, + &route_params, + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat); assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat); @@ -7670,34 +9889,47 @@ mod tests { #[test] fn blinded_route_ser() { // (De)serialize a Route with 1 blinded path out of two total paths. - let mut route = Route { paths: vec![Path { - hops: vec![RouteHop { - pubkey: ln_test_utils::pubkey(50), - node_features: NodeFeatures::empty(), - short_channel_id: 42, - channel_features: ChannelFeatures::empty(), - fee_msat: 100, - cltv_expiry_delta: 0, - maybe_announced_channel: true, - }], - blinded_tail: Some(BlindedTail { - hops: vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() }, - BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() } - ], - blinding_point: ln_test_utils::pubkey(43), - excess_final_cltv_expiry_delta: 40, - final_value_msat: 100, - })}, Path { - hops: vec![RouteHop { - pubkey: ln_test_utils::pubkey(51), - node_features: NodeFeatures::empty(), - short_channel_id: 43, - channel_features: ChannelFeatures::empty(), - fee_msat: 100, - cltv_expiry_delta: 0, - maybe_announced_channel: true, - }], blinded_tail: None }], + let mut route = Route { + paths: vec![ + Path { + hops: vec![RouteHop { + pubkey: ln_test_utils::pubkey(50), + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: 100, + cltv_expiry_delta: 0, + maybe_announced_channel: true, + }], + blinded_tail: Some(BlindedTail { + hops: vec![ + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(44), + encrypted_payload: Vec::new(), + }, + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(45), + encrypted_payload: Vec::new(), + }, + ], + blinding_point: ln_test_utils::pubkey(43), + excess_final_cltv_expiry_delta: 40, + final_value_msat: 100, + }), + }, + Path { + hops: vec![RouteHop { + pubkey: ln_test_utils::pubkey(51), + node_features: NodeFeatures::empty(), + short_channel_id: 43, + channel_features: ChannelFeatures::empty(), + fee_msat: 100, + cltv_expiry_delta: 0, + maybe_announced_channel: true, + }], + blinded_tail: None, + }, + ], route_params: None, }; let encoded_route = route.encode(); @@ -7708,8 +9940,14 @@ mod tests { // (De)serialize a Route with two paths, each containing a blinded tail. route.paths[1].blinded_tail = Some(BlindedTail { hops: vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() }, - BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() } + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(48), + encrypted_payload: Vec::new(), + }, + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(49), + encrypted_payload: Vec::new(), + }, ], blinding_point: ln_test_utils::pubkey(47), excess_final_cltv_expiry_delta: 41, @@ -7727,26 +9965,31 @@ mod tests { // account for the blinded tail's final amount_msat. let mut inflight_htlcs = InFlightHtlcs::new(); let path = Path { - hops: vec![RouteHop { - pubkey: ln_test_utils::pubkey(42), - node_features: NodeFeatures::empty(), - short_channel_id: 42, - channel_features: ChannelFeatures::empty(), - fee_msat: 100, - cltv_expiry_delta: 0, - maybe_announced_channel: false, - }, - RouteHop { - pubkey: ln_test_utils::pubkey(43), - node_features: NodeFeatures::empty(), - short_channel_id: 43, - channel_features: ChannelFeatures::empty(), - fee_msat: 1, - cltv_expiry_delta: 0, - maybe_announced_channel: false, - }], + hops: vec![ + RouteHop { + pubkey: ln_test_utils::pubkey(42), + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: 100, + cltv_expiry_delta: 0, + maybe_announced_channel: false, + }, + RouteHop { + pubkey: ln_test_utils::pubkey(43), + node_features: NodeFeatures::empty(), + short_channel_id: 43, + channel_features: ChannelFeatures::empty(), + fee_msat: 1, + cltv_expiry_delta: 0, + maybe_announced_channel: false, + }, + ], blinded_tail: Some(BlindedTail { - hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }], + hops: vec![BlindedHop { + blinded_node_id: ln_test_utils::pubkey(49), + encrypted_payload: Vec::new(), + }], blinding_point: ln_test_utils::pubkey(48), excess_final_cltv_expiry_delta: 0, final_value_msat: 200, @@ -7760,41 +10003,54 @@ mod tests { #[test] fn blinded_path_cltv_shadow_offset() { // Make sure we add a shadow offset when sending to blinded paths. - let mut route = Route { paths: vec![Path { - hops: vec![RouteHop { - pubkey: ln_test_utils::pubkey(42), - node_features: NodeFeatures::empty(), - short_channel_id: 42, - channel_features: ChannelFeatures::empty(), - fee_msat: 100, - cltv_expiry_delta: 0, - maybe_announced_channel: false, - }, - RouteHop { - pubkey: ln_test_utils::pubkey(43), - node_features: NodeFeatures::empty(), - short_channel_id: 43, - channel_features: ChannelFeatures::empty(), - fee_msat: 1, - cltv_expiry_delta: 0, - maybe_announced_channel: false, - } - ], - blinded_tail: Some(BlindedTail { + let mut route = Route { + paths: vec![Path { hops: vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }, - BlindedHop { blinded_node_id: ln_test_utils::pubkey(46), encrypted_payload: Vec::new() } + RouteHop { + pubkey: ln_test_utils::pubkey(42), + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: 100, + cltv_expiry_delta: 0, + maybe_announced_channel: false, + }, + RouteHop { + pubkey: ln_test_utils::pubkey(43), + node_features: NodeFeatures::empty(), + short_channel_id: 43, + channel_features: ChannelFeatures::empty(), + fee_msat: 1, + cltv_expiry_delta: 0, + maybe_announced_channel: false, + }, ], - blinding_point: ln_test_utils::pubkey(44), - excess_final_cltv_expiry_delta: 0, - final_value_msat: 200, - }), - }], route_params: None}; + blinded_tail: Some(BlindedTail { + hops: vec![ + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(45), + encrypted_payload: Vec::new(), + }, + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(46), + encrypted_payload: Vec::new(), + }, + ], + blinding_point: ln_test_utils::pubkey(44), + excess_final_cltv_expiry_delta: 0, + final_value_msat: 200, + }), + }], + route_params: None, + }; let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18); let (_, network_graph, _, _, _) = build_line_graph(); add_random_cltv_offset(&mut route, &payment_params, &network_graph.read_only(), &[0; 32]); - assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40); + assert_eq!( + route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, + 40 + ); assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40); } @@ -7816,9 +10072,10 @@ mod tests { let mut blinded_hops = Vec::new(); for i in 0..num_blinded_hops { - blinded_hops.push( - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 + i as u8), encrypted_payload: Vec::new() }, - ); + blinded_hops.push(BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 + i as u8), + encrypted_payload: Vec::new(), + }); } let blinded_payinfo = BlindedPayInfo { fee_base_msat: 100, @@ -7828,8 +10085,14 @@ mod tests { cltv_expiry_delta: 15, features: BlindedHopFeatures::empty(), }; - let blinded_path = BlindedPaymentPath::from_raw(nodes[2], ln_test_utils::pubkey(42), blinded_hops, blinded_payinfo.clone()); - let payment_params = PaymentParameters::blinded(vec![blinded_path.clone(), blinded_path.clone()]); + let blinded_path = BlindedPaymentPath::from_raw( + nodes[2], + ln_test_utils::pubkey(42), + blinded_hops, + blinded_payinfo.clone(), + ); + let payment_params = + PaymentParameters::blinded(vec![blinded_path.clone(), blinded_path.clone()]); // Make sure we can round-trip read and write blinded payment params. let encoded_params = payment_params.encode(); @@ -7838,10 +10101,18 @@ mod tests { let decoded_params: PaymentParameters = ReadableArgs::read(&mut reader, 42).unwrap(); assert_eq!(payment_params, decoded_params); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, 1001); - let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.paths[0].hops.len(), 2); @@ -7856,8 +10127,12 @@ mod tests { *blinded_path.public_introduction_node_id(&network_graph).unwrap() ); if tail.hops.len() > 1 { - assert_eq!(final_hop.fee_msat, - blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000); + assert_eq!( + final_hop.fee_msat, + blinded_payinfo.fee_base_msat as u64 + + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat + / 1000000 + ); assert_eq!(final_hop.cltv_expiry_delta, blinded_payinfo.cltv_expiry_delta as u32); } else { assert_eq!(final_hop.fee_msat, 0); @@ -7886,41 +10161,63 @@ mod tests { let invalid_blinded_path_2 = dummy_one_hop_blinded_path(nodes[2], blinded_payinfo.clone()); let invalid_blinded_path_3 = dummy_one_hop_blinded_path(nodes[3], blinded_payinfo.clone()); - let payment_params = PaymentParameters::blinded(vec![ - invalid_blinded_path_2, invalid_blinded_path_3]); + let payment_params = + PaymentParameters::blinded(vec![invalid_blinded_path_2, invalid_blinded_path_3]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); - match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes) - { + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { Err(LightningError { err, .. }) => { assert_eq!(err, "1-hop blinded paths must all have matching introduction node ids"); }, - _ => panic!("Expected error") + _ => panic!("Expected error"), } let invalid_blinded_path = dummy_blinded_path(our_id, blinded_payinfo.clone()); let payment_params = PaymentParameters::blinded(vec![invalid_blinded_path]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); - match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) - { + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { Err(LightningError { err, .. }) => { assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them"); }, - _ => panic!("Expected error") + _ => panic!("Expected error"), } - let mut invalid_blinded_path = dummy_one_hop_blinded_path(ln_test_utils::pubkey(46), blinded_payinfo); + let mut invalid_blinded_path = + dummy_one_hop_blinded_path(ln_test_utils::pubkey(46), blinded_payinfo); invalid_blinded_path.clear_blinded_hops(); let payment_params = PaymentParameters::blinded(vec![invalid_blinded_path]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); - match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) - { + match get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { Err(LightningError { err, .. }) => { assert_eq!(err, "0-hop blinded path provided"); }, - _ => panic!("Expected error") + _ => panic!("Expected error"), } } @@ -7949,31 +10246,57 @@ mod tests { let mut blinded_payinfo_2 = blinded_payinfo_1; blinded_payinfo_2.htlc_maximum_msat = 70_000; - let blinded_path_2 = BlindedPaymentPath::from_raw(nodes[2], ln_test_utils::pubkey(43), + let blinded_path_2 = BlindedPaymentPath::from_raw( + nodes[2], + ln_test_utils::pubkey(43), vec![ - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, - BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() } + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 as u8), + encrypted_payload: Vec::new(), + }, + BlindedHop { + blinded_node_id: ln_test_utils::pubkey(42 as u8), + encrypted_payload: Vec::new(), + }, ], - blinded_payinfo_2 + blinded_payinfo_2, ); let blinded_hints = vec![blinded_path_1.clone(), blinded_path_2.clone()]; let payment_params = PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features).unwrap(); + .with_bolt12_features(bolt12_features) + .unwrap(); - let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100_000); + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, 100_000); route_params.max_total_routing_fee_msat = Some(100_000); - let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), - &scorer, &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_id, + &route_params, + &network_graph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 2); let mut total_amount_paid_msat = 0; for path in route.paths.into_iter() { assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]); if let Some(bt) = &path.blinded_tail { - assert_eq!(bt.blinding_point, - blinded_hints.iter().find(|p| p.payinfo.htlc_maximum_msat == path.final_value_msat()) - .map(|bp| bp.blinding_point()).unwrap()); - } else { panic!(); } + assert_eq!( + bt.blinding_point, + blinded_hints + .iter() + .find(|p| p.payinfo.htlc_maximum_msat == path.final_value_msat()) + .map(|bp| bp.blinding_point()) + .unwrap() + ); + } else { + panic!(); + } total_amount_paid_msat += path.final_value_msat(); } assert_eq!(total_amount_paid_msat, 100_000); @@ -7999,36 +10322,56 @@ mod tests { let amt_msat = 10_000_000; let (_, _, privkeys, nodes) = get_nodes(&secp_ctx); - add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1], - ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - let first_hops = vec![ - get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)]; + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + &privkeys[1], + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + 1, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + let first_hops = vec![get_channel_details( + Some(1), + nodes[1], + InitFeatures::from_le_bytes(vec![0b11]), + 10_000_000, + )]; let blinded_payinfo = BlindedPayInfo { fee_base_msat: 1000, @@ -8044,21 +10387,40 @@ mod tests { let payment_params = PaymentParameters::blinded(blinded_hints.clone()); let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params.clone(), amt_msat); - if let Err(LightningError { err, .. }) = get_route(&nodes[0], &route_params, &netgraph, - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes) { - assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!("Expected error") } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params.clone(), amt_msat); + if let Err(LightningError { err, .. }) = get_route( + &nodes[0], + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { + panic!("Expected error") + } // Sending an exact amount accounting for the blinded path fee works. let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64; let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_minus_blinded_path_fee); - let route = get_route(&nodes[0], &route_params, &netgraph, - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + payment_params, + amt_minus_blinded_path_fee, + ); + let route = get_route( + &nodes[0], + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64); assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee); } @@ -8087,9 +10449,12 @@ mod tests { // Values are taken from the fuzz input that uncovered this panic. let amt_msat = 21_7020_5185_1403_2640; let (_, _, _, nodes) = get_nodes(&secp_ctx); - let first_hops = vec![ - get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config), - 18446744073709551615)]; + let first_hops = vec![get_channel_details( + Some(1), + nodes[1], + channelmanager::provided_init_features(&config), + 18446744073709551615, + )]; let blinded_payinfo = BlindedPayInfo { fee_base_msat: 5046_2720, @@ -8109,14 +10474,22 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features).unwrap(); + .with_bolt12_features(bolt12_features) + .unwrap(); let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); - let route = get_route(&nodes[0], &route_params, &netgraph, - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); + let route = get_route( + &nodes[0], + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64); assert_eq!(route.get_total_amount(), amt_msat); } @@ -8150,16 +10523,25 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features.clone()).unwrap(); + .with_bolt12_features(bolt12_features.clone()) + .unwrap(); let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); if let Err(LightningError { err, .. }) = get_route( - &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes + &our_id, + &route_params, + &netgraph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, ) { assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!() } + } else { + panic!() + } } #[test] @@ -8186,9 +10568,8 @@ mod tests { features: BlindedHopFeatures::empty(), }; let blinded_path = dummy_blinded_path(our_id, blinded_payinfo.clone()); - let mut blinded_hints = vec![ - blinded_path.clone(), blinded_path.clone(), blinded_path.clone(), - ]; + let mut blinded_hints = + vec![blinded_path.clone(), blinded_path.clone(), blinded_path.clone()]; blinded_hints[1].payinfo.fee_base_msat = 5052_9027; blinded_hints[1].payinfo.htlc_minimum_msat = 21_7020_5185_1423_0019; blinded_hints[1].payinfo.htlc_maximum_msat = 1844_6744_0737_0955_1615; @@ -8197,16 +10578,25 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features.clone()).unwrap(); + .with_bolt12_features(bolt12_features.clone()) + .unwrap(); let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); if let Err(LightningError { err, .. }) = get_route( - &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes + &our_id, + &route_params, + &netgraph, + None, + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, ) { assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!() } + } else { + panic!() + } } #[test] @@ -8231,8 +10621,10 @@ mod tests { let (_, our_id, _, nodes) = get_nodes(&secp_ctx); let first_hop_outbound_capacity = 2_7345_2000; let first_hops = vec![get_channel_details( - Some(200), nodes[0], channelmanager::provided_init_features(&config), - first_hop_outbound_capacity + Some(200), + nodes[0], + channelmanager::provided_init_features(&config), + first_hop_outbound_capacity, )]; let base_fee = 1_6778_3453; @@ -8249,41 +10641,47 @@ mod tests { let blinded_path = dummy_blinded_path(nodes[0], blinded_payinfo); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); PaymentParameters::blinded(vec![blinded_path]) - .with_bolt12_features(bolt12_features.clone()).unwrap() + .with_bolt12_features(bolt12_features.clone()) + .unwrap() } else { let route_hint = RouteHint(vec![RouteHintHop { src_node_id: nodes[0], short_channel_id: 42, - fees: RoutingFees { - base_msat: base_fee, - proportional_millionths: 0, - }, + fees: RoutingFees { base_msat: base_fee, proportional_millionths: 0 }, cltv_expiry_delta: 10, htlc_minimum_msat: Some(htlc_min), htlc_maximum_msat: None, }]); PaymentParameters::from_node_id(nodes[1], 42) - .with_route_hints(vec![route_hint]).unwrap() - .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap() + .with_route_hints(vec![route_hint]) + .unwrap() + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap() }; let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); if let Err(LightningError { err, .. }) = get_route( - &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::>()), - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes + &our_id, + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, ) { assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!() } + } else { + panic!() + } } #[test] fn previously_used_liquidity_violates_max_htlc() { do_previously_used_liquidity_violates_max_htlc(true); do_previously_used_liquidity_violates_max_htlc(false); - } fn do_previously_used_liquidity_violates_max_htlc(blinded_payee: bool) { // Test that if a candidate first_hop<>route_hint_src_node channel does not have enough @@ -8301,11 +10699,20 @@ mod tests { // Values are taken from the fuzz input that uncovered this panic. let amt_msat = 52_4288; let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let first_hops = vec![get_channel_details( - Some(161), nodes[0], channelmanager::provided_init_features(&config), 486_4000 - ), get_channel_details( - Some(122), nodes[0], channelmanager::provided_init_features(&config), 179_5000 - )]; + let first_hops = vec![ + get_channel_details( + Some(161), + nodes[0], + channelmanager::provided_init_features(&config), + 486_4000, + ), + get_channel_details( + Some(122), + nodes[0], + channelmanager::provided_init_features(&config), + 179_5000, + ), + ]; let base_fees = [0, 425_9840, 0, 0]; let htlc_mins = [1_4392, 19_7401, 1027, 6_5535]; @@ -8324,35 +10731,41 @@ mod tests { } let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features.clone()).unwrap() + .with_bolt12_features(bolt12_features.clone()) + .unwrap() } else { let mut route_hints = Vec::new(); for (idx, (base_fee, htlc_min)) in base_fees.iter().zip(htlc_mins.iter()).enumerate() { route_hints.push(RouteHint(vec![RouteHintHop { src_node_id: nodes[0], short_channel_id: 42 + idx as u64, - fees: RoutingFees { - base_msat: *base_fee, - proportional_millionths: 0, - }, + fees: RoutingFees { base_msat: *base_fee, proportional_millionths: 0 }, cltv_expiry_delta: 10, htlc_minimum_msat: Some(*htlc_min), htlc_maximum_msat: Some(htlc_min * 100), }])); } PaymentParameters::from_node_id(nodes[1], 42) - .with_route_hints(route_hints).unwrap() - .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap() + .with_route_hints(route_hints) + .unwrap() + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap() }; let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); let route = get_route( - &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::>()), - Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes - ).unwrap(); + &our_id, + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.get_total_amount(), amt_msat); } @@ -8368,7 +10781,11 @@ mod tests { let logger = Arc::new(ln_test_utils::TestLogger::new()); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger))); let gossip_sync = P2PGossipSync::new(network_graph.clone(), None, logger.clone()); - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone()); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + network_graph.clone(), + logger.clone(), + ); let random_seed_bytes = [42; 32]; let config = UserConfig::default(); @@ -8376,50 +10793,79 @@ mod tests { let amt_msat = 7_4009_8048; let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx); let first_hops = vec![get_channel_details( - Some(200), nodes[0], channelmanager::provided_init_features(&config), 2_7345_2000 + Some(200), + nodes[0], + channelmanager::provided_init_features(&config), + 2_7345_2000, )]; - add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (6 << 4) | 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + &privkeys[6], + ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), + 6, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (6 << 4) | 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[0], + NodeFeatures::from_le_bytes(id_to_feature_flags(1)), + 0, + ); let htlc_min = 2_5165_8240; - let blinded_hints = vec![ - dummy_blinded_path(nodes[0], BlindedPayInfo { + let blinded_hints = vec![dummy_blinded_path( + nodes[0], + BlindedPayInfo { fee_base_msat: 1_6778_3453, fee_proportional_millionths: 0, htlc_minimum_msat: htlc_min, htlc_maximum_msat: htlc_min * 100, cltv_expiry_delta: 10, features: BlindedHopFeatures::empty(), - }) - ]; + }, + )]; let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features.clone()).unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + .with_bolt12_features(bolt12_features.clone()) + .unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); let netgraph = network_graph.read_only(); if let Err(LightningError { err, .. }) = get_route( - &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::>()), - Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), - &random_seed_bytes + &our_id, + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &ProbabilisticScoringFeeParameters::default(), + &random_seed_bytes, ) { assert_eq!(err, "Failed to find a path to the given destination"); - } else { panic!() } + } else { + panic!() + } } #[test] @@ -8431,18 +10877,23 @@ mod tests { let secp_ctx = Secp256k1::new(); let logger = Arc::new(ln_test_utils::TestLogger::new()); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger))); - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone()); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + network_graph.clone(), + logger.clone(), + ); let random_seed_bytes = [42; 32]; let config = UserConfig::default(); // Values are taken from the fuzz input that uncovered this panic. let amt_msat = 562_0000; let (_, our_id, _, nodes) = get_nodes(&secp_ctx); - let first_hops = vec![ - get_channel_details( - Some(83), nodes[0], channelmanager::provided_init_features(&config), 2199_0000, - ), - ]; + let first_hops = vec![get_channel_details( + Some(83), + nodes[0], + channelmanager::provided_init_features(&config), + 2199_0000, + )]; let htlc_mins = [49_0000, 1125_0000]; let payment_params = { @@ -8460,17 +10911,23 @@ mod tests { } let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); PaymentParameters::blinded(blinded_hints.clone()) - .with_bolt12_features(bolt12_features.clone()).unwrap() + .with_bolt12_features(bolt12_features.clone()) + .unwrap() }; let netgraph = network_graph.read_only(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); let route = get_route( - &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::>()), - Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), - &random_seed_bytes - ).unwrap(); + &our_id, + &route_params, + &netgraph, + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &ProbabilisticScoringFeeParameters::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.get_total_amount(), amt_msat); } @@ -8490,104 +10947,145 @@ mod tests { let amt_msat = 1_000_000; let (our_privkey, our_node_id, privkeys, nodes) = get_nodes(&secp_ctx); - add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[0], - ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + &privkeys[0], + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + 1, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); - add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1], - ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 2); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 2, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 42, - htlc_minimum_msat: 1_000, - htlc_maximum_msat: 10_000_000, - fee_base_msat: 800, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + &privkeys[1], + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + 2, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 2, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 42, + htlc_minimum_msat: 1_000, + htlc_maximum_msat: 10_000_000, + fee_base_msat: 800, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); let dest_node_id = nodes[2]; - let route_hint = RouteHint(vec![RouteHintHop { - src_node_id: our_node_id, - short_channel_id: 44, - fees: RoutingFees { - base_msat: 234, - proportional_millionths: 0, + let route_hint = RouteHint(vec![ + RouteHintHop { + src_node_id: our_node_id, + short_channel_id: 44, + fees: RoutingFees { base_msat: 234, proportional_millionths: 0 }, + cltv_expiry_delta: 10, + htlc_minimum_msat: None, + htlc_maximum_msat: Some(5_000_000), }, - cltv_expiry_delta: 10, - htlc_minimum_msat: None, - htlc_maximum_msat: Some(5_000_000), - }, - RouteHintHop { - src_node_id: nodes[0], - short_channel_id: 45, - fees: RoutingFees { - base_msat: 123, - proportional_millionths: 0, + RouteHintHop { + src_node_id: nodes[0], + short_channel_id: 45, + fees: RoutingFees { base_msat: 123, proportional_millionths: 0 }, + cltv_expiry_delta: 10, + htlc_minimum_msat: None, + htlc_maximum_msat: None, }, - cltv_expiry_delta: 10, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }]); + ]); let payment_params = PaymentParameters::from_node_id(dest_node_id, 42) - .with_route_hints(vec![route_hint]).unwrap() - .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + .with_route_hints(vec![route_hint]) + .unwrap() + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); // First create an insufficient first hop for channel with SCID 1 and check we'd use the // route hint. - let first_hop = get_channel_details(Some(1), nodes[0], - channelmanager::provided_init_features(&config), 999_999); + let first_hop = get_channel_details( + Some(1), + nodes[0], + channelmanager::provided_init_features(&config), + 999_999, + ); let first_hops = vec![first_hop]; - let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_node_id, + &route_params.clone(), + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.get_total_amount(), amt_msat); assert_eq!(route.paths[0].hops.len(), 2); @@ -8597,25 +11095,49 @@ mod tests { // Now check we would trust our first hop info, i.e., fail if we detect the route hint is // for a first hop channel. - let mut first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 999_999); + let mut first_hop = get_channel_details( + Some(1), + nodes[0], + channelmanager::provided_init_features(&config), + 999_999, + ); first_hop.outbound_scid_alias = Some(44); let first_hops = vec![first_hop]; - let route_res = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes); + let route_res = get_route( + &our_node_id, + &route_params.clone(), + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ); assert!(route_res.is_err()); // Finally check we'd use the first hop if has sufficient outbound capacity. But we'd stil // use the cheaper second hop of the route hint. - let mut first_hop = get_channel_details(Some(1), nodes[0], - channelmanager::provided_init_features(&config), 10_000_000); + let mut first_hop = get_channel_details( + Some(1), + nodes[0], + channelmanager::provided_init_features(&config), + 10_000_000, + ); first_hop.outbound_scid_alias = Some(44); let first_hops = vec![first_hop]; - let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_node_id, + &route_params.clone(), + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.get_total_amount(), amt_msat); assert_eq!(route.paths[0].hops.len(), 2); @@ -8639,32 +11161,42 @@ mod tests { let amt_msat = 1_000_000; let dest_node_id = nodes[1]; - let first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 10_000_000); + let first_hop = get_channel_details( + Some(1), + nodes[0], + channelmanager::provided_init_features(&config), + 10_000_000, + ); let first_hops = vec![first_hop]; let route_hint = RouteHint(vec![RouteHintHop { src_node_id: our_node_id, short_channel_id: 44, - fees: RoutingFees { - base_msat: 123, - proportional_millionths: 0, - }, + fees: RoutingFees { base_msat: 123, proportional_millionths: 0 }, cltv_expiry_delta: 10, htlc_minimum_msat: None, htlc_maximum_msat: None, }]); let payment_params = PaymentParameters::from_node_id(dest_node_id, 42) - .with_route_hints(vec![route_hint]).unwrap() - .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap(); - - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, amt_msat); + .with_route_hints(vec![route_hint]) + .unwrap() + .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)) + .unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); - let route = get_route(&our_node_id, &route_params, &network_graph.read_only(), - Some(&first_hops.iter().collect::>()), Arc::clone(&logger), &scorer, - &Default::default(), &random_seed_bytes).unwrap(); + let route = get_route( + &our_node_id, + &route_params, + &network_graph.read_only(), + Some(&first_hops.iter().collect::>()), + Arc::clone(&logger), + &scorer, + &Default::default(), + &random_seed_bytes, + ) + .unwrap(); assert_eq!(route.paths.len(), 1); assert_eq!(route.get_total_amount(), amt_msat); @@ -8678,26 +11210,27 @@ mod tests { #[cfg(any(test, ldk_bench))] pub(crate) mod bench_utils { use super::*; - use std::fs::File; - use std::io::Read; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; + use std::fs::File; + use std::io::Read; use crate::chain::transaction::OutPoint; - use crate::routing::scoring::{ProbabilisticScorer, ScoreUpdate}; use crate::ln::channel_state::{ChannelCounterparty, ChannelShutdownState}; use crate::ln::channelmanager; use crate::ln::types::ChannelId; + use crate::routing::scoring::{ProbabilisticScorer, ScoreUpdate}; + use crate::sync::Arc; use crate::util::config::UserConfig; use crate::util::test_utils::TestLogger; - use crate::sync::Arc; /// Tries to open a network graph file, or panics with a URL to fetch it. pub(crate) fn get_graph_scorer_file() -> Result<(std::fs::File, std::fs::File), &'static str> { let load_file = |fname, err_str| { File::open(fname) // By default we're run in RL/lightning .or_else(|_| File::open(&format!("lightning/{}", fname))) // We may be run manually in RL/ - .or_else(|_| { // Fall back to guessing based on the binary location + .or_else(|_| { + // Fall back to guessing based on the binary location // path is likely something like .../rust-lightning/target/debug/deps/lightning-... let mut path = std::env::current_exe().unwrap(); path.pop(); // lightning-... @@ -8708,7 +11241,8 @@ pub(crate) mod bench_utils { path.push(fname); File::open(path) }) - .or_else(|_| { // Fall back to guessing based on the binary location for a subcrate + .or_else(|_| { + // Fall back to guessing based on the binary location for a subcrate // path is likely something like .../rust-lightning/bench/target/debug/deps/bench.. let mut path = std::env::current_exe().unwrap(); path.pop(); // bench... @@ -8720,7 +11254,7 @@ pub(crate) mod bench_utils { path.push(fname); File::open(path) }) - .map_err(|_| err_str) + .map_err(|_| err_str) }; let graph_res = load_file( "net_graph-2023-12-10.bin", @@ -8736,8 +11270,15 @@ pub(crate) mod bench_utils { return Ok((graph_res?, scorer_res?)); } - pub(crate) fn read_graph_scorer(logger: &TestLogger) - -> Result<(Arc>, ProbabilisticScorer>, &TestLogger>), &'static str> { + pub(crate) fn read_graph_scorer( + logger: &TestLogger, + ) -> Result< + ( + Arc>, + ProbabilisticScorer>, &TestLogger>, + ), + &'static str, + > { let (mut graph_file, mut scorer_file) = get_graph_scorer_file()?; let mut graph_buffer = Vec::new(); let mut scorer_buffer = Vec::new(); @@ -8768,7 +11309,8 @@ pub(crate) mod bench_utils { outbound_htlc_maximum_msat: None, }, funding_txo: Some(OutPoint { - txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 + txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), + index: 0, }), channel_type: None, short_channel_id: Some(1), @@ -8798,9 +11340,9 @@ pub(crate) mod bench_utils { } } - pub(crate) fn generate_test_routes(graph: &NetworkGraph<&TestLogger>, scorer: &mut S, - score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, mut seed: u64, - starting_amount: u64, route_count: usize, + pub(crate) fn generate_test_routes( + graph: &NetworkGraph<&TestLogger>, scorer: &mut S, score_params: &S::ScoreParams, + features: Bolt11InvoiceFeatures, mut seed: u64, starting_amount: u64, route_count: usize, ) -> Vec<(ChannelDetails, PaymentParameters, u64)> { let payer = payer_pubkey(); let random_seed_bytes = [42; 32]; @@ -8810,20 +11352,43 @@ pub(crate) mod bench_utils { for _ in 0..route_count { loop { seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; - let src = PublicKey::from_slice(nodes.unordered_keys() - .skip((seed as usize) % nodes.len()).next().unwrap().as_slice()).unwrap(); + let src = PublicKey::from_slice( + nodes + .unordered_keys() + .skip((seed as usize) % nodes.len()) + .next() + .unwrap() + .as_slice(), + ) + .unwrap(); seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; - let dst = PublicKey::from_slice(nodes.unordered_keys() - .skip((seed as usize) % nodes.len()).next().unwrap().as_slice()).unwrap(); + let dst = PublicKey::from_slice( + nodes + .unordered_keys() + .skip((seed as usize) % nodes.len()) + .next() + .unwrap() + .as_slice(), + ) + .unwrap(); let params = PaymentParameters::from_node_id(dst, 42) - .with_bolt11_features(features.clone()).unwrap(); + .with_bolt11_features(features.clone()) + .unwrap(); let first_hop = first_hop(src); let amt_msat = starting_amount + seed % 1_000_000; - let route_params = RouteParameters::from_payment_params_and_value( - params.clone(), amt_msat); - let path_exists = - get_route(&payer, &route_params, &graph.read_only(), Some(&[&first_hop]), - &TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok(); + let route_params = + RouteParameters::from_payment_params_and_value(params.clone(), amt_msat); + let path_exists = get_route( + &payer, + &route_params, + &graph.read_only(), + Some(&[&first_hop]), + &TestLogger::new(), + scorer, + score_params, + &random_seed_bytes, + ) + .is_ok(); if path_exists { route_endpoints.push((first_hop, params, amt_msat)); break; @@ -8838,11 +11403,11 @@ pub(crate) mod bench_utils { #[cfg(ldk_bench)] pub mod benches { use super::*; - use crate::routing::scoring::{ScoreUpdate, ScoreLookUp}; use crate::ln::channelmanager; use crate::ln::features::Bolt11InvoiceFeatures; use crate::routing::gossip::NetworkGraph; use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScoringFeeParameters}; + use crate::routing::scoring::{ScoreLookUp, ScoreUpdate}; use crate::util::config::UserConfig; use crate::util::logger::{Logger, Record}; use crate::util::test_utils::TestLogger; @@ -8858,43 +11423,75 @@ pub mod benches { let logger = TestLogger::new(); let (network_graph, _) = bench_utils::read_graph_scorer(&logger).unwrap(); let scorer = FixedPenaltyScorer::with_penalty(0); - generate_routes(bench, &network_graph, scorer, &Default::default(), - Bolt11InvoiceFeatures::empty(), 0, "generate_routes_with_zero_penalty_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + &Default::default(), + Bolt11InvoiceFeatures::empty(), + 0, + "generate_routes_with_zero_penalty_scorer", + ); } pub fn generate_mpp_routes_with_zero_penalty_scorer(bench: &mut Criterion) { let logger = TestLogger::new(); let (network_graph, _) = bench_utils::read_graph_scorer(&logger).unwrap(); let scorer = FixedPenaltyScorer::with_penalty(0); - generate_routes(bench, &network_graph, scorer, &Default::default(), - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0, - "generate_mpp_routes_with_zero_penalty_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + &Default::default(), + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 0, + "generate_mpp_routes_with_zero_penalty_scorer", + ); } pub fn generate_routes_with_probabilistic_scorer(bench: &mut Criterion) { let logger = TestLogger::new(); let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let params = ProbabilisticScoringFeeParameters::default(); - generate_routes(bench, &network_graph, scorer, ¶ms, Bolt11InvoiceFeatures::empty(), 0, - "generate_routes_with_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + Bolt11InvoiceFeatures::empty(), + 0, + "generate_routes_with_probabilistic_scorer", + ); } pub fn generate_mpp_routes_with_probabilistic_scorer(bench: &mut Criterion) { let logger = TestLogger::new(); let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let params = ProbabilisticScoringFeeParameters::default(); - generate_routes(bench, &network_graph, scorer, ¶ms, - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0, - "generate_mpp_routes_with_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 0, + "generate_mpp_routes_with_probabilistic_scorer", + ); } pub fn generate_large_mpp_routes_with_probabilistic_scorer(bench: &mut Criterion) { let logger = TestLogger::new(); let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let params = ProbabilisticScoringFeeParameters::default(); - generate_routes(bench, &network_graph, scorer, ¶ms, - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000, - "generate_large_mpp_routes_with_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 100_000_000, + "generate_large_mpp_routes_with_probabilistic_scorer", + ); } pub fn generate_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) { @@ -8902,9 +11499,15 @@ pub mod benches { let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let mut params = ProbabilisticScoringFeeParameters::default(); params.linear_success_probability = false; - generate_routes(bench, &network_graph, scorer, ¶ms, - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0, - "generate_routes_with_nonlinear_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 0, + "generate_routes_with_nonlinear_probabilistic_scorer", + ); } pub fn generate_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) { @@ -8912,9 +11515,15 @@ pub mod benches { let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let mut params = ProbabilisticScoringFeeParameters::default(); params.linear_success_probability = false; - generate_routes(bench, &network_graph, scorer, ¶ms, - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0, - "generate_mpp_routes_with_nonlinear_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 0, + "generate_mpp_routes_with_nonlinear_probabilistic_scorer", + ); } pub fn generate_large_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) { @@ -8922,9 +11531,15 @@ pub mod benches { let (network_graph, scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let mut params = ProbabilisticScoringFeeParameters::default(); params.linear_success_probability = false; - generate_routes(bench, &network_graph, scorer, ¶ms, - channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000, - "generate_large_mpp_routes_with_nonlinear_probabilistic_scorer"); + generate_routes( + bench, + &network_graph, + scorer, + ¶ms, + channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), + 100_000_000, + "generate_large_mpp_routes_with_nonlinear_probabilistic_scorer", + ); } fn generate_routes( @@ -8933,7 +11548,15 @@ pub mod benches { bench_name: &'static str, ) { // First, get 100 (source, destination) pairs for which route-getting actually succeeds... - let route_endpoints = bench_utils::generate_test_routes(graph, &mut scorer, score_params, features, 0xdeadbeef, starting_amount, 50); + let route_endpoints = bench_utils::generate_test_routes( + graph, + &mut scorer, + score_params, + features, + 0xdeadbeef, + starting_amount, + 50, + ); // ...then benchmark finding paths between the nodes we learned. do_route_bench(bench, graph, scorer, score_params, bench_name, route_endpoints); @@ -8949,12 +11572,24 @@ pub mod benches { let random_seed_bytes = [42; 32]; let mut idx = 0; - bench.bench_function(bench_name, |b| b.iter(|| { - let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()]; - let route_params = RouteParameters::from_payment_params_and_value(params.clone(), *amt); - assert!(get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]), - &DummyLogger{}, &scorer, score_params, &random_seed_bytes).is_ok()); - idx += 1; - })); + bench.bench_function(bench_name, |b| { + b.iter(|| { + let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()]; + let route_params = + RouteParameters::from_payment_params_and_value(params.clone(), *amt); + assert!(get_route( + &payer, + &route_params, + &graph.read_only(), + Some(&[first_hop]), + &DummyLogger {}, + &scorer, + score_params, + &random_seed_bytes + ) + .is_ok()); + idx += 1; + }) + }); } } diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index b27a9f97b02..11b7ca77d0d 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -53,21 +53,21 @@ use crate::ln::msgs::DecodeError; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; -use crate::routing::router::{Path, CandidateRouteHop, PublicHopCandidate}; use crate::routing::log_approx; -use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use crate::routing::router::{CandidateRouteHop, Path, PublicHopCandidate}; use crate::util::logger::Logger; +use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use crate::io::{self, Read}; use crate::prelude::*; -use core::{cmp, fmt}; +use crate::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use core::ops::{Deref, DerefMut}; use core::time::Duration; -use crate::io::{self, Read}; -use crate::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use core::{cmp, fmt}; #[cfg(not(c_bindings))] use { - core::cell::{RefCell, RefMut, Ref}, crate::sync::{Mutex, MutexGuard}, + core::cell::{Ref, RefCell, RefMut}, }; /// We define Score ever-so-slightly differently based on whether we are being built for C bindings @@ -323,7 +323,8 @@ impl<'a, T: 'a + Score> Deref for MultiThreadedScoreLockRead<'a, T> { #[cfg(c_bindings)] impl<'a, T: Score> ScoreLookUp for MultiThreadedScoreLockRead<'a, T> { type ScoreParams = T::ScoreParams; - fn channel_penalty_msat(&self, candidate:&CandidateRouteHop, usage: ChannelUsage, score_params: &Self::ScoreParams + fn channel_penalty_msat( + &self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &Self::ScoreParams, ) -> u64 { self.0.channel_penalty_msat(candidate, usage, score_params) } @@ -354,7 +355,9 @@ impl<'a, T: 'a + Score> DerefMut for MultiThreadedScoreLockWrite<'a, T> { #[cfg(c_bindings)] impl<'a, T: Score> ScoreUpdate for MultiThreadedScoreLockWrite<'a, T> { - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + fn payment_path_failed( + &mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration, + ) { self.0.payment_path_failed(path, short_channel_id, duration_since_epoch) } @@ -375,7 +378,6 @@ impl<'a, T: Score> ScoreUpdate for MultiThreadedScoreLockWrite<'a, T> { } } - /// Proposed use of a channel passed as a parameter to [`ScoreLookUp::channel_penalty_msat`]. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ChannelUsage { @@ -405,17 +407,25 @@ impl FixedPenaltyScorer { impl ScoreLookUp for FixedPenaltyScorer { type ScoreParams = (); - fn channel_penalty_msat(&self, _: &CandidateRouteHop, _: ChannelUsage, _score_params: &Self::ScoreParams) -> u64 { + fn channel_penalty_msat( + &self, _: &CandidateRouteHop, _: ChannelUsage, _score_params: &Self::ScoreParams, + ) -> u64 { self.penalty_msat } } impl ScoreUpdate for FixedPenaltyScorer { - fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration) {} + fn payment_path_failed( + &mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration, + ) { + } fn payment_path_successful(&mut self, _path: &Path, _duration_since_epoch: Duration) {} - fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration) {} + fn probe_failed( + &mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration, + ) { + } fn probe_successful(&mut self, _path: &Path, _duration_since_epoch: Duration) {} @@ -470,7 +480,9 @@ impl ReadableArgs for FixedPenaltyScorer { /// [`historical_liquidity_penalty_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_multiplier_msat /// [`historical_liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_amount_multiplier_msat pub struct ProbabilisticScorer>, L: Deref> -where L::Target: Logger { +where + L::Target: Logger, +{ decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L, @@ -804,10 +816,15 @@ struct ChannelLiquidity { // The next two cache lines will have the historical points, which we only access last during // scoring, followed by the last_updated `Duration`s (which we do not need during scoring). const _LIQUIDITY_MAP_SIZING_CHECK: usize = 192 - ::core::mem::size_of::<(u64, ChannelLiquidity)>(); -const _LIQUIDITY_MAP_SIZING_CHECK_2: usize = ::core::mem::size_of::<(u64, ChannelLiquidity)>() - 192; +const _LIQUIDITY_MAP_SIZING_CHECK_2: usize = + ::core::mem::size_of::<(u64, ChannelLiquidity)>() - 192; /// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity. -struct DirectedChannelLiquidity, HT: Deref, T: Deref> { +struct DirectedChannelLiquidity< + L: Deref, + HT: Deref, + T: Deref, +> { min_liquidity_offset_msat: L, max_liquidity_offset_msat: L, liquidity_history: DirectedHistoricalLiquidityTracker, @@ -816,16 +833,16 @@ struct DirectedChannelLiquidity, HT: Deref>, L: Deref> ProbabilisticScorer where L::Target: Logger { +impl>, L: Deref> ProbabilisticScorer +where + L::Target: Logger, +{ /// Creates a new scorer using the given scoring parameters for sending payments from a node /// through a network graph. - pub fn new(decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L) -> Self { - Self { - decay_params, - network_graph, - logger, - channel_liquidities: new_hash_map(), - } + pub fn new( + decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L, + ) -> Self { + Self { decay_params, network_graph, logger, channel_liquidities: new_hash_map() } } #[cfg(test)] @@ -847,8 +864,10 @@ impl>, L: Deref> ProbabilisticScorer whe let amt = directed_info.effective_capacity().as_msat(); let dir_liq = liq.as_directed(source, target, amt); - let min_buckets = &dir_liq.liquidity_history.min_liquidity_offset_history_buckets(); - let max_buckets = &dir_liq.liquidity_history.max_liquidity_offset_history_buckets(); + let min_buckets = + &dir_liq.liquidity_history.min_liquidity_offset_history_buckets(); + let max_buckets = + &dir_liq.liquidity_history.max_liquidity_offset_history_buckets(); log_debug!(self.logger, core::concat!( "Liquidity from {} to {} via {} is in the range ({}, {}).\n", @@ -876,7 +895,13 @@ impl>, L: Deref> ProbabilisticScorer whe max_buckets[ 7], max_buckets[ 6], max_buckets[ 5], max_buckets[ 4], max_buckets[ 3], max_buckets[ 2], max_buckets[ 1], max_buckets[ 0]); } else { - log_debug!(self.logger, "No amount known for SCID {} from {:?} to {:?}", scid, source, target); + log_debug!( + self.logger, + "No amount known for SCID {} from {:?} to {:?}", + scid, + source, + target + ); } }; @@ -890,7 +915,9 @@ impl>, L: Deref> ProbabilisticScorer whe /// Query the estimated minimum and maximum liquidity available for sending a payment over the /// channel with `scid` towards the given `target` node. - pub fn estimated_channel_liquidity_range(&self, scid: u64, target: &NodeId) -> Option<(u64, u64)> { + pub fn estimated_channel_liquidity_range( + &self, scid: u64, target: &NodeId, + ) -> Option<(u64, u64)> { let graph = self.network_graph.read_only(); if let Some(chan) = graph.channels().get(&scid) { @@ -931,8 +958,9 @@ impl>, L: Deref> ProbabilisticScorer whe /// /// In order to fetch a single success probability from the buckets provided here, as used in /// the scoring model, see [`Self::historical_estimated_payment_success_probability`]. - pub fn historical_estimated_channel_liquidity_probabilities(&self, scid: u64, target: &NodeId) - -> Option<([u16; 32], [u16; 32])> { + pub fn historical_estimated_channel_liquidity_probabilities( + &self, scid: u64, target: &NodeId, + ) -> Option<([u16; 32], [u16; 32])> { let graph = self.network_graph.read_only(); if let Some(chan) = graph.channels().get(&scid) { @@ -941,8 +969,10 @@ impl>, L: Deref> ProbabilisticScorer whe let amt = directed_info.effective_capacity().as_msat(); let dir_liq = liq.as_directed(source, target, amt); - let min_buckets = *dir_liq.liquidity_history.min_liquidity_offset_history_buckets(); - let mut max_buckets = *dir_liq.liquidity_history.max_liquidity_offset_history_buckets(); + let min_buckets = + *dir_liq.liquidity_history.min_liquidity_offset_history_buckets(); + let mut max_buckets = + *dir_liq.liquidity_history.max_liquidity_offset_history_buckets(); // Note that the liquidity buckets are an offset from the edge, so we inverse // the max order to get the probabilities from zero. @@ -962,8 +992,9 @@ impl>, L: Deref> ProbabilisticScorer whe /// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by /// [`Self::estimated_channel_liquidity_range`]). pub fn historical_estimated_payment_success_probability( - &self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters) - -> Option { + &self, scid: u64, target: &NodeId, amount_msat: u64, + params: &ProbabilisticScoringFeeParameters, + ) -> Option { let graph = self.network_graph.read_only(); if let Some(chan) = graph.channels().get(&scid) { @@ -972,9 +1003,14 @@ impl>, L: Deref> ProbabilisticScorer whe let capacity_msat = directed_info.effective_capacity().as_msat(); let dir_liq = liq.as_directed(source, target, capacity_msat); - return dir_liq.liquidity_history.calculate_success_probability_times_billion( - ¶ms, amount_msat, capacity_msat - ).map(|p| p as f64 / (1024 * 1024 * 1024) as f64); + return dir_liq + .liquidity_history + .calculate_success_probability_times_billion( + ¶ms, + amount_msat, + capacity_msat, + ) + .map(|p| p as f64 / (1024 * 1024 * 1024) as f64); } } } @@ -999,12 +1035,11 @@ impl ChannelLiquidity { &self, source: &NodeId, target: &NodeId, capacity_msat: u64, ) -> DirectedChannelLiquidity<&u64, &HistoricalLiquidityTracker, &Duration> { let source_less_than_target = source < target; - let (min_liquidity_offset_msat, max_liquidity_offset_msat) = - if source_less_than_target { - (&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat) - } else { - (&self.max_liquidity_offset_msat, &self.min_liquidity_offset_msat) - }; + let (min_liquidity_offset_msat, max_liquidity_offset_msat) = if source_less_than_target { + (&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat) + } else { + (&self.max_liquidity_offset_msat, &self.min_liquidity_offset_msat) + }; DirectedChannelLiquidity { min_liquidity_offset_msat, @@ -1022,12 +1057,11 @@ impl ChannelLiquidity { &mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, ) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalLiquidityTracker, &mut Duration> { let source_less_than_target = source < target; - let (min_liquidity_offset_msat, max_liquidity_offset_msat) = - if source_less_than_target { - (&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat) - } else { - (&mut self.max_liquidity_offset_msat, &mut self.min_liquidity_offset_msat) - }; + let (min_liquidity_offset_msat, max_liquidity_offset_msat) = if source_less_than_target { + (&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat) + } else { + (&mut self.max_liquidity_offset_msat, &mut self.min_liquidity_offset_msat) + }; DirectedChannelLiquidity { min_liquidity_offset_msat, @@ -1087,42 +1121,54 @@ fn success_probability( debug_assert!(amount_msat < max_liquidity_msat); debug_assert!(max_liquidity_msat <= capacity_msat); - let (numerator, mut denominator) = - if params.linear_success_probability { - (max_liquidity_msat - amount_msat, - (max_liquidity_msat - min_liquidity_msat).saturating_add(1)) - } else { - let capacity = capacity_msat as f64; - let min = (min_liquidity_msat as f64) / capacity; - let max = (max_liquidity_msat as f64) / capacity; - let amount = (amount_msat as f64) / capacity; - - // Assume the channel has a probability density function of (x - 0.5)^2 for values from - // 0 to 1 (where 1 is the channel's full capacity). The success probability given some - // liquidity bounds is thus the integral under the curve from the amount to maximum - // estimated liquidity, divided by the same integral from the minimum to the maximum - // estimated liquidity bounds. - // - // Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can - // calculate the cumulative density function between the min/max bounds trivially. Note - // that we don't bother to normalize the CDF to total to 1, as it will come out in the - // division of num / den. - let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5); - let num = max_pow - amt_pow; - let den = max_pow - min_pow; - - // Because our numerator and denominator max out at 0.5^3 we need to multiply them by - // quite a large factor to get something useful (ideally in the 2^30 range). - const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0; - let numerator = (num * BILLIONISH) as u64 + 1; - let denominator = (den * BILLIONISH) as u64 + 1; - debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num); - debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den); - (numerator, denominator) - }; - - if min_zero_implies_no_successes && min_liquidity_msat == 0 && - denominator < u64::max_value() / 21 + let (numerator, mut denominator) = if params.linear_success_probability { + ( + max_liquidity_msat - amount_msat, + (max_liquidity_msat - min_liquidity_msat).saturating_add(1), + ) + } else { + let capacity = capacity_msat as f64; + let min = (min_liquidity_msat as f64) / capacity; + let max = (max_liquidity_msat as f64) / capacity; + let amount = (amount_msat as f64) / capacity; + + // Assume the channel has a probability density function of (x - 0.5)^2 for values from + // 0 to 1 (where 1 is the channel's full capacity). The success probability given some + // liquidity bounds is thus the integral under the curve from the amount to maximum + // estimated liquidity, divided by the same integral from the minimum to the maximum + // estimated liquidity bounds. + // + // Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can + // calculate the cumulative density function between the min/max bounds trivially. Note + // that we don't bother to normalize the CDF to total to 1, as it will come out in the + // division of num / den. + let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5); + let num = max_pow - amt_pow; + let den = max_pow - min_pow; + + // Because our numerator and denominator max out at 0.5^3 we need to multiply them by + // quite a large factor to get something useful (ideally in the 2^30 range). + const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0; + let numerator = (num * BILLIONISH) as u64 + 1; + let denominator = (den * BILLIONISH) as u64 + 1; + debug_assert!( + numerator <= 1 << 30, + "Got large numerator ({}) from float {}.", + numerator, + num + ); + debug_assert!( + denominator <= 1 << 30, + "Got large denominator ({}) from float {}.", + denominator, + den + ); + (numerator, denominator) + }; + + if min_zero_implies_no_successes + && min_liquidity_msat == 0 + && denominator < u64::max_value() / 21 { // If we have no knowledge of the channel, scale probability down by ~75% // Note that we prefer to increase the denominator rather than decrease the numerator as @@ -1134,11 +1180,17 @@ fn success_probability( (numerator, denominator) } -impl, HT: Deref, T: Deref> -DirectedChannelLiquidity< L, HT, T> { +impl< + L: Deref, + HT: Deref, + T: Deref, + > DirectedChannelLiquidity +{ /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in /// this direction. - fn penalty_msat(&self, amount_msat: u64, score_params: &ProbabilisticScoringFeeParameters) -> u64 { + fn penalty_msat( + &self, amount_msat: u64, score_params: &ProbabilisticScoringFeeParameters, + ) -> u64 { let available_capacity = self.capacity_msat; let max_liquidity_msat = self.max_liquidity_msat(); let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat); @@ -1150,13 +1202,22 @@ DirectedChannelLiquidity< L, HT, T> { // capacity and without any certainty on the liquidity upper bound, plus the // impossibility penalty. let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048; - Self::combined_penalty_msat(amount_msat, negative_log10_times_2048, - score_params.liquidity_penalty_multiplier_msat, - score_params.liquidity_penalty_amount_multiplier_msat) - .saturating_add(score_params.considered_impossible_penalty_msat) + Self::combined_penalty_msat( + amount_msat, + negative_log10_times_2048, + score_params.liquidity_penalty_multiplier_msat, + score_params.liquidity_penalty_amount_multiplier_msat, + ) + .saturating_add(score_params.considered_impossible_penalty_msat) } else { - let (numerator, denominator) = success_probability(amount_msat, - min_liquidity_msat, max_liquidity_msat, available_capacity, score_params, false); + let (numerator, denominator) = success_probability( + amount_msat, + min_liquidity_msat, + max_liquidity_msat, + available_capacity, + score_params, + false, + ); if denominator - numerator < denominator / PRECISION_LOWER_BOUND_DENOMINATOR { // If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64), // don't bother trying to use the log approximation as it gets too noisy to be @@ -1165,43 +1226,65 @@ DirectedChannelLiquidity< L, HT, T> { } else { let negative_log10_times_2048 = log_approx::negative_log10_times_2048(numerator, denominator); - Self::combined_penalty_msat(amount_msat, negative_log10_times_2048, + Self::combined_penalty_msat( + amount_msat, + negative_log10_times_2048, score_params.liquidity_penalty_multiplier_msat, - score_params.liquidity_penalty_amount_multiplier_msat) + score_params.liquidity_penalty_amount_multiplier_msat, + ) } }; if amount_msat >= available_capacity { // We're trying to send more than the capacity, use a max penalty. - res = res.saturating_add(Self::combined_penalty_msat(amount_msat, + res = res.saturating_add(Self::combined_penalty_msat( + amount_msat, NEGATIVE_LOG10_UPPER_BOUND * 2048, score_params.historical_liquidity_penalty_multiplier_msat, - score_params.historical_liquidity_penalty_amount_multiplier_msat)); + score_params.historical_liquidity_penalty_amount_multiplier_msat, + )); return res; } - if score_params.historical_liquidity_penalty_multiplier_msat != 0 || - score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 { - if let Some(cumulative_success_prob_times_billion) = self.liquidity_history - .calculate_success_probability_times_billion( - score_params, amount_msat, self.capacity_msat) - { - let historical_negative_log10_times_2048 = - log_approx::negative_log10_times_2048(cumulative_success_prob_times_billion + 1, 1024 * 1024 * 1024); - res = res.saturating_add(Self::combined_penalty_msat(amount_msat, - historical_negative_log10_times_2048, score_params.historical_liquidity_penalty_multiplier_msat, - score_params.historical_liquidity_penalty_amount_multiplier_msat)); + if score_params.historical_liquidity_penalty_multiplier_msat != 0 + || score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 + { + if let Some(cumulative_success_prob_times_billion) = + self.liquidity_history.calculate_success_probability_times_billion( + score_params, + amount_msat, + self.capacity_msat, + ) { + let historical_negative_log10_times_2048 = log_approx::negative_log10_times_2048( + cumulative_success_prob_times_billion + 1, + 1024 * 1024 * 1024, + ); + res = res.saturating_add(Self::combined_penalty_msat( + amount_msat, + historical_negative_log10_times_2048, + score_params.historical_liquidity_penalty_multiplier_msat, + score_params.historical_liquidity_penalty_amount_multiplier_msat, + )); } else { // If we don't have any valid points (or, once decayed, we have less than a full // point), redo the non-historical calculation with no liquidity bounds tracked and // the historical penalty multipliers. - let (numerator, denominator) = success_probability(amount_msat, 0, - available_capacity, available_capacity, score_params, true); + let (numerator, denominator) = success_probability( + amount_msat, + 0, + available_capacity, + available_capacity, + score_params, + true, + ); let negative_log10_times_2048 = log_approx::negative_log10_times_2048(numerator, denominator); - res = res.saturating_add(Self::combined_penalty_msat(amount_msat, negative_log10_times_2048, + res = res.saturating_add(Self::combined_penalty_msat( + amount_msat, + negative_log10_times_2048, score_params.historical_liquidity_penalty_multiplier_msat, - score_params.historical_liquidity_penalty_amount_multiplier_msat)); + score_params.historical_liquidity_penalty_amount_multiplier_msat, + )); } } @@ -1210,18 +1293,20 @@ DirectedChannelLiquidity< L, HT, T> { /// Computes the liquidity penalty from the penalty multipliers. #[inline(always)] - fn combined_penalty_msat(amount_msat: u64, mut negative_log10_times_2048: u64, + fn combined_penalty_msat( + amount_msat: u64, mut negative_log10_times_2048: u64, liquidity_penalty_multiplier_msat: u64, liquidity_penalty_amount_multiplier_msat: u64, ) -> u64 { negative_log10_times_2048 = negative_log10_times_2048.min(NEGATIVE_LOG10_UPPER_BOUND * 2048); // Upper bound the liquidity penalty to ensure some channel is selected. - let liquidity_penalty_msat = negative_log10_times_2048 - .saturating_mul(liquidity_penalty_multiplier_msat) / 2048; + let liquidity_penalty_msat = + negative_log10_times_2048.saturating_mul(liquidity_penalty_multiplier_msat) / 2048; let amount_penalty_msat = negative_log10_times_2048 .saturating_mul(liquidity_penalty_amount_multiplier_msat) - .saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR; + .saturating_mul(amount_msat) + / 2048 / AMOUNT_PENALTY_DIVISOR; liquidity_penalty_msat.saturating_add(amount_penalty_msat) } @@ -1235,49 +1320,89 @@ DirectedChannelLiquidity< L, HT, T> { /// Returns the upper bound of the channel liquidity balance in this direction. #[inline(always)] fn max_liquidity_msat(&self) -> u64 { - self.capacity_msat - .saturating_sub(*self.max_liquidity_offset_msat) + self.capacity_msat.saturating_sub(*self.max_liquidity_offset_msat) } } -impl, HT: DerefMut, T: DerefMut> -DirectedChannelLiquidity { +impl< + L: DerefMut, + HT: DerefMut, + T: DerefMut, + > DirectedChannelLiquidity +{ /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`. fn failed_at_channel( - &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log - ) where Log::Target: Logger { + &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, + logger: &Log, + ) where + Log::Target: Logger, + { let existing_max_msat = self.max_liquidity_msat(); if amount_msat < existing_max_msat { - log_debug!(logger, "Setting max liquidity of {} from {} to {}", chan_descr, existing_max_msat, amount_msat); + log_debug!( + logger, + "Setting max liquidity of {} from {} to {}", + chan_descr, + existing_max_msat, + amount_msat + ); self.set_max_liquidity_msat(amount_msat, duration_since_epoch); } else { - log_trace!(logger, "Max liquidity of {} is {} (already less than or equal to {})", - chan_descr, existing_max_msat, amount_msat); + log_trace!( + logger, + "Max liquidity of {} is {} (already less than or equal to {})", + chan_descr, + existing_max_msat, + amount_msat + ); } self.update_history_buckets(0, duration_since_epoch); } /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat` downstream. fn failed_downstream( - &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log - ) where Log::Target: Logger { + &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, + logger: &Log, + ) where + Log::Target: Logger, + { let existing_min_msat = self.min_liquidity_msat(); if amount_msat > existing_min_msat { - log_debug!(logger, "Setting min liquidity of {} from {} to {}", existing_min_msat, chan_descr, amount_msat); + log_debug!( + logger, + "Setting min liquidity of {} from {} to {}", + existing_min_msat, + chan_descr, + amount_msat + ); self.set_min_liquidity_msat(amount_msat, duration_since_epoch); } else { - log_trace!(logger, "Min liquidity of {} is {} (already greater than or equal to {})", - chan_descr, existing_min_msat, amount_msat); + log_trace!( + logger, + "Min liquidity of {} is {} (already greater than or equal to {})", + chan_descr, + existing_min_msat, + amount_msat + ); } self.update_history_buckets(0, duration_since_epoch); } /// Adjusts the channel liquidity balance bounds when successfully routing `amount_msat`. - fn successful(&mut self, - amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log - ) where Log::Target: Logger { + fn successful( + &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, + logger: &Log, + ) where + Log::Target: Logger, + { let max_liquidity_msat = self.max_liquidity_msat().checked_sub(amount_msat).unwrap_or(0); - log_debug!(logger, "Subtracting {} from max liquidity of {} (setting it to {})", amount_msat, chan_descr, max_liquidity_msat); + log_debug!( + logger, + "Subtracting {} from max liquidity of {} (setting it to {})", + amount_msat, + chan_descr, + max_liquidity_msat + ); self.set_max_liquidity_msat(max_liquidity_msat, duration_since_epoch); self.update_history_buckets(amount_msat, duration_since_epoch); } @@ -1314,10 +1439,14 @@ DirectedChannelLiquidity { } } -impl>, L: Deref> ScoreLookUp for ProbabilisticScorer where L::Target: Logger { +impl>, L: Deref> ScoreLookUp for ProbabilisticScorer +where + L::Target: Logger, +{ type ScoreParams = ProbabilisticScoringFeeParameters; fn channel_penalty_msat( - &self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &ProbabilisticScoringFeeParameters + &self, candidate: &CandidateRouteHop, usage: ChannelUsage, + score_params: &ProbabilisticScoringFeeParameters, ) -> u64 { let (scid, target) = match candidate { CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id }) => { @@ -1331,14 +1460,14 @@ impl>, L: Deref> ScoreLookUp for Probabilistic } let base_penalty_msat = score_params.base_penalty_msat.saturating_add( - score_params.base_penalty_amount_multiplier_msat - .saturating_mul(usage.amount_msat) / BASE_AMOUNT_PENALTY_DIVISOR); + score_params.base_penalty_amount_multiplier_msat.saturating_mul(usage.amount_msat) + / BASE_AMOUNT_PENALTY_DIVISOR, + ); let mut anti_probing_penalty_msat = 0; match usage.effective_capacity { - EffectiveCapacity::ExactLiquidity { liquidity_msat: amount_msat } | - EffectiveCapacity::HintMaxHTLC { amount_msat } => - { + EffectiveCapacity::ExactLiquidity { liquidity_msat: amount_msat } + | EffectiveCapacity::HintMaxHTLC { amount_msat } => { if usage.amount_msat > amount_msat { return u64::max_value(); } else { @@ -1346,7 +1475,7 @@ impl>, L: Deref> ScoreLookUp for Probabilistic } }, EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat } => { - if htlc_maximum_msat >= capacity_msat/2 { + if htlc_maximum_msat >= capacity_msat / 2 { anti_probing_penalty_msat = score_params.anti_probing_penalty_msat; } }, @@ -1365,14 +1494,25 @@ impl>, L: Deref> ScoreLookUp for Probabilistic } } -impl>, L: Deref> ScoreUpdate for ProbabilisticScorer where L::Target: Logger { - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { +impl>, L: Deref> ScoreUpdate for ProbabilisticScorer +where + L::Target: Logger, +{ + fn payment_path_failed( + &mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration, + ) { let amount_msat = path.final_value_msat(); - log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat); + log_trace!( + self.logger, + "Scoring path through to SCID {} as having failed at {} msat", + short_channel_id, + amount_msat + ); let network_graph = self.network_graph.read_only(); for (hop_idx, hop) in path.hops.iter().enumerate() { let target = NodeId::from_pubkey(&hop.pubkey); - let channel_directed_from_source = network_graph.channels() + let channel_directed_from_source = network_graph + .channels() .get(&hop.short_channel_id) .and_then(|channel| channel.as_directed_to(&target)); @@ -1389,32 +1529,47 @@ impl>, L: Deref> ScoreUpdate for Probabilistic .entry(hop.short_channel_id) .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) .as_directed_mut(source, &target, capacity_msat) - .failed_at_channel(amount_msat, duration_since_epoch, - format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + .failed_at_channel( + amount_msat, + duration_since_epoch, + format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), + &self.logger, + ); } else { self.channel_liquidities .entry(hop.short_channel_id) .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) .as_directed_mut(source, &target, capacity_msat) - .failed_downstream(amount_msat, duration_since_epoch, - format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + .failed_downstream( + amount_msat, + duration_since_epoch, + format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), + &self.logger, + ); } } else { log_debug!(self.logger, "Not able to penalize channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).", hop.short_channel_id); } - if at_failed_channel { break; } + if at_failed_channel { + break; + } } } fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) { let amount_msat = path.final_value_msat(); - log_trace!(self.logger, "Scoring path through SCID {} as having succeeded at {} msat.", - path.hops.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat); + log_trace!( + self.logger, + "Scoring path through SCID {} as having succeeded at {} msat.", + path.hops.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), + amount_msat + ); let network_graph = self.network_graph.read_only(); for hop in &path.hops { let target = NodeId::from_pubkey(&hop.pubkey); - let channel_directed_from_source = network_graph.channels() + let channel_directed_from_source = network_graph + .channels() .get(&hop.short_channel_id) .and_then(|channel| channel.as_directed_to(&target)); @@ -1425,8 +1580,12 @@ impl>, L: Deref> ScoreUpdate for Probabilistic .entry(hop.short_channel_id) .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) .as_directed_mut(source, &target, capacity_msat) - .successful(amount_msat, duration_since_epoch, - format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + .successful( + amount_msat, + duration_since_epoch, + format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), + &self.logger, + ); } else { log_debug!(self.logger, "Not able to learn for channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).", hop.short_channel_id); @@ -1445,10 +1604,16 @@ impl>, L: Deref> ScoreUpdate for Probabilistic fn time_passed(&mut self, duration_since_epoch: Duration) { let decay_params = self.decay_params; self.channel_liquidities.retain(|_scid, liquidity| { - liquidity.min_liquidity_offset_msat = - liquidity.decayed_offset(liquidity.min_liquidity_offset_msat, duration_since_epoch, decay_params); - liquidity.max_liquidity_offset_msat = - liquidity.decayed_offset(liquidity.max_liquidity_offset_msat, duration_since_epoch, decay_params); + liquidity.min_liquidity_offset_msat = liquidity.decayed_offset( + liquidity.min_liquidity_offset_msat, + duration_since_epoch, + decay_params, + ); + liquidity.max_liquidity_offset_msat = liquidity.decayed_offset( + liquidity.max_liquidity_offset_msat, + duration_since_epoch, + decay_params, + ); liquidity.last_updated = duration_since_epoch; let elapsed_time = @@ -1456,19 +1621,24 @@ impl>, L: Deref> ScoreUpdate for Probabilistic if elapsed_time > decay_params.historical_no_updates_half_life { let half_life = decay_params.historical_no_updates_half_life.as_secs_f64(); if half_life != 0.0 { - liquidity.liquidity_history.decay_buckets(elapsed_time.as_secs_f64() / half_life); + liquidity + .liquidity_history + .decay_buckets(elapsed_time.as_secs_f64() / half_life); liquidity.offset_history_last_updated = duration_since_epoch; } } - liquidity.min_liquidity_offset_msat != 0 || liquidity.max_liquidity_offset_msat != 0 || - liquidity.liquidity_history.has_datapoints() + liquidity.min_liquidity_offset_msat != 0 + || liquidity.max_liquidity_offset_msat != 0 + || liquidity.liquidity_history.has_datapoints() }); } } #[cfg(c_bindings)] -impl>, L: Deref> Score for ProbabilisticScorer -where L::Target: Logger {} +impl>, L: Deref> Score for ProbabilisticScorer where + L::Target: Logger +{ +} #[cfg(feature = "std")] #[inline] @@ -1503,21 +1673,23 @@ mod bucketed_history { impl BucketStartPos { const fn new() -> Self { Self([ - 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 3072, 4096, 6144, 8192, 10240, 12288, - 13312, 14336, 15360, 15872, 16128, 16256, 16320, 16352, 16368, 16376, 16380, 16382, 16383, 16384, + 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 3072, 4096, 6144, 8192, + 10240, 12288, 13312, 14336, 15360, 15872, 16128, 16256, 16320, 16352, 16368, 16376, + 16380, 16382, 16383, 16384, ]) } } impl core::ops::Index for BucketStartPos { type Output = u16; #[inline(always)] - fn index(&self, index: usize) -> &u16 { &self.0[index] } + fn index(&self, index: usize) -> &u16 { + &self.0[index] + } } const BUCKET_START_POS: BucketStartPos = BucketStartPos::new(); - const LEGACY_TO_BUCKET_RANGE: [(u8, u8); 8] = [ - (0, 12), (12, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 20), (20, 32) - ]; + const LEGACY_TO_BUCKET_RANGE: [(u8, u8); 8] = + [(0, 12), (12, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 20), (20, 32)]; const POSITION_TICKS: u16 = 1 << 14; @@ -1535,8 +1707,9 @@ mod bucketed_history { #[test] fn check_bucket_maps() { const BUCKET_WIDTH_IN_16384S: [u16; 32] = [ - 1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024, 1024, 2048, 2048, - 2048, 2048, 1024, 1024, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 1]; + 1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024, 1024, 2048, 2048, 2048, 2048, + 1024, 1024, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 1, + ]; let mut min_size_iter = 0; let mut legacy_bucket_iter = 0; @@ -1549,7 +1722,10 @@ mod bucketed_history { if min_size_iter % (POSITION_TICKS / 8) == 0 { assert_eq!(LEGACY_TO_BUCKET_RANGE[legacy_bucket_iter].1 as usize, bucket + 1); if legacy_bucket_iter + 1 < 8 { - assert_eq!(LEGACY_TO_BUCKET_RANGE[legacy_bucket_iter + 1].0 as usize, bucket + 1); + assert_eq!( + LEGACY_TO_BUCKET_RANGE[legacy_bucket_iter + 1].0 as usize, + bucket + 1 + ); } legacy_bucket_iter += 1; } @@ -1562,14 +1738,16 @@ mod bucketed_history { fn amount_to_pos(amount_msat: u64, capacity_msat: u64) -> u16 { let pos = if amount_msat < u64::max_value() / (POSITION_TICKS as u64) { (amount_msat * (POSITION_TICKS as u64) / capacity_msat.saturating_add(1)) - .try_into().unwrap_or(POSITION_TICKS) + .try_into() + .unwrap_or(POSITION_TICKS) } else { // Only use 128-bit arithmetic when multiplication will overflow to avoid 128-bit // division. This branch should only be hit in fuzz testing since the amount would // need to be over 2.88 million BTC in practice. ((amount_msat as u128) * (POSITION_TICKS as u128) - / (capacity_msat as u128).saturating_add(1)) - .try_into().unwrap_or(POSITION_TICKS) + / (capacity_msat as u128).saturating_add(1)) + .try_into() + .unwrap_or(POSITION_TICKS) }; // If we are running in a client that doesn't validate gossip, its possible for a channel's // capacity to change due to a `channel_update` message which, if received while a payment @@ -1613,7 +1791,9 @@ mod bucketed_history { pub const BUCKET_FIXED_POINT_ONE: u16 = 32; impl HistoricalBucketRangeTracker { - pub(super) fn new() -> Self { Self { buckets: [0; 32] } } + pub(super) fn new() -> Self { + Self { buckets: [0; 32] } + } fn track_datapoint(&mut self, liquidity_offset_msat: u64, capacity_msat: u64) { // We have 32 leaky buckets for min and max liquidity. Each bucket tracks the amount of time // we spend in each bucket as a 16-bit fixed-point number with a 5 bit fractional part. @@ -1689,8 +1869,8 @@ mod bucketed_history { } pub(super) fn has_datapoints(&self) -> bool { - self.min_liquidity_offset_history.buckets != [0; 32] || - self.max_liquidity_offset_history.buckets != [0; 32] + self.min_liquidity_offset_history.buckets != [0; 32] + || self.max_liquidity_offset_history.buckets != [0; 32] } pub(super) fn decay_buckets(&mut self, half_lives: f64) { @@ -1706,8 +1886,12 @@ mod bucketed_history { fn recalculate_valid_point_count(&mut self) { self.total_valid_points_tracked = 0; - for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() { - for max_bucket in self.max_liquidity_offset_history.buckets.iter().take(32 - min_idx) { + for (min_idx, min_bucket) in + self.min_liquidity_offset_history.buckets.iter().enumerate() + { + for max_bucket in + self.max_liquidity_offset_history.buckets.iter().take(32 - min_idx) + { self.total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64); } } @@ -1721,20 +1905,24 @@ mod bucketed_history { &self.max_liquidity_offset_history } - pub(super) fn as_directed<'a>(&'a self, source_less_than_target: bool) - -> DirectedHistoricalLiquidityTracker<&'a HistoricalLiquidityTracker> { + pub(super) fn as_directed<'a>( + &'a self, source_less_than_target: bool, + ) -> DirectedHistoricalLiquidityTracker<&'a HistoricalLiquidityTracker> { DirectedHistoricalLiquidityTracker { source_less_than_target, tracker: self } } - pub(super) fn as_directed_mut<'a>(&'a mut self, source_less_than_target: bool) - -> DirectedHistoricalLiquidityTracker<&'a mut HistoricalLiquidityTracker> { + pub(super) fn as_directed_mut<'a>( + &'a mut self, source_less_than_target: bool, + ) -> DirectedHistoricalLiquidityTracker<&'a mut HistoricalLiquidityTracker> { DirectedHistoricalLiquidityTracker { source_less_than_target, tracker: self } } } /// A set of buckets representing the history of where we've seen the minimum- and maximum- /// liquidity bounds for a given channel. - pub(super) struct DirectedHistoricalLiquidityTracker> { + pub(super) struct DirectedHistoricalLiquidityTracker< + D: Deref, + > { source_less_than_target: bool, tracker: D, } @@ -1744,11 +1932,19 @@ mod bucketed_history { &mut self, min_offset_msat: u64, max_offset_msat: u64, capacity_msat: u64, ) { if self.source_less_than_target { - self.tracker.min_liquidity_offset_history.track_datapoint(min_offset_msat, capacity_msat); - self.tracker.max_liquidity_offset_history.track_datapoint(max_offset_msat, capacity_msat); + self.tracker + .min_liquidity_offset_history + .track_datapoint(min_offset_msat, capacity_msat); + self.tracker + .max_liquidity_offset_history + .track_datapoint(max_offset_msat, capacity_msat); } else { - self.tracker.max_liquidity_offset_history.track_datapoint(min_offset_msat, capacity_msat); - self.tracker.min_liquidity_offset_history.track_datapoint(max_offset_msat, capacity_msat); + self.tracker + .max_liquidity_offset_history + .track_datapoint(min_offset_msat, capacity_msat); + self.tracker + .min_liquidity_offset_history + .track_datapoint(max_offset_msat, capacity_msat); } self.tracker.recalculate_valid_point_count(); } @@ -1773,8 +1969,7 @@ mod bucketed_history { #[inline] pub(super) fn calculate_success_probability_times_billion( - &self, params: &ProbabilisticScoringFeeParameters, amount_msat: u64, - capacity_msat: u64 + &self, params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64, ) -> Option { // If historical penalties are enabled, we try to calculate a probability of success // given our historical distribution of min- and max-liquidity bounds in a channel. @@ -1784,18 +1979,21 @@ mod bucketed_history { // min- and max- liquidity bounds were our current liquidity bounds and then multiply // that probability by the weight of the selected buckets. let payment_pos = amount_to_pos(amount_msat, capacity_msat); - if payment_pos >= POSITION_TICKS { return None; } + if payment_pos >= POSITION_TICKS { + return None; + } - let min_liquidity_offset_history_buckets = - self.min_liquidity_offset_history_buckets(); - let max_liquidity_offset_history_buckets = - self.max_liquidity_offset_history_buckets(); + let min_liquidity_offset_history_buckets = self.min_liquidity_offset_history_buckets(); + let max_liquidity_offset_history_buckets = self.max_liquidity_offset_history_buckets(); let total_valid_points_tracked = self.tracker.total_valid_points_tracked; - #[cfg(debug_assertions)] { + #[cfg(debug_assertions)] + { let mut actual_valid_points_tracked = 0; - for (min_idx, min_bucket) in min_liquidity_offset_history_buckets.iter().enumerate() { - for max_bucket in max_liquidity_offset_history_buckets.iter().take(32 - min_idx) { + for (min_idx, min_bucket) in min_liquidity_offset_history_buckets.iter().enumerate() + { + for max_bucket in max_liquidity_offset_history_buckets.iter().take(32 - min_idx) + { actual_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64); } } @@ -1820,43 +2018,62 @@ mod bucketed_history { if min_liquidity_offset_history_buckets[0] != 0 { let mut highest_max_bucket_with_points = 0; // The highest max-bucket with any data let mut total_max_points = 0; // Total points in max-buckets to consider - for (max_idx, max_bucket) in max_liquidity_offset_history_buckets.iter().enumerate() { + for (max_idx, max_bucket) in max_liquidity_offset_history_buckets.iter().enumerate() + { if *max_bucket >= BUCKET_FIXED_POINT_ONE { - highest_max_bucket_with_points = cmp::max(highest_max_bucket_with_points, max_idx); + highest_max_bucket_with_points = + cmp::max(highest_max_bucket_with_points, max_idx); } total_max_points += *max_bucket as u64; } let max_bucket_end_pos = BUCKET_START_POS[32 - highest_max_bucket_with_points] - 1; if payment_pos < max_bucket_end_pos { - let (numerator, denominator) = success_probability(payment_pos as u64, 0, - max_bucket_end_pos as u64, POSITION_TICKS as u64 - 1, params, true); - let bucket_prob_times_billion = - (min_liquidity_offset_history_buckets[0] as u64) * total_max_points - * 1024 * 1024 * 1024 / total_valid_points_tracked; - cumulative_success_prob_times_billion += bucket_prob_times_billion * - numerator / denominator; + let (numerator, denominator) = success_probability( + payment_pos as u64, + 0, + max_bucket_end_pos as u64, + POSITION_TICKS as u64 - 1, + params, + true, + ); + let bucket_prob_times_billion = (min_liquidity_offset_history_buckets[0] + as u64) * total_max_points + * 1024 * 1024 * 1024 + / total_valid_points_tracked; + cumulative_success_prob_times_billion += + bucket_prob_times_billion * numerator / denominator; } } - for (min_idx, min_bucket) in min_liquidity_offset_history_buckets.iter().enumerate().skip(1) { + for (min_idx, min_bucket) in + min_liquidity_offset_history_buckets.iter().enumerate().skip(1) + { let min_bucket_start_pos = BUCKET_START_POS[min_idx]; - for (max_idx, max_bucket) in max_liquidity_offset_history_buckets.iter().enumerate().take(32 - min_idx) { + for (max_idx, max_bucket) in + max_liquidity_offset_history_buckets.iter().enumerate().take(32 - min_idx) + { let max_bucket_end_pos = BUCKET_START_POS[32 - max_idx] - 1; // Note that this multiply can only barely not overflow - two 16 bit ints plus // 30 bits is 62 bits. - let bucket_prob_times_billion = (*min_bucket as u64) * (*max_bucket as u64) - * 1024 * 1024 * 1024 / total_valid_points_tracked; + let bucket_prob_times_billion = (*min_bucket as u64) + * (*max_bucket as u64) * 1024 + * 1024 * 1024 / total_valid_points_tracked; if payment_pos >= max_bucket_end_pos { // Success probability 0, the payment amount may be above the max liquidity break; } else if payment_pos < min_bucket_start_pos { cumulative_success_prob_times_billion += bucket_prob_times_billion; } else { - let (numerator, denominator) = success_probability(payment_pos as u64, - min_bucket_start_pos as u64, max_bucket_end_pos as u64, - POSITION_TICKS as u64 - 1, params, true); - cumulative_success_prob_times_billion += bucket_prob_times_billion * - numerator / denominator; + let (numerator, denominator) = success_probability( + payment_pos as u64, + min_bucket_start_pos as u64, + max_bucket_end_pos as u64, + POSITION_TICKS as u64 - 1, + params, + true, + ); + cumulative_success_prob_times_billion += + bucket_prob_times_billion * numerator / denominator; } } } @@ -1865,9 +2082,15 @@ mod bucketed_history { } } } -use bucketed_history::{LegacyHistoricalBucketRangeTracker, HistoricalBucketRangeTracker, DirectedHistoricalLiquidityTracker, HistoricalLiquidityTracker}; +use bucketed_history::{ + DirectedHistoricalLiquidityTracker, HistoricalBucketRangeTracker, HistoricalLiquidityTracker, + LegacyHistoricalBucketRangeTracker, +}; -impl>, L: Deref> Writeable for ProbabilisticScorer where L::Target: Logger { +impl>, L: Deref> Writeable for ProbabilisticScorer +where + L::Target: Logger, +{ #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { write_tlv_fields!(w, { @@ -1878,22 +2101,20 @@ impl>, L: Deref> Writeable for ProbabilisticSc } impl>, L: Deref> -ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorer where L::Target: Logger { + ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorer +where + L::Target: Logger, +{ #[inline] fn read( - r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L) + r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L), ) -> Result { let (decay_params, network_graph, logger) = args; let mut channel_liquidities = new_hash_map(); read_tlv_fields!(r, { (0, channel_liquidities, required), }); - Ok(Self { - decay_params, - network_graph, - logger, - channel_liquidities, - }) + Ok(Self { decay_params, network_graph, logger, channel_liquidities }) } } @@ -1954,7 +2175,8 @@ impl Readable for ChannelLiquidity { min_liquidity_offset_msat, max_liquidity_offset_msat, liquidity_history: HistoricalLiquidityTracker::from_min_max( - min_liquidity_offset_history.unwrap(), max_liquidity_offset_history.unwrap() + min_liquidity_offset_history.unwrap(), + max_liquidity_offset_history.unwrap(), ), last_updated, offset_history_last_updated: offset_history_last_updated.unwrap_or(last_updated), @@ -1964,25 +2186,32 @@ impl Readable for ChannelLiquidity { #[cfg(test)] mod tests { - use super::{ChannelLiquidity, HistoricalLiquidityTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer}; + use super::{ + ChannelLiquidity, HistoricalLiquidityTracker, ProbabilisticScorer, + ProbabilisticScoringDecayParameters, ProbabilisticScoringFeeParameters, + }; use crate::blinded_path::BlindedHop; use crate::util::config::UserConfig; use crate::ln::channelmanager; - use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate}; + use crate::ln::msgs::{ + ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate, + }; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; - use crate::routing::router::{BlindedTail, Path, RouteHop, CandidateRouteHop, PublicHopCandidate}; + use crate::routing::router::{ + BlindedTail, CandidateRouteHop, Path, PublicHopCandidate, RouteHop, + }; use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate}; use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_utils::{self, TestLogger}; + use crate::io; use bitcoin::constants::ChainHash; - use bitcoin::hashes::Hash; use bitcoin::hashes::sha256d::Hash as Sha256dHash; + use bitcoin::hashes::Hash; use bitcoin::network::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use core::time::Duration; - use crate::io; fn source_privkey() -> SecretKey { SecretKey::from_slice(&[42; 32]).unwrap() @@ -2043,8 +2272,8 @@ mod tests { } fn add_channel( - network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_1_key: SecretKey, - node_2_key: SecretKey + network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, + node_1_key: SecretKey, node_2_key: SecretKey, ) { let genesis_hash = ChainHash::using_genesis_block(Network::Testnet); let node_1_secret = &SecretKey::from_slice(&[39; 32]).unwrap(); @@ -2056,8 +2285,14 @@ mod tests { short_channel_id, node_id_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_1_key)), node_id_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_2_key)), - bitcoin_key_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_1_secret)), - bitcoin_key_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_2_secret)), + bitcoin_key_1: NodeId::from_pubkey(&PublicKey::from_secret_key( + &secp_ctx, + &node_1_secret, + )), + bitcoin_key_2: NodeId::from_pubkey(&PublicKey::from_secret_key( + &secp_ctx, + &node_2_secret, + )), excess_data: Vec::new(), }; let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); @@ -2069,8 +2304,9 @@ mod tests { contents: unsigned_announcement, }; let chain_source: Option<&crate::util::test_utils::TestChainSource> = None; - network_graph.update_channel_from_announcement( - &signed_announcement, &chain_source).unwrap(); + network_graph + .update_channel_from_announcement(&signed_announcement, &chain_source) + .unwrap(); update_channel(network_graph, short_channel_id, node_1_key, 0, 1_000, 100); update_channel(network_graph, short_channel_id, node_2_key, 1, 0, 100); } @@ -2121,7 +2357,8 @@ mod tests { path_hop(source_pubkey(), 41, 1), path_hop(target_pubkey(), 42, 2), path_hop(recipient_pubkey(), 43, amount_msat), - ], blinded_tail: None, + ], + blinded_tail: None, } } @@ -2133,18 +2370,26 @@ mod tests { let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) - .with_channel(42, + .with_channel( + 42, ChannelLiquidity { - min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, - last_updated, offset_history_last_updated, + min_liquidity_offset_msat: 700, + max_liquidity_offset_msat: 100, + last_updated, + offset_history_last_updated, liquidity_history: HistoricalLiquidityTracker::new(), - }) - .with_channel(43, + }, + ) + .with_channel( + 43, ChannelLiquidity { - min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, - last_updated, offset_history_last_updated, + min_liquidity_offset_msat: 700, + max_liquidity_offset_msat: 100, + last_updated, + offset_history_last_updated, liquidity_history: HistoricalLiquidityTracker::new(), - }); + }, + ); let source = source_node_id(); let target = target_node_id(); let recipient = recipient_node_id(); @@ -2153,53 +2398,59 @@ mod tests { // Update minimum liquidity. - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 100); assert_eq!(liquidity.max_liquidity_msat(), 300); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 900); - scorer.channel_liquidities.get_mut(&42).unwrap() + scorer + .channel_liquidities + .get_mut(&42) + .unwrap() .as_directed_mut(&source, &target, 1_000) .set_min_liquidity_msat(200, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 300); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 800); // Update maximum liquidity. - let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&target, &recipient, 1_000); + let liquidity = + scorer.channel_liquidities.get(&43).unwrap().as_directed(&target, &recipient, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 900); - let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&recipient, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&43).unwrap().as_directed(&recipient, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 100); assert_eq!(liquidity.max_liquidity_msat(), 300); - scorer.channel_liquidities.get_mut(&43).unwrap() + scorer + .channel_liquidities + .get_mut(&43) + .unwrap() .as_directed_mut(&target, &recipient, 1_000) .set_max_liquidity_msat(200, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&target, &recipient, 1_000); + let liquidity = + scorer.channel_liquidities.get(&43).unwrap().as_directed(&target, &recipient, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 200); - let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&recipient, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&43).unwrap().as_directed(&recipient, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 800); assert_eq!(liquidity.max_liquidity_msat(), 1000); } @@ -2212,54 +2463,64 @@ mod tests { let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) - .with_channel(42, + .with_channel( + 42, ChannelLiquidity { - min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, - last_updated, offset_history_last_updated, + min_liquidity_offset_msat: 200, + max_liquidity_offset_msat: 400, + last_updated, + offset_history_last_updated, liquidity_history: HistoricalLiquidityTracker::new(), - }); + }, + ); let source = source_node_id(); let target = target_node_id(); assert!(source > target); // Check initial bounds. - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 800); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 600); // Reset from source to target. - scorer.channel_liquidities.get_mut(&42).unwrap() + scorer + .channel_liquidities + .get_mut(&42) + .unwrap() .as_directed_mut(&source, &target, 1_000) .set_min_liquidity_msat(900, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 900); assert_eq!(liquidity.max_liquidity_msat(), 1_000); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 100); // Reset from target to source. - scorer.channel_liquidities.get_mut(&42).unwrap() + scorer + .channel_liquidities + .get_mut(&42) + .unwrap() .as_directed_mut(&target, &source, 1_000) .set_min_liquidity_msat(400, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 600); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 1_000); } @@ -2272,54 +2533,64 @@ mod tests { let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) - .with_channel(42, + .with_channel( + 42, ChannelLiquidity { - min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, - last_updated, offset_history_last_updated, + min_liquidity_offset_msat: 200, + max_liquidity_offset_msat: 400, + last_updated, + offset_history_last_updated, liquidity_history: HistoricalLiquidityTracker::new(), - }); + }, + ); let source = source_node_id(); let target = target_node_id(); assert!(source > target); // Check initial bounds. - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 800); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 200); assert_eq!(liquidity.max_liquidity_msat(), 600); // Reset from source to target. - scorer.channel_liquidities.get_mut(&42).unwrap() + scorer + .channel_liquidities + .get_mut(&42) + .unwrap() .as_directed_mut(&source, &target, 1_000) .set_max_liquidity_msat(300, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 300); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 700); assert_eq!(liquidity.max_liquidity_msat(), 1_000); // Reset from target to source. - scorer.channel_liquidities.get_mut(&42).unwrap() + scorer + .channel_liquidities + .get_mut(&42) + .unwrap() .as_directed_mut(&target, &source, 1_000) .set_max_liquidity_msat(600, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 1_000); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&target, &source, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 600); } @@ -2339,15 +2610,16 @@ mod tests { let usage = ChannelUsage { amount_msat: 1_024, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 1_000, + }, }; let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 10_240, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2359,7 +2631,10 @@ mod tests { let usage = ChannelUsage { amount_msat: 128, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_000, + }, }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 58); let usage = ChannelUsage { amount_msat: 256, ..usage }; @@ -2390,26 +2665,30 @@ mod tests { let decay_params = ProbabilisticScoringDecayParameters { ..ProbabilisticScoringDecayParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) - .with_channel(42, - ChannelLiquidity { - min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, - last_updated, offset_history_last_updated, - liquidity_history: HistoricalLiquidityTracker::new(), - }); + let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger).with_channel( + 42, + ChannelLiquidity { + min_liquidity_offset_msat: 40, + max_liquidity_offset_msat: 40, + last_updated, + offset_history_last_updated, + liquidity_history: HistoricalLiquidityTracker::new(), + }, + ); let source = source_node_id(); let usage = ChannelUsage { amount_msat: 39, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 100, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 100, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 50, ..usage }; assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2426,21 +2705,26 @@ mod tests { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let mut scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let usage = ChannelUsage { amount_msat: 500, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let failed_path = payment_path_for_amount(500); let successful_path = payment_path_for_amount(200); let channel = &network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 41, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2459,21 +2743,26 @@ mod tests { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let mut scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let path = payment_path_for_amount(500); let usage = ChannelUsage { amount_msat: 250, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2499,21 +2788,26 @@ mod tests { considered_impossible_penalty_msat: u64::max_value(), ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let mut scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let path = payment_path_for_amount(500); let usage = ChannelUsage { amount_msat: 250, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); @@ -2553,11 +2847,7 @@ mod tests { let pub_c = PublicKey::from_secret_key(&secp_ctx, &secret_c); let pub_d = PublicKey::from_secret_key(&secp_ctx, &secret_d); - let path = vec![ - path_hop(pub_b, 42, 1), - path_hop(pub_c, 43, 2), - path_hop(pub_d, 44, 100), - ]; + let path = vec![path_hop(pub_b, 42, 1), path_hop(pub_c, 43, 2), path_hop(pub_d, 44, 100)]; let node_a = NodeId::from_pubkey(&pub_a); let node_b = NodeId::from_pubkey(&pub_b); @@ -2567,59 +2857,54 @@ mod tests { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let mut scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let usage = ChannelUsage { amount_msat: 250, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 43, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 44, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 43, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 44, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); } @@ -2631,31 +2916,32 @@ mod tests { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let mut scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let usage = ChannelUsage { amount_msat: 250, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let network_graph = network_graph.read_only().channels().clone(); let channel_42 = network_graph.get(&42).unwrap(); let channel_43 = network_graph.get(&43).unwrap(); let (info, _) = channel_42.as_directed_from(&source).unwrap(); - let candidate_41 = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 41, - }); + let candidate_41 = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41 }); let (info, target) = channel_42.as_directed_from(&source).unwrap(); - let candidate_42 = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate_42 = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); let (info, _) = channel_43.as_directed_from(&target).unwrap(); - let candidate_43 = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 43, - }); + let candidate_43 = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43 }); assert_eq!(scorer.channel_penalty_msat(&candidate_41, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_42, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_43, usage, ¶ms), 128); @@ -2686,14 +2972,15 @@ mod tests { let usage = ChannelUsage { amount_msat: 0, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_024, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_023, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2_000); @@ -2775,14 +3062,15 @@ mod tests { let usage = ChannelUsage { amount_msat: 512, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); @@ -2828,16 +3116,17 @@ mod tests { let usage = ChannelUsage { amount_msat: 500, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); scorer.time_passed(Duration::from_secs(10)); @@ -2850,8 +3139,11 @@ mod tests { scorer.write(&mut serialized_scorer).unwrap(); let mut serialized_scorer = io::Cursor::new(&serialized_scorer); - let deserialized_scorer = - >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + let deserialized_scorer = >::read( + &mut serialized_scorer, + (decay_params, &network_graph, &logger), + ) + .unwrap(); assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); } @@ -2872,16 +3164,17 @@ mod tests { let usage = ChannelUsage { amount_msat: 500, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); if decay_before_reload { @@ -2892,8 +3185,11 @@ mod tests { scorer.write(&mut serialized_scorer).unwrap(); let mut serialized_scorer = io::Cursor::new(&serialized_scorer); - let mut deserialized_scorer = - >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + let mut deserialized_scorer = >::read( + &mut serialized_scorer, + (decay_params, &network_graph, &logger), + ) + .unwrap(); if !decay_before_reload { scorer.time_passed(Duration::from_secs(10)); deserialized_scorer.time_passed(Duration::from_secs(10)); @@ -2920,59 +3216,104 @@ mod tests { let logger = TestLogger::new(); let network_graph = network_graph(&logger); let params = ProbabilisticScoringFeeParameters::default(); - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let usage = ChannelUsage { amount_msat: 100_000_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 950_000_000, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 11497); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 7408); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 2_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 6151); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 3_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 5427); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 4_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4955); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 5_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4736); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 6_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4484); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 7_450_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4484); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 7_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4263); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 8_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4263); let usage = ChannelUsage { - effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: 1_000 }, ..usage + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 9_950_000_000, + htlc_maximum_msat: 1_000, + }, + ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 4044); } @@ -2985,36 +3326,53 @@ mod tests { let usage = ChannelUsage { amount_msat: 128, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_000, + }, }; let params = ProbabilisticScoringFeeParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 58); let params = ProbabilisticScoringFeeParameters { - base_penalty_msat: 500, liquidity_penalty_multiplier_msat: 1_000, - anti_probing_penalty_msat: 0, ..ProbabilisticScoringFeeParameters::zero_penalty() + base_penalty_msat: 500, + liquidity_penalty_multiplier_msat: 1_000, + anti_probing_penalty_msat: 0, + ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 558); let params = ProbabilisticScoringFeeParameters { - base_penalty_msat: 500, liquidity_penalty_multiplier_msat: 1_000, + base_penalty_msat: 500, + liquidity_penalty_multiplier_msat: 1_000, base_penalty_amount_multiplier_msat: (1 << 30), - anti_probing_penalty_msat: 0, ..ProbabilisticScoringFeeParameters::zero_penalty() + anti_probing_penalty_msat: 0, + ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 558 + 128); } @@ -3026,7 +3384,10 @@ mod tests { let usage = ChannelUsage { amount_msat: 512_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 1_000, + }, }; let params = ProbabilisticScoringFeeParameters { @@ -3034,13 +3395,15 @@ mod tests { liquidity_penalty_amount_multiplier_msat: 0, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let params = ProbabilisticScoringFeeParameters { @@ -3048,7 +3411,11 @@ mod tests { liquidity_penalty_amount_multiplier_msat: 256, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 337); } @@ -3069,10 +3436,8 @@ mod tests { let decay_params = ProbabilisticScoringDecayParameters::zero_penalty(); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80_000); } @@ -3085,21 +3450,26 @@ mod tests { considered_impossible_penalty_msat: u64::max_value(), ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let usage = ChannelUsage { amount_msat: 750, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_000, + htlc_maximum_msat: 1_000, + }, }; let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); let usage = ChannelUsage { inflight_htlc_msat: 251, ..usage }; @@ -3111,7 +3481,11 @@ mod tests { let logger = TestLogger::new(); let network_graph = network_graph(&logger); let params = ProbabilisticScoringFeeParameters::default(); - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); let source = source_node_id(); let base_penalty_msat = params.base_penalty_msat; @@ -3123,10 +3497,8 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), base_penalty_msat); let usage = ChannelUsage { amount_msat: 1_000, ..usage }; @@ -3156,53 +3528,71 @@ mod tests { let usage = ChannelUsage { amount_msat: 100, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_024, + }, }; let usage_1 = ChannelUsage { amount_msat: 1, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_024, + }, }; { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); // With no historical data the normal liquidity penalty calculation is used. assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 168); } - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - None); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), - None); + assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); + assert_eq!( + scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), + None + ); scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::ZERO); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2048); assert_eq!(scorer.channel_penalty_msat(&candidate, usage_1, ¶ms), 249); } // The "it failed" increment is 32, where the probability should lie several buckets into // the first octile. - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); - assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms) - .unwrap() > 0.35); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms), - Some(0.0)); + assert_eq!( + scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + Some(( + [ + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ] + )) + ); + assert!( + scorer + .historical_estimated_payment_success_probability(42, &target, 1, ¶ms) + .unwrap() > 0.35 + ); + assert_eq!( + scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms), + Some(0.0) + ); // Even after we tell the scorer we definitely have enough available liquidity, it will // still remember that there was some failure in the past, and assign a non-0 penalty. @@ -3211,26 +3601,36 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 105); } // The first points should be decayed just slightly and the last bucket has a new point. - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - Some(([31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32]))); + assert_eq!( + scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + Some(( + [ + 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 0, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32 + ] + )) + ); // The exact success probability is a bit complicated and involves integer rounding, so we // simply check bounds here. - let five_hundred_prob = - scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms).unwrap(); + let five_hundred_prob = scorer + .historical_estimated_payment_success_probability(42, &target, 500, ¶ms) + .unwrap(); assert!(five_hundred_prob > 0.59); assert!(five_hundred_prob < 0.60); - let one_prob = - scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms).unwrap(); + let one_prob = scorer + .historical_estimated_payment_success_probability(42, &target, 1, ¶ms) + .unwrap(); assert!(one_prob < 0.85); assert!(one_prob > 0.84); @@ -3241,33 +3641,37 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 168); } // Once fully decayed we still have data, but its all-0s. In the future we may remove the // data entirely instead. - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - Some(([0; 32], [0; 32]))); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None); + assert_eq!( + scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + Some(([0; 32], [0; 32])) + ); + assert_eq!( + scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), + None + ); let usage = ChannelUsage { amount_msat: 100, inflight_htlc_msat: 1024, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_024, + }, }; scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::from_secs(10 * 16)); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2050); @@ -3284,8 +3688,7 @@ mod tests { // Once even the bounds have decayed information about the channel should be removed // entirely. - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - None); + assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); // Use a path in the opposite direction, which have zero for htlc_maximum_msat. This will // ensure that the effective capacity is zero to test division-by-zero edge cases. @@ -3294,7 +3697,11 @@ mod tests { path_hop(source_pubkey(), 42, 1), path_hop(sender_pubkey(), 41, 0), ]; - scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 42, Duration::from_secs(10 * (16 + 60 * 60))); + scorer.payment_path_failed( + &Path { hops: path, blinded_tail: None }, + 42, + Duration::from_secs(10 * (16 + 60 * 60)), + ); } #[test] @@ -3306,28 +3713,36 @@ mod tests { anti_probing_penalty_msat: 500, ..ProbabilisticScoringFeeParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); + let scorer = ProbabilisticScorer::new( + ProbabilisticScoringDecayParameters::default(), + &network_graph, + &logger, + ); // Check we receive no penalty for a low htlc_maximum_msat. let usage = ChannelUsage { amount_msat: 512_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 1_000, + }, }; let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); // Check we receive anti-probing penalty for htlc_maximum_msat == channel_capacity. let usage = ChannelUsage { amount_msat: 512_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_024_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 1_024_000, + }, }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 500); @@ -3335,7 +3750,10 @@ mod tests { let usage = ChannelUsage { amount_msat: 512_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 512_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 512_000, + }, }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 500); @@ -3343,7 +3761,10 @@ mod tests { let usage = ChannelUsage { amount_msat: 512_000, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 511_999 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024_000, + htlc_maximum_msat: 511_999, + }, }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); } @@ -3363,20 +3784,24 @@ mod tests { let usage = ChannelUsage { amount_msat: 512, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat: 1_024, + htlc_maximum_msat: 1_000, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let mut path = payment_path_for_amount(768); let recipient_hop = path.hops.pop().unwrap(); path.blinded_tail = Some(BlindedTail { - hops: vec![BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() }], + hops: vec![BlindedHop { + blinded_node_id: test_utils::pubkey(44), + encrypted_payload: Vec::new(), + }], blinding_point: test_utils::pubkey(42), excess_final_cltv_expiry_delta: recipient_hop.cltv_expiry_delta, final_value_msat: recipient_hop.fee_msat, @@ -3390,8 +3815,8 @@ mod tests { path.blinded_tail.as_mut().unwrap().final_value_msat = 256; scorer.payment_path_failed(&path, 43, Duration::ZERO); - let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000); + let liquidity = + scorer.channel_liquidities.get(&42).unwrap().as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 256); assert_eq!(liquidity.max_liquidity_msat(), 768); } @@ -3425,73 +3850,120 @@ mod tests { let usage = ChannelUsage { amount_msat, inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: capacity_msat }, + effective_capacity: EffectiveCapacity::Total { + capacity_msat, + htlc_maximum_msat: capacity_msat, + }, }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { - info, - short_channel_id: 42, - }); + let candidate = + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42 }); // With no historical data the normal liquidity penalty calculation is used, which results // in a success probability of ~75%. assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 1269); - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - None); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), - None); + assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); + assert_eq!( + scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), + None + ); // Fail to pay once, and then check the buckets and penalty. scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42, Duration::ZERO); // The penalty should be the maximum penalty, as the payment we're scoring is now in the // same bucket which is the only maximum datapoint. - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), - 2048 + 2048 * amount_msat / super::AMOUNT_PENALTY_DIVISOR); + assert_eq!( + scorer.channel_penalty_msat(&candidate, usage, ¶ms), + 2048 + 2048 * amount_msat / super::AMOUNT_PENALTY_DIVISOR + ); // The "it failed" increment is 32, which we should apply to the first upper-bound (between // 6k sats and 12k sats). - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); + assert_eq!( + scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + Some(( + [ + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ], + [ + 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ] + )) + ); // The success probability estimate itself should be zero. - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), - Some(0.0)); + assert_eq!( + scorer.historical_estimated_payment_success_probability( + 42, + &target, + amount_msat, + ¶ms + ), + Some(0.0) + ); // Now test again with the amount in the bottom bucket. amount_msat /= 2; // The new amount is entirely within the only minimum bucket with score, so the probability // we assign is 1/2. - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), - Some(0.5)); + assert_eq!( + scorer.historical_estimated_payment_success_probability( + 42, + &target, + amount_msat, + ¶ms + ), + Some(0.5) + ); // ...but once we see a failure, we consider the payment to be substantially less likely, // even though not a probability of zero as we still look at the second max bucket which // now shows 31. scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42, Duration::ZERO); - assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), - Some(([63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [32, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); - assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms), - Some(0.0)); + assert_eq!( + scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + Some(( + [ + 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ], + [ + 32, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + ] + )) + ); + assert_eq!( + scorer.historical_estimated_payment_success_probability( + 42, + &target, + amount_msat, + ¶ms + ), + Some(0.0) + ); } } #[cfg(ldk_bench)] pub mod benches { use super::*; - use criterion::Criterion; + use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::routing::router::{bench_utils, RouteHop}; use crate::util::test_utils::TestLogger; - use crate::ln::features::{ChannelFeatures, NodeFeatures}; + use criterion::Criterion; pub fn decay_100k_channel_bounds(bench: &mut Criterion) { let logger = TestLogger::new(); let (network_graph, mut scorer) = bench_utils::read_graph_scorer(&logger).unwrap(); let mut cur_time = Duration::ZERO; - cur_time += Duration::from_millis(1); - scorer.time_passed(cur_time); - bench.bench_function("decay_100k_channel_bounds", |b| b.iter(|| { - cur_time += Duration::from_millis(1); - scorer.time_passed(cur_time); - })); + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + bench.bench_function("decay_100k_channel_bounds", |b| { + b.iter(|| { + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + }) + }); } } diff --git a/lightning/src/routing/test_utils.rs b/lightning/src/routing/test_utils.rs index a64955cd015..bf69f3def55 100644 --- a/lightning/src/routing/test_utils.rs +++ b/lightning/src/routing/test_utils.rs @@ -7,19 +7,22 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::routing::gossip::{NetworkGraph, NodeAlias, P2PGossipSync}; use crate::ln::features::{ChannelFeatures, NodeFeatures}; -use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, MAX_VALUE_MSAT, NodeAnnouncement, RoutingMessageHandler, SocketAddress, UnsignedChannelAnnouncement, UnsignedChannelUpdate, UnsignedNodeAnnouncement}; -use crate::util::test_utils; +use crate::ln::msgs::{ + ChannelAnnouncement, ChannelUpdate, NodeAnnouncement, RoutingMessageHandler, SocketAddress, + UnsignedChannelAnnouncement, UnsignedChannelUpdate, UnsignedNodeAnnouncement, MAX_VALUE_MSAT, +}; +use crate::routing::gossip::{NetworkGraph, NodeAlias, P2PGossipSync}; use crate::util::ser::Writeable; +use crate::util::test_utils; use bitcoin::constants::ChainHash; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash; use bitcoin::hex::FromHex; use bitcoin::network::Network; -use bitcoin::secp256k1::{PublicKey,SecretKey}; -use bitcoin::secp256k1::{Secp256k1, All}; +use bitcoin::secp256k1::{All, Secp256k1}; +use bitcoin::secp256k1::{PublicKey, SecretKey}; #[allow(unused)] use crate::prelude::*; @@ -29,8 +32,13 @@ use crate::routing::gossip::NodeId; // Using the same keys for LN and BTC ids pub(crate) fn add_channel( - gossip_sync: &P2PGossipSync>>, Arc, Arc>, - secp_ctx: &Secp256k1, node_1_privkey: &SecretKey, node_2_privkey: &SecretKey, features: ChannelFeatures, short_channel_id: u64 + gossip_sync: &P2PGossipSync< + Arc>>, + Arc, + Arc, + >, + secp_ctx: &Secp256k1, node_1_privkey: &SecretKey, node_2_privkey: &SecretKey, + features: ChannelFeatures, short_channel_id: u64, ) { let node_1_pubkey = PublicKey::from_secret_key(&secp_ctx, node_1_privkey); let node_id_1 = NodeId::from_pubkey(&node_1_pubkey); @@ -57,13 +65,17 @@ pub(crate) fn add_channel( }; match gossip_sync.handle_channel_announcement(Some(node_1_pubkey), &valid_announcement) { Ok(res) => assert!(res), - _ => panic!() + _ => panic!(), }; } pub(crate) fn add_or_update_node( - gossip_sync: &P2PGossipSync>>, Arc, Arc>, - secp_ctx: &Secp256k1, node_privkey: &SecretKey, features: NodeFeatures, timestamp: u32 + gossip_sync: &P2PGossipSync< + Arc>>, + Arc, + Arc, + >, + secp_ctx: &Secp256k1, node_privkey: &SecretKey, features: NodeFeatures, timestamp: u32, ) { let node_pubkey = PublicKey::from_secret_key(&secp_ctx, node_privkey); let node_id = NodeId::from_pubkey(&node_pubkey); @@ -80,40 +92,53 @@ pub(crate) fn add_or_update_node( let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); let valid_announcement = NodeAnnouncement { signature: secp_ctx.sign_ecdsa(&msghash, node_privkey), - contents: unsigned_announcement.clone() + contents: unsigned_announcement.clone(), }; match gossip_sync.handle_node_announcement(Some(node_pubkey), &valid_announcement) { Ok(_) => (), - Err(_) => panic!() + Err(_) => panic!(), }; } pub(crate) fn update_channel( - gossip_sync: &P2PGossipSync>>, Arc, Arc>, - secp_ctx: &Secp256k1, node_privkey: &SecretKey, update: UnsignedChannelUpdate + gossip_sync: &P2PGossipSync< + Arc>>, + Arc, + Arc, + >, + secp_ctx: &Secp256k1, node_privkey: &SecretKey, update: UnsignedChannelUpdate, ) { let node_pubkey = PublicKey::from_secret_key(&secp_ctx, node_privkey); let msghash = hash_to_message!(&Sha256dHash::hash(&update.encode()[..])[..]); let valid_channel_update = ChannelUpdate { signature: secp_ctx.sign_ecdsa(&msghash, node_privkey), - contents: update.clone() + contents: update.clone(), }; match gossip_sync.handle_channel_update(Some(node_pubkey), &valid_channel_update) { Ok(res) => assert!(res), - Err(_) => panic!() + Err(_) => panic!(), }; } -pub(super) fn get_nodes(secp_ctx: &Secp256k1) -> (SecretKey, PublicKey, Vec, Vec) { - let privkeys: Vec = (2..22).map(|i| { - SecretKey::from_slice(&>::from_hex(&format!("{:02x}", i).repeat(32)).unwrap()[..]).unwrap() - }).collect(); - - let pubkeys = privkeys.iter().map(|secret| PublicKey::from_secret_key(&secp_ctx, secret)).collect(); - - let our_privkey = SecretKey::from_slice(&>::from_hex(&"01".repeat(32)).unwrap()[..]).unwrap(); +pub(super) fn get_nodes( + secp_ctx: &Secp256k1, +) -> (SecretKey, PublicKey, Vec, Vec) { + let privkeys: Vec = (2..22) + .map(|i| { + SecretKey::from_slice( + &>::from_hex(&format!("{:02x}", i).repeat(32)).unwrap()[..], + ) + .unwrap() + }) + .collect(); + + let pubkeys = + privkeys.iter().map(|secret| PublicKey::from_secret_key(&secp_ctx, secret)).collect(); + + let our_privkey = + SecretKey::from_slice(&>::from_hex(&"01".repeat(32)).unwrap()[..]).unwrap(); let our_id = PublicKey::from_secret_key(&secp_ctx, &our_privkey); (our_privkey, our_id, privkeys, pubkeys) @@ -123,21 +148,27 @@ pub(super) fn id_to_feature_flags(id: u8) -> Vec { // Set the feature flags to the id'th odd (ie non-required) feature bit so that we can // test for it later. let idx = (id - 1) * 2 + 1; - if idx > 8*3 { - vec![1 << (idx - 8*3), 0, 0, 0] - } else if idx > 8*2 { - vec![1 << (idx - 8*2), 0, 0] - } else if idx > 8*1 { - vec![1 << (idx - 8*1), 0] + if idx > 8 * 3 { + vec![1 << (idx - 8 * 3), 0, 0, 0] + } else if idx > 8 * 2 { + vec![1 << (idx - 8 * 2), 0, 0] + } else if idx > 8 * 1 { + vec![1 << (idx - 8 * 1), 0] } else { vec![1 << idx] } } pub(super) fn build_line_graph() -> ( - Secp256k1, sync::Arc>>, - P2PGossipSync>>, sync::Arc, sync::Arc>, - sync::Arc, sync::Arc, + Secp256k1, + sync::Arc>>, + P2PGossipSync< + sync::Arc>>, + sync::Arc, + sync::Arc, + >, + sync::Arc, + sync::Arc, ) { let secp_ctx = Secp256k1::new(); let logger = Arc::new(test_utils::TestLogger::new()); @@ -149,12 +180,23 @@ pub(super) fn build_line_graph() -> ( // our_id -1(1)2- node0 -1(2)2- node1 - ... - node19 let (our_privkey, _, privkeys, _) = get_nodes(&secp_ctx); - for (idx, (cur_privkey, next_privkey)) in core::iter::once(&our_privkey) - .chain(privkeys.iter()).zip(privkeys.iter()).enumerate() { - let cur_short_channel_id = (idx as u64) + 1; - add_channel(&gossip_sync, &secp_ctx, &cur_privkey, &next_privkey, - ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), cur_short_channel_id); - update_channel(&gossip_sync, &secp_ctx, &cur_privkey, UnsignedChannelUpdate { + for (idx, (cur_privkey, next_privkey)) in + core::iter::once(&our_privkey).chain(privkeys.iter()).zip(privkeys.iter()).enumerate() + { + let cur_short_channel_id = (idx as u64) + 1; + add_channel( + &gossip_sync, + &secp_ctx, + &cur_privkey, + &next_privkey, + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + cur_short_channel_id, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &cur_privkey, + UnsignedChannelUpdate { chain_hash: ChainHash::using_genesis_block(Network::Testnet), short_channel_id: cur_short_channel_id, timestamp: idx as u32, @@ -165,12 +207,17 @@ pub(super) fn build_line_graph() -> ( htlc_maximum_msat: MAX_VALUE_MSAT, fee_base_msat: 0, fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &next_privkey, UnsignedChannelUpdate { + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &next_privkey, + UnsignedChannelUpdate { chain_hash: ChainHash::using_genesis_block(Network::Testnet), short_channel_id: cur_short_channel_id, - timestamp: (idx as u32)+1, + timestamp: (idx as u32) + 1, message_flags: 1, // Only must_be_one channel_flags: 1, cltv_expiry_delta: 0, @@ -178,11 +225,17 @@ pub(super) fn build_line_graph() -> ( htlc_maximum_msat: MAX_VALUE_MSAT, fee_base_msat: 0, fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - add_or_update_node(&gossip_sync, &secp_ctx, &next_privkey, - NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); - } + excess_data: Vec::new(), + }, + ); + add_or_update_node( + &gossip_sync, + &secp_ctx, + &next_privkey, + NodeFeatures::from_le_bytes(id_to_feature_flags(1)), + 0, + ); + } (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) } @@ -190,7 +243,11 @@ pub(super) fn build_line_graph() -> ( pub(super) fn build_graph() -> ( Secp256k1, sync::Arc>>, - P2PGossipSync>>, sync::Arc, sync::Arc>, + P2PGossipSync< + sync::Arc>>, + sync::Arc, + sync::Arc, + >, sync::Arc, sync::Arc, ) { @@ -260,258 +317,448 @@ pub(super) fn build_graph() -> ( let (our_privkey, _, privkeys, _) = get_nodes(&secp_ctx); - add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[0], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 1, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); - - add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (5 << 4) | 3, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: u32::max_value(), - fee_proportional_millionths: u32::max_value(), - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 2, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], NodeFeatures::from_le_bytes(id_to_feature_flags(2)), 0); - - add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[7], ChannelFeatures::from_le_bytes(id_to_feature_flags(12)), 12); - update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (5 << 4) | 3, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: u32::max_value(), - fee_proportional_millionths: u32::max_value(), - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 12, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: 0, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], NodeFeatures::from_le_bytes(id_to_feature_flags(8)), 0); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3); - update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (3 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 3, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (3 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 100, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4); - update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (4 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 1000000, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 4, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (4 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[7], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(13)), 13); - update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (13 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 2000000, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 13, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (13 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], NodeFeatures::from_le_bytes(id_to_feature_flags(3)), 0); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (6 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 6, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (6 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new(), - }); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(11)), 11); - update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (11 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 11, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (11 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], NodeFeatures::from_le_bytes(id_to_feature_flags(5)), 0); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], NodeFeatures::from_le_bytes(id_to_feature_flags(4)), 0); - - add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[5], ChannelFeatures::from_le_bytes(id_to_feature_flags(7)), 7); - update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 0, - cltv_expiry_delta: (7 << 4) | 1, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 1000000, - excess_data: Vec::new() - }); - update_channel(&gossip_sync, &secp_ctx, &privkeys[5], UnsignedChannelUpdate { - chain_hash: ChainHash::using_genesis_block(Network::Testnet), - short_channel_id: 7, - timestamp: 1, - message_flags: 1, // Only must_be_one - channel_flags: 1, - cltv_expiry_delta: (7 << 4) | 2, - htlc_minimum_msat: 0, - htlc_maximum_msat: MAX_VALUE_MSAT, - fee_base_msat: 0, - fee_proportional_millionths: 0, - excess_data: Vec::new() - }); - - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[5], NodeFeatures::from_le_bytes(id_to_feature_flags(6)), 0); + add_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + &privkeys[0], + ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), + 1, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 1, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[0], + NodeFeatures::from_le_bytes(id_to_feature_flags(1)), + 0, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + &privkeys[1], + ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), + 2, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (5 << 4) | 3, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: u32::max_value(), + fee_proportional_millionths: u32::max_value(), + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 2, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[1], + NodeFeatures::from_le_bytes(id_to_feature_flags(2)), + 0, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + &privkeys[7], + ChannelFeatures::from_le_bytes(id_to_feature_flags(12)), + 12, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &our_privkey, + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (5 << 4) | 3, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: u32::max_value(), + fee_proportional_millionths: u32::max_value(), + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 12, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: 0, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[7], + NodeFeatures::from_le_bytes(id_to_feature_flags(8)), + 0, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + &privkeys[2], + ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), + 3, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[0], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (3 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 3, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (3 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 100, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + &privkeys[2], + ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), + 4, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[1], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (4 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 1000000, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 4, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (4 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + &privkeys[2], + ChannelFeatures::from_le_bytes(id_to_feature_flags(13)), + 13, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[7], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (13 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 2000000, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 13, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (13 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[2], + NodeFeatures::from_le_bytes(id_to_feature_flags(3)), + 0, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[4], + ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), + 6, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (6 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 6, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (6 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + &privkeys[3], + ChannelFeatures::from_le_bytes(id_to_feature_flags(11)), + 11, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[4], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (11 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[3], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 11, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (11 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[4], + NodeFeatures::from_le_bytes(id_to_feature_flags(5)), + 0, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[3], + NodeFeatures::from_le_bytes(id_to_feature_flags(4)), + 0, + ); + + add_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + &privkeys[5], + ChannelFeatures::from_le_bytes(id_to_feature_flags(7)), + 7, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[2], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 0, + cltv_expiry_delta: (7 << 4) | 1, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 1000000, + excess_data: Vec::new(), + }, + ); + update_channel( + &gossip_sync, + &secp_ctx, + &privkeys[5], + UnsignedChannelUpdate { + chain_hash: ChainHash::using_genesis_block(Network::Testnet), + short_channel_id: 7, + timestamp: 1, + message_flags: 1, // Only must_be_one + channel_flags: 1, + cltv_expiry_delta: (7 << 4) | 2, + htlc_minimum_msat: 0, + htlc_maximum_msat: MAX_VALUE_MSAT, + fee_base_msat: 0, + fee_proportional_millionths: 0, + excess_data: Vec::new(), + }, + ); + + add_or_update_node( + &gossip_sync, + &secp_ctx, + &privkeys[5], + NodeFeatures::from_le_bytes(id_to_feature_flags(6)), + 0, + ); (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) } diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs index c960846638a..691658022ea 100644 --- a/lightning/src/routing/utxo.rs +++ b/lightning/src/routing/utxo.rs @@ -13,22 +13,22 @@ //! channel matches a UTXO on-chain, requiring at least some marginal on-chain transacting in //! order to announce a channel. This module handles that checking. -use bitcoin::TxOut; use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; +use bitcoin::TxOut; use bitcoin::hex::DisplayHex; use crate::events::MessageSendEvent; use crate::ln::chan_utils::make_funding_redeemscript_from_slices; -use crate::ln::msgs::{self, LightningError, ErrorAction}; +use crate::ln::msgs::{self, ErrorAction, LightningError}; use crate::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync}; use crate::util::logger::{Level, Logger}; use crate::prelude::*; +use crate::sync::{LockTestExt, Mutex}; use alloc::sync::{Arc, Weak}; -use crate::sync::{Mutex, LockTestExt}; use core::ops::Deref; /// An error when accessing the chain via [`UtxoLookup`]. @@ -137,14 +137,16 @@ impl UtxoLookup for UtxoResolver { impl UtxoFuture { /// Builds a new future for later resolution. pub fn new() -> Self { - Self { state: Arc::new(Mutex::new(UtxoMessages { - complete: None, - channel_announce: None, - latest_node_announce_a: None, - latest_node_announce_b: None, - latest_channel_update_a: None, - latest_channel_update_b: None, - }))} + Self { + state: Arc::new(Mutex::new(UtxoMessages { + complete: None, + channel_announce: None, + latest_node_announce_a: None, + latest_node_announce_b: None, + latest_channel_update_a: None, + latest_channel_update_b: None, + })), + } } /// Resolves this future against the given `graph` and with the given `result`. @@ -158,9 +160,11 @@ impl UtxoFuture { /// /// [`processing_queue_high`]: crate::ln::msgs::RoutingMessageHandler::processing_queue_high /// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events - pub fn resolve_without_forwarding(&self, - graph: &NetworkGraph, result: Result) - where L::Target: Logger { + pub fn resolve_without_forwarding( + &self, graph: &NetworkGraph, result: Result, + ) where + L::Target: Logger, + { self.do_resolve(graph, result); } @@ -175,9 +179,17 @@ impl UtxoFuture { /// /// [`processing_queue_high`]: crate::ln::msgs::RoutingMessageHandler::processing_queue_high /// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events - pub fn resolve>, U: Deref, GS: Deref>>(&self, - graph: &NetworkGraph, gossip: GS, result: Result - ) where L::Target: Logger, U::Target: UtxoLookup { + pub fn resolve< + L: Deref, + G: Deref>, + U: Deref, + GS: Deref>, + >( + &self, graph: &NetworkGraph, gossip: GS, result: Result, + ) where + L::Target: Logger, + U::Target: UtxoLookup, + { let mut res = self.do_resolve(graph, result); for msg_opt in res.iter_mut() { if let Some(msg) = msg_opt.take() { @@ -186,8 +198,12 @@ impl UtxoFuture { } } - fn do_resolve(&self, graph: &NetworkGraph, result: Result) - -> [Option; 5] where L::Target: Logger { + fn do_resolve( + &self, graph: &NetworkGraph, result: Result, + ) -> [Option; 5] + where + L::Target: Logger, + { let (announcement, node_a, node_b, update_a, update_b) = { let mut pending_checks = graph.pending_checks.internal.lock().unwrap(); let mut async_messages = self.state.lock().unwrap(); @@ -207,11 +223,13 @@ impl UtxoFuture { pending_checks.lookup_completed(announcement_msg, &Arc::downgrade(&self.state)); - (async_messages.channel_announce.take().unwrap(), + ( + async_messages.channel_announce.take().unwrap(), async_messages.latest_node_announce_a.take(), async_messages.latest_node_announce_b.take(), async_messages.latest_channel_update_a.take(), - async_messages.latest_channel_update_b.take()) + async_messages.latest_channel_update_b.take(), + ) }; let mut res = [None, None, None, None, None]; @@ -226,7 +244,8 @@ impl UtxoFuture { ChannelAnnouncement::Full(signed_msg) => { if graph.update_channel_from_announcement(&signed_msg, &Some(&resolver)).is_ok() { res[res_idx] = Some(MessageSendEvent::BroadcastChannelAnnouncement { - msg: signed_msg, update_msg: None, + msg: signed_msg, + update_msg: None, }); res_idx += 1; } @@ -240,9 +259,8 @@ impl UtxoFuture { match announce { Some(NodeAnnouncement::Full(signed_msg)) => { if graph.update_node_from_announcement(&signed_msg).is_ok() { - res[res_idx] = Some(MessageSendEvent::BroadcastNodeAnnouncement { - msg: signed_msg, - }); + res[res_idx] = + Some(MessageSendEvent::BroadcastNodeAnnouncement { msg: signed_msg }); res_idx += 1; } }, @@ -257,9 +275,8 @@ impl UtxoFuture { match update { Some(ChannelUpdate::Full(signed_msg)) => { if graph.update_channel(&signed_msg).is_ok() { - res[res_idx] = Some(MessageSendEvent::BroadcastChannelUpdate { - msg: signed_msg, - }); + res[res_idx] = + Some(MessageSendEvent::BroadcastChannelUpdate { msg: signed_msg }); res_idx += 1; } }, @@ -280,8 +297,9 @@ struct PendingChecksContext { } impl PendingChecksContext { - fn lookup_completed(&mut self, - msg: &msgs::UnsignedChannelAnnouncement, completed_state: &Weak> + fn lookup_completed( + &mut self, msg: &msgs::UnsignedChannelAnnouncement, + completed_state: &Weak>, ) { if let hash_map::Entry::Occupied(e) = self.channels.entry(msg.short_channel_id) { if Weak::ptr_eq(e.get(), &completed_state) { @@ -291,11 +309,15 @@ impl PendingChecksContext { if let hash_map::Entry::Occupied(mut e) = self.nodes.entry(msg.node_id_1) { e.get_mut().retain(|elem| !Weak::ptr_eq(&elem, &completed_state)); - if e.get().is_empty() { e.remove(); } + if e.get().is_empty() { + e.remove(); + } } if let hash_map::Entry::Occupied(mut e) = self.nodes.entry(msg.node_id_2) { e.get_mut().retain(|elem| !Weak::ptr_eq(&elem, &completed_state)); - if e.get().is_empty() { e.remove(); } + if e.get().is_empty() { + e.remove(); + } } } } @@ -307,15 +329,18 @@ pub(super) struct PendingChecks { impl PendingChecks { pub(super) fn new() -> Self { - PendingChecks { internal: Mutex::new(PendingChecksContext { - channels: new_hash_map(), nodes: new_hash_map(), - }) } + PendingChecks { + internal: Mutex::new(PendingChecksContext { + channels: new_hash_map(), + nodes: new_hash_map(), + }), + } } /// Checks if there is a pending `channel_update` UTXO validation for the given channel, /// and, if so, stores the channel message for handling later and returns an `Err`. pub(super) fn check_hold_pending_channel_update( - &self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate> + &self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, ) -> Result<(), LightningError> { let mut pending_checks = self.internal.lock().unwrap(); if let hash_map::Entry::Occupied(e) = pending_checks.channels.entry(msg.short_channel_id) { @@ -324,25 +349,32 @@ impl PendingChecks { Some(msgs_ref) => { let mut messages = msgs_ref.lock().unwrap(); let latest_update = if is_from_a { - &mut messages.latest_channel_update_a - } else { - &mut messages.latest_channel_update_b - }; - if latest_update.is_none() || latest_update.as_ref().unwrap().timestamp() < msg.timestamp { + &mut messages.latest_channel_update_a + } else { + &mut messages.latest_channel_update_b + }; + if latest_update.is_none() + || latest_update.as_ref().unwrap().timestamp() < msg.timestamp + { // If the messages we got has a higher timestamp, just blindly assume the // signatures on the new message are correct and drop the old message. This // may cause us to end up dropping valid `channel_update`s if a peer is // malicious, but we should get the correct ones when the node updates them. - *latest_update = Some( - if let Some(msg) = full_msg { ChannelUpdate::Full(msg.clone()) } - else { ChannelUpdate::Unsigned(msg.clone()) }); + *latest_update = Some(if let Some(msg) = full_msg { + ChannelUpdate::Full(msg.clone()) + } else { + ChannelUpdate::Unsigned(msg.clone()) + }); } return Err(LightningError { - err: "Awaiting channel_announcement validation to accept channel_update".to_owned(), + err: "Awaiting channel_announcement validation to accept channel_update" + .to_owned(), action: ErrorAction::IgnoreAndLog(Level::Gossip), }); }, - None => { e.remove(); }, + None => { + e.remove(); + }, } } Ok(()) @@ -351,43 +383,48 @@ impl PendingChecks { /// Checks if there is a pending `node_announcement` UTXO validation for a channel with the /// given node and, if so, stores the channel message for handling later and returns an `Err`. pub(super) fn check_hold_pending_node_announcement( - &self, msg: &msgs::UnsignedNodeAnnouncement, full_msg: Option<&msgs::NodeAnnouncement> + &self, msg: &msgs::UnsignedNodeAnnouncement, full_msg: Option<&msgs::NodeAnnouncement>, ) -> Result<(), LightningError> { let mut pending_checks = self.internal.lock().unwrap(); if let hash_map::Entry::Occupied(mut e) = pending_checks.nodes.entry(msg.node_id) { let mut found_at_least_one_chan = false; - e.get_mut().retain(|node_msgs| { - match Weak::upgrade(&node_msgs) { - Some(chan_mtx) => { - let mut chan_msgs = chan_mtx.lock().unwrap(); - if let Some(chan_announce) = &chan_msgs.channel_announce { - let latest_announce = - if *chan_announce.node_id_1() == msg.node_id { - &mut chan_msgs.latest_node_announce_a - } else { - &mut chan_msgs.latest_node_announce_b - }; - if latest_announce.is_none() || - latest_announce.as_ref().unwrap().timestamp() < msg.timestamp - { - *latest_announce = Some( - if let Some(msg) = full_msg { NodeAnnouncement::Full(msg.clone()) } - else { NodeAnnouncement::Unsigned(msg.clone()) }); - } - found_at_least_one_chan = true; - true + e.get_mut().retain(|node_msgs| match Weak::upgrade(&node_msgs) { + Some(chan_mtx) => { + let mut chan_msgs = chan_mtx.lock().unwrap(); + if let Some(chan_announce) = &chan_msgs.channel_announce { + let latest_announce = if *chan_announce.node_id_1() == msg.node_id { + &mut chan_msgs.latest_node_announce_a } else { - debug_assert!(false, "channel_announce is set before struct is added to node map"); - false + &mut chan_msgs.latest_node_announce_b + }; + if latest_announce.is_none() + || latest_announce.as_ref().unwrap().timestamp() < msg.timestamp + { + *latest_announce = Some(if let Some(msg) = full_msg { + NodeAnnouncement::Full(msg.clone()) + } else { + NodeAnnouncement::Unsigned(msg.clone()) + }); } - }, - None => false, - } + found_at_least_one_chan = true; + true + } else { + debug_assert!( + false, + "channel_announce is set before struct is added to node map" + ); + false + } + }, + None => false, }); - if e.get().is_empty() { e.remove(); } + if e.get().is_empty() { + e.remove(); + } if found_at_least_one_chan { return Err(LightningError { - err: "Awaiting channel_announcement validation to accept node_announcement".to_owned(), + err: "Awaiting channel_announcement validation to accept node_announcement" + .to_owned(), action: ErrorAction::IgnoreAndLog(Level::Gossip), }); } @@ -395,9 +432,10 @@ impl PendingChecks { Ok(()) } - fn check_replace_previous_entry(msg: &msgs::UnsignedChannelAnnouncement, - full_msg: Option<&msgs::ChannelAnnouncement>, replacement: Option>>, - pending_channels: &mut HashMap>> + fn check_replace_previous_entry( + msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, + replacement: Option>>, + pending_channels: &mut HashMap>>, ) -> Result<(), msgs::LightningError> { match pending_channels.entry(msg.short_channel_id) { hash_map::Entry::Occupied(mut e) => { @@ -409,8 +447,13 @@ impl PendingChecks { // This may be called with the mutex held on a different UtxoMessages // struct, however in that case we have a global lockorder of new messages // -> old messages, which makes this safe. - let pending_matches = match &pending_msgs.unsafe_well_ordered_double_lock_self().channel_announce { - Some(ChannelAnnouncement::Full(pending_msg)) => Some(pending_msg) == full_msg, + let pending_matches = match &pending_msgs + .unsafe_well_ordered_double_lock_self() + .channel_announce + { + Some(ChannelAnnouncement::Full(pending_msg)) => { + Some(pending_msg) == full_msg + }, Some(ChannelAnnouncement::Unsigned(pending_msg)) => pending_msg == msg, None => { // This shouldn't actually be reachable. We set the @@ -442,53 +485,66 @@ impl PendingChecks { // so just remove/replace it and move on. if let Some(item) = replacement { *e.get_mut() = item; - } else { e.remove(); } + } else { + e.remove(); + } }, } }, hash_map::Entry::Vacant(v) => { - if let Some(item) = replacement { v.insert(item); } + if let Some(item) = replacement { + v.insert(item); + } }, } Ok(()) } - pub(super) fn check_channel_announcement(&self, - utxo_lookup: &Option, msg: &msgs::UnsignedChannelAnnouncement, - full_msg: Option<&msgs::ChannelAnnouncement> - ) -> Result, msgs::LightningError> where U::Target: UtxoLookup { - let handle_result = |res| { - match res { - Ok(TxOut { value, script_pubkey }) => { - let expected_script = - make_funding_redeemscript_from_slices(msg.bitcoin_key_1.as_array(), msg.bitcoin_key_2.as_array()).to_p2wsh(); - if script_pubkey != expected_script { - return Err(LightningError{ - err: format!("Channel announcement key ({}) didn't match on-chain script ({})", - expected_script.to_hex_string(), script_pubkey.to_hex_string()), - action: ErrorAction::IgnoreError - }); - } - Ok(Some(value)) - }, - Err(UtxoLookupError::UnknownChain) => { - Err(LightningError { - err: format!("Channel announced on an unknown chain ({})", - msg.chain_hash.to_bytes().as_hex()), - action: ErrorAction::IgnoreError - }) - }, - Err(UtxoLookupError::UnknownTx) => { - Err(LightningError { - err: "Channel announced without corresponding UTXO entry".to_owned(), - action: ErrorAction::IgnoreError - }) - }, - } + pub(super) fn check_channel_announcement( + &self, utxo_lookup: &Option, msg: &msgs::UnsignedChannelAnnouncement, + full_msg: Option<&msgs::ChannelAnnouncement>, + ) -> Result, msgs::LightningError> + where + U::Target: UtxoLookup, + { + let handle_result = |res| match res { + Ok(TxOut { value, script_pubkey }) => { + let expected_script = make_funding_redeemscript_from_slices( + msg.bitcoin_key_1.as_array(), + msg.bitcoin_key_2.as_array(), + ) + .to_p2wsh(); + if script_pubkey != expected_script { + return Err(LightningError { + err: format!( + "Channel announcement key ({}) didn't match on-chain script ({})", + expected_script.to_hex_string(), + script_pubkey.to_hex_string() + ), + action: ErrorAction::IgnoreError, + }); + } + Ok(Some(value)) + }, + Err(UtxoLookupError::UnknownChain) => Err(LightningError { + err: format!( + "Channel announced on an unknown chain ({})", + msg.chain_hash.to_bytes().as_hex() + ), + action: ErrorAction::IgnoreError, + }), + Err(UtxoLookupError::UnknownTx) => Err(LightningError { + err: "Channel announced without corresponding UTXO entry".to_owned(), + action: ErrorAction::IgnoreError, + }), }; - Self::check_replace_previous_entry(msg, full_msg, None, - &mut self.internal.lock().unwrap().channels)?; + Self::check_replace_previous_entry( + msg, + full_msg, + None, + &mut self.internal.lock().unwrap().channels, + )?; match utxo_lookup { &None => { @@ -506,15 +562,27 @@ impl PendingChecks { // handle the result in-line. handle_result(res) } else { - Self::check_replace_previous_entry(msg, full_msg, - Some(Arc::downgrade(&future.state)), &mut pending_checks.channels)?; - async_messages.channel_announce = Some( - if let Some(msg) = full_msg { ChannelAnnouncement::Full(msg.clone()) } - else { ChannelAnnouncement::Unsigned(msg.clone()) }); - pending_checks.nodes.entry(msg.node_id_1) - .or_default().push(Arc::downgrade(&future.state)); - pending_checks.nodes.entry(msg.node_id_2) - .or_default().push(Arc::downgrade(&future.state)); + Self::check_replace_previous_entry( + msg, + full_msg, + Some(Arc::downgrade(&future.state)), + &mut pending_checks.channels, + )?; + async_messages.channel_announce = Some(if let Some(msg) = full_msg { + ChannelAnnouncement::Full(msg.clone()) + } else { + ChannelAnnouncement::Unsigned(msg.clone()) + }); + pending_checks + .nodes + .entry(msg.node_id_1) + .or_default() + .push(Arc::downgrade(&future.state)); + pending_checks + .nodes + .entry(msg.node_id_2) + .or_default() + .push(Arc::downgrade(&future.state)); Err(LightningError { err: "Channel being checked async".to_owned(), action: ErrorAction::IgnoreAndLog(Level::Gossip), @@ -522,7 +590,7 @@ impl PendingChecks { } }, } - } + }, } } @@ -545,9 +613,7 @@ impl PendingChecks { // If we have many channel checks pending, ensure we don't have any dangling checks // (i.e. checks where the user told us they'd call back but drop'd the `UtxoFuture` // instead) before we commit to applying backpressure. - pending_checks.channels.retain(|_, chan| { - Weak::upgrade(&chan).is_some() - }); + pending_checks.channels.retain(|_, chan| Weak::upgrade(&chan).is_some()); pending_checks.nodes.retain(|_, channels| { channels.retain(|chan| Weak::upgrade(&chan).is_some()); !channels.is_empty() @@ -578,10 +644,17 @@ mod tests { (chain_source, network_graph) } - fn get_test_objects() -> (msgs::ChannelAnnouncement, TestChainSource, - NetworkGraph>, bitcoin::ScriptBuf, msgs::NodeAnnouncement, - msgs::NodeAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, msgs::ChannelUpdate) - { + fn get_test_objects() -> ( + msgs::ChannelAnnouncement, + TestChainSource, + NetworkGraph>, + bitcoin::ScriptBuf, + msgs::NodeAnnouncement, + msgs::NodeAnnouncement, + msgs::ChannelUpdate, + msgs::ChannelUpdate, + msgs::ChannelUpdate, + ) { let secp_ctx = Secp256k1::new(); let (chain_source, network_graph) = get_network(); @@ -589,19 +662,37 @@ mod tests { let good_script = get_channel_script(&secp_ctx); let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap(); let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); - let valid_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); let node_a_announce = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); let node_b_announce = get_signed_node_announcement(|_| {}, node_2_privkey, &secp_ctx); // Note that we have to set the "direction" flag correctly on both messages - let chan_update_a = get_signed_channel_update(|msg| msg.channel_flags = 0, node_1_privkey, &secp_ctx); - let chan_update_b = get_signed_channel_update(|msg| msg.channel_flags = 1, node_2_privkey, &secp_ctx); - let chan_update_c = get_signed_channel_update(|msg| { - msg.channel_flags = 1; msg.timestamp += 1; }, node_2_privkey, &secp_ctx); - - (valid_announcement, chain_source, network_graph, good_script, node_a_announce, - node_b_announce, chan_update_a, chan_update_b, chan_update_c) + let chan_update_a = + get_signed_channel_update(|msg| msg.channel_flags = 0, node_1_privkey, &secp_ctx); + let chan_update_b = + get_signed_channel_update(|msg| msg.channel_flags = 1, node_2_privkey, &secp_ctx); + let chan_update_c = get_signed_channel_update( + |msg| { + msg.channel_flags = 1; + msg.timestamp += 1; + }, + node_2_privkey, + &secp_ctx, + ); + + ( + valid_announcement, + chain_source, + network_graph, + good_script, + node_a_announce, + node_b_announce, + chan_update_a, + chan_update_b, + chan_update_c, + ) } #[test] @@ -611,41 +702,84 @@ mod tests { let (valid_announcement, chain_source, network_graph, good_script, ..) = get_test_objects(); let future = UtxoFuture::new(); - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script })); + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }), + ); *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap(); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_some()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap(); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_some()); } #[test] fn test_async_lookup() { // Test a simple async lookup - let (valid_announcement, chain_source, network_graph, good_script, - node_a_announce, node_b_announce, ..) = get_test_objects(); + let ( + valid_announcement, + chain_source, + network_graph, + good_script, + node_a_announce, + node_b_announce, + .., + ) = get_test_objects(); let future = UtxoFuture::new(); *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); - - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script })); - network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).unwrap(); - network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).unwrap(); - - assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1) - .unwrap().announcement_info.is_none()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); + + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script }), + ); + network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .unwrap(); + network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .unwrap(); + + assert!(network_graph + .read_only() + .nodes() + .get(&valid_announcement.contents.node_id_1) + .unwrap() + .announcement_info + .is_none()); network_graph.update_node_from_announcement(&node_a_announce).unwrap(); network_graph.update_node_from_announcement(&node_b_announce).unwrap(); - assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1) - .unwrap().announcement_info.is_some()); + assert!(network_graph + .read_only() + .nodes() + .get(&valid_announcement.contents.node_id_1) + .unwrap() + .announcement_info + .is_some()); } #[test] @@ -657,13 +791,30 @@ mod tests { *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); - - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: bitcoin::ScriptBuf::new() })); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); + + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { + value: Amount::from_sat(1_000_000), + script_pubkey: bitcoin::ScriptBuf::new(), + }), + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); } #[test] @@ -675,88 +826,184 @@ mod tests { *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); future.resolve_without_forwarding(&network_graph, Err(UtxoLookupError::UnknownTx)); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); } #[test] fn test_updates_async_lookup() { // Test async lookups will process pending channel_update/node_announcements once they // complete. - let (valid_announcement, chain_source, network_graph, good_script, node_a_announce, - node_b_announce, chan_update_a, chan_update_b, ..) = get_test_objects(); + let ( + valid_announcement, + chain_source, + network_graph, + good_script, + node_a_announce, + node_b_announce, + chan_update_a, + chan_update_b, + .., + ) = get_test_objects(); let future = UtxoFuture::new(); *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); assert_eq!( network_graph.update_node_from_announcement(&node_a_announce).unwrap_err().err, - "Awaiting channel_announcement validation to accept node_announcement"); + "Awaiting channel_announcement validation to accept node_announcement" + ); assert_eq!( network_graph.update_node_from_announcement(&node_b_announce).unwrap_err().err, - "Awaiting channel_announcement validation to accept node_announcement"); - - assert_eq!(network_graph.update_channel(&chan_update_a).unwrap_err().err, - "Awaiting channel_announcement validation to accept channel_update"); - assert_eq!(network_graph.update_channel(&chan_update_b).unwrap_err().err, - "Awaiting channel_announcement validation to accept channel_update"); + "Awaiting channel_announcement validation to accept node_announcement" + ); - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script })); - - assert!(network_graph.read_only().channels() - .get(&valid_announcement.contents.short_channel_id).unwrap().one_to_two.is_some()); - assert!(network_graph.read_only().channels() - .get(&valid_announcement.contents.short_channel_id).unwrap().two_to_one.is_some()); - - assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1) - .unwrap().announcement_info.is_some()); - assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_2) - .unwrap().announcement_info.is_some()); + assert_eq!( + network_graph.update_channel(&chan_update_a).unwrap_err().err, + "Awaiting channel_announcement validation to accept channel_update" + ); + assert_eq!( + network_graph.update_channel(&chan_update_b).unwrap_err().err, + "Awaiting channel_announcement validation to accept channel_update" + ); + + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }), + ); + + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .unwrap() + .one_to_two + .is_some()); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .unwrap() + .two_to_one + .is_some()); + + assert!(network_graph + .read_only() + .nodes() + .get(&valid_announcement.contents.node_id_1) + .unwrap() + .announcement_info + .is_some()); + assert!(network_graph + .read_only() + .nodes() + .get(&valid_announcement.contents.node_id_2) + .unwrap() + .announcement_info + .is_some()); } #[test] fn test_latest_update_async_lookup() { // Test async lookups will process the latest channel_update if two are received while // awaiting an async UTXO lookup. - let (valid_announcement, chain_source, network_graph, good_script, _, - _, chan_update_a, chan_update_b, chan_update_c, ..) = get_test_objects(); + let ( + valid_announcement, + chain_source, + network_graph, + good_script, + _, + _, + chan_update_a, + chan_update_b, + chan_update_c, + .., + ) = get_test_objects(); let future = UtxoFuture::new(); *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); - assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none()); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); + assert!(network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .is_none()); - assert_eq!(network_graph.update_channel(&chan_update_a).unwrap_err().err, - "Awaiting channel_announcement validation to accept channel_update"); - assert_eq!(network_graph.update_channel(&chan_update_b).unwrap_err().err, - "Awaiting channel_announcement validation to accept channel_update"); - assert_eq!(network_graph.update_channel(&chan_update_c).unwrap_err().err, - "Awaiting channel_announcement validation to accept channel_update"); + assert_eq!( + network_graph.update_channel(&chan_update_a).unwrap_err().err, + "Awaiting channel_announcement validation to accept channel_update" + ); + assert_eq!( + network_graph.update_channel(&chan_update_b).unwrap_err().err, + "Awaiting channel_announcement validation to accept channel_update" + ); + assert_eq!( + network_graph.update_channel(&chan_update_c).unwrap_err().err, + "Awaiting channel_announcement validation to accept channel_update" + ); - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script })); + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }), + ); assert_eq!(chan_update_a.contents.timestamp, chan_update_b.contents.timestamp); let graph_lock = network_graph.read_only(); - assert!(graph_lock.channels() - .get(&valid_announcement.contents.short_channel_id).as_ref().unwrap() - .one_to_two.as_ref().unwrap().last_update != - graph_lock.channels() - .get(&valid_announcement.contents.short_channel_id).as_ref().unwrap() - .two_to_one.as_ref().unwrap().last_update); + assert!( + graph_lock + .channels() + .get(&valid_announcement.contents.short_channel_id) + .as_ref() + .unwrap() + .one_to_two + .as_ref() + .unwrap() + .last_update != graph_lock + .channels() + .get(&valid_announcement.contents.short_channel_id) + .as_ref() + .unwrap() + .two_to_one + .as_ref() + .unwrap() + .last_update + ); } #[test] @@ -769,16 +1016,24 @@ mod tests { *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); assert_eq!(chain_source.get_utxo_call_count.load(Ordering::Relaxed), 1); // If we make a second request with the same message, the call count doesn't increase... let future_b = UtxoFuture::new(); *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future_b.clone()); assert_eq!( - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel announcement is already being checked"); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel announcement is already being checked" + ); assert_eq!(chain_source.get_utxo_call_count.load(Ordering::Relaxed), 1); // But if we make a third request with a tweaked message, we should get a second call @@ -786,19 +1041,33 @@ mod tests { let secp_ctx = Secp256k1::new(); let replacement_pk_1 = &SecretKey::from_slice(&[99; 32]).unwrap(); let replacement_pk_2 = &SecretKey::from_slice(&[98; 32]).unwrap(); - let invalid_announcement = get_signed_channel_announcement(|_| {}, replacement_pk_1, replacement_pk_2, &secp_ctx); + let invalid_announcement = + get_signed_channel_announcement(|_| {}, replacement_pk_1, replacement_pk_2, &secp_ctx); assert_eq!( - network_graph.update_channel_from_announcement(&invalid_announcement, &Some(&chain_source)).unwrap_err().err, - "Channel being checked async"); + network_graph + .update_channel_from_announcement(&invalid_announcement, &Some(&chain_source)) + .unwrap_err() + .err, + "Channel being checked async" + ); assert_eq!(chain_source.get_utxo_call_count.load(Ordering::Relaxed), 2); // Still, if we resolve the original future, the original channel will be accepted. - future.resolve_without_forwarding(&network_graph, - Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script })); - assert!(!network_graph.read_only().channels() - .get(&valid_announcement.contents.short_channel_id).unwrap() - .announcement_message.as_ref().unwrap() - .contents.features.supports_unknown_test_feature()); + future.resolve_without_forwarding( + &network_graph, + Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }), + ); + assert!(!network_graph + .read_only() + .channels() + .get(&valid_announcement.contents.short_channel_id) + .unwrap() + .announcement_message + .as_ref() + .unwrap() + .contents + .features + .supports_unknown_test_feature()); } #[test] @@ -817,14 +1086,22 @@ mod tests { for i in 0..PendingChecks::MAX_PENDING_LOOKUPS { let valid_announcement = get_signed_channel_announcement( - |msg| msg.short_channel_id += 1 + i as u64, node_1_privkey, node_2_privkey, &secp_ctx); - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err(); + |msg| msg.short_channel_id += 1 + i as u64, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err(); assert!(!network_graph.pending_checks.too_many_checks_pending()); } - let valid_announcement = get_signed_channel_announcement( - |_| {}, node_1_privkey, node_2_privkey, &secp_ctx); - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err(); + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err(); assert!(network_graph.pending_checks.too_many_checks_pending()); // Once the future completes the "too many checks" flag should reset. @@ -847,14 +1124,22 @@ mod tests { for i in 0..PendingChecks::MAX_PENDING_LOOKUPS { let valid_announcement = get_signed_channel_announcement( - |msg| msg.short_channel_id += 1 + i as u64, node_1_privkey, node_2_privkey, &secp_ctx); - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err(); + |msg| msg.short_channel_id += 1 + i as u64, + node_1_privkey, + node_2_privkey, + &secp_ctx, + ); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err(); assert!(!network_graph.pending_checks.too_many_checks_pending()); } - let valid_announcement = get_signed_channel_announcement( - |_| {}, node_1_privkey, node_2_privkey, &secp_ctx); - network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap_err(); + let valid_announcement = + get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + network_graph + .update_channel_from_announcement(&valid_announcement, &Some(&chain_source)) + .unwrap_err(); assert!(network_graph.pending_checks.too_many_checks_pending()); // Once the future is drop'd (by resetting the `utxo_ret` value) the "too many checks" flag diff --git a/rustfmt_excluded_files b/rustfmt_excluded_files index 16bc8200088..c324e837a87 100644 --- a/rustfmt_excluded_files +++ b/rustfmt_excluded_files @@ -66,12 +66,6 @@ lightning/src/onion_message/messenger.rs lightning/src/onion_message/mod.rs lightning/src/onion_message/offers.rs lightning/src/onion_message/packet.rs -lightning/src/routing/gossip.rs -lightning/src/routing/mod.rs -lightning/src/routing/router.rs -lightning/src/routing/scoring.rs -lightning/src/routing/test_utils.rs -lightning/src/routing/utxo.rs lightning/src/util/atomic_counter.rs lightning/src/util/base32.rs lightning/src/util/byte_utils.rs