X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fnetwork_graph.rs;h=cafe4fc10a38f5c51259256c3c87fe6e1248a90b;hb=ddebf36eaeef90684eb48ddab8db924b46ee74ac;hp=7e41c025c84de03ae36cba688094f802571203f7;hpb=afd42821076854acac0d775a3448e97a94ddc475;p=rust-lightning diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index 7e41c025..cafe4fc1 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! The top-level network map tracking logic lives here. use bitcoin::secp256k1::key::PublicKey; @@ -7,35 +16,59 @@ use bitcoin::secp256k1; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash; use bitcoin::blockdata::script::Builder; +use bitcoin::blockdata::transaction::TxOut; use bitcoin::blockdata::opcodes; -use chain::chaininterface::{ChainError, ChainWatchInterface}; +use chain; +use chain::Access; use ln::features::{ChannelFeatures, NodeFeatures}; -use ln::msgs::{DecodeError,ErrorAction,LightningError,RoutingMessageHandler,NetAddress}; +use ln::msgs::{DecodeError, ErrorAction, LightningError, RoutingMessageHandler, NetAddress, MAX_VALUE_MSAT}; +use ln::msgs::{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement, OptionalField}; use ln::msgs; -use util::ser::{Writeable, Readable, Writer, ReadableArgs}; +use util::ser::{Writeable, Readable, Writer}; use util::logger::Logger; -use std::cmp; -use std::sync::{RwLock,Arc}; +use std::{cmp, fmt}; +use std::sync::{RwLock, RwLockReadGuard}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::collections::BTreeMap; use std::collections::btree_map::Entry as BtreeEntry; -use std; +use std::ops::Deref; +use bitcoin::hashes::hex::ToHex; + +/// Represents the network as nodes and channels between them +#[derive(PartialEq)] +pub struct NetworkGraph { + channels: BTreeMap, + nodes: BTreeMap, +} -/// Receives network updates from peers to track view of the network. -pub struct NetGraphMsgHandler { +/// A simple newtype for RwLockReadGuard<'a, NetworkGraph>. +/// This exists only to make accessing a RwLock possible from +/// the C bindings, as it can be done directly in Rust code. +pub struct LockedNetworkGraph<'a>(pub RwLockReadGuard<'a, NetworkGraph>); + +/// Receives and validates network updates from peers, +/// stores authentic and relevant data as a network graph. +/// This network graph is then used for routing payments. +/// Provides interface to help with initial routing sync by +/// serving historical announcements. +pub struct NetGraphMsgHandler where C::Target: chain::Access, L::Target: Logger { secp_ctx: Secp256k1, /// Representation of the payment channel network pub network_graph: RwLock, - chain_monitor: Arc, + chain_access: Option, full_syncs_requested: AtomicUsize, - logger: Arc, + logger: L, } -impl NetGraphMsgHandler { - /// Creates a new tracker of the actual state of the network of channels and nodes. - pub fn new(chain_monitor: Arc, logger: Arc) -> Self { +impl NetGraphMsgHandler where C::Target: chain::Access, L::Target: Logger { + /// Creates a new tracker of the actual state of the network of channels and nodes, + /// assuming a fresh network graph. + /// Chain monitor is used to make sure announced channels exist on-chain, + /// channel data is correct, and that the announcement is signed with + /// channel owners' keys. + pub fn new(chain_access: Option, logger: L) -> Self { NetGraphMsgHandler { secp_ctx: Secp256k1::verification_only(), network_graph: RwLock::new(NetworkGraph { @@ -43,26 +76,36 @@ impl NetGraphMsgHandler { nodes: BTreeMap::new(), }), full_syncs_requested: AtomicUsize::new(0), - chain_monitor, - logger: logger.clone(), + chain_access, + logger, } } - /// Get network addresses by node id - pub fn get_addresses(&self, pubkey: &PublicKey) -> Option> { - let network = self.network_graph.read().unwrap(); - if let Some(node) = network.get_nodes().get(pubkey) { - if let Some(node_info) = node.announcement_info.as_ref() { - return Some(node_info.addresses.clone()) - } + /// Creates a new tracker of the actual state of the network of channels and nodes, + /// assuming an existing Network Graph. + pub fn from_net_graph(chain_access: Option, logger: L, network_graph: NetworkGraph) -> Self { + NetGraphMsgHandler { + secp_ctx: Secp256k1::verification_only(), + network_graph: RwLock::new(network_graph), + full_syncs_requested: AtomicUsize::new(0), + chain_access, + logger, } - None } - /// Dumps the entire network view of this NetGraphMsgHandler to the logger provided in the constructor at - /// level Trace - pub fn trace_state(&self) { - log_trace!(self, "{}", self.network_graph.read().unwrap()); + /// Take a read lock on the network_graph and return it in the C-bindings + /// newtype helper. This is likely only useful when called via the C + /// bindings as you can call `self.network_graph.read().unwrap()` in Rust + /// yourself. + pub fn read_locked_graph<'a>(&'a self) -> LockedNetworkGraph<'a> { + LockedNetworkGraph(self.network_graph.read().unwrap()) + } +} + +impl<'a> LockedNetworkGraph<'a> { + /// Get a reference to the NetworkGraph which this read-lock contains. + pub fn graph(&self) -> &NetworkGraph { + &*self.0 } } @@ -71,48 +114,52 @@ macro_rules! secp_verify_sig { ( $secp_ctx: expr, $msg: expr, $sig: expr, $pubkey: expr ) => { match $secp_ctx.verify($msg, $sig, $pubkey) { Ok(_) => {}, - Err(_) => return Err(LightningError{err: "Invalid signature from remote node", action: ErrorAction::IgnoreError}), + Err(_) => return Err(LightningError{err: "Invalid signature from remote node".to_owned(), action: ErrorAction::IgnoreError}), } }; } -impl RoutingMessageHandler for NetGraphMsgHandler { +impl RoutingMessageHandler for NetGraphMsgHandler where C::Target: chain::Access, L::Target: Logger { fn handle_node_announcement(&self, msg: &msgs::NodeAnnouncement) -> Result { self.network_graph.write().unwrap().update_node_from_announcement(msg, Some(&self.secp_ctx)) } fn handle_channel_announcement(&self, msg: &msgs::ChannelAnnouncement) -> Result { if msg.contents.node_id_1 == msg.contents.node_id_2 || msg.contents.bitcoin_key_1 == msg.contents.bitcoin_key_2 { - return Err(LightningError{err: "Channel announcement node had a channel with itself", action: ErrorAction::IgnoreError}); + return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError}); } - let checked_utxo = match self.chain_monitor.get_chain_utxo(msg.contents.chain_hash, msg.contents.short_channel_id) { - Ok((script_pubkey, _value)) => { - let expected_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_slice(&msg.contents.bitcoin_key_1.serialize()) - .push_slice(&msg.contents.bitcoin_key_2.serialize()) - .push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh(); - if script_pubkey != expected_script { - return Err(LightningError{err: "Channel announcement keys didn't match on-chain script", action: ErrorAction::IgnoreError}); - } - //TODO: Check if value is worth storing, use it to inform routing, and compare it - //to the new HTLC max field in channel_update - true - }, - Err(ChainError::NotSupported) => { + let utxo_value = match &self.chain_access { + &None => { // Tentatively accept, potentially exposing us to DoS attacks - false - }, - Err(ChainError::NotWatched) => { - return Err(LightningError{err: "Channel announced on an unknown chain", action: ErrorAction::IgnoreError}); + None }, - Err(ChainError::UnknownTx) => { - return Err(LightningError{err: "Channel announced without corresponding UTXO entry", action: ErrorAction::IgnoreError}); + &Some(ref chain_access) => { + match chain_access.get_utxo(&msg.contents.chain_hash, msg.contents.short_channel_id) { + Ok(TxOut { value, script_pubkey }) => { + let expected_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_slice(&msg.contents.bitcoin_key_1.serialize()) + .push_slice(&msg.contents.bitcoin_key_2.serialize()) + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh(); + if script_pubkey != expected_script { + return Err(LightningError{err: format!("Channel announcement key ({}) didn't match on-chain script ({})", script_pubkey.to_hex(), expected_script.to_hex()), action: ErrorAction::IgnoreError}); + } + //TODO: Check if value is worth storing, use it to inform routing, and compare it + //to the new HTLC max field in channel_update + Some(value) + }, + Err(chain::AccessError::UnknownChain) => { + return Err(LightningError{err: format!("Channel announced on an unknown chain ({})", msg.contents.chain_hash.encode().to_hex()), action: ErrorAction::IgnoreError}); + }, + Err(chain::AccessError::UnknownTx) => { + return Err(LightningError{err: "Channel announced without corresponding UTXO entry".to_owned(), action: ErrorAction::IgnoreError}); + }, + } }, }; - let result = self.network_graph.write().unwrap().update_channel_from_announcement(msg, checked_utxo, Some(&self.secp_ctx)); - log_trace!(self, "Added channel_announcement for {}{}", msg.contents.short_channel_id, if !msg.contents.excess_data.is_empty() { " with excess uninterpreted data!" } else { "" }); + let result = self.network_graph.write().unwrap().update_channel_from_announcement(msg, utxo_value, Some(&self.secp_ctx)); + log_trace!(self.logger, "Added channel_announcement for {}{}", msg.contents.short_channel_id, if !msg.contents.excess_data.is_empty() { " with excess uninterpreted data!" } else { "" }); result } @@ -121,11 +168,11 @@ impl RoutingMessageHandler for NetGraphMsgHandler { &msgs::HTLCFailChannelUpdate::ChannelUpdateMessage { ref msg } => { let _ = self.network_graph.write().unwrap().update_channel(msg, Some(&self.secp_ctx)); }, - &msgs::HTLCFailChannelUpdate::ChannelClosed { ref short_channel_id, ref is_permanent } => { - self.network_graph.write().unwrap().close_channel_from_update(short_channel_id, &is_permanent); + &msgs::HTLCFailChannelUpdate::ChannelClosed { short_channel_id, is_permanent } => { + self.network_graph.write().unwrap().close_channel_from_update(short_channel_id, is_permanent); }, - &msgs::HTLCFailChannelUpdate::NodeFailure { ref node_id, ref is_permanent } => { - self.network_graph.write().unwrap().fail_node(node_id, &is_permanent); + &msgs::HTLCFailChannelUpdate::NodeFailure { ref node_id, is_permanent } => { + self.network_graph.write().unwrap().fail_node(node_id, is_permanent); }, } } @@ -134,16 +181,23 @@ impl RoutingMessageHandler for NetGraphMsgHandler { self.network_graph.write().unwrap().update_channel(msg, Some(&self.secp_ctx)) } - fn get_next_channel_announcements(&self, starting_point: u64, batch_amount: u8) -> Vec<(msgs::ChannelAnnouncement, Option, Option)> { + fn get_next_channel_announcements(&self, starting_point: u64, batch_amount: u8) -> Vec<(ChannelAnnouncement, Option, Option)> { let network_graph = self.network_graph.read().unwrap(); let mut result = Vec::with_capacity(batch_amount as usize); let mut iter = network_graph.get_channels().range(starting_point..); while result.len() < batch_amount as usize { if let Some((_, ref chan)) = iter.next() { if chan.announcement_message.is_some() { - result.push((chan.announcement_message.clone().unwrap(), - chan.one_to_two.last_update_message.clone(), - chan.two_to_one.last_update_message.clone())); + let chan_announcement = chan.announcement_message.clone().unwrap(); + let mut one_to_two_announcement: Option = None; + let mut two_to_one_announcement: Option = None; + if let Some(one_to_two) = chan.one_to_two.as_ref() { + one_to_two_announcement = one_to_two.last_update_message.clone(); + } + if let Some(two_to_one) = chan.two_to_one.as_ref() { + two_to_one_announcement = two_to_one.last_update_message.clone(); + } + result.push((chan_announcement, one_to_two_announcement, two_to_one_announcement)); } else { // TODO: We may end up sending un-announced channel_updates if we are sending // initial sync data while receiving announce/updates for this channel. @@ -155,7 +209,7 @@ impl RoutingMessageHandler for NetGraphMsgHandler { result } - fn get_next_node_announcements(&self, starting_point: Option<&PublicKey>, batch_amount: u8) -> Vec { + fn get_next_node_announcements(&self, starting_point: Option<&PublicKey>, batch_amount: u8) -> Vec { let network_graph = self.network_graph.read().unwrap(); let mut result = Vec::with_capacity(batch_amount as usize); let mut iter = if let Some(pubkey) = starting_point { @@ -191,115 +245,85 @@ impl RoutingMessageHandler for NetGraphMsgHandler { } } - -const SERIALIZATION_VERSION: u8 = 1; -const MIN_SERIALIZATION_VERSION: u8 = 1; - -impl Writeable for NetGraphMsgHandler { - fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { - writer.write_all(&[SERIALIZATION_VERSION; 1])?; - writer.write_all(&[MIN_SERIALIZATION_VERSION; 1])?; - - let network = self.network_graph.read().unwrap(); - network.write(writer)?; - Ok(()) - } -} - -/// Arguments for the creation of a NetGraphMsgHandler that are not deserialized. -/// At a high-level, the process for deserializing a NetGraphMsgHandler and resuming normal operation is: -/// 1) Deserialize the NetGraphMsgHandler by filling in this struct and calling ::read(reaser, args). -/// 2) Register the new NetGraphMsgHandler with your ChainWatchInterface -pub struct NetGraphMsgHandlerReadArgs { - /// The ChainWatchInterface for use in the NetGraphMsgHandler in the future. - /// - /// No calls to the ChainWatchInterface will be made during deserialization. - pub chain_monitor: Arc, - /// The Logger for use in the ChannelManager and which may be used to log information during - /// deserialization. - pub logger: Arc, -} - -impl ReadableArgs for NetGraphMsgHandler { - fn read(reader: &mut R, args: NetGraphMsgHandlerReadArgs) -> Result { - let _ver: u8 = Readable::read(reader)?; - let min_ver: u8 = Readable::read(reader)?; - if min_ver > SERIALIZATION_VERSION { - return Err(DecodeError::UnknownVersion); - } - let network_graph = Readable::read(reader)?; - Ok(NetGraphMsgHandler { - secp_ctx: Secp256k1::verification_only(), - network_graph: RwLock::new(network_graph), - chain_monitor: args.chain_monitor, - full_syncs_requested: AtomicUsize::new(0), - logger: args.logger.clone(), - }) - } -} - -#[derive(PartialEq)] -/// Details regarding one direction of a channel +#[derive(PartialEq, Debug)] +/// Details about one direction of a channel. Received +/// within a channel update. pub struct DirectionalChannelInfo { - /// A node from which the channel direction starts - pub src_node_id: PublicKey, - /// When the last update to the channel direction was issued + /// When the last update to the channel direction was issued. + /// Value is opaque, as set in the announcement. pub last_update: u32, - /// Whether the channel can be currently used for payments + /// Whether the channel can be currently used for payments (in this one direction). pub enabled: bool, - /// The difference in CLTV values between the source and the destination node of the channel + /// The difference in CLTV values that you must have when routing through this channel. pub cltv_expiry_delta: u16, /// The minimum value, which must be relayed to the next hop via the channel pub htlc_minimum_msat: u64, + /// The maximum value which may be relayed to the next hop via the channel. + pub htlc_maximum_msat: Option, /// Fees charged when the channel is used for routing pub fees: RoutingFees, /// Most recent update for the channel received from the network - pub last_update_message: Option, + /// Mostly redundant with the data we store in fields explicitly. + /// Everything else is useful only for sending out for initial routing sync. + /// Not stored if contains excess data to prevent DoS. + pub last_update_message: Option, } -impl std::fmt::Display for DirectionalChannelInfo { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "src_node_id {}, last_update {}, enabled {}, cltv_expiry_delta {}, htlc_minimum_msat {}, fees {:?}", log_pubkey!(self.src_node_id), self.last_update, self.enabled, self.cltv_expiry_delta, self.htlc_minimum_msat, self.fees)?; +impl fmt::Display for DirectionalChannelInfo { + 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!(DirectionalChannelInfo, 0, { - src_node_id, last_update, enabled, cltv_expiry_delta, htlc_minimum_msat, + htlc_maximum_msat, fees, last_update_message }); #[derive(PartialEq)] -/// Details regarding a channel (both directions) +/// Details about a channel (both directions). +/// Received within a channel announcement. pub struct ChannelInfo { /// Protocol features of a channel communicated during its announcement pub features: ChannelFeatures, - /// Details regarding one of the directions of a channel - pub one_to_two: DirectionalChannelInfo, - /// Details regarding another direction of a channel - pub two_to_one: DirectionalChannelInfo, + /// Source node of the first direction of a channel + pub node_one: PublicKey, + /// Details about the first direction of a channel + pub one_to_two: Option, + /// Source node of the second direction of a channel + pub node_two: PublicKey, + /// Details about the second direction of a channel + 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 - //this is cached here so we can send out it later if required by initial routing sync - //keep an eye on this to see if the extra memory is a problem - pub announcement_message: Option, + /// Mostly redundant with the data we store in fields explicitly. + /// Everything else is useful only for sending out for initial routing sync. + /// Not stored if contains excess data to prevent DoS. + pub announcement_message: Option, } -impl std::fmt::Display for ChannelInfo { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "features: {}, one_to_two: {}, two_to_one: {}", log_bytes!(self.features.encode()), self.one_to_two, self.two_to_one)?; +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()), log_pubkey!(self.node_one), self.one_to_two, log_pubkey!(self.node_two), self.two_to_one)?; Ok(()) } } impl_writeable!(ChannelInfo, 0, { features, + node_one, one_to_two, + node_two, two_to_one, + capacity_sats, announcement_message }); @@ -307,9 +331,10 @@ impl_writeable!(ChannelInfo, 0, { /// Fees for routing via a given channel or a node #[derive(Eq, PartialEq, Copy, Clone, Debug)] pub struct RoutingFees { - /// Flat routing fee + /// Flat routing fee in satoshis pub base_msat: u32, - /// Liquidity-based routing fee + /// Liquidity-based routing fee in millionths of a routed amount. + /// In other words, 10000 is 1%. pub proportional_millionths: u32, } @@ -336,19 +361,23 @@ impl Writeable for RoutingFees { /// Information received in the latest node_announcement from this node. pub struct NodeAnnouncementInfo { /// Protocol features the node announced support for - pub features: NodeFeatures, - /// When the last known update to the node state was issued - pub last_update: u32, + pub features: NodeFeatures, + /// When the last known update to the node state was issued. + /// Value is opaque, as set in the announcement. + pub last_update: u32, /// Color assigned to the node pub rgb: [u8; 3], - /// Moniker assigned to the node + /// Moniker assigned to the node. + /// May be invalid or malicious (eg control chars), + /// should not be exposed to the user. pub alias: [u8; 32], /// Internet-level addresses via which one can connect to the node pub addresses: Vec, /// An initial announcement of the node - // this is cached here so we can send out it later if required by initial routing sync - // keep an eye on this to see if the extra memory is a problem - pub announcement_message: Option + /// Mostly redundant with the data we store in fields explicitly. + /// Everything else is useful only for sending out for initial routing sync. + /// Not stored if contains excess data to prevent DoS. + pub announcement_message: Option } impl Writeable for NodeAnnouncementInfo { @@ -395,19 +424,22 @@ impl Readable for NodeAnnouncementInfo { } #[derive(PartialEq)] -/// Details regarding a node in the network +/// Details about a node in the network, known from the network announcement. pub struct NodeInfo { /// All valid channels a node has announced pub channels: Vec, - /// Lowest fees enabling routing via any of the known channels to a node + /// Lowest fees enabling routing via any of the enabled, known channels to a node. + /// The two fields (flat and proportional fee) are independent, + /// meaning they don't have to refer to the same channel. pub lowest_inbound_channel_fees: Option, - /// More information about a node from node_announcement - /// Optional because we may have a NodeInfo entry before having received the announcement + /// More information about a node from node_announcement. + /// Optional because we store a Node entry after learning about it from + /// a channel announcement, but before receiving a node announcement. pub announcement_info: Option } -impl std::fmt::Display for NodeInfo { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { +impl fmt::Display for NodeInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "lowest_inbound_channel_fees: {:?}, channels: {:?}, announcement_info: {:?}", self.lowest_inbound_channel_fees, &self.channels[..], self.announcement_info)?; Ok(()) @@ -445,13 +477,6 @@ impl Readable for NodeInfo { } } -/// Represents the network as nodes and channels between them -#[derive(PartialEq)] -pub struct NetworkGraph { - channels: BTreeMap, - nodes: BTreeMap, -} - impl Writeable for NetworkGraph { fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { (self.channels.len() as u64).write(writer)?; @@ -491,8 +516,8 @@ impl Readable for NetworkGraph { } } -impl std::fmt::Display for NetworkGraph { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { +impl fmt::Display for NetworkGraph { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "Network map\n[Channels]\n")?; for (key, val) in self.channels.iter() { write!(f, " {}: {}\n", key, val)?; @@ -506,11 +531,37 @@ impl std::fmt::Display for NetworkGraph { } impl NetworkGraph { - /// Returns all known valid channels + /// Returns all known valid channels' short ids along with announced channel info. + /// + /// (C-not exported) because we have no mapping for `BTreeMap`s pub fn get_channels<'a>(&'a self) -> &'a BTreeMap { &self.channels } - /// Returns all known nodes + /// Returns all known nodes' public keys along with announced node info. + /// + /// (C-not exported) because we have no mapping for `BTreeMap`s pub fn get_nodes<'a>(&'a self) -> &'a BTreeMap { &self.nodes } + /// Get network addresses by node id. + /// Returns None if the requested node is completely unknown, + /// or if node announcement for the node was never received. + /// + /// (C-not exported) as there is no practical way to track lifetimes of returned values. + pub fn get_addresses<'a>(&'a self, pubkey: &PublicKey) -> Option<&'a Vec> { + if let Some(node) = self.nodes.get(pubkey) { + if let Some(node_info) = node.announcement_info.as_ref() { + return Some(&node_info.addresses) + } + } + None + } + + /// Creates a new, empty, network graph. + pub fn new() -> NetworkGraph { + Self { + channels: BTreeMap::new(), + nodes: BTreeMap::new(), + } + } + /// For an already known node (from channel announcements), update its stored properties from a given node announcement /// Announcement signatures are checked here only if Secp256k1 object is provided. fn update_node_from_announcement(&mut self, msg: &msgs::NodeAnnouncement, secp_ctx: Option<&Secp256k1>) -> Result { @@ -520,11 +571,11 @@ impl NetworkGraph { } match self.nodes.get_mut(&msg.contents.node_id) { - None => Err(LightningError{err: "No existing channels for node_announcement", action: ErrorAction::IgnoreError}), + None => 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() { if node_info.last_update >= msg.contents.timestamp { - return Err(LightningError{err: "Update older than last processed update", action: ErrorAction::IgnoreError}); + return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreError}); } } @@ -543,11 +594,13 @@ impl NetworkGraph { } } - /// For a new or already known (from previous announcement) channel, store or update channel info, - /// after making sure it corresponds to a real transaction on-chain. + /// For a new or already known (from previous announcement) channel, store or update channel info. /// Also store nodes (if not stored yet) the channel is between, and make node aware of this channel. + /// Checking utxo on-chain is useful if we receive an update for already known channel id, + /// which is probably result of a reorg. In that case, we update channel info only if the + /// utxo was checked, otherwise stick to the existing update, to prevent DoS risks. /// Announcement signatures are checked here only if Secp256k1 object is provided. - fn update_channel_from_announcement(&mut self, msg: &msgs::ChannelAnnouncement, checked_utxo: bool, secp_ctx: Option<&Secp256k1>) -> Result { + fn update_channel_from_announcement(&mut self, msg: &msgs::ChannelAnnouncement, utxo_value: Option, secp_ctx: Option<&Secp256k1>) -> Result { if let Some(sig_verifier) = secp_ctx { let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); secp_verify_sig!(sig_verifier, &msg_hash, &msg.node_signature_1, &msg.contents.node_id_1); @@ -560,30 +613,11 @@ impl NetworkGraph { let chan_info = ChannelInfo { features: msg.contents.features.clone(), - one_to_two: DirectionalChannelInfo { - src_node_id: msg.contents.node_id_1.clone(), - last_update: 0, - enabled: false, - cltv_expiry_delta: u16::max_value(), - htlc_minimum_msat: u64::max_value(), - fees: RoutingFees { - base_msat: u32::max_value(), - proportional_millionths: u32::max_value(), - }, - last_update_message: None, - }, - two_to_one: DirectionalChannelInfo { - src_node_id: msg.contents.node_id_2.clone(), - last_update: 0, - enabled: false, - cltv_expiry_delta: u16::max_value(), - htlc_minimum_msat: u64::max_value(), - fees: RoutingFees { - base_msat: u32::max_value(), - proportional_millionths: u32::max_value(), - }, - last_update_message: None, - }, + node_one: msg.contents.node_id_1.clone(), + one_to_two: None, + node_two: msg.contents.node_id_2.clone(), + two_to_one: None, + capacity_sats: utxo_value, announcement_message: if should_relay { Some(msg.clone()) } else { None }, }; @@ -592,7 +626,7 @@ impl NetworkGraph { //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 checked_utxo { + 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 @@ -604,7 +638,7 @@ impl NetworkGraph { Self::remove_channel_in_nodes(&mut self.nodes, &entry.get(), msg.contents.short_channel_id); *entry.get_mut() = chan_info; } else { - return Err(LightningError{err: "Already have knowledge of channel", action: ErrorAction::IgnoreError}) + return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreError}) } }, BtreeEntry::Vacant(entry) => { @@ -639,28 +673,32 @@ impl NetworkGraph { /// If permanent, removes a channel from the local storage. /// May cause the removal of nodes too, if this was their last channel. /// If not permanent, makes channels unavailable for routing. - pub fn close_channel_from_update(&mut self, short_channel_id: &u64, is_permanent: &bool) { - if *is_permanent { - if let Some(chan) = self.channels.remove(short_channel_id) { - Self::remove_channel_in_nodes(&mut self.nodes, &chan, *short_channel_id); + pub fn close_channel_from_update(&mut self, short_channel_id: u64, is_permanent: bool) { + if is_permanent { + if let Some(chan) = self.channels.remove(&short_channel_id) { + Self::remove_channel_in_nodes(&mut self.nodes, &chan, short_channel_id); } } else { if let Some(chan) = self.channels.get_mut(&short_channel_id) { - chan.one_to_two.enabled = false; - chan.two_to_one.enabled = false; + if let Some(one_to_two) = chan.one_to_two.as_mut() { + one_to_two.enabled = false; + } + if let Some(two_to_one) = chan.two_to_one.as_mut() { + two_to_one.enabled = false; + } } } } - fn fail_node(&mut self, _node_id: &PublicKey, is_permanent: &bool) { - if *is_permanent { + fn fail_node(&mut self, _node_id: &PublicKey, is_permanent: bool) { + if is_permanent { // TODO: Wholly remove the node } else { // TODO: downgrade the node } } - /// For an already known (from announcement) channel, update info regarding one of the directions of a channel. + /// For an already known (from announcement) channel, update info about one of the directions of a channel. /// Announcement signatures are checked here only if Secp256k1 object is provided. fn update_channel(&mut self, msg: &msgs::ChannelUpdate, secp_ctx: Option<&Secp256k1>) -> Result { let dest_node_id; @@ -668,40 +706,67 @@ impl NetworkGraph { let chan_was_enabled; match self.channels.get_mut(&msg.contents.short_channel_id) { - None => return Err(LightningError{err: "Couldn't find channel for update", action: ErrorAction::IgnoreError}), + None => return Err(LightningError{err: "Couldn't find channel for update".to_owned(), action: ErrorAction::IgnoreError}), Some(channel) => { + if let OptionalField::Present(htlc_maximum_msat) = msg.contents.htlc_maximum_msat { + if htlc_maximum_msat > MAX_VALUE_MSAT { + return Err(LightningError{err: "htlc_maximum_msat is larger than maximum possible msats".to_owned(), action: ErrorAction::IgnoreError}); + } + + 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 htlc_maximum_msat > capacity_sats * 1000 { + return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity".to_owned(), action: ErrorAction::IgnoreError}); + } + } + } macro_rules! maybe_update_channel_info { - ( $target: expr) => { - if $target.last_update >= msg.contents.timestamp { - return Err(LightningError{err: "Update older than last processed update", action: ErrorAction::IgnoreError}); + ( $target: expr, $src_node: expr) => { + if let Some(existing_chan_info) = $target.as_ref() { + if existing_chan_info.last_update >= msg.contents.timestamp { + return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreError}); + } + chan_was_enabled = existing_chan_info.enabled; + } else { + chan_was_enabled = false; } - chan_was_enabled = $target.enabled; - $target.last_update = msg.contents.timestamp; - $target.enabled = chan_enabled; - $target.cltv_expiry_delta = msg.contents.cltv_expiry_delta; - $target.htlc_minimum_msat = msg.contents.htlc_minimum_msat; - $target.fees.base_msat = msg.contents.fee_base_msat; - $target.fees.proportional_millionths = msg.contents.fee_proportional_millionths; - $target.last_update_message = if msg.contents.excess_data.is_empty() { + + let last_update_message = if msg.contents.excess_data.is_empty() { Some(msg.clone()) } else { None }; + + let updated_channel_dir_info = DirectionalChannelInfo { + enabled: chan_enabled, + last_update: msg.contents.timestamp, + cltv_expiry_delta: msg.contents.cltv_expiry_delta, + htlc_minimum_msat: msg.contents.htlc_minimum_msat, + htlc_maximum_msat: if let OptionalField::Present(max_value) = msg.contents.htlc_maximum_msat { Some(max_value) } else { None }, + fees: RoutingFees { + base_msat: msg.contents.fee_base_msat, + proportional_millionths: msg.contents.fee_proportional_millionths, + }, + last_update_message + }; + $target = Some(updated_channel_dir_info); } } + let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); if msg.contents.flags & 1 == 1 { - dest_node_id = channel.one_to_two.src_node_id.clone(); + dest_node_id = channel.node_one.clone(); if let Some(sig_verifier) = secp_ctx { - secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &channel.two_to_one.src_node_id); + secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &channel.node_two); } - maybe_update_channel_info!(channel.two_to_one); + maybe_update_channel_info!(channel.two_to_one, channel.node_two); } else { - dest_node_id = channel.two_to_one.src_node_id.clone(); + dest_node_id = channel.node_two.clone(); if let Some(sig_verifier) = secp_ctx { - secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &channel.one_to_two.src_node_id); + secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &channel.node_one); } - maybe_update_channel_info!(channel.one_to_two); + maybe_update_channel_info!(channel.one_to_two, channel.node_one); } } } @@ -719,32 +784,28 @@ impl NetworkGraph { proportional_millionths }); } else if chan_was_enabled { - let mut lowest_inbound_channel_fee_base_msat = u32::max_value(); - let mut lowest_inbound_channel_fee_proportional_millionths = u32::max_value(); - - { - let node = self.nodes.get(&dest_node_id).unwrap(); - - for chan_id in node.channels.iter() { - let chan = self.channels.get(chan_id).unwrap(); - if chan.one_to_two.src_node_id == dest_node_id { - lowest_inbound_channel_fee_base_msat = cmp::min(lowest_inbound_channel_fee_base_msat, chan.two_to_one.fees.base_msat); - lowest_inbound_channel_fee_proportional_millionths = cmp::min(lowest_inbound_channel_fee_proportional_millionths, chan.two_to_one.fees.proportional_millionths); - } else { - lowest_inbound_channel_fee_base_msat = cmp::min(lowest_inbound_channel_fee_base_msat, chan.one_to_two.fees.base_msat); - lowest_inbound_channel_fee_proportional_millionths = cmp::min(lowest_inbound_channel_fee_proportional_millionths, chan.one_to_two.fees.proportional_millionths); + let node = self.nodes.get_mut(&dest_node_id).unwrap(); + let mut lowest_inbound_channel_fees = None; + + for chan_id in node.channels.iter() { + let chan = self.channels.get(chan_id).unwrap(); + let chan_info_opt; + if chan.node_one == dest_node_id { + chan_info_opt = chan.two_to_one.as_ref(); + } else { + chan_info_opt = chan.one_to_two.as_ref(); + } + if let Some(chan_info) = chan_info_opt { + if chan_info.enabled { + let fees = lowest_inbound_channel_fees.get_or_insert(RoutingFees { + base_msat: u32::max_value(), proportional_millionths: u32::max_value() }); + fees.base_msat = cmp::min(fees.base_msat, chan_info.fees.base_msat); + fees.proportional_millionths = cmp::min(fees.proportional_millionths, chan_info.fees.proportional_millionths); } } } - //TODO: satisfy the borrow-checker without a double-map-lookup :( - let mut_node = self.nodes.get_mut(&dest_node_id).unwrap(); - if mut_node.channels.len() > 0 { - mut_node.lowest_inbound_channel_fees = Some(RoutingFees { - base_msat: lowest_inbound_channel_fee_base_msat, - proportional_millionths: lowest_inbound_channel_fee_proportional_millionths - }); - } + node.lowest_inbound_channel_fees = lowest_inbound_channel_fees; } Ok(msg.contents.excess_data.is_empty()) @@ -765,18 +826,20 @@ impl NetworkGraph { } } } - remove_from_node!(chan.one_to_two.src_node_id); - remove_from_node!(chan.two_to_one.src_node_id); + + remove_from_node!(chan.node_one); + remove_from_node!(chan.node_two); } } #[cfg(test)] mod tests { - use chain::chaininterface; + use chain; use ln::features::{ChannelFeatures, NodeFeatures}; use routing::network_graph::{NetGraphMsgHandler, NetworkGraph}; - use ln::msgs::{RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, - UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, HTLCFailChannelUpdate}; + use ln::msgs::{OptionalField, RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, + UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, HTLCFailChannelUpdate, + MAX_VALUE_MSAT}; use util::test_utils; use util::logger::Logger; use util::ser::{Readable, Writeable}; @@ -786,8 +849,8 @@ mod tests { use bitcoin::network::constants::Network; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::script::Builder; + use bitcoin::blockdata::transaction::TxOut; use bitcoin::blockdata::opcodes; - use bitcoin::util::hash::BitcoinHash; use hex; @@ -796,11 +859,10 @@ mod tests { use std::sync::Arc; - fn create_net_graph_msg_handler() -> (Secp256k1, NetGraphMsgHandler) { + fn create_net_graph_msg_handler() -> (Secp256k1, NetGraphMsgHandler, Arc>) { let secp_ctx = Secp256k1::new(); - let logger: Arc = Arc::new(test_utils::TestLogger::new()); - let chain_monitor = Arc::new(chaininterface::ChainWatchInterfaceUtil::new(Network::Testnet, Arc::clone(&logger))); - let net_graph_msg_handler = NetGraphMsgHandler::new(chain_monitor, Arc::clone(&logger)); + let logger = Arc::new(test_utils::TestLogger::new()); + let net_graph_msg_handler = NetGraphMsgHandler::new(None, Arc::clone(&logger)); (secp_ctx, net_graph_msg_handler) } @@ -855,7 +917,7 @@ mod tests { // Announce a channel to add a corresponding node. let unsigned_announcement = UnsignedChannelAnnouncement { features: ChannelFeatures::known(), - chain_hash: genesis_block(Network::Testnet).header.bitcoin_hash(), + chain_hash: genesis_block(Network::Testnet).header.block_hash(), short_channel_id: 0, node_id_1, node_id_2, @@ -925,9 +987,6 @@ mod tests { fn handling_channel_announcements() { let secp_ctx = Secp256k1::new(); let logger: Arc = Arc::new(test_utils::TestLogger::new()); - let chain_monitor = Arc::new(test_utils::TestChainWatcher::new()); - let net_graph_msg_handler = NetGraphMsgHandler::new(chain_monitor.clone(), Arc::clone(&logger)); - let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap(); let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); @@ -945,7 +1004,7 @@ mod tests { let mut unsigned_announcement = UnsignedChannelAnnouncement { features: ChannelFeatures::known(), - chain_hash: genesis_block(Network::Testnet).header.bitcoin_hash(), + chain_hash: genesis_block(Network::Testnet).header.block_hash(), short_channel_id: 0, node_id_1, node_id_2, @@ -964,8 +1023,7 @@ mod tests { }; // Test if the UTXO lookups were not supported - *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::NotSupported); - + let mut net_graph_msg_handler = NetGraphMsgHandler::new(None, Arc::clone(&logger)); match net_graph_msg_handler.handle_channel_announcement(&valid_announcement) { Ok(res) => assert!(res), _ => panic!() @@ -979,7 +1037,6 @@ mod tests { } } - // If we receive announcement for the same channel (with UTXO lookups disabled), // drop new one on the floor, since we can't see any changes. match net_graph_msg_handler.handle_channel_announcement(&valid_announcement) { @@ -987,9 +1044,10 @@ mod tests { Err(e) => assert_eq!(e.err, "Already have knowledge of channel") }; - // Test if an associated transaction were not on-chain (or not confirmed). - *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::UnknownTx); + let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet)); + *chain_source.utxo_ret.lock().unwrap() = Err(chain::AccessError::UnknownTx); + net_graph_msg_handler = NetGraphMsgHandler::new(Some(chain_source.clone()), Arc::clone(&logger)); unsigned_announcement.short_channel_id += 1; msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); @@ -1006,10 +1064,9 @@ mod tests { 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. unsigned_announcement.short_channel_id += 1; - *chain_monitor.utxo_ret.lock().unwrap() = Ok((good_script.clone(), 0)); + *chain_source.utxo_ret.lock().unwrap() = Ok(TxOut { value: 0, script_pubkey: good_script.clone() }); msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); let valid_announcement = ChannelAnnouncement { @@ -1034,14 +1091,14 @@ mod tests { // If we receive announcement for the same channel (but TX is not confirmed), // drop new one on the floor, since we can't see any changes. - *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::UnknownTx); + *chain_source.utxo_ret.lock().unwrap() = Err(chain::AccessError::UnknownTx); match net_graph_msg_handler.handle_channel_announcement(&valid_announcement) { Ok(_) => panic!(), Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry") }; // But if it is confirmed, replace the channel - *chain_monitor.utxo_ret.lock().unwrap() = Ok((good_script, 0)); + *chain_source.utxo_ret.lock().unwrap() = Ok(TxOut { value: 0, script_pubkey: good_script }); unsigned_announcement.features = ChannelFeatures::empty(); msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); let valid_announcement = ChannelAnnouncement { @@ -1111,7 +1168,11 @@ mod tests { #[test] fn handling_channel_update() { - let (secp_ctx, net_graph_msg_handler) = create_net_graph_msg_handler(); + let secp_ctx = Secp256k1::new(); + let logger: Arc = Arc::new(test_utils::TestLogger::new()); + let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet)); + let net_graph_msg_handler = NetGraphMsgHandler::new(Some(chain_source.clone()), Arc::clone(&logger)); + let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap(); let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); let node_id_1 = PublicKey::from_secret_key(&secp_ctx, node_1_privkey); @@ -1121,9 +1182,17 @@ mod tests { let zero_hash = Sha256dHash::hash(&[0; 32]); let short_channel_id = 0; - let chain_hash = genesis_block(Network::Testnet).header.bitcoin_hash(); + let chain_hash = genesis_block(Network::Testnet).header.block_hash(); + let amount_sats = 1000_000; + { // Announce a channel we will update + let good_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, node_1_btckey).serialize()) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, node_2_btckey).serialize()) + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh(); + *chain_source.utxo_ret.lock().unwrap() = Ok(TxOut { value: amount_sats, script_pubkey: good_script.clone() }); let unsigned_announcement = UnsignedChannelAnnouncement { features: ChannelFeatures::empty(), chain_hash, @@ -1157,6 +1226,7 @@ mod tests { flags: 0, cltv_expiry_delta: 144, htlc_minimum_msat: 1000000, + htlc_maximum_msat: OptionalField::Absent, fee_base_msat: 10000, fee_proportional_millionths: 20, excess_data: Vec::new() @@ -1177,8 +1247,8 @@ mod tests { match network.get_channels().get(&short_channel_id) { None => panic!(), Some(channel_info) => { - assert_eq!(channel_info.one_to_two.cltv_expiry_delta, 144); - assert_eq!(channel_info.two_to_one.cltv_expiry_delta, u16::max_value()); + assert_eq!(channel_info.one_to_two.as_ref().unwrap().cltv_expiry_delta, 144); + assert!(channel_info.two_to_one.is_none()); } } } @@ -1195,6 +1265,7 @@ mod tests { Ok(res) => assert!(!res), _ => panic!() }; + unsigned_channel_update.timestamp += 10; unsigned_channel_update.short_channel_id += 1; let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); @@ -1209,6 +1280,31 @@ mod tests { }; unsigned_channel_update.short_channel_id = short_channel_id; + unsigned_channel_update.htlc_maximum_msat = OptionalField::Present(MAX_VALUE_MSAT + 1); + let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); + let valid_channel_update = ChannelUpdate { + signature: secp_ctx.sign(&msghash, node_1_privkey), + contents: unsigned_channel_update.clone() + }; + + match net_graph_msg_handler.handle_channel_update(&valid_channel_update) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than maximum possible msats") + }; + unsigned_channel_update.htlc_maximum_msat = OptionalField::Absent; + + unsigned_channel_update.htlc_maximum_msat = OptionalField::Present(amount_sats * 1000 + 1); + let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); + let valid_channel_update = ChannelUpdate { + signature: secp_ctx.sign(&msghash, node_1_privkey), + contents: unsigned_channel_update.clone() + }; + + match net_graph_msg_handler.handle_channel_update(&valid_channel_update) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity") + }; + unsigned_channel_update.htlc_maximum_msat = OptionalField::Absent; // Even though previous update was not relayed further, we still accepted it, // so we now won't accept update before the previous one. @@ -1249,7 +1345,7 @@ mod tests { let node_2_btckey = &SecretKey::from_slice(&[39; 32]).unwrap(); let short_channel_id = 0; - let chain_hash = genesis_block(Network::Testnet).header.bitcoin_hash(); + let chain_hash = genesis_block(Network::Testnet).header.block_hash(); { // There is no nodes in the table at the beginning. @@ -1283,6 +1379,39 @@ mod tests { Err(_) => panic!() }; + let unsigned_channel_update = UnsignedChannelUpdate { + chain_hash, + short_channel_id, + timestamp: 100, + flags: 0, + cltv_expiry_delta: 144, + htlc_minimum_msat: 1000000, + htlc_maximum_msat: OptionalField::Absent, + fee_base_msat: 10000, + fee_proportional_millionths: 20, + excess_data: Vec::new() + }; + let msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_channel_update.encode()[..])[..]); + let valid_channel_update = ChannelUpdate { + signature: secp_ctx.sign(&msghash, node_1_privkey), + contents: unsigned_channel_update.clone() + }; + + match net_graph_msg_handler.handle_channel_update(&valid_channel_update) { + Ok(res) => assert!(res), + _ => panic!() + }; + } + + // Non-permanent closing just disables a channel + { + let network = net_graph_msg_handler.network_graph.read().unwrap(); + match network.get_channels().get(&short_channel_id) { + None => panic!(), + Some(channel_info) => { + assert!(channel_info.one_to_two.is_some()); + } + } } let channel_close_msg = HTLCFailChannelUpdate::ChannelClosed { @@ -1298,8 +1427,7 @@ mod tests { match network.get_channels().get(&short_channel_id) { None => panic!(), Some(channel_info) => { - assert!(!channel_info.one_to_two.enabled); - assert!(!channel_info.two_to_one.enabled); + assert!(!channel_info.one_to_two.as_ref().unwrap().enabled); } } } @@ -1332,7 +1460,7 @@ mod tests { let node_2_btckey = &SecretKey::from_slice(&[39; 32]).unwrap(); let short_channel_id = 1; - let chain_hash = genesis_block(Network::Testnet).header.bitcoin_hash(); + let chain_hash = genesis_block(Network::Testnet).header.block_hash(); // Channels were not announced yet. let channels_with_announcements = net_graph_msg_handler.get_next_channel_announcements(0, 1); @@ -1386,6 +1514,7 @@ mod tests { flags: 0, cltv_expiry_delta: 144, htlc_minimum_msat: 1000000, + htlc_maximum_msat: OptionalField::Absent, fee_base_msat: 10000, fee_proportional_millionths: 20, excess_data: Vec::new() @@ -1422,6 +1551,7 @@ mod tests { flags: 0, cltv_expiry_delta: 144, htlc_minimum_msat: 1000000, + htlc_maximum_msat: OptionalField::Absent, fee_base_msat: 10000, fee_proportional_millionths: 20, excess_data: [1; 3].to_vec() @@ -1464,7 +1594,7 @@ mod tests { let node_2_btckey = &SecretKey::from_slice(&[39; 32]).unwrap(); let short_channel_id = 1; - let chain_hash = genesis_block(Network::Testnet).header.bitcoin_hash(); + let chain_hash = genesis_block(Network::Testnet).header.block_hash(); // No nodes yet. let next_announcements = net_graph_msg_handler.get_next_node_announcements(None, 10); @@ -1584,7 +1714,7 @@ mod tests { let node_id_2 = PublicKey::from_secret_key(&secp_ctx, node_2_privkey); let unsigned_announcement = UnsignedChannelAnnouncement { features: ChannelFeatures::known(), - chain_hash: genesis_block(Network::Testnet).header.bitcoin_hash(), + chain_hash: genesis_block(Network::Testnet).header.block_hash(), short_channel_id: 0, node_id_1, node_id_2,