X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fnetwork_graph.rs;h=668e70969a85ae45db174a1ff68ec745bc6c377c;hb=0b7700830b440613d66a4494213b8bbfa63b8f68;hp=282c87051dd48f2c3b3a82db778cc20a78f7e2c5;hpb=7671ae54522197dbca495c4c7b3fec5c814a1841;p=rust-lightning diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index 282c8705..668e7096 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -10,7 +10,7 @@ //! The top-level network map tracking logic lives here. use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; -use bitcoin::secp256k1::key::PublicKey; +use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1; @@ -67,7 +67,7 @@ impl NodeId { pub fn from_pubkey(pubkey: &PublicKey) -> Self { NodeId(pubkey.serialize()) } - + /// Get the public key slice from this NodeId pub fn as_slice(&self) -> &[u8] { &self.0 @@ -123,6 +123,9 @@ impl Readable for NodeId { /// Represents the network as nodes and channels between them pub struct NetworkGraph { + /// The unix timestamp in UTC provided by the most recent rapid gossip sync + /// It will be set by the rapid sync process after every sync completion + pub last_rapid_gossip_sync_timestamp: Option, genesis_hash: BlockHash, // Lock order: channels -> nodes channels: RwLock>, @@ -137,6 +140,7 @@ impl Clone for NetworkGraph { genesis_hash: self.genesis_hash.clone(), channels: RwLock::new(channels.clone()), nodes: RwLock::new(nodes.clone()), + last_rapid_gossip_sync_timestamp: self.last_rapid_gossip_sync_timestamp.clone(), } } } @@ -150,7 +154,7 @@ pub struct ReadOnlyNetworkGraph<'a> { /// Update to the [`NetworkGraph`] based on payment failure information conveyed via the Onion /// return packet by a node along the route. See [BOLT #4] for details. /// -/// [BOLT #4]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md +/// [BOLT #4]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md #[derive(Clone, Debug, PartialEq)] pub enum NetworkUpdate { /// An error indicating a `channel_update` messages should be applied via @@ -295,7 +299,7 @@ where C::Target: chain::Access, L::Target: Logger macro_rules! secp_verify_sig { ( $secp_ctx: expr, $msg: expr, $sig: expr, $pubkey: expr, $msg_type: expr ) => { - match $secp_ctx.verify($msg, $sig, $pubkey) { + match $secp_ctx.verify_ecdsa($msg, $sig, $pubkey) { Ok(_) => {}, Err(_) => { return Err(LightningError { @@ -695,7 +699,7 @@ impl ChannelInfo { return None; } }; - Some((DirectedChannelInfo { channel: self, direction }, source)) + Some((DirectedChannelInfo::new(self, direction), source)) } /// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a @@ -710,7 +714,17 @@ impl ChannelInfo { return None; } }; - Some((DirectedChannelInfo { channel: self, direction }, target)) + Some((DirectedChannelInfo::new(self, direction), target)) + } + + /// Returns a [`ChannelUpdateInfo`] based on the direction implied by the channel_flag. + pub fn get_directional_info(&self, channel_flags: u8) -> Option<&ChannelUpdateInfo> { + let direction = channel_flags & 1u8; + if direction == 0 { + self.one_to_two.as_ref() + } else { + self.two_to_one.as_ref() + } } } @@ -739,35 +753,53 @@ impl_writeable_tlv_based!(ChannelInfo, { pub struct DirectedChannelInfo<'a> { channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>, + htlc_maximum_msat: u64, + effective_capacity: EffectiveCapacity, } impl<'a> DirectedChannelInfo<'a> { + #[inline] + fn new(channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>) -> Self { + let htlc_maximum_msat = direction.and_then(|direction| direction.htlc_maximum_msat); + let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000); + + let (htlc_maximum_msat, effective_capacity) = match (htlc_maximum_msat, capacity_msat) { + (Some(amount_msat), Some(capacity_msat)) => { + let htlc_maximum_msat = cmp::min(amount_msat, capacity_msat); + (htlc_maximum_msat, EffectiveCapacity::Total { capacity_msat }) + }, + (Some(amount_msat), None) => { + (amount_msat, EffectiveCapacity::MaximumHTLC { amount_msat }) + }, + (None, Some(capacity_msat)) => { + (capacity_msat, EffectiveCapacity::Total { capacity_msat }) + }, + (None, None) => (EffectiveCapacity::Unknown.as_msat(), EffectiveCapacity::Unknown), + }; + + Self { + channel, direction, htlc_maximum_msat, effective_capacity + } + } + /// 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 maximum HTLC amount allowed over the channel in the direction. + pub fn htlc_maximum_msat(&self) -> u64 { + self.htlc_maximum_msat + } + /// 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. + /// otherwise. 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) + self.effective_capacity } /// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction. @@ -805,6 +837,10 @@ impl<'a> DirectedChannelInfoWithUpdate<'a> { /// Returns the [`EffectiveCapacity`] of the channel in the direction. #[inline] pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() } + + /// Returns the maximum HTLC amount allowed over the channel in the direction. + #[inline] + pub(super) fn htlc_maximum_msat(&self) -> u64 { self.inner.htlc_maximum_msat() } } impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { @@ -817,6 +853,7 @@ impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { /// /// While this may be smaller than the actual channel capacity, amounts greater than /// [`Self::as_msat`] should not be routed through the channel. +#[derive(Clone, Copy)] pub enum EffectiveCapacity { /// The available liquidity in the channel known from being a channel counterparty, and thus a /// direct hop. @@ -957,7 +994,9 @@ impl Writeable for NetworkGraph { node_info.write(writer)?; } - write_tlv_fields!(writer, {}); + write_tlv_fields!(writer, { + (1, self.last_rapid_gossip_sync_timestamp, option), + }); Ok(()) } } @@ -981,12 +1020,17 @@ impl Readable for NetworkGraph { let node_info = Readable::read(reader)?; nodes.insert(node_id, node_info); } - read_tlv_fields!(reader, {}); + + let mut last_rapid_gossip_sync_timestamp: Option = None; + read_tlv_fields!(reader, { + (1, last_rapid_gossip_sync_timestamp, option), + }); Ok(NetworkGraph { genesis_hash, channels: RwLock::new(channels), nodes: RwLock::new(nodes), + last_rapid_gossip_sync_timestamp, }) } } @@ -1020,6 +1064,7 @@ impl NetworkGraph { genesis_hash, channels: RwLock::new(BTreeMap::new()), nodes: RwLock::new(BTreeMap::new()), + last_rapid_gossip_sync_timestamp: None, } } @@ -1033,6 +1078,15 @@ impl NetworkGraph { } } + /// Clears the `NodeAnnouncementInfo` field for all nodes in the `NetworkGraph` for testing + /// purposes. + #[cfg(test)] + pub fn clear_nodes_announcement_info(&self) { + for node in self.nodes.write().unwrap().iter_mut() { + node.1.announcement_info = None; + } + } + /// For an already known node (from channel announcements), update its stored properties from a /// given node announcement. /// @@ -1123,6 +1177,83 @@ impl NetworkGraph { self.update_channel_from_unsigned_announcement_intern(msg, None, chain_access) } + /// Update channel from partial announcement data received via rapid gossip sync + /// + /// `timestamp: u64`: Timestamp emulating the backdated original announcement receipt (by the + /// 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> { + if node_id_1 == node_id_2 { + 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); + let node_2 = NodeId::from_pubkey(&node_id_2); + let channel_info = ChannelInfo { + features, + node_one: node_1.clone(), + one_to_two: None, + node_two: node_2.clone(), + two_to_one: None, + capacity_sats: None, + announcement_message: None, + announcement_received_time: timestamp, + }; + + 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> { + 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(); + + match channels.entry(short_channel_id) { + BtreeEntry::Occupied(mut entry) => { + //TODO: because asking the blockchain if short_channel_id is valid is only optional + //in the blockchain API, we need to handle it smartly here, though it's unclear + //exactly how... + if utxo_value.is_some() { + // Either our UTXO provider is busted, there was a reorg, or the UTXO provider + // only sometimes returns results. In any case remove the previous entry. Note + // that the spec expects us to "blacklist" the node_ids involved, but we can't + // do that because + // a) we don't *require* a UTXO provider that always returns results. + // b) we don't track UTXOs of channels we know about and remove them if they + // get reorg'd out. + // c) it's unclear how to do so without exposing ourselves to massive DoS risk. + Self::remove_channel_in_nodes(&mut nodes, &entry.get(), short_channel_id); + *entry.get_mut() = channel_info; + } else { + return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); + } + }, + BtreeEntry::Vacant(entry) => { + entry.insert(channel_info); + } + }; + + for current_node_id in [node_id_a, node_id_b].iter() { + match nodes.entry(current_node_id.clone()) { + BtreeEntry::Occupied(node_entry) => { + node_entry.into_mut().channels.push(short_channel_id); + }, + BtreeEntry::Vacant(node_entry) => { + node_entry.insert(NodeInfo { + channels: vec!(short_channel_id), + lowest_inbound_channel_fees: None, + announcement_info: None, + }); + } + }; + }; + + Ok(()) + } + fn update_channel_from_unsigned_announcement_intern( &self, msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, chain_access: &Option ) -> Result<(), LightningError> @@ -1171,65 +1302,18 @@ impl NetworkGraph { } let chan_info = ChannelInfo { - features: msg.features.clone(), - node_one: NodeId::from_pubkey(&msg.node_id_1), - one_to_two: None, - node_two: NodeId::from_pubkey(&msg.node_id_2), - two_to_one: None, - capacity_sats: utxo_value, - announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY - { full_msg.cloned() } else { None }, - announcement_received_time, - }; - - let mut channels = self.channels.write().unwrap(); - let mut nodes = self.nodes.write().unwrap(); - match channels.entry(msg.short_channel_id) { - BtreeEntry::Occupied(mut entry) => { - //TODO: because asking the blockchain if short_channel_id is valid is only optional - //in the blockchain API, we need to handle it smartly here, though it's unclear - //exactly how... - if utxo_value.is_some() { - // Either our UTXO provider is busted, there was a reorg, or the UTXO provider - // only sometimes returns results. In any case remove the previous entry. Note - // that the spec expects us to "blacklist" the node_ids involved, but we can't - // do that because - // a) we don't *require* a UTXO provider that always returns results. - // b) we don't track UTXOs of channels we know about and remove them if they - // get reorg'd out. - // c) it's unclear how to do so without exposing ourselves to massive DoS risk. - Self::remove_channel_in_nodes(&mut nodes, &entry.get(), msg.short_channel_id); - *entry.get_mut() = chan_info; - } else { - return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip}); - } - }, - BtreeEntry::Vacant(entry) => { - entry.insert(chan_info); - } + features: msg.features.clone(), + node_one: NodeId::from_pubkey(&msg.node_id_1), + one_to_two: None, + node_two: NodeId::from_pubkey(&msg.node_id_2), + two_to_one: None, + capacity_sats: utxo_value, + announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY + { full_msg.cloned() } else { None }, + announcement_received_time, }; - macro_rules! add_channel_to_node { - ( $node_id: expr ) => { - match nodes.entry($node_id) { - BtreeEntry::Occupied(node_entry) => { - node_entry.into_mut().channels.push(msg.short_channel_id); - }, - BtreeEntry::Vacant(node_entry) => { - node_entry.insert(NodeInfo { - channels: vec!(msg.short_channel_id), - lowest_inbound_channel_fees: None, - announcement_info: None, - }); - } - } - }; - } - - add_channel_to_node!(NodeId::from_pubkey(&msg.node_id_1)); - add_channel_to_node!(NodeId::from_pubkey(&msg.node_id_2)); - - Ok(()) + self.add_channel_between_nodes(msg.short_channel_id, chan_info, utxo_value) } /// Close a channel if a corresponding HTLC fail was sent. @@ -1347,10 +1431,10 @@ impl NetworkGraph { /// 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)>) + self.update_channel_intern(msg, None, None::<(&secp256k1::ecdsa::Signature, &Secp256k1)>) } - fn update_channel_intern(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig_info: Option<(&secp256k1::Signature, &Secp256k1)>) -> Result<(), LightningError> { + fn update_channel_intern(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig_info: Option<(&secp256k1::ecdsa::Signature, &Secp256k1)>) -> Result<(), LightningError> { let dest_node_id; let chan_enabled = msg.flags & (1 << 1) != (1 << 1); let chan_was_enabled; @@ -1549,7 +1633,7 @@ mod tests { use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use routing::network_graph::{NetGraphMsgHandler, NetworkGraph, NetworkUpdate, MAX_EXCESS_BYTES_FOR_RELAY}; use ln::msgs::{Init, OptionalField, RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, - UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, + UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT}; use util::test_utils; use util::logger::Logger; @@ -1569,10 +1653,11 @@ mod tests { use hex; - use bitcoin::secp256k1::key::{PublicKey, SecretKey}; + use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::secp256k1::{All, Secp256k1}; use io; + use bitcoin::secp256k1; use prelude::*; use sync::Arc; @@ -1619,7 +1704,7 @@ mod tests { f(&mut unsigned_announcement); let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); NodeAnnouncement { - signature: secp_ctx.sign(&msghash, node_key), + signature: secp_ctx.sign_ecdsa(&msghash, node_key), contents: unsigned_announcement } } @@ -1643,10 +1728,10 @@ mod tests { f(&mut unsigned_announcement); let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); ChannelAnnouncement { - node_signature_1: secp_ctx.sign(&msghash, node_1_key), - node_signature_2: secp_ctx.sign(&msghash, node_2_key), - bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), - bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + node_signature_1: secp_ctx.sign_ecdsa(&msghash, node_1_key), + node_signature_2: secp_ctx.sign_ecdsa(&msghash, node_2_key), + bitcoin_signature_1: secp_ctx.sign_ecdsa(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign_ecdsa(&msghash, node_2_btckey), contents: unsigned_announcement, } } @@ -1678,7 +1763,7 @@ mod tests { f(&mut unsigned_channel_update); let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); ChannelUpdate { - signature: secp_ctx.sign(&msghash, node_key), + signature: secp_ctx.sign_ecdsa(&msghash, node_key), contents: unsigned_channel_update } } @@ -1715,7 +1800,7 @@ mod tests { let fake_msghash = hash_to_message!(&zero_hash); match net_graph_msg_handler.handle_node_announcement( &NodeAnnouncement { - signature: secp_ctx.sign(&fake_msghash, node_1_privkey), + signature: secp_ctx.sign_ecdsa(&fake_msghash, node_1_privkey), contents: valid_announcement.contents.clone() }) { Ok(_) => panic!(), @@ -1954,7 +2039,7 @@ mod tests { }, node_1_privkey, &secp_ctx); let zero_hash = Sha256dHash::hash(&[0; 32]); let fake_msghash = hash_to_message!(&zero_hash); - invalid_sig_channel_update.signature = secp_ctx.sign(&fake_msghash, node_1_privkey); + invalid_sig_channel_update.signature = secp_ctx.sign_ecdsa(&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 on channel_update message") @@ -2286,6 +2371,18 @@ mod tests { assert!(::read(&mut io::Cursor::new(&w.0)).unwrap() == network_graph); } + #[test] + fn network_graph_tlv_serialization() { + let mut network_graph = create_network_graph(); + network_graph.last_rapid_gossip_sync_timestamp.replace(42); + + let mut w = test_utils::TestVecWriter(Vec::new()); + network_graph.write(&mut w).unwrap(); + let reassembled_network_graph: NetworkGraph = Readable::read(&mut io::Cursor::new(&w.0)).unwrap(); + assert!(reassembled_network_graph == network_graph); + assert_eq!(reassembled_network_graph.last_rapid_gossip_sync_timestamp.unwrap(), 42); + } + #[test] #[cfg(feature = "std")] fn calling_sync_routing_table() {