X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fnetwork_graph.rs;h=5a84f35c28dd4f1990ef0eda4611c53a139cb615;hb=40a4f861144e28c5e287fbc52fad57b1a24017b4;hp=67dc302246325b7efa219e98acdde875e5be702c;hpb=4677e14c007a5453613afc20b088cbe938251226;p=rust-lightning diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index 67dc3022..5a84f35c 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -249,6 +249,14 @@ where C::Target: chain::Access, L::Target: Logger self.chain_access = chain_access; } + /// Gets a reference to the underlying [`NetworkGraph`] which was provided in + /// [`NetGraphMsgHandler::new`]. + /// + /// (C-not exported) as bindings don't support a reference-to-a-reference yet + pub fn network_graph(&self) -> &G { + &self.network_graph + } + /// Returns true when a full routing table sync should be performed with a peer. fn should_request_full_sync(&self, _node_id: &PublicKey) -> bool { //TODO: Determine whether to request a full sync based on the network map. @@ -286,10 +294,21 @@ where C::Target: chain::Access, L::Target: Logger } macro_rules! secp_verify_sig { - ( $secp_ctx: expr, $msg: expr, $sig: expr, $pubkey: expr ) => { + ( $secp_ctx: expr, $msg: expr, $sig: expr, $pubkey: expr, $msg_type: expr ) => { match $secp_ctx.verify($msg, $sig, $pubkey) { Ok(_) => {}, - Err(_) => return Err(LightningError{err: "Invalid signature from remote node".to_owned(), action: ErrorAction::IgnoreError}), + Err(_) => { + return Err(LightningError { + err: format!("Invalid signature on {} message", $msg_type), + action: ErrorAction::SendWarningMessage { + msg: msgs::WarningMessage { + channel_id: [0; 32], + data: format!("Invalid signature on {} message", $msg_type), + }, + log_level: Level::Trace, + }, + }); + }, } }; } @@ -574,9 +593,8 @@ where } #[derive(Clone, Debug, PartialEq)] -/// Details about one direction of a channel. Received -/// within a channel update. -pub struct DirectionalChannelInfo { +/// Details about one direction of a channel as received within a [`ChannelUpdate`]. +pub struct ChannelUpdateInfo { /// When the last update to the channel direction was issued. /// Value is opaque, as set in the announcement. pub last_update: u32, @@ -597,14 +615,14 @@ pub struct DirectionalChannelInfo { pub last_update_message: Option, } -impl fmt::Display for DirectionalChannelInfo { +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)?; Ok(()) } } -impl_writeable_tlv_based!(DirectionalChannelInfo, { +impl_writeable_tlv_based!(ChannelUpdateInfo, { (0, last_update, required), (2, enabled, required), (4, cltv_expiry_delta, required), @@ -623,11 +641,11 @@ pub struct ChannelInfo { /// Source node of the first direction of a channel pub node_one: NodeId, /// Details about the first direction of a channel - pub one_to_two: Option, + pub one_to_two: Option, /// Source node of the second direction of a channel pub node_two: NodeId, /// Details about the second direction of a channel - pub two_to_one: Option, + pub two_to_one: Option, /// The channel capacity as seen on-chain, if chain lookup is available. pub capacity_sats: Option, /// An initial announcement of the channel @@ -641,6 +659,23 @@ pub struct ChannelInfo { announcement_received_time: u64, } +impl ChannelInfo { + /// Returns a [`DirectedChannelInfo`] for the channel directed to the given `target` from a + /// returned `source`, or `None` if `target` is not one of the channel's counterparties. + pub fn as_directed_to(&self, target: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> { + let (direction, source) = { + if target == &self.node_one { + (self.two_to_one.as_ref(), &self.node_two) + } else if target == &self.node_two { + (self.one_to_two.as_ref(), &self.node_one) + } else { + return None; + } + }; + Some((DirectedChannelInfo { channel: self, direction }, source)) + } +} + 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: {:?}", @@ -660,6 +695,132 @@ impl_writeable_tlv_based!(ChannelInfo, { (12, announcement_message, required), }); +/// A wrapper around [`ChannelInfo`] representing information about the channel as directed from a +/// source node to a target node. +#[derive(Clone)] +pub struct DirectedChannelInfo<'a> { + channel: &'a ChannelInfo, + direction: Option<&'a ChannelUpdateInfo>, +} + +impl<'a> DirectedChannelInfo<'a> { + /// Returns information for the channel. + pub fn channel(&self) -> &'a ChannelInfo { self.channel } + + /// Returns information for the direction. + pub fn direction(&self) -> Option<&'a ChannelUpdateInfo> { self.direction } + + /// Returns the [`EffectiveCapacity`] of the channel in the direction. + /// + /// This is either the total capacity from the funding transaction, if known, or the + /// `htlc_maximum_msat` for the direction as advertised by the gossip network, if known, + /// whichever is smaller. + pub fn effective_capacity(&self) -> EffectiveCapacity { + let capacity_msat = self.channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000); + self.direction + .and_then(|direction| direction.htlc_maximum_msat) + .map(|max_htlc_msat| { + let capacity_msat = capacity_msat.unwrap_or(u64::max_value()); + if max_htlc_msat < capacity_msat { + EffectiveCapacity::MaximumHTLC { amount_msat: max_htlc_msat } + } else { + EffectiveCapacity::Total { capacity_msat } + } + }) + .or_else(|| capacity_msat.map(|capacity_msat| + EffectiveCapacity::Total { capacity_msat })) + .unwrap_or(EffectiveCapacity::Unknown) + } + + /// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction. + pub(super) fn with_update(self) -> Option> { + match self.direction { + Some(_) => Some(DirectedChannelInfoWithUpdate { inner: self }), + None => None, + } + } +} + +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() + } +} + +/// A [`DirectedChannelInfo`] with [`ChannelUpdateInfo`] available in its direction. +#[derive(Clone)] +pub(super) struct DirectedChannelInfoWithUpdate<'a> { + inner: DirectedChannelInfo<'a>, +} + +impl<'a> DirectedChannelInfoWithUpdate<'a> { + /// Returns information for the channel. + #[inline] + pub(super) fn channel(&self) -> &'a ChannelInfo { &self.inner.channel } + + /// Returns information for the direction. + #[inline] + pub(super) fn direction(&self) -> &'a ChannelUpdateInfo { self.inner.direction.unwrap() } + + /// Returns the [`EffectiveCapacity`] of the channel in the direction. + #[inline] + pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() } +} + +impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.inner.fmt(f) + } +} + +/// The effective capacity of a channel for routing purposes. +/// +/// While this may be smaller than the actual channel capacity, amounts greater than +/// [`Self::as_msat`] should not be routed through the channel. +pub enum EffectiveCapacity { + /// The available liquidity in the channel known from being a channel counterparty, and thus a + /// direct hop. + ExactLiquidity { + /// Either the inbound or outbound liquidity depending on the direction, denominated in + /// millisatoshi. + liquidity_msat: u64, + }, + /// The maximum HTLC amount in one direction as advertised on the gossip network. + MaximumHTLC { + /// The maximum HTLC amount denominated in millisatoshi. + amount_msat: u64, + }, + /// The total capacity of the channel as determined by the funding transaction. + Total { + /// The funding amount denominated in millisatoshi. + capacity_msat: u64, + }, + /// A capacity sufficient to route any payment, typically used for private channels provided by + /// an invoice. + Infinite, + /// A capacity that is unknown possibly because either the chain state is unavailable to know + /// the total capacity or the `htlc_maximum_msat` was not advertised on the gossip network. + Unknown, +} + +/// The presumed channel capacity denominated in millisatoshi for [`EffectiveCapacity::Unknown`] to +/// use when making routing decisions. +pub const UNKNOWN_CHANNEL_CAPACITY_MSAT: u64 = 250_000 * 1000; + +impl EffectiveCapacity { + /// Returns the effective capacity denominated in millisatoshi. + pub fn as_msat(&self) -> u64 { + match self { + EffectiveCapacity::ExactLiquidity { liquidity_msat } => *liquidity_msat, + EffectiveCapacity::MaximumHTLC { amount_msat } => *amount_msat, + EffectiveCapacity::Total { capacity_msat } => *capacity_msat, + EffectiveCapacity::Infinite => u64::max_value(), + EffectiveCapacity::Unknown => UNKNOWN_CHANNEL_CAPACITY_MSAT, + } + } +} /// Fees for routing via a given channel or a node #[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] @@ -842,7 +1003,7 @@ impl NetworkGraph { /// routing messages from a source using a protocol other than the lightning P2P protocol. pub fn update_node_from_announcement(&self, msg: &msgs::NodeAnnouncement, secp_ctx: &Secp256k1) -> Result<(), LightningError> { let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.signature, &msg.contents.node_id); + secp_verify_sig!(secp_ctx, &msg_hash, &msg.signature, &msg.contents.node_id, "node_announcement"); self.update_node_from_announcement_intern(&msg.contents, Some(&msg)) } @@ -902,10 +1063,10 @@ impl NetworkGraph { C::Target: chain::Access, { let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_1, &msg.contents.node_id_1); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_2, &msg.contents.node_id_2); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &msg.contents.bitcoin_key_1); - secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &msg.contents.bitcoin_key_2); + secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_1, &msg.contents.node_id_1, "channel_announcement"); + secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_2, &msg.contents.node_id_2, "channel_announcement"); + secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &msg.contents.bitcoin_key_1, "channel_announcement"); + secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &msg.contents.bitcoin_key_2, "channel_announcement"); self.update_channel_from_unsigned_announcement_intern(&msg.contents, Some(msg), chain_access) } @@ -1074,6 +1235,8 @@ impl NetworkGraph { /// updates every two weeks, the non-normative section of BOLT 7 currently suggests that /// pruning occur for updates which are at least two weeks old, which we implement here. /// + /// Note that for users of the `lightning-background-processor` crate this method may be + /// automatically called regularly for you. /// /// This method is only available with the `std` feature. See /// [`NetworkGraph::remove_stale_channels_with_time`] for `no-std` use. @@ -1132,6 +1295,9 @@ impl NetworkGraph { /// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler'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. + /// + /// If built with `no-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(&self, msg: &msgs::ChannelUpdate, secp_ctx: &Secp256k1) -> Result<(), LightningError> { self.update_channel_intern(&msg.contents, Some(&msg), Some((&msg.signature, secp_ctx))) } @@ -1139,6 +1305,9 @@ impl NetworkGraph { /// For an already known (from announcement) channel, update info about one of the directions /// of the channel without verifying the associated signatures. Because we aren't given the /// associated signatures here we cannot relay the channel update to any of our peers. + /// + /// If built with `no-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> { self.update_channel_intern(msg, None, None::<(&secp256k1::Signature, &Secp256k1)>) } @@ -1148,6 +1317,19 @@ impl NetworkGraph { let chan_enabled = msg.flags & (1 << 1) != (1 << 1); let chan_was_enabled; + #[cfg(all(feature = "std", not(test), not(feature = "_test_utils")))] + { + // 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(); + 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)}); + } + 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)}); + } + } + let mut channels = self.channels.write().unwrap(); match channels.get_mut(&msg.short_channel_id) { None => return Err(LightningError{err: "Couldn't find channel for update".to_owned(), action: ErrorAction::IgnoreError}), @@ -1187,7 +1369,7 @@ impl NetworkGraph { let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY { full_msg.cloned() } else { None }; - let updated_channel_dir_info = DirectionalChannelInfo { + let updated_channel_update_info = ChannelUpdateInfo { enabled: chan_enabled, last_update: msg.timestamp, cltv_expiry_delta: msg.cltv_expiry_delta, @@ -1199,7 +1381,7 @@ impl NetworkGraph { }, last_update_message }; - $target = Some(updated_channel_dir_info); + $target = Some(updated_channel_update_info); } } @@ -1210,7 +1392,7 @@ impl NetworkGraph { secp_verify_sig!(ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_two.as_slice()).map_err(|_| LightningError{ err: "Couldn't parse source node pubkey".to_owned(), action: ErrorAction::IgnoreAndLog(Level::Debug) - })?); + })?, "channel_update"); } maybe_update_channel_info!(channel.two_to_one, channel.node_two); } else { @@ -1219,7 +1401,7 @@ impl NetworkGraph { secp_verify_sig!(ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_one.as_slice()).map_err(|_| LightningError{ err: "Couldn't parse destination node pubkey".to_owned(), action: ErrorAction::IgnoreAndLog(Level::Debug) - })?); + })?, "channel_update"); } maybe_update_channel_info!(channel.one_to_two, channel.node_one); } @@ -1493,7 +1675,7 @@ mod tests { contents: valid_announcement.contents.clone() }) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature from remote node") + Err(e) => assert_eq!(e.err, "Invalid signature on node_announcement message") }; let announcement_with_data = get_signed_node_announcement(|unsigned_announcement| { @@ -1622,7 +1804,7 @@ mod tests { invalid_sig_announcement.contents.excess_data = Vec::new(); match net_graph_msg_handler.handle_channel_announcement(&invalid_sig_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature from remote node") + Err(e) => assert_eq!(e.err, "Invalid signature on channel_announcement message") }; let channel_to_itself_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_1_privkey, &secp_ctx); @@ -1731,7 +1913,7 @@ mod tests { invalid_sig_channel_update.signature = secp_ctx.sign(&fake_msghash, node_1_privkey); match net_graph_msg_handler.handle_channel_update(&invalid_sig_channel_update) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Invalid signature from remote node") + Err(e) => assert_eq!(e.err, "Invalid signature on channel_update message") }; } @@ -2505,7 +2687,7 @@ mod tests { } } -#[cfg(all(test, feature = "unstable"))] +#[cfg(all(test, feature = "_bench_unstable"))] mod benches { use super::*;