Skip to content

Commit 2825d65

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 2825d65

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
@@ -513,8 +513,11 @@ 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,
517-
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr ) => {
518+
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr ) => { {
519+
// We "return" whether we updated the path at the end, via this:
520+
let mut did_add_update_path_to_src_node = false;
518521
// Channels to self should not be used. This is more of belt-and-suspenders, because in
519522
// practice these cases should be caught earlier:
520523
// - for regular channels at channel announcement (TODO)
@@ -726,6 +729,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
726729
{
727730
old_entry.value_contribution_msat = value_contribution_msat;
728731
}
732+
did_add_update_path_to_src_node = true;
729733
} else if old_entry.was_processed && new_cost < old_cost {
730734
#[cfg(any(test, feature = "fuzztarget"))]
731735
{
@@ -756,7 +760,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
756760
}
757761
}
758762
}
759-
};
763+
did_add_update_path_to_src_node
764+
} }
760765
}
761766

762767
let empty_node_features = NodeFeatures::empty();
@@ -859,22 +864,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
859864
// it matters only if the fees are exactly the same.
860865
for hop in last_hops.iter() {
861866
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-
};
867+
// Only add the last hop to our candidate set if either we have a direct channel or
868+
// they are in the regular network graph.
869+
first_hop_targets.get(&hop.src_node_id).is_some() ||
870+
network.get_nodes().get(&hop.src_node_id).is_some();
878871
if have_hop_src_in_graph {
879872
// BOLT 11 doesn't allow inclusion of features for the last hop hints, which
880873
// really sucks, cause we're gonna need that eventually.
@@ -888,7 +881,18 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
888881
htlc_maximum_msat: hop.htlc_maximum_msat,
889882
fees: hop.fees,
890883
};
891-
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 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 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+
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
893+
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);
894+
}
895+
}
892896
}
893897
}
894898

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

11601164
#[cfg(test)]
11611165
mod tests {
1162-
use routing::router::{get_route, RouteHint, RouteHintHop, RoutingFees};
1166+
use routing::router::{get_route, Route, RouteHint, RouteHintHop, RoutingFees};
11631167
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler};
11641168
use chain::transaction::OutPoint;
11651169
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
@@ -2307,11 +2311,7 @@ mod tests {
23072311
assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
23082312
}
23092313

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.
2314+
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> {
23152315
let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
23162316
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
23172317
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
@@ -2322,11 +2322,11 @@ mod tests {
23222322
short_channel_id: 8,
23232323
fees: RoutingFees {
23242324
base_msat: 1000,
2325-
proportional_millionths: 0,
2325+
proportional_millionths: last_hop_fee_prop,
23262326
},
23272327
cltv_expiry_delta: (8 << 8) | 1,
23282328
htlc_minimum_msat: None,
2329-
htlc_maximum_msat: None,
2329+
htlc_maximum_msat: last_hop_htlc_max,
23302330
}]);
23312331
let our_chans = vec![channelmanager::ChannelDetails {
23322332
channel_id: [0; 32],
@@ -2336,31 +2336,59 @@ mod tests {
23362336
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
23372337
channel_value_satoshis: 100000,
23382338
user_id: 0,
2339-
outbound_capacity_msat: 100000,
2339+
outbound_capacity_msat: outbound_capacity_msat,
23402340
inbound_capacity_msat: 100000,
23412341
is_outbound: true, is_funding_locked: true,
23422342
is_usable: true, is_public: true,
23432343
counterparty_forwarding_info: None,
23442344
}];
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();
2345+
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()))
2346+
}
23462347

2348+
#[test]
2349+
fn unannounced_path_test() {
2350+
// We should be able to send a payment to a destination without any help of a routing graph
2351+
// if we have a channel with a common counterparty that appears in the first and last hop
2352+
// hints.
2353+
let route = do_unannounced_path_test(None, 1, 2000000, 1000000).unwrap();
2354+
2355+
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
2356+
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
23472357
assert_eq!(route.paths[0].len(), 2);
23482358

23492359
assert_eq!(route.paths[0][0].pubkey, middle_node_id);
23502360
assert_eq!(route.paths[0][0].short_channel_id, 42);
2351-
assert_eq!(route.paths[0][0].fee_msat, 1000);
2361+
assert_eq!(route.paths[0][0].fee_msat, 1001);
23522362
assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
23532363
assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]);
23542364
assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
23552365

23562366
assert_eq!(route.paths[0][1].pubkey, target_node_id);
23572367
assert_eq!(route.paths[0][1].short_channel_id, 8);
2358-
assert_eq!(route.paths[0][1].fee_msat, 100);
2368+
assert_eq!(route.paths[0][1].fee_msat, 1000000);
23592369
assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
23602370
assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet
23612371
assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
23622372
}
23632373

2374+
#[test]
2375+
fn overflow_unannounced_path_test_liquidity_underflow() {
2376+
// Previously, when we had a last-hop hint connected directly to a first-hop channel, where
2377+
// the last-hop had a fee which overflowed a u64, we'd panic.
2378+
// This was due to us adding the first-hop from us unconditionally, causing us to think
2379+
// we'd built a path (as our node is in the "best candidate" set), when we had not.
2380+
// In this test, we previously hit a subtraction underflow due to having less available
2381+
// liquidity at the last hop than 0.
2382+
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());
2383+
}
2384+
2385+
#[test]
2386+
fn overflow_unannounced_path_test_feerate_overflow() {
2387+
// This tests for the same case as above, except instead of hitting a subtraction
2388+
// underflow, we hit a case where the fee charged at a hop overflowed.
2389+
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());
2390+
}
2391+
23642392
#[test]
23652393
fn available_amount_while_routing_test() {
23662394
// Tests whether we choose the correct available channel amount while routing.

0 commit comments

Comments
 (0)