Skip to content

Commit d0a6c37

Browse files
committed
Fix panic in router given to bogus last-hop hints
See new comments and test cases for more info
1 parent 2940099 commit d0a6c37

File tree

1 file changed

+64
-34
lines changed

1 file changed

+64
-34
lines changed

lightning/src/routing/router.rs

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
513513
// $directional_info.
514514
// $next_hops_fee_msat represents the fees paid for using all the channel *after* this one,
515515
// since that value has to be transferred over this channel.
516+
// Returns whether this channel caused an update to `targets`.
516517
( $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,
517518
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr ) => {
518519
// Channels to self should not be used. This is more of belt-and-suspenders, because in
@@ -592,6 +593,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
592593
// as not sufficient.
593594
if !over_path_minimum_msat {
594595
hit_minimum_limit = true;
596+
false
595597
} else if contributes_sufficient_value {
596598
// Note that low contribution here (limited by available_liquidity_msat)
597599
// might violate htlc_minimum_msat on the hops which are next along the
@@ -726,6 +728,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
726728
{
727729
old_entry.value_contribution_msat = value_contribution_msat;
728730
}
731+
true
729732
} else if old_entry.was_processed && new_cost < old_cost {
730733
#[cfg(any(test, feature = "fuzztarget"))]
731734
{
@@ -751,11 +754,12 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
751754
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
752755
debug_assert!(value_contribution_msat < old_entry.value_contribution_msat);
753756
}
754-
}
755-
}
756-
}
757-
}
758-
}
757+
false
758+
} else { false }
759+
} else { false }
760+
} else { false }
761+
} else { false }
762+
} else { false }
759763
};
760764
}
761765

@@ -859,22 +863,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
859863
// it matters only if the fees are exactly the same.
860864
for hop in last_hops.iter() {
861865
let have_hop_src_in_graph =
862-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
863-
// If this hop connects to a node with which we have a direct channel, ignore
864-
// the network graph and add both the hop and our direct channel to
865-
// the candidate set.
866-
//
867-
// Currently there are no channel-context features defined, so we are a
868-
// bit lazy here. In the future, we should pull them out via our
869-
// ChannelManager, but there's no reason to waste the space until we
870-
// need them.
871-
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);
872-
true
873-
} else {
874-
// In any other case, only add the hop if the source is in the regular network
875-
// graph:
876-
network.get_nodes().get(&hop.src_node_id).is_some()
877-
};
866+
// Only add the last hop to our candidate set if either we have a direct channel or
867+
// they are in the regular network graph.
868+
first_hop_targets.get(&hop.src_node_id).is_some() ||
869+
network.get_nodes().get(&hop.src_node_id).is_some();
878870
if have_hop_src_in_graph {
879871
// BOLT 11 doesn't allow inclusion of features for the last hop hints, which
880872
// really sucks, cause we're gonna need that eventually.
@@ -888,7 +880,23 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
888880
htlc_maximum_msat: hop.htlc_maximum_msat,
889881
fees: hop.fees,
890882
};
891-
add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, &empty_channel_features, 0, path_value_msat, 0);
883+
if add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, &empty_channel_features, 0, path_value_msat, 0) {
884+
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
885+
// If this hop connects to a node with which we have a direct channel,
886+
// ignore the network graph and, if the last hop was added, add our
887+
// direct channel to the candidate set.
888+
//
889+
// Note that we *must* check if the last hop was added as `add_entry`
890+
// always assumes that the third argument is a node to which we have a
891+
// path.
892+
//
893+
// Currently there are no channel-context features defined, so we are a
894+
// bit lazy here. In the future, we should pull them out via our
895+
// ChannelManager, but there's no reason to waste the space until we
896+
// need them.
897+
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);
898+
}
899+
}
892900
}
893901
}
894902

@@ -1159,7 +1167,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
11591167

11601168
#[cfg(test)]
11611169
mod tests {
1162-
use routing::router::{get_route, RouteHint, RouteHintHop, RoutingFees};
1170+
use routing::router::{get_route, Route, RouteHint, RouteHintHop, RoutingFees};
11631171
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler};
11641172
use chain::transaction::OutPoint;
11651173
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
@@ -2307,11 +2315,7 @@ mod tests {
23072315
assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
23082316
}
23092317

2310-
#[test]
2311-
fn unannounced_path_test() {
2312-
// We should be able to send a payment to a destination without any help of a routing graph
2313-
// if we have a channel with a common counterparty that appears in the first and last hop
2314-
// hints.
2318+
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> {
23152319
let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
23162320
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
23172321
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
@@ -2322,11 +2326,11 @@ mod tests {
23222326
short_channel_id: 8,
23232327
fees: RoutingFees {
23242328
base_msat: 1000,
2325-
proportional_millionths: 0,
2329+
proportional_millionths: last_hop_fee_prop,
23262330
},
23272331
cltv_expiry_delta: (8 << 8) | 1,
23282332
htlc_minimum_msat: None,
2329-
htlc_maximum_msat: None,
2333+
htlc_maximum_msat: last_hop_htlc_max,
23302334
}]);
23312335
let our_chans = vec![channelmanager::ChannelDetails {
23322336
channel_id: [0; 32],
@@ -2336,31 +2340,57 @@ mod tests {
23362340
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
23372341
channel_value_satoshis: 100000,
23382342
user_id: 0,
2339-
outbound_capacity_msat: 100000,
2343+
outbound_capacity_msat: outbound_capacity_msat,
23402344
inbound_capacity_msat: 100000,
23412345
is_outbound: true, is_funding_locked: true,
23422346
is_usable: true, is_public: true,
23432347
counterparty_forwarding_info: None,
23442348
}];
2345-
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();
2349+
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()))
2350+
}
2351+
#[test]
2352+
fn unannounced_path_test() {
2353+
// We should be able to send a payment to a destination without any help of a routing graph
2354+
// if we have a channel with a common counterparty that appears in the first and last hop
2355+
// hints.
2356+
let route = do_unannounced_path_test(None, 1, 2000000, 1000000).unwrap();
23462357

2358+
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
2359+
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
23472360
assert_eq!(route.paths[0].len(), 2);
23482361

23492362
assert_eq!(route.paths[0][0].pubkey, middle_node_id);
23502363
assert_eq!(route.paths[0][0].short_channel_id, 42);
2351-
assert_eq!(route.paths[0][0].fee_msat, 1000);
2364+
assert_eq!(route.paths[0][0].fee_msat, 1001);
23522365
assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
23532366
assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]);
23542367
assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
23552368

23562369
assert_eq!(route.paths[0][1].pubkey, target_node_id);
23572370
assert_eq!(route.paths[0][1].short_channel_id, 8);
2358-
assert_eq!(route.paths[0][1].fee_msat, 100);
2371+
assert_eq!(route.paths[0][1].fee_msat, 1000000);
23592372
assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
23602373
assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet
23612374
assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
2375+
2376+
}
2377+
2378+
#[test]
2379+
fn overflow_unannounced_path_test_a() {
2380+
// Previously, when we had a last-hop hint connected directly to a first-hop channel, where
2381+
// the last-hop had a value-overflowing fee, we'd panic.
2382+
// This was due to us adding the first-hop from us unconditionally, causing us to think
2383+
// we'd built a path (as our node is in the "best candidate" set), when we had not.
2384+
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());
23622385
}
23632386

2387+
#[test]
2388+
fn overflow_unannounced_path_test_b() {
2389+
// This tests for the same case as above, but with a slightly different codepath.
2390+
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());
2391+
}
2392+
2393+
23642394
#[test]
23652395
fn available_amount_while_routing_test() {
23662396
// Tests whether we choose the correct available channel amount while routing.

0 commit comments

Comments
 (0)