Skip to content

Commit 84967fa

Browse files
authored
Merge pull request #958 from TheBlueMatt/2021-06-fix-router-panic
Fix panic in router given to bogus last-hop hints
2 parents 0c57018 + 2825d65 commit 84967fa

File tree

1 file changed

+59
-31
lines changed

1 file changed

+59
-31
lines changed

lightning/src/routing/router.rs

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -514,8 +514,11 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
514514
// $directional_info.
515515
// $next_hops_fee_msat represents the fees paid for using all the channel *after* this one,
516516
// since that value has to be transferred over this channel.
517+
// Returns whether this channel caused an update to `targets`.
517518
( $chan_id: expr, $src_node_id: expr, $dest_node_id: expr, $directional_info: expr, $capacity_sats: expr, $chan_features: expr, $next_hops_fee_msat: expr,
518-
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr ) => {
519+
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr ) => { {
520+
// We "return" whether we updated the path at the end, via this:
521+
let mut did_add_update_path_to_src_node = false;
519522
// Channels to self should not be used. This is more of belt-and-suspenders, because in
520523
// practice these cases should be caught earlier:
521524
// - for regular channels at channel announcement (TODO)
@@ -727,6 +730,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
727730
{
728731
old_entry.value_contribution_msat = value_contribution_msat;
729732
}
733+
did_add_update_path_to_src_node = true;
730734
} else if old_entry.was_processed && new_cost < old_cost {
731735
#[cfg(any(test, feature = "fuzztarget"))]
732736
{
@@ -757,7 +761,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
757761
}
758762
}
759763
}
760-
};
764+
did_add_update_path_to_src_node
765+
} }
761766
}
762767

763768
let empty_node_features = NodeFeatures::empty();
@@ -860,22 +865,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
860865
// it matters only if the fees are exactly the same.
861866
for hop in last_hops.iter() {
862867
let have_hop_src_in_graph =
863-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
864-
// If this hop connects to a node with which we have a direct channel, ignore
865-
// the network graph and add both the hop and our direct channel to
866-
// the candidate set.
867-
//
868-
// Currently there are no channel-context features defined, so we are a
869-
// bit lazy here. In the future, we should pull them out via our
870-
// ChannelManager, but there's no reason to waste the space until we
871-
// need them.
872-
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0);
873-
true
874-
} else {
875-
// In any other case, only add the hop if the source is in the regular network
876-
// graph:
877-
network.get_nodes().get(&hop.src_node_id).is_some()
878-
};
868+
// Only add the last hop to our candidate set if either we have a direct channel or
869+
// they are in the regular network graph.
870+
first_hop_targets.get(&hop.src_node_id).is_some() ||
871+
network.get_nodes().get(&hop.src_node_id).is_some();
879872
if have_hop_src_in_graph {
880873
// BOLT 11 doesn't allow inclusion of features for the last hop hints, which
881874
// really sucks, cause we're gonna need that eventually.
@@ -889,7 +882,18 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
889882
htlc_maximum_msat: hop.htlc_maximum_msat,
890883
fees: hop.fees,
891884
};
892-
add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, &empty_channel_features, 0, path_value_msat, 0);
885+
if add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, &empty_channel_features, 0, path_value_msat, 0) {
886+
// If this hop connects to a node with which we have a direct channel,
887+
// ignore the network graph and, if the last hop was added, add our
888+
// direct channel to the candidate set.
889+
//
890+
// Note that we *must* check if the last hop was added as `add_entry`
891+
// always assumes that the third argument is a node to which we have a
892+
// path.
893+
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
894+
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0);
895+
}
896+
}
893897
}
894898
}
895899

@@ -1170,7 +1174,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
11701174

11711175
#[cfg(test)]
11721176
mod tests {
1173-
use routing::router::{get_route, RouteHint, RouteHintHop, RoutingFees};
1177+
use routing::router::{get_route, Route, RouteHint, RouteHintHop, RoutingFees};
11741178
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler};
11751179
use chain::transaction::OutPoint;
11761180
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
@@ -2318,11 +2322,7 @@ mod tests {
23182322
assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
23192323
}
23202324

2321-
#[test]
2322-
fn unannounced_path_test() {
2323-
// We should be able to send a payment to a destination without any help of a routing graph
2324-
// if we have a channel with a common counterparty that appears in the first and last hop
2325-
// hints.
2325+
fn do_unannounced_path_test(last_hop_htlc_max: Option<u64>, last_hop_fee_prop: u32, outbound_capacity_msat: u64, route_val: u64) -> Result<Route, LightningError> {
23262326
let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
23272327
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
23282328
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
@@ -2333,11 +2333,11 @@ mod tests {
23332333
short_channel_id: 8,
23342334
fees: RoutingFees {
23352335
base_msat: 1000,
2336-
proportional_millionths: 0,
2336+
proportional_millionths: last_hop_fee_prop,
23372337
},
23382338
cltv_expiry_delta: (8 << 8) | 1,
23392339
htlc_minimum_msat: None,
2340-
htlc_maximum_msat: None,
2340+
htlc_maximum_msat: last_hop_htlc_max,
23412341
}]);
23422342
let our_chans = vec![channelmanager::ChannelDetails {
23432343
channel_id: [0; 32],
@@ -2347,31 +2347,59 @@ mod tests {
23472347
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
23482348
channel_value_satoshis: 100000,
23492349
user_id: 0,
2350-
outbound_capacity_msat: 100000,
2350+
outbound_capacity_msat: outbound_capacity_msat,
23512351
inbound_capacity_msat: 100000,
23522352
is_outbound: true, is_funding_locked: true,
23532353
is_usable: true, is_public: true,
23542354
counterparty_forwarding_info: None,
23552355
}];
2356-
let route = get_route(&source_node_id, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), &target_node_id, None, Some(&our_chans.iter().collect::<Vec<_>>()), &vec![&last_hops], 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap();
2356+
get_route(&source_node_id, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), &target_node_id, None, Some(&our_chans.iter().collect::<Vec<_>>()), &vec![&last_hops], route_val, 42, Arc::new(test_utils::TestLogger::new()))
2357+
}
23572358

2359+
#[test]
2360+
fn unannounced_path_test() {
2361+
// We should be able to send a payment to a destination without any help of a routing graph
2362+
// if we have a channel with a common counterparty that appears in the first and last hop
2363+
// hints.
2364+
let route = do_unannounced_path_test(None, 1, 2000000, 1000000).unwrap();
2365+
2366+
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
2367+
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
23582368
assert_eq!(route.paths[0].len(), 2);
23592369

23602370
assert_eq!(route.paths[0][0].pubkey, middle_node_id);
23612371
assert_eq!(route.paths[0][0].short_channel_id, 42);
2362-
assert_eq!(route.paths[0][0].fee_msat, 1000);
2372+
assert_eq!(route.paths[0][0].fee_msat, 1001);
23632373
assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
23642374
assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]);
23652375
assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
23662376

23672377
assert_eq!(route.paths[0][1].pubkey, target_node_id);
23682378
assert_eq!(route.paths[0][1].short_channel_id, 8);
2369-
assert_eq!(route.paths[0][1].fee_msat, 100);
2379+
assert_eq!(route.paths[0][1].fee_msat, 1000000);
23702380
assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
23712381
assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet
23722382
assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
23732383
}
23742384

2385+
#[test]
2386+
fn overflow_unannounced_path_test_liquidity_underflow() {
2387+
// Previously, when we had a last-hop hint connected directly to a first-hop channel, where
2388+
// the last-hop had a fee which overflowed a u64, we'd panic.
2389+
// This was due to us adding the first-hop from us unconditionally, causing us to think
2390+
// we'd built a path (as our node is in the "best candidate" set), when we had not.
2391+
// In this test, we previously hit a subtraction underflow due to having less available
2392+
// liquidity at the last hop than 0.
2393+
assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 0, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err());
2394+
}
2395+
2396+
#[test]
2397+
fn overflow_unannounced_path_test_feerate_overflow() {
2398+
// This tests for the same case as above, except instead of hitting a subtraction
2399+
// underflow, we hit a case where the fee charged at a hop overflowed.
2400+
assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 50000, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err());
2401+
}
2402+
23752403
#[test]
23762404
fn available_amount_while_routing_test() {
23772405
// Tests whether we choose the correct available channel amount while routing.

0 commit comments

Comments
 (0)