From: Matt Corallo Date: Sat, 28 Dec 2019 06:10:14 +0000 (-0500) Subject: Pass node features through to RouteHops X-Git-Tag: v0.0.12~153^2~1 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=912f877;p=rust-lightning Pass node features through to RouteHops This exposes the latest Init-context features in the ChannelDetails passed to the Router during route calculation, which combines those with the Node-context features tracked from node_announcements to provide the latest Node-context features in RouteHop structs. Fields are also added for Channel-context features, though those are only partially used since no such features are defined today anyway. These will be useful when determining whether to use new TLV-formatted onion hop datas when generating onions for peers. --- diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f4fa9b954..adc00ed8d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -29,7 +29,7 @@ use lightning::ln::channelmonitor; use lightning::ln::channelmonitor::{ChannelMonitor, ChannelMonitorUpdateErr, HTLCUpdate}; use lightning::ln::channelmanager::{ChannelManager, PaymentHash, PaymentPreimage, ChannelManagerReadArgs}; use lightning::ln::router::{Route, RouteHop}; -use lightning::ln::features::InitFeatures; +use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use lightning::ln::msgs::{CommitmentUpdate, ChannelMessageHandler, ErrorAction, UpdateAddHTLC, Init}; use lightning::util::enforcing_trait_impls::EnforcingChannelKeys; use lightning::util::events; @@ -414,7 +414,9 @@ pub fn do_test(data: &[u8]) { if let Err(_) = $source.send_payment(Route { hops: vec![RouteHop { pubkey: $dest.0.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: $dest.1, + channel_features: ChannelFeatures::empty(), fee_msat: 5000000, cltv_expiry_delta: 200, }], @@ -429,12 +431,16 @@ pub fn do_test(data: &[u8]) { if let Err(_) = $source.send_payment(Route { hops: vec![RouteHop { pubkey: $middle.0.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: $middle.1, + channel_features: ChannelFeatures::empty(), fee_msat: 50000, cltv_expiry_delta: 100, },RouteHop { pubkey: $dest.0.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: $dest.1, + channel_features: ChannelFeatures::empty(), fee_msat: 5000000, cltv_expiry_delta: 200, }], diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 3e8082e47..fbb629220 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -5,8 +5,9 @@ use bitcoin::blockdata::transaction::Transaction; use lightning::chain::chaininterface::{ChainError,ChainWatchInterface}; use lightning::ln::channelmanager::ChannelDetails; +use lightning::ln::features::InitFeatures; use lightning::ln::msgs; -use lightning::ln::msgs::{RoutingMessageHandler}; +use lightning::ln::msgs::RoutingMessageHandler; use lightning::ln::router::{Router, RouteHint}; use lightning::util::logger::Logger; use lightning::util::ser::Readable; @@ -198,6 +199,7 @@ pub fn do_test(data: &[u8]) { channel_id: [0; 32], short_channel_id: Some(slice_to_be64(get_slice!(8))), remote_network_id: get_pubkey!(), + counterparty_features: InitFeatures::empty(), channel_value_satoshis: slice_to_be64(get_slice!(8)), user_id: 0, inbound_capacity_msat: 0, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 72193bafe..3c13154c7 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -404,6 +404,10 @@ pub struct ChannelDetails { pub short_channel_id: Option, /// The node_id of our counterparty pub remote_network_id: PublicKey, + /// The Features the channel counterparty provided upon last connection. + /// Useful for routing as it is the most up-to-date copy of the counterparty's features and + /// many routing-relevant features are present in the init context. + pub counterparty_features: InitFeatures, /// The value, in satoshis, of this channel as appears in the funding output pub channel_value_satoshis: u64, /// The user_id passed in to create_channel, or 0 if the channel was inbound. @@ -679,50 +683,70 @@ impl ChannelManager { /// Gets the list of open channels, in random order. See ChannelDetail field documentation for /// more information. pub fn list_channels(&self) -> Vec { - let channel_state = self.channel_state.lock().unwrap(); - let mut res = Vec::with_capacity(channel_state.by_id.len()); - for (channel_id, channel) in channel_state.by_id.iter() { - let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat(); - res.push(ChannelDetails { - channel_id: (*channel_id).clone(), - short_channel_id: channel.get_short_channel_id(), - remote_network_id: channel.get_their_node_id(), - channel_value_satoshis: channel.get_value_satoshis(), - inbound_capacity_msat, - outbound_capacity_msat, - user_id: channel.get_user_id(), - is_live: channel.is_live(), - }); - } - res - } - - /// Gets the list of usable channels, in random order. Useful as an argument to - /// Router::get_route to ensure non-announced channels are used. - /// - /// These are guaranteed to have their is_live value set to true, see the documentation for - /// ChannelDetails::is_live for more info on exactly what the criteria are. - pub fn list_usable_channels(&self) -> Vec { - let channel_state = self.channel_state.lock().unwrap(); - let mut res = Vec::with_capacity(channel_state.by_id.len()); - for (channel_id, channel) in channel_state.by_id.iter() { - // Note we use is_live here instead of usable which leads to somewhat confused - // internal/external nomenclature, but that's ok cause that's probably what the user - // really wanted anyway. - if channel.is_live() { + let mut res = Vec::new(); + { + let channel_state = self.channel_state.lock().unwrap(); + res.reserve(channel_state.by_id.len()); + for (channel_id, channel) in channel_state.by_id.iter() { let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat(); res.push(ChannelDetails { channel_id: (*channel_id).clone(), short_channel_id: channel.get_short_channel_id(), remote_network_id: channel.get_their_node_id(), + counterparty_features: InitFeatures::empty(), channel_value_satoshis: channel.get_value_satoshis(), inbound_capacity_msat, outbound_capacity_msat, user_id: channel.get_user_id(), - is_live: true, + is_live: channel.is_live(), }); } } + let per_peer_state = self.per_peer_state.read().unwrap(); + for chan in res.iter_mut() { + if let Some(peer_state) = per_peer_state.get(&chan.remote_network_id) { + chan.counterparty_features = peer_state.lock().unwrap().latest_features.clone(); + } + } + res + } + + /// Gets the list of usable channels, in random order. Useful as an argument to + /// Router::get_route to ensure non-announced channels are used. + /// + /// These are guaranteed to have their is_live value set to true, see the documentation for + /// ChannelDetails::is_live for more info on exactly what the criteria are. + pub fn list_usable_channels(&self) -> Vec { + let mut res = Vec::new(); + { + let channel_state = self.channel_state.lock().unwrap(); + res.reserve(channel_state.by_id.len()); + for (channel_id, channel) in channel_state.by_id.iter() { + // Note we use is_live here instead of usable which leads to somewhat confused + // internal/external nomenclature, but that's ok cause that's probably what the user + // really wanted anyway. + if channel.is_live() { + let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat(); + res.push(ChannelDetails { + channel_id: (*channel_id).clone(), + short_channel_id: channel.get_short_channel_id(), + remote_network_id: channel.get_their_node_id(), + counterparty_features: InitFeatures::empty(), + channel_value_satoshis: channel.get_value_satoshis(), + inbound_capacity_msat, + outbound_capacity_msat, + user_id: channel.get_user_id(), + is_live: true, + }); + } + } + } + let per_peer_state = self.per_peer_state.read().unwrap(); + for chan in res.iter_mut() { + if let Some(peer_state) = per_peer_state.get(&chan.remote_network_id) { + chan.counterparty_features = peer_state.lock().unwrap().latest_features.clone(); + } + } res } diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index c33407487..eee534f71 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -118,6 +118,13 @@ impl ChannelFeatures { mark: PhantomData, } } + + /// Takes the flags that we know how to interpret in an init-context features that are also + /// relevant in a channel-context features and creates a channel-context features from them. + pub(crate) fn with_known_relevant_init_flags(_init_ctx: &InitFeatures) -> Self { + // There are currently no channel flags defined that we understand. + Self { flags: Vec::new(), mark: PhantomData, } + } } impl NodeFeatures { @@ -136,6 +143,17 @@ impl NodeFeatures { mark: PhantomData, } } + + /// Takes the flags that we know how to interpret in an init-context features that are also + /// relevant in a node-context features and creates a node-context features from them. + pub(crate) fn with_known_relevant_init_flags(init_ctx: &InitFeatures) -> Self { + let mut flags = Vec::new(); + if init_ctx.flags.len() > 0 { + // Pull out data_loss_protect and upfront_shutdown_script (bits 0, 1, 4, and 5) + flags.push(init_ctx.flags.last().unwrap() & 0b00110011); + } + Self { flags, mark: PhantomData, } + } } impl Features { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 787148c32..c5785ea5c 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -11,7 +11,7 @@ use ln::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD use ln::channel::{Channel, ChannelError}; use ln::onion_utils; use ln::router::{Route, RouteHop}; -use ln::features::{ChannelFeatures, InitFeatures}; +use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use ln::msgs; use ln::msgs::{ChannelMessageHandler,RoutingMessageHandler,HTLCFailChannelUpdate, ErrorAction}; use util::enforcing_trait_impls::EnforcingChannelKeys; @@ -1029,19 +1029,25 @@ fn fake_network_test() { let mut hops = Vec::with_capacity(3); hops.push(RouteHop { pubkey: nodes[2].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_2.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 0, cltv_expiry_delta: chan_3.0.contents.cltv_expiry_delta as u32 }); hops.push(RouteHop { pubkey: nodes[3].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_3.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 0, cltv_expiry_delta: chan_4.1.contents.cltv_expiry_delta as u32 }); hops.push(RouteHop { pubkey: nodes[1].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_4.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 1000000, cltv_expiry_delta: TEST_FINAL_CLTV, }); @@ -1052,19 +1058,25 @@ fn fake_network_test() { let mut hops = Vec::with_capacity(3); hops.push(RouteHop { pubkey: nodes[3].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_4.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 0, cltv_expiry_delta: chan_3.1.contents.cltv_expiry_delta as u32 }); hops.push(RouteHop { pubkey: nodes[2].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_3.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 0, cltv_expiry_delta: chan_2.1.contents.cltv_expiry_delta as u32 }); hops.push(RouteHop { pubkey: nodes[1].node.get_our_node_id(), + node_features: NodeFeatures::empty(), short_channel_id: chan_2.0.contents.short_channel_id, + channel_features: ChannelFeatures::empty(), fee_msat: 1000000, cltv_expiry_delta: TEST_FINAL_CLTV, }); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index c8d3bae5f..98f4b6449 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -425,6 +425,7 @@ pub(super) fn process_onion_failure(secp_ctx: &Secp256k1< #[cfg(test)] mod tests { use ln::channelmanager::PaymentHash; + use ln::features::{ChannelFeatures, NodeFeatures}; use ln::router::{Route, RouteHop}; use ln::msgs; use util::ser::Writeable; @@ -444,22 +445,27 @@ mod tests { hops: vec!( RouteHop { pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(), + channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(), + channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(), + channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(), + channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(), + channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually }, ), diff --git a/lightning/src/ln/router.rs b/lightning/src/ln/router.rs index 15390f98e..002565608 100644 --- a/lightning/src/ln/router.rs +++ b/lightning/src/ln/router.rs @@ -31,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 @@ -54,7 +60,9 @@ impl Writeable for Route { (self.hops.len() as u8).write(writer)?; for hop in self.hops.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)?; } @@ -69,7 +77,9 @@ impl Readable for Route { 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)?, }); @@ -840,13 +850,15 @@ impl Router { return Ok(Route { hops: 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}); @@ -857,7 +869,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); @@ -873,7 +885,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, }) @@ -899,7 +913,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, } @@ -912,8 +928,8 @@ 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); } } @@ -925,13 +941,13 @@ impl Router { // 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); + 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, $fee_to_target_msat); + add_entry!(chan_id, chan.two_to_one.src_node_id, chan.one_to_two, chan.features, $fee_to_target_msat); } } } @@ -952,11 +968,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); } } } @@ -964,7 +986,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}), @@ -997,7 +1034,7 @@ mod tests { use chain::chaininterface; use ln::channelmanager; use ln::router::{Router,NodeInfo,NetworkMap,ChannelInfo,DirectionalChannelInfo,RouteHint}; - use ln::features::{ChannelFeatures, NodeFeatures}; + use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use ln::msgs::{LightningError, ErrorAction}; use util::test_utils; use util::test_utils::TestVecWriter; @@ -1091,6 +1128,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(); @@ -1098,7 +1152,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(1)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1106,7 +1160,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(1, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(1)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1132,7 +1186,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(2)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1140,7 +1194,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(2, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(2)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1166,7 +1220,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(8)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1174,7 +1228,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(12, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(12)), one_to_two: DirectionalChannelInfo { src_node_id: our_id.clone(), last_update: 0, @@ -1206,7 +1260,7 @@ mod tests { NetworkMap::get_key(7, zero_hash.clone())), lowest_inbound_channel_fee_base_msat: 0, lowest_inbound_channel_fee_proportional_millionths: 0, - features: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(3)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1214,7 +1268,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(3, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(3)), one_to_two: DirectionalChannelInfo { src_node_id: node1.clone(), last_update: 0, @@ -1237,7 +1291,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(4, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(4)), one_to_two: DirectionalChannelInfo { src_node_id: node2.clone(), last_update: 0, @@ -1260,7 +1314,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(13, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(13)), one_to_two: DirectionalChannelInfo { src_node_id: node8.clone(), last_update: 0, @@ -1286,7 +1340,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(4)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1294,7 +1348,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(5, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(5)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1320,7 +1374,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(5)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1328,7 +1382,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(6, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(6)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1351,7 +1405,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(11, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(11)), one_to_two: DirectionalChannelInfo { src_node_id: node5.clone(), last_update: 0, @@ -1377,7 +1431,7 @@ 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: NodeFeatures::empty(), + features: NodeFeatures::from_le_bytes(id_to_feature_flags!(6)), last_update: 1, rgb: [0; 3], alias: [0; 32], @@ -1385,7 +1439,7 @@ mod tests { announcement_message: None, }); network.channels.insert(NetworkMap::get_key(7, zero_hash.clone()), ChannelInfo { - features: ChannelFeatures::empty(), + features: ChannelFeatures::from_le_bytes(id_to_feature_flags!(7)), one_to_two: DirectionalChannelInfo { src_node_id: node3.clone(), last_update: 0, @@ -1417,11 +1471,15 @@ mod tests { 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[0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2)); 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); + assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4)); } { // Disable channels 4 and 12 by requiring unknown feature bits @@ -1441,6 +1499,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, @@ -1454,11 +1513,15 @@ mod tests { 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); + assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features + assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion 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); + assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13)); } { // Re-enable channels 4 and 12 by wiping the unknown feature bits @@ -1485,6 +1548,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, @@ -1498,11 +1562,15 @@ mod tests { 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); + assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features + assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion 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); + assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13)); } { // Re-enable nodes 1, 2, and 8 @@ -1524,16 +1592,22 @@ mod tests { 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[0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2)); 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[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4)); 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); + assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(1)); + assert_eq!(route.hops[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 @@ -1541,6 +1615,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, @@ -1554,11 +1629,15 @@ mod tests { 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); + assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); + assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion 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); + assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13)); } let mut last_hops = vec!(RouteHint { @@ -1592,26 +1671,38 @@ mod tests { 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[0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2)); 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[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4)); 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[2].node_features.le_flags(), &id_to_feature_flags!(5)); + assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6)); 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); + // 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.hops[3].node_features.le_flags(), &id_to_feature_flags!(4)); + assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11)); 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.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.hops[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 @@ -1619,6 +1710,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, @@ -1632,11 +1724,15 @@ mod tests { 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[0].node_features.le_flags(), &vec![0b11]); + assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion 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.hops[1].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.hops[1].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } last_hops[0].fee_base_msat = 1000; @@ -1649,21 +1745,31 @@ mod tests { 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[0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2)); 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[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4)); 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); + // 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.hops[2].node_features.le_flags(), &id_to_feature_flags!(6)); + assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(7)); 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.hops[3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.hops[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 @@ -1674,26 +1780,38 @@ mod tests { 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[0].node_features.le_flags(), &id_to_feature_flags!(2)); + assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2)); 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[1].node_features.le_flags(), &id_to_feature_flags!(3)); + assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4)); 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[2].node_features.le_flags(), &id_to_feature_flags!(5)); + assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6)); 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); + // 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.hops[3].node_features.le_flags(), &id_to_feature_flags!(4)); + assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11)); 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.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet + assert_eq!(route.hops[4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly } { // Test Router serialization/deserialization