//! 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;
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
/// Represents the network as nodes and channels between them
pub struct NetworkGraph {
+ last_rapid_gossip_sync_timestamp: Mutex<Option<u32>>,
genesis_hash: BlockHash,
// Lock order: channels -> nodes
channels: RwLock<BTreeMap<u64, ChannelInfo>>,
fn clone(&self) -> Self {
let channels = self.channels.read().unwrap();
let nodes = self.nodes.read().unwrap();
+ let last_rapid_gossip_sync_timestamp = self.get_last_rapid_gossip_sync_timestamp();
Self {
genesis_hash: self.genesis_hash.clone(),
channels: RwLock::new(channels.clone()),
nodes: RwLock::new(nodes.clone()),
+ last_rapid_gossip_sync_timestamp: Mutex::new(last_rapid_gossip_sync_timestamp)
}
}
}
/// 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
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 {
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
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()
+ }
}
}
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.
/// 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> {
///
/// 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.
node_info.write(writer)?;
}
- write_tlv_fields!(writer, {});
+ let last_rapid_gossip_sync_timestamp = self.get_last_rapid_gossip_sync_timestamp();
+ write_tlv_fields!(writer, {
+ (1, last_rapid_gossip_sync_timestamp, option),
+ });
Ok(())
}
}
let node_info = Readable::read(reader)?;
nodes.insert(node_id, node_info);
}
- read_tlv_fields!(reader, {});
+
+ let mut last_rapid_gossip_sync_timestamp: Option<u32> = 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: Mutex::new(last_rapid_gossip_sync_timestamp),
})
}
}
genesis_hash,
channels: RwLock::new(BTreeMap::new()),
nodes: RwLock::new(BTreeMap::new()),
+ last_rapid_gossip_sync_timestamp: Mutex::new(None),
}
}
}
}
+ /// The unix timestamp provided by the most recent rapid gossip sync.
+ /// It will be set by the rapid sync process after every sync completion.
+ pub fn get_last_rapid_gossip_sync_timestamp(&self) -> Option<u32> {
+ self.last_rapid_gossip_sync_timestamp.lock().unwrap().clone()
+ }
+
+ /// 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);
+ }
+
+ /// 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.
///
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<u64>) -> 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<C: Deref>(
&self, msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, chain_access: &Option<C>
) -> Result<(), LightningError>
}
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.
/// 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<secp256k1::VerifyOnly>)>)
+ self.update_channel_intern(msg, None, None::<(&secp256k1::ecdsa::Signature, &Secp256k1<secp256k1::VerifyOnly>)>)
}
- fn update_channel_intern<T: secp256k1::Verification>(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig_info: Option<(&secp256k1::Signature, &Secp256k1<T>)>) -> Result<(), LightningError> {
+ fn update_channel_intern<T: secp256k1::Verification>(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig_info: Option<(&secp256k1::ecdsa::Signature, &Secp256k1<T>)>) -> Result<(), LightningError> {
let dest_node_id;
let chan_enabled = msg.flags & (1 << 1) != (1 << 1);
let chan_was_enabled;
}
}
}
- macro_rules! maybe_update_channel_info {
- ( $target: expr, $src_node: expr) => {
+ macro_rules! check_update_latest {
+ ($target: expr) => {
if let Some(existing_chan_info) = $target.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
} else {
chan_was_enabled = false;
}
+ }
+ }
+ macro_rules! get_new_channel_info {
+ () => { {
let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
{ full_msg.cloned() } else { None };
},
last_update_message
};
- $target = Some(updated_channel_update_info);
- }
+ Some(updated_channel_update_info)
+ } }
}
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
if msg.flags & 1 == 1 {
dest_node_id = channel.node_one.clone();
+ check_update_latest!(channel.two_to_one);
if let Some((sig, ctx)) = sig_info {
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);
+ channel.two_to_one = get_new_channel_info!();
} else {
dest_node_id = channel.node_two.clone();
+ check_update_latest!(channel.one_to_two);
if let Some((sig, ctx)) = sig_info {
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);
+ channel.one_to_two = get_new_channel_info!();
}
}
}
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;
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;
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
}
}
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,
}
}
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
}
}
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!(),
}, 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")
assert!(<NetworkGraph>::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.set_last_rapid_gossip_sync_timestamp(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.get_last_rapid_gossip_sync_timestamp().unwrap(), 42);
+ }
+
#[test]
#[cfg(feature = "std")]
fn calling_sync_routing_table() {