Make router_target a bit easier for fuzzers to explore and fix two found bugs
Showing 2 of 3 files from the diff.
lightning/src/routing/router.rs
changed.
Other files ignored by Codecov
fuzz/src/router.rs
has changed.
@@ -237,13 +237,12 @@
Loading
237 | 237 | let mut total_fee = $starting_fee_msat as u64; |
|
238 | 238 | let hm_entry = dist.entry(&$src_node_id); |
|
239 | 239 | let old_entry = hm_entry.or_insert_with(|| { |
|
240 | - | let node = network.get_nodes().get(&$src_node_id).unwrap(); |
|
241 | 240 | let mut fee_base_msat = u32::max_value(); |
|
242 | 241 | let mut fee_proportional_millionths = u32::max_value(); |
|
243 | - | if let Some(fees) = node.lowest_inbound_channel_fees { |
|
242 | + | if let Some(fees) = network.get_nodes().get(&$src_node_id).and_then(|node| node.lowest_inbound_channel_fees) { |
|
244 | 243 | fee_base_msat = fees.base_msat; |
|
245 | 244 | fee_proportional_millionths = fees.proportional_millionths; |
|
246 | - | }; |
|
245 | + | } |
|
247 | 246 | (u64::max_value(), |
|
248 | 247 | fee_base_msat, |
|
249 | 248 | fee_proportional_millionths, |
@@ -342,21 +341,26 @@
Loading
342 | 341 | } |
|
343 | 342 | ||
344 | 343 | for hop in last_hops.iter() { |
|
345 | - | if first_hops.is_none() || hop.src_node_id != *our_node_id { // first_hop overrules last_hops |
|
346 | - | if network.get_nodes().get(&hop.src_node_id).is_some() { |
|
347 | - | if first_hops.is_some() { |
|
348 | - | if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&hop.src_node_id) { |
|
349 | - | // Currently there are no channel-context features defined, so we are a |
|
350 | - | // bit lazy here. In the future, we should pull them out via our |
|
351 | - | // ChannelManager, but there's no reason to waste the space until we |
|
352 | - | // need them. |
|
353 | - | add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, features.to_context(), 0); |
|
354 | - | } |
|
355 | - | } |
|
356 | - | // BOLT 11 doesn't allow inclusion of features for the last hop hints, which |
|
357 | - | // really sucks, cause we're gonna need that eventually. |
|
358 | - | add_entry!(hop.short_channel_id, hop.src_node_id, target, hop, ChannelFeatures::empty(), 0); |
|
359 | - | } |
|
344 | + | let have_hop_src_in_graph = |
|
345 | + | if let Some(&(ref first_hop, ref features)) = first_hop_targets.get(&hop.src_node_id) { |
|
346 | + | // If this hop connects to a node with which we have a direct channel, ignore the |
|
347 | + | // network graph and add both the hop and our direct channel to the candidate set: |
|
348 | + | // |
|
349 | + | // Currently there are no channel-context features defined, so we are a |
|
350 | + | // bit lazy here. In the future, we should pull them out via our |
|
351 | + | // ChannelManager, but there's no reason to waste the space until we |
|
352 | + | // need them. |
|
353 | + | add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, features.to_context(), 0); |
|
354 | + | true |
|
355 | + | } else { |
|
356 | + | // In any other case, only add the hop if the source is in the regular network |
|
357 | + | // graph: |
|
358 | + | network.get_nodes().get(&hop.src_node_id).is_some() |
|
359 | + | }; |
|
360 | + | if have_hop_src_in_graph { |
|
361 | + | // BOLT 11 doesn't allow inclusion of features for the last hop hints, which |
|
362 | + | // really sucks, cause we're gonna need that eventually. |
|
363 | + | add_entry!(hop.short_channel_id, hop.src_node_id, target, hop, ChannelFeatures::empty(), 0); |
|
360 | 364 | } |
|
361 | 365 | } |
|
362 | 366 |
@@ -412,7 +416,7 @@
Loading
412 | 416 | #[cfg(test)] |
|
413 | 417 | mod tests { |
|
414 | 418 | use routing::router::{get_route, RouteHint, RoutingFees}; |
|
415 | - | use routing::network_graph::NetGraphMsgHandler; |
|
419 | + | use routing::network_graph::{NetworkGraph, NetGraphMsgHandler}; |
|
416 | 420 | use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; |
|
417 | 421 | use ln::msgs::{ErrorAction, LightningError, OptionalField, UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler, |
|
418 | 422 | NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate}; |
@@ -1222,4 +1226,54 @@
Loading
1222 | 1226 | assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet |
|
1223 | 1227 | assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly |
|
1224 | 1228 | } |
|
1229 | + | ||
1230 | + | #[test] |
|
1231 | + | fn unannounced_path_test() { |
|
1232 | + | // We should be able to send a payment to a destination without any help of a routing graph |
|
1233 | + | // if we have a channel with a common counterparty that appears in the first and last hop |
|
1234 | + | // hints. |
|
1235 | + | let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap()); |
|
1236 | + | let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap()); |
|
1237 | + | let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap()); |
|
1238 | + | ||
1239 | + | // If we specify a channel to a middle hop, that overrides our local channel view and that gets used |
|
1240 | + | let last_hops = vec![RouteHint { |
|
1241 | + | src_node_id: middle_node_id, |
|
1242 | + | short_channel_id: 8, |
|
1243 | + | fees: RoutingFees { |
|
1244 | + | base_msat: 1000, |
|
1245 | + | proportional_millionths: 0, |
|
1246 | + | }, |
|
1247 | + | cltv_expiry_delta: (8 << 8) | 1, |
|
1248 | + | htlc_minimum_msat: 0, |
|
1249 | + | }]; |
|
1250 | + | let our_chans = vec![channelmanager::ChannelDetails { |
|
1251 | + | channel_id: [0; 32], |
|
1252 | + | short_channel_id: Some(42), |
|
1253 | + | remote_network_id: middle_node_id, |
|
1254 | + | counterparty_features: InitFeatures::from_le_bytes(vec![0b11]), |
|
1255 | + | channel_value_satoshis: 100000, |
|
1256 | + | user_id: 0, |
|
1257 | + | outbound_capacity_msat: 100000, |
|
1258 | + | inbound_capacity_msat: 100000, |
|
1259 | + | is_live: true, |
|
1260 | + | }]; |
|
1261 | + | let route = get_route(&source_node_id, &NetworkGraph::new(), &target_node_id, Some(&our_chans.iter().collect::<Vec<_>>()), &last_hops.iter().collect::<Vec<_>>(), 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap(); |
|
1262 | + | ||
1263 | + | assert_eq!(route.paths[0].len(), 2); |
|
1264 | + | ||
1265 | + | assert_eq!(route.paths[0][0].pubkey, middle_node_id); |
|
1266 | + | assert_eq!(route.paths[0][0].short_channel_id, 42); |
|
1267 | + | assert_eq!(route.paths[0][0].fee_msat, 1000); |
|
1268 | + | assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1); |
|
1269 | + | assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]); |
|
1270 | + | assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly |
|
1271 | + | ||
1272 | + | assert_eq!(route.paths[0][1].pubkey, target_node_id); |
|
1273 | + | assert_eq!(route.paths[0][1].short_channel_id, 8); |
|
1274 | + | assert_eq!(route.paths[0][1].fee_msat, 100); |
|
1275 | + | assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); |
|
1276 | + | assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet |
|
1277 | + | assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly |
|
1278 | + | } |
|
1225 | 1279 | } |
@@ -562,9 +562,14 @@
Loading
562 | 562 | } |
|
563 | 563 | } |
|
564 | 564 | ||
565 | - | /// For an already known node (from channel announcements), update its stored properties from a given node announcement |
|
565 | + | /// For an already known node (from channel announcements), update its stored properties from a given node announcement. |
|
566 | + | /// |
|
567 | + | /// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's |
|
568 | + | /// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept |
|
569 | + | /// routing messages without checking their signatures. |
|
570 | + | /// |
|
566 | 571 | /// Announcement signatures are checked here only if Secp256k1 object is provided. |
|
567 | - | fn update_node_from_announcement(&mut self, msg: &msgs::NodeAnnouncement, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> { |
|
572 | + | pub fn update_node_from_announcement<T: secp256k1::Verification>(&mut self, msg: &msgs::NodeAnnouncement, secp_ctx: Option<&Secp256k1<T>>) -> Result<bool, LightningError> { |
|
568 | 573 | if let Some(sig_verifier) = secp_ctx { |
|
569 | 574 | let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); |
|
570 | 575 | secp_verify_sig!(sig_verifier, &msg_hash, &msg.signature, &msg.contents.node_id); |
@@ -594,13 +599,27 @@
Loading
594 | 599 | } |
|
595 | 600 | } |
|
596 | 601 | ||
597 | - | /// For a new or already known (from previous announcement) channel, store or update channel info. |
|
598 | - | /// Also store nodes (if not stored yet) the channel is between, and make node aware of this channel. |
|
599 | - | /// Checking utxo on-chain is useful if we receive an update for already known channel id, |
|
600 | - | /// which is probably result of a reorg. In that case, we update channel info only if the |
|
601 | - | /// utxo was checked, otherwise stick to the existing update, to prevent DoS risks. |
|
602 | + | /// Store or update channel info from a channel announcement. |
|
603 | + | /// |
|
604 | + | /// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's |
|
605 | + | /// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept |
|
606 | + | /// routing messages without checking their signatures. |
|
607 | + | /// |
|
608 | + | /// If the channel has been confirmed to exist on chain (with correctly-formatted scripts on |
|
609 | + | /// chain), set utxo_value to the value of the output on chain, otherwise leave it as None. |
|
610 | + | /// The UTXO value is then used in routing calculation if we have no better information on the |
|
611 | + | /// maximum HTLC value that can be sent over the channel. |
|
612 | + | /// |
|
613 | + | /// Further, setting utxo_value to Some indicates that the announcement message is genuine, |
|
614 | + | /// allowing us to update existing channel data in the case of reorgs or to replace bogus |
|
615 | + | /// channel data generated by a DoS attacker. |
|
616 | + | /// |
|
602 | 617 | /// Announcement signatures are checked here only if Secp256k1 object is provided. |
|
603 | - | fn update_channel_from_announcement(&mut self, msg: &msgs::ChannelAnnouncement, utxo_value: Option<u64>, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> { |
|
618 | + | pub fn update_channel_from_announcement<T: secp256k1::Verification>(&mut self, msg: &msgs::ChannelAnnouncement, utxo_value: Option<u64>, secp_ctx: Option<&Secp256k1<T>>) -> Result<bool, LightningError> { |
|
619 | + | if msg.contents.node_id_1 == msg.contents.node_id_2 || msg.contents.bitcoin_key_1 == msg.contents.bitcoin_key_2 { |
|
620 | + | return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError}); |
|
621 | + | } |
|
622 | + | ||
604 | 623 | if let Some(sig_verifier) = secp_ctx { |
|
605 | 624 | let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]); |
|
606 | 625 | secp_verify_sig!(sig_verifier, &msg_hash, &msg.node_signature_1, &msg.contents.node_id_1); |
@@ -699,8 +718,13 @@
Loading
699 | 718 | } |
|
700 | 719 | ||
701 | 720 | /// For an already known (from announcement) channel, update info about one of the directions of a channel. |
|
721 | + | /// |
|
722 | + | /// You probably don't want to call this directly, instead relying on a NetGraphMsgHandler's |
|
723 | + | /// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept |
|
724 | + | /// routing messages without checking their signatures. |
|
725 | + | /// |
|
702 | 726 | /// Announcement signatures are checked here only if Secp256k1 object is provided. |
|
703 | - | fn update_channel(&mut self, msg: &msgs::ChannelUpdate, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> { |
|
727 | + | pub fn update_channel(&mut self, msg: &msgs::ChannelUpdate, secp_ctx: Option<&Secp256k1<secp256k1::VerifyOnly>>) -> Result<bool, LightningError> { |
|
704 | 728 | let dest_node_id; |
|
705 | 729 | let chan_enabled = msg.contents.flags & (1 << 1) != (1 << 1); |
|
706 | 730 | let chan_was_enabled; |
@@ -716,8 +740,8 @@
Loading
716 | 740 | if let Some(capacity_sats) = channel.capacity_sats { |
|
717 | 741 | // It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None). |
|
718 | 742 | // Don't query UTXO set here to reduce DoS risks. |
|
719 | - | if htlc_maximum_msat > capacity_sats * 1000 { |
|
720 | - | return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity".to_owned(), action: ErrorAction::IgnoreError}); |
|
743 | + | if capacity_sats > MAX_VALUE_MSAT / 1000 || htlc_maximum_msat > capacity_sats * 1000 { |
|
744 | + | return Err(LightningError{err: "htlc_maximum_msat is larger than channel capacity or capacity is bogus".to_owned(), action: ErrorAction::IgnoreError}); |
|
721 | 745 | } |
|
722 | 746 | } |
|
723 | 747 | } |
@@ -1302,7 +1326,7 @@
Loading
1302 | 1326 | ||
1303 | 1327 | match net_graph_msg_handler.handle_channel_update(&valid_channel_update) { |
|
1304 | 1328 | Ok(_) => panic!(), |
|
1305 | - | Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity") |
|
1329 | + | Err(e) => assert_eq!(e.err, "htlc_maximum_msat is larger than channel capacity or capacity is bogus") |
|
1306 | 1330 | }; |
|
1307 | 1331 | unsigned_channel_update.htlc_maximum_msat = OptionalField::Absent; |
|
1308 | 1332 |
Files | Coverage |
---|---|
lightning/src | 91.41% |
Project Totals (37 files) | 91.41% |
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.