X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Frouter.rs;h=592e3cdb3e902ced6b5cb4c12070b6511791bd6e;hb=9ed4f521f3950980a1a4680b708576890e8c0bcf;hp=5686e5a3bf7b333f4458d384b3a4ad5b7f2cd3ca;hpb=53052234959a3c0a7323a194c351c4ba9b46339b;p=rust-lightning diff --git a/lightning/src/ln/router.rs b/lightning/src/ln/router.rs index 5686e5a3..592e3cdb 100644 --- a/lightning/src/ln/router.rs +++ b/lightning/src/ln/router.rs @@ -14,7 +14,8 @@ use bitcoin::blockdata::opcodes; use chain::chaininterface::{ChainError, ChainWatchInterface}; use ln::channelmanager; -use ln::msgs::{DecodeError,ErrorAction,LightningError,RoutingMessageHandler,NetAddress,GlobalFeatures}; +use ln::features::{ChannelFeatures, NodeFeatures}; +use ln::msgs::{DecodeError,ErrorAction,LightningError,RoutingMessageHandler,NetAddress}; use ln::msgs; use util::ser::{Writeable, Readable, Writer, ReadableArgs}; use util::logger::Logger; @@ -30,8 +31,14 @@ use std; pub struct RouteHop { /// The node_id of the node at this hop. pub pubkey: PublicKey, + /// The node_announcement features of the node at this hop. For the last hop, these may be + /// amended to match the features present in the invoice this node generated. + pub node_features: NodeFeatures, /// The channel that should be used from the previous hop to reach this node. pub short_channel_id: u64, + /// The channel_announcement features of the channel that should be used from the previous hop + /// to reach this node. + pub channel_features: ChannelFeatures, /// The fee taken on this hop. For the last hop, this should be the full value of the payment. pub fee_msat: u64, /// The CLTV delta added for this hop. For the last hop, this should be the full CLTV value @@ -39,21 +46,14 @@ pub struct RouteHop { pub cltv_expiry_delta: u32, } -/// A route from us through the network to a destination -#[derive(Clone, PartialEq)] -pub struct Route { - /// The list of hops, NOT INCLUDING our own, where the last hop is the destination. Thus, this - /// must always be at least length one. By protocol rules, this may not currently exceed 20 in - /// length. - pub hops: Vec, -} - -impl Writeable for Route { +impl Writeable for Vec { fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { - (self.hops.len() as u8).write(writer)?; - for hop in self.hops.iter() { + (self.len() as u8).write(writer)?; + for hop in self.iter() { hop.pubkey.write(writer)?; + hop.node_features.write(writer)?; hop.short_channel_id.write(writer)?; + hop.channel_features.write(writer)?; hop.fee_msat.write(writer)?; hop.cltv_expiry_delta.write(writer)?; } @@ -61,21 +61,54 @@ impl Writeable for Route { } } -impl Readable for Route { - fn read(reader: &mut R) -> Result { +impl Readable for Vec { + fn read(reader: &mut R) -> Result, DecodeError> { let hops_count: u8 = Readable::read(reader)?; let mut hops = Vec::with_capacity(hops_count as usize); for _ in 0..hops_count { hops.push(RouteHop { pubkey: Readable::read(reader)?, + node_features: Readable::read(reader)?, short_channel_id: Readable::read(reader)?, + channel_features: Readable::read(reader)?, fee_msat: Readable::read(reader)?, cltv_expiry_delta: Readable::read(reader)?, }); } - Ok(Route { - hops - }) + Ok(hops) + } +} + +/// A route from us through the network to a destination +#[derive(Clone, PartialEq)] +pub struct Route { + /// The list of routes taken for a single (potentially-multi-)path payment. The pubkey of the + /// last RouteHop in each path must be the same. + /// Each entry represents a list of hops, NOT INCLUDING our own, where the last hop is the + /// destination. Thus, this must always be at least length one. While the maximum length of any + /// given path is variable, keeping the length of any path to less than 20 should currently + /// ensure it is viable. + pub paths: Vec>, +} + +impl Writeable for Route { + fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + (self.paths.len() as u64).write(writer)?; + for hops in self.paths.iter() { + hops.write(writer)?; + } + Ok(()) + } +} + +impl Readable for Route { + fn read(reader: &mut R) -> Result { + let path_count: u64 = Readable::read(reader)?; + let mut paths = Vec::with_capacity(cmp::min(path_count, 128) as usize); + for _ in 0..path_count { + paths.push(Readable::read(reader)?); + } + Ok(Route { paths }) } } @@ -111,7 +144,7 @@ impl_writeable!(DirectionalChannelInfo, 0, { #[derive(PartialEq)] struct ChannelInfo { - features: GlobalFeatures, + features: ChannelFeatures, one_to_two: DirectionalChannelInfo, two_to_one: DirectionalChannelInfo, //this is cached here so we can send out it later if required by route_init_sync @@ -143,8 +176,8 @@ struct NodeInfo { lowest_inbound_channel_fee_base_msat: u32, lowest_inbound_channel_fee_proportional_millionths: u32, - features: GlobalFeatures, - last_update: u32, + features: NodeFeatures, + last_update: Option, rgb: [u8; 3], alias: [u8; 32], addresses: Vec, @@ -155,7 +188,7 @@ struct NodeInfo { impl std::fmt::Display for NodeInfo { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "features: {}, last_update: {}, lowest_inbound_channel_fee_base_msat: {}, lowest_inbound_channel_fee_proportional_millionths: {}, channels: {:?}", log_bytes!(self.features.encode()), self.last_update, self.lowest_inbound_channel_fee_base_msat, self.lowest_inbound_channel_fee_proportional_millionths, &self.channels[..])?; + write!(f, "features: {}, last_update: {:?}, lowest_inbound_channel_fee_base_msat: {}, lowest_inbound_channel_fee_proportional_millionths: {}, channels: {:?}", log_bytes!(self.features.encode()), self.last_update, self.lowest_inbound_channel_fee_base_msat, self.lowest_inbound_channel_fee_proportional_millionths, &self.channels[..])?; Ok(()) } } @@ -274,21 +307,6 @@ impl Readable for NetworkMap { } } -struct MutNetworkMap<'a> { - #[cfg(feature = "non_bitcoin_chain_hash_routing")] - channels: &'a mut BTreeMap<(u64, Sha256dHash), ChannelInfo>, - #[cfg(not(feature = "non_bitcoin_chain_hash_routing"))] - channels: &'a mut BTreeMap, - nodes: &'a mut BTreeMap, -} -impl NetworkMap { - fn borrow_parts(&mut self) -> MutNetworkMap { - MutNetworkMap { - channels: &mut self.channels, - nodes: &mut self.nodes, - } - } -} impl std::fmt::Display for NetworkMap { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "Node id {} network map\n[Channels]\n", log_pubkey!(self.our_node_id))?; @@ -414,25 +432,24 @@ impl RoutingMessageHandler for Router { let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.signature, &msg.contents.node_id); - if msg.contents.features.requires_unknown_bits() { - panic!("Unknown-required-features NodeAnnouncements should never deserialize!"); - } - let mut network = self.network_map.write().unwrap(); match network.nodes.get_mut(&msg.contents.node_id) { None => Err(LightningError{err: "No existing channels for node_announcement", action: ErrorAction::IgnoreError}), Some(node) => { - if node.last_update >= msg.contents.timestamp { - return Err(LightningError{err: "Update older than last processed update", action: ErrorAction::IgnoreError}); + match node.last_update { + Some(last_update) => if last_update >= msg.contents.timestamp { + return Err(LightningError{err: "Update older than last processed update", action: ErrorAction::IgnoreError}); + }, + None => {}, } node.features = msg.contents.features.clone(); - node.last_update = msg.contents.timestamp; + node.last_update = Some(msg.contents.timestamp); node.rgb = msg.contents.rgb; node.alias = msg.contents.alias; node.addresses = msg.contents.addresses.clone(); - let should_relay = msg.contents.excess_data.is_empty() && msg.contents.excess_address_data.is_empty() && !msg.contents.features.supports_unknown_bits(); + let should_relay = msg.contents.excess_data.is_empty() && msg.contents.excess_address_data.is_empty(); node.announcement_message = if should_relay { Some(msg.clone()) } else { None }; Ok(should_relay) } @@ -450,10 +467,6 @@ impl RoutingMessageHandler for Router { secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &msg.contents.bitcoin_key_1); secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &msg.contents.bitcoin_key_2); - if msg.contents.features.requires_unknown_bits() { - panic!("Unknown-required-features ChannelAnnouncements should never deserialize!"); - } - 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) @@ -481,9 +494,9 @@ impl RoutingMessageHandler for Router { }; let mut network_lock = self.network_map.write().unwrap(); - let network = network_lock.borrow_parts(); + let network = &mut *network_lock; - let should_relay = msg.contents.excess_data.is_empty() && !msg.contents.features.supports_unknown_bits(); + let should_relay = msg.contents.excess_data.is_empty(); let chan_info = ChannelInfo { features: msg.contents.features.clone(), @@ -524,7 +537,7 @@ impl RoutingMessageHandler for Router { // 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(network.nodes, &entry.get(), msg.contents.short_channel_id); + Self::remove_channel_in_nodes(&mut network.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}) @@ -546,8 +559,8 @@ impl RoutingMessageHandler for Router { channels: vec!(NetworkMap::get_key(msg.contents.short_channel_id, msg.contents.chain_hash)), lowest_inbound_channel_fee_base_msat: u32::max_value(), lowest_inbound_channel_fee_proportional_millionths: u32::max_value(), - features: GlobalFeatures::new(), - last_update: 0, + features: NodeFeatures::empty(), + last_update: None, rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), @@ -748,8 +761,8 @@ impl Router { channels: Vec::new(), lowest_inbound_channel_fee_base_msat: u32::max_value(), lowest_inbound_channel_fee_proportional_millionths: u32::max_value(), - features: GlobalFeatures::new(), - last_update: 0, + features: NodeFeatures::empty(), + last_update: None, rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), @@ -860,15 +873,17 @@ impl Router { let short_channel_id = chan.short_channel_id.expect("first_hops should be filled in with usable channels, not pending ones"); if chan.remote_network_id == *target { return Ok(Route { - hops: vec![RouteHop { + paths: vec![vec![RouteHop { pubkey: chan.remote_network_id, + node_features: NodeFeatures::with_known_relevant_init_flags(&chan.counterparty_features), short_channel_id, + channel_features: ChannelFeatures::with_known_relevant_init_flags(&chan.counterparty_features), fee_msat: final_value_msat, cltv_expiry_delta: final_cltv, - }], + }]], }); } - first_hop_targets.insert(chan.remote_network_id, short_channel_id); + first_hop_targets.insert(chan.remote_network_id, (short_channel_id, chan.counterparty_features.clone())); } if first_hop_targets.is_empty() { return Err(LightningError{err: "Cannot route when there are no outbound routes away from us", action: ErrorAction::IgnoreError}); @@ -879,7 +894,7 @@ impl Router { // Adds entry which goes from the node pointed to by $directional_info to // $dest_node_id over the channel with id $chan_id with fees described in // $directional_info. - ( $chan_id: expr, $dest_node_id: expr, $directional_info: expr, $starting_fee_msat: expr ) => { + ( $chan_id: expr, $dest_node_id: expr, $directional_info: expr, $chan_features: expr, $starting_fee_msat: expr ) => { //TODO: Explore simply adding fee to hit htlc_minimum_msat if $starting_fee_msat as u64 + final_value_msat >= $directional_info.htlc_minimum_msat { let proportional_fee_millions = ($starting_fee_msat + final_value_msat).checked_mul($directional_info.fee_proportional_millionths as u64); @@ -895,7 +910,9 @@ impl Router { node.lowest_inbound_channel_fee_proportional_millionths, RouteHop { pubkey: $dest_node_id.clone(), + node_features: NodeFeatures::empty(), short_channel_id: 0, + channel_features: $chan_features.clone(), fee_msat: 0, cltv_expiry_delta: 0, }) @@ -921,7 +938,9 @@ impl Router { old_entry.0 = total_fee; old_entry.3 = RouteHop { pubkey: $dest_node_id.clone(), + node_features: NodeFeatures::empty(), short_channel_id: $chan_id.clone(), + channel_features: $chan_features.clone(), fee_msat: new_fee, // This field is ignored on the last-hop anyway cltv_expiry_delta: $directional_info.cltv_expiry_delta as u32, } @@ -934,24 +953,28 @@ impl Router { macro_rules! add_entries_to_cheapest_to_target_node { ( $node: expr, $node_id: expr, $fee_to_target_msat: expr ) => { if first_hops.is_some() { - if let Some(first_hop) = first_hop_targets.get(&$node_id) { - add_entry!(first_hop, $node_id, dummy_directional_info, $fee_to_target_msat); + if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&$node_id) { + add_entry!(first_hop, $node_id, dummy_directional_info, ChannelFeatures::with_known_relevant_init_flags(&features), $fee_to_target_msat); } } - for chan_id in $node.channels.iter() { - let chan = network.channels.get(chan_id).unwrap(); - if chan.one_to_two.src_node_id == *$node_id { - // ie $node is one, ie next hop in A* is two, via the two_to_one channel - if first_hops.is_none() || chan.two_to_one.src_node_id != network.our_node_id { - if chan.two_to_one.enabled { - add_entry!(chan_id, chan.one_to_two.src_node_id, chan.two_to_one, $fee_to_target_msat); - } - } - } else { - if first_hops.is_none() || chan.one_to_two.src_node_id != network.our_node_id { - if chan.one_to_two.enabled { - add_entry!(chan_id, chan.two_to_one.src_node_id, chan.one_to_two, $fee_to_target_msat); + if !$node.features.requires_unknown_bits() { + for chan_id in $node.channels.iter() { + let chan = network.channels.get(chan_id).unwrap(); + if !chan.features.requires_unknown_bits() { + if chan.one_to_two.src_node_id == *$node_id { + // ie $node is one, ie next hop in A* is two, via the two_to_one channel + if first_hops.is_none() || chan.two_to_one.src_node_id != network.our_node_id { + if chan.two_to_one.enabled { + add_entry!(chan_id, chan.one_to_two.src_node_id, chan.two_to_one, chan.features, $fee_to_target_msat); + } + } + } else { + if first_hops.is_none() || chan.one_to_two.src_node_id != network.our_node_id { + if chan.one_to_two.enabled { + add_entry!(chan_id, chan.two_to_one.src_node_id, chan.one_to_two, chan.features, $fee_to_target_msat); + } + } } } } @@ -970,11 +993,17 @@ impl Router { if first_hops.is_none() || hop.src_node_id != network.our_node_id { // first_hop overrules last_hops if network.nodes.get(&hop.src_node_id).is_some() { if first_hops.is_some() { - if let Some(first_hop) = first_hop_targets.get(&hop.src_node_id) { - add_entry!(first_hop, hop.src_node_id, dummy_directional_info, 0); + if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&hop.src_node_id) { + // Currently there are no channel-context features defined, so we are a + // bit lazy here. In the future, we should pull them out via our + // ChannelManager, but there's no reason to waste the space until we + // need them. + add_entry!(first_hop, hop.src_node_id, dummy_directional_info, ChannelFeatures::with_known_relevant_init_flags(&features), 0); } } - add_entry!(hop.short_channel_id, target, hop, 0); + // BOLT 11 doesn't allow inclusion of features for the last hop hints, which + // really sucks, cause we're gonna need that eventually. + add_entry!(hop.short_channel_id, target, hop, ChannelFeatures::empty(), 0); } } } @@ -982,7 +1011,22 @@ impl Router { while let Some(RouteGraphNode { pubkey, lowest_fee_to_node, .. }) = targets.pop() { if pubkey == network.our_node_id { let mut res = vec!(dist.remove(&network.our_node_id).unwrap().3); - while res.last().unwrap().pubkey != *target { + loop { + if let Some(&(_, ref features)) = first_hop_targets.get(&res.last().unwrap().pubkey) { + res.last_mut().unwrap().node_features = NodeFeatures::with_known_relevant_init_flags(&features); + } else if let Some(node) = network.nodes.get(&res.last().unwrap().pubkey) { + res.last_mut().unwrap().node_features = node.features.clone(); + } else { + // We should be able to fill in features for everything except the last + // hop, if the last hop was provided via a BOLT 11 invoice (though we + // should be able to extend it further as BOLT 11 does have feature + // flags for the last hop node itself). + assert!(res.last().unwrap().pubkey == *target); + } + if res.last().unwrap().pubkey == *target { + break; + } + let new_entry = match dist.remove(&res.last().unwrap().pubkey) { Some(hop) => hop.3, None => return Err(LightningError{err: "Failed to find a non-fee-overflowing path to the given destination", action: ErrorAction::IgnoreError}), @@ -993,7 +1037,7 @@ impl Router { } res.last_mut().unwrap().fee_msat = final_value_msat; res.last_mut().unwrap().cltv_expiry_delta = final_cltv; - let route = Route { hops: res }; + let route = Route { paths: vec![res] }; log_trace!(self, "Got route: {}", log_route!(route)); return Ok(route); } @@ -1015,7 +1059,8 @@ mod tests { use chain::chaininterface; use ln::channelmanager; use ln::router::{Router,NodeInfo,NetworkMap,ChannelInfo,DirectionalChannelInfo,RouteHint}; - use ln::msgs::GlobalFeatures; + use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; + use ln::msgs::{LightningError, ErrorAction}; use util::test_utils; use util::test_utils::TestVecWriter; use util::logger::Logger; @@ -1108,6 +1153,23 @@ mod tests { let zero_hash = Sha256dHash::hash(&[0; 32]); + macro_rules! id_to_feature_flags { + // Set the feature flags to the id'th odd (ie non-required) feature bit so that we can + // test for it later. + ($id: expr) => { { + let idx = ($id - 1) * 2 + 1; + if idx > 8*3 { + vec![1 << (idx - 8*3), 0, 0, 0] + } else if idx > 8*2 { + vec![1 << (idx - 8*2), 0, 0] + } else if idx > 8*1 { + vec![1 << (idx - 8*1), 0] + } else { + vec![1 << idx] + } + } } + } + { let mut network = router.network_map.write().unwrap(); @@ -1115,15 +1177,15 @@ mod tests { channels: vec!(NetworkMap::get_key(1, zero_hash.clone()), NetworkMap::get_key(3, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 100, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(1)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(1, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(1)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1149,15 +1211,15 @@ mod tests { channels: vec!(NetworkMap::get_key(2, zero_hash.clone()), NetworkMap::get_key(4, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(2)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(2, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(2)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1183,15 +1245,15 @@ mod tests { channels: vec!(NetworkMap::get_key(12, zero_hash.clone()), NetworkMap::get_key(13, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(8)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(12, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(12)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1223,15 +1285,15 @@ mod tests { NetworkMap::get_key(7, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(3)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(3, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(3)), one_to_two: DirectionalChannelInfo { src_node_id: node1.clone(), last_update: 0, @@ -1254,7 +1316,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(4, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(4)), one_to_two: DirectionalChannelInfo { src_node_id: node2.clone(), last_update: 0, @@ -1277,7 +1339,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(13, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(13)), one_to_two: DirectionalChannelInfo { src_node_id: node8.clone(), last_update: 0, @@ -1303,15 +1365,15 @@ mod tests { channels: vec!(NetworkMap::get_key(5, zero_hash.clone()), NetworkMap::get_key(11, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(4)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(5, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(5)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1337,15 +1399,15 @@ mod tests { channels: vec!(NetworkMap::get_key(6, zero_hash.clone()), NetworkMap::get_key(11, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(5)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(6, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(6)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1368,7 +1430,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(11, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(11)), one_to_two: DirectionalChannelInfo { src_node_id: node5.clone(), last_update: 0, @@ -1394,15 +1456,15 @@ mod tests { channels: vec!(NetworkMap::get_key(7, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: GlobalFeatures::new(), - last_update: 1, + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(6)), + last_update: Some(1), rgb: [0; 3], alias: [0; 32], addresses: Vec::new(), announcement_message: None, }); network.channels.insert(NetworkMap::get_key(7, zero_hash.clone()), ChannelInfo { - features: GlobalFeatures::new(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(7)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1428,37 +1490,82 @@ mod tests { { // Simple route to 3 via 2 let route = router.get_route(&node3, None, &Vec::new(), 100, 42).unwrap(); - assert_eq!(route.hops.len(), 2); + assert_eq!(route.paths[0].len(), 2); + + assert_eq!(route.paths[0][0].pubkey, node2); + assert_eq!(route.paths[0][0].short_channel_id, 2); + assert_eq!(route.paths[0][0].fee_msat, 100); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2)); + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 4); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4)); + } - assert_eq!(route.hops[0].pubkey, node2); - assert_eq!(route.hops[0].short_channel_id, 2); - assert_eq!(route.hops[0].fee_msat, 100); - assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1); + { // Disable channels 4 and 12 by requiring unknown feature bits + let mut network = router.network_map.write().unwrap(); + network.channels.get_mut(&NetworkMap::get_key(4, zero_hash.clone())).unwrap().features.set_require_unknown_bits(); + network.channels.get_mut(&NetworkMap::get_key(12, zero_hash.clone())).unwrap().features.set_require_unknown_bits(); + } - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 4); - assert_eq!(route.hops[1].fee_msat, 100); - assert_eq!(route.hops[1].cltv_expiry_delta, 42); + { // If all the channels require some features we don't understand, route should fail + if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = router.get_route(&node3, None, &Vec::new(), 100, 42) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { panic!(); } } - { // Route to 1 via 2 and 3 because our channel to 1 is disabled - let route = router.get_route(&node1, None, &Vec::new(), 100, 42).unwrap(); - assert_eq!(route.hops.len(), 3); - - assert_eq!(route.hops[0].pubkey, node2); - assert_eq!(route.hops[0].short_channel_id, 2); - assert_eq!(route.hops[0].fee_msat, 200); - assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1); - - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 4); - assert_eq!(route.hops[1].fee_msat, 100); - assert_eq!(route.hops[1].cltv_expiry_delta, (3 << 8) | 2); - - assert_eq!(route.hops[2].pubkey, node1); - assert_eq!(route.hops[2].short_channel_id, 3); - assert_eq!(route.hops[2].fee_msat, 100); - assert_eq!(route.hops[2].cltv_expiry_delta, 42); + { // If we specify a channel to node8, that overrides our local channel view and that gets used + let our_chans = vec![channelmanager::ChannelDetails { + channel_id: [0; 32], + short_channel_id: Some(42), + remote_network_id: node8.clone(), + counterparty_features: InitFeatures::from_le_bytes(vec![0b11]), + channel_value_satoshis: 0, + user_id: 0, + outbound_capacity_msat: 0, + inbound_capacity_msat: 0, + is_live: true, + }]; + let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap(); + assert_eq!(route.paths[0].len(), 2); + + assert_eq!(route.paths[0][0].pubkey, node8); + assert_eq!(route.paths[0][0].short_channel_id, 42); + assert_eq!(route.paths[0][0].fee_msat, 200); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features + assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 13); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13)); + } + + { // Re-enable channels 4 and 12 by wiping the unknown feature bits + let mut network = router.network_map.write().unwrap(); + network.channels.get_mut(&NetworkMap::get_key(4, zero_hash.clone())).unwrap().features.clear_require_unknown_bits(); + network.channels.get_mut(&NetworkMap::get_key(12, zero_hash.clone())).unwrap().features.clear_require_unknown_bits(); + } + + { // Disable nodes 1, 2, and 8 by requiring unknown feature bits + let mut network = router.network_map.write().unwrap(); + network.nodes.get_mut(&node1).unwrap().features.set_require_unknown_bits(); + network.nodes.get_mut(&node2).unwrap().features.set_require_unknown_bits(); + network.nodes.get_mut(&node8).unwrap().features.set_require_unknown_bits(); + } + + { // If all nodes require some features we don't understand, route should fail + if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = router.get_route(&node3, None, &Vec::new(), 100, 42) { + assert_eq!(err, "Failed to find a path to the given destination"); + } else { panic!(); } } { // If we specify a channel to node8, that overrides our local channel view and that gets used @@ -1466,6 +1573,7 @@ mod tests { channel_id: [0; 32], short_channel_id: Some(42), remote_network_id: node8.clone(), + counterparty_features: InitFeatures::from_le_bytes(vec![0b11]), channel_value_satoshis: 0, user_id: 0, outbound_capacity_msat: 0, @@ -1473,17 +1581,88 @@ mod tests { is_live: true, }]; let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap(); - assert_eq!(route.hops.len(), 2); + assert_eq!(route.paths[0].len(), 2); + + assert_eq!(route.paths[0][0].pubkey, node8); + assert_eq!(route.paths[0][0].short_channel_id, 42); + assert_eq!(route.paths[0][0].fee_msat, 200); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features + assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 13); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13)); + } + + { // Re-enable nodes 1, 2, and 8 + let mut network = router.network_map.write().unwrap(); + network.nodes.get_mut(&node1).unwrap().features.clear_require_unknown_bits(); + network.nodes.get_mut(&node2).unwrap().features.clear_require_unknown_bits(); + network.nodes.get_mut(&node8).unwrap().features.clear_require_unknown_bits(); + } - assert_eq!(route.hops[0].pubkey, node8); - assert_eq!(route.hops[0].short_channel_id, 42); - assert_eq!(route.hops[0].fee_msat, 200); - assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1); + // Note that we don't test disabling node 3 and failing to route to it, as we (somewhat + // naively) assume that the user checked the feature bits on the invoice, which override + // the node_announcement. - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 13); - assert_eq!(route.hops[1].fee_msat, 100); - assert_eq!(route.hops[1].cltv_expiry_delta, 42); + { // Route to 1 via 2 and 3 because our channel to 1 is disabled + let route = router.get_route(&node1, None, &Vec::new(), 100, 42).unwrap(); + assert_eq!(route.paths[0].len(), 3); + + assert_eq!(route.paths[0][0].pubkey, node2); + assert_eq!(route.paths[0][0].short_channel_id, 2); + assert_eq!(route.paths[0][0].fee_msat, 200); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2)); + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 4); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, (3 << 8) | 2); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4)); + + assert_eq!(route.paths[0][2].pubkey, node1); + assert_eq!(route.paths[0][2].short_channel_id, 3); + assert_eq!(route.paths[0][2].fee_msat, 100); + assert_eq!(route.paths[0][2].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(1)); + assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(3)); + } + + { // If we specify a channel to node8, that overrides our local channel view and that gets used + let our_chans = vec![channelmanager::ChannelDetails { + channel_id: [0; 32], + short_channel_id: Some(42), + remote_network_id: node8.clone(), + counterparty_features: InitFeatures::from_le_bytes(vec![0b11]), + channel_value_satoshis: 0, + user_id: 0, + outbound_capacity_msat: 0, + inbound_capacity_msat: 0, + is_live: true, + }]; + let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap(); + assert_eq!(route.paths[0].len(), 2); + + assert_eq!(route.paths[0][0].pubkey, node8); + assert_eq!(route.paths[0][0].short_channel_id, 42); + assert_eq!(route.paths[0][0].fee_msat, 200); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 13); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13)); } let mut last_hops = vec!(RouteHint { @@ -1511,32 +1690,44 @@ mod tests { { // Simple test across 2, 3, 5, and 4 via a last_hop channel let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap(); - assert_eq!(route.hops.len(), 5); - - assert_eq!(route.hops[0].pubkey, node2); - assert_eq!(route.hops[0].short_channel_id, 2); - assert_eq!(route.hops[0].fee_msat, 100); - assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1); - - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 4); - assert_eq!(route.hops[1].fee_msat, 0); - assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1); - - assert_eq!(route.hops[2].pubkey, node5); - assert_eq!(route.hops[2].short_channel_id, 6); - assert_eq!(route.hops[2].fee_msat, 0); - assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1); - - assert_eq!(route.hops[3].pubkey, node4); - assert_eq!(route.hops[3].short_channel_id, 11); - assert_eq!(route.hops[3].fee_msat, 0); - assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1); - - assert_eq!(route.hops[4].pubkey, node7); - assert_eq!(route.hops[4].short_channel_id, 8); - assert_eq!(route.hops[4].fee_msat, 100); - assert_eq!(route.hops[4].cltv_expiry_delta, 42); + assert_eq!(route.paths[0].len(), 5); + + assert_eq!(route.paths[0][0].pubkey, node2); + assert_eq!(route.paths[0][0].short_channel_id, 2); + assert_eq!(route.paths[0][0].fee_msat, 100); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2)); + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 4); + assert_eq!(route.paths[0][1].fee_msat, 0); + assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4)); + + assert_eq!(route.paths[0][2].pubkey, node5); + assert_eq!(route.paths[0][2].short_channel_id, 6); + assert_eq!(route.paths[0][2].fee_msat, 0); + assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1); + assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5)); + assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6)); + + assert_eq!(route.paths[0][3].pubkey, node4); + assert_eq!(route.paths[0][3].short_channel_id, 11); + assert_eq!(route.paths[0][3].fee_msat, 0); + assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1); + // If we have a peer in the node map, we'll use their features here since we don't have + // a way of figuring out their features from the invoice: + assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4)); + assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11)); + + assert_eq!(route.paths[0][4].pubkey, node7); + assert_eq!(route.paths[0][4].short_channel_id, 8); + assert_eq!(route.paths[0][4].fee_msat, 100); + assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } { // Simple test with outbound channel to 4 to test that last_hops and first_hops connect @@ -1544,6 +1735,7 @@ mod tests { channel_id: [0; 32], short_channel_id: Some(42), remote_network_id: node4.clone(), + counterparty_features: InitFeatures::from_le_bytes(vec![0b11]), channel_value_satoshis: 0, user_id: 0, outbound_capacity_msat: 0, @@ -1551,74 +1743,100 @@ mod tests { is_live: true, }]; let route = router.get_route(&node7, Some(&our_chans), &last_hops, 100, 42).unwrap(); - assert_eq!(route.hops.len(), 2); - - assert_eq!(route.hops[0].pubkey, node4); - assert_eq!(route.hops[0].short_channel_id, 42); - assert_eq!(route.hops[0].fee_msat, 0); - assert_eq!(route.hops[0].cltv_expiry_delta, (8 << 8) | 1); - - assert_eq!(route.hops[1].pubkey, node7); - assert_eq!(route.hops[1].short_channel_id, 8); - assert_eq!(route.hops[1].fee_msat, 100); - assert_eq!(route.hops[1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0].len(), 2); + + assert_eq!(route.paths[0][0].pubkey, node4); + assert_eq!(route.paths[0][0].short_channel_id, 42); + assert_eq!(route.paths[0][0].fee_msat, 0); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion + + assert_eq!(route.paths[0][1].pubkey, node7); + assert_eq!(route.paths[0][1].short_channel_id, 8); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][1].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } last_hops[0].fee_base_msat = 1000; { // Revert to via 6 as the fee on 8 goes up let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap(); - assert_eq!(route.hops.len(), 4); - - assert_eq!(route.hops[0].pubkey, node2); - assert_eq!(route.hops[0].short_channel_id, 2); - assert_eq!(route.hops[0].fee_msat, 200); // fee increased as its % of value transferred across node - assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1); - - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 4); - assert_eq!(route.hops[1].fee_msat, 100); - assert_eq!(route.hops[1].cltv_expiry_delta, (7 << 8) | 1); - - assert_eq!(route.hops[2].pubkey, node6); - assert_eq!(route.hops[2].short_channel_id, 7); - assert_eq!(route.hops[2].fee_msat, 0); - assert_eq!(route.hops[2].cltv_expiry_delta, (10 << 8) | 1); - - assert_eq!(route.hops[3].pubkey, node7); - assert_eq!(route.hops[3].short_channel_id, 10); - assert_eq!(route.hops[3].fee_msat, 100); - assert_eq!(route.hops[3].cltv_expiry_delta, 42); + assert_eq!(route.paths[0].len(), 4); + + assert_eq!(route.paths[0][0].pubkey, node2); + assert_eq!(route.paths[0][0].short_channel_id, 2); + assert_eq!(route.paths[0][0].fee_msat, 200); // fee increased as its % of value transferred across node + assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2)); + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 4); + assert_eq!(route.paths[0][1].fee_msat, 100); + assert_eq!(route.paths[0][1].cltv_expiry_delta, (7 << 8) | 1); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4)); + + assert_eq!(route.paths[0][2].pubkey, node6); + assert_eq!(route.paths[0][2].short_channel_id, 7); + assert_eq!(route.paths[0][2].fee_msat, 0); + assert_eq!(route.paths[0][2].cltv_expiry_delta, (10 << 8) | 1); + // If we have a peer in the node map, we'll use their features here since we don't have + // a way of figuring out their features from the invoice: + assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(6)); + assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(7)); + + assert_eq!(route.paths[0][3].pubkey, node7); + assert_eq!(route.paths[0][3].short_channel_id, 10); + assert_eq!(route.paths[0][3].fee_msat, 100); + assert_eq!(route.paths[0][3].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } { // ...but still use 8 for larger payments as 6 has a variable feerate let route = router.get_route(&node7, None, &last_hops, 2000, 42).unwrap(); - assert_eq!(route.hops.len(), 5); - - assert_eq!(route.hops[0].pubkey, node2); - assert_eq!(route.hops[0].short_channel_id, 2); - assert_eq!(route.hops[0].fee_msat, 3000); - assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1); - - assert_eq!(route.hops[1].pubkey, node3); - assert_eq!(route.hops[1].short_channel_id, 4); - assert_eq!(route.hops[1].fee_msat, 0); - assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1); - - assert_eq!(route.hops[2].pubkey, node5); - assert_eq!(route.hops[2].short_channel_id, 6); - assert_eq!(route.hops[2].fee_msat, 0); - assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1); - - assert_eq!(route.hops[3].pubkey, node4); - assert_eq!(route.hops[3].short_channel_id, 11); - assert_eq!(route.hops[3].fee_msat, 1000); - assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1); - - assert_eq!(route.hops[4].pubkey, node7); - assert_eq!(route.hops[4].short_channel_id, 8); - assert_eq!(route.hops[4].fee_msat, 2000); - assert_eq!(route.hops[4].cltv_expiry_delta, 42); + assert_eq!(route.paths[0].len(), 5); + + assert_eq!(route.paths[0][0].pubkey, node2); + assert_eq!(route.paths[0][0].short_channel_id, 2); + assert_eq!(route.paths[0][0].fee_msat, 3000); + assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1); + assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2)); + + assert_eq!(route.paths[0][1].pubkey, node3); + assert_eq!(route.paths[0][1].short_channel_id, 4); + assert_eq!(route.paths[0][1].fee_msat, 0); + assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1); + assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4)); + + assert_eq!(route.paths[0][2].pubkey, node5); + assert_eq!(route.paths[0][2].short_channel_id, 6); + assert_eq!(route.paths[0][2].fee_msat, 0); + assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1); + assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5)); + assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6)); + + assert_eq!(route.paths[0][3].pubkey, node4); + assert_eq!(route.paths[0][3].short_channel_id, 11); + assert_eq!(route.paths[0][3].fee_msat, 1000); + assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1); + // If we have a peer in the node map, we'll use their features here since we don't have + // a way of figuring out their features from the invoice: + assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4)); + assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11)); + + assert_eq!(route.paths[0][4].pubkey, node7); + assert_eq!(route.paths[0][4].short_channel_id, 8); + assert_eq!(route.paths[0][4].fee_msat, 2000); + assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); + assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } { // Test Router serialization/deserialization