Skip to content

Commit a3f7b79

Browse files
committed
Drop A* implementation in the router for simple Dijkstra's
As evidenced by the previous commit, it appears our A* router does worse than a more naive approach. This isn't super surpsising, as the A* heuristic calculation requires a map lookup, which is relatively expensive. ``` test routing::router::benches::generate_mpp_routes_with_probabilistic_scorer ... bench: 169,991,943 ns/iter (+/- 30,838,048) test routing::router::benches::generate_mpp_routes_with_zero_penalty_scorer ... bench: 122,144,987 ns/iter (+/- 61,708,911) test routing::router::benches::generate_routes_with_probabilistic_scorer ... bench: 48,546,068 ns/iter (+/- 10,379,642) test routing::router::benches::generate_routes_with_zero_penalty_scorer ... bench: 32,898,557 ns/iter (+/- 14,157,641) ```
1 parent efdd221 commit a3f7b79

File tree

2 files changed

+16
-116
lines changed

2 files changed

+16
-116
lines changed

lightning/src/routing/gossip.rs

Lines changed: 10 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,10 +1054,6 @@ impl Readable for NodeAlias {
10541054
pub struct NodeInfo {
10551055
/// All valid channels a node has announced
10561056
pub channels: Vec<u64>,
1057-
/// Lowest fees enabling routing via any of the enabled, known channels to a node.
1058-
/// The two fields (flat and proportional fee) are independent,
1059-
/// meaning they don't have to refer to the same channel.
1060-
pub lowest_inbound_channel_fees: Option<RoutingFees>,
10611057
/// More information about a node from node_announcement.
10621058
/// Optional because we store a Node entry after learning about it from
10631059
/// a channel announcement, but before receiving a node announcement.
@@ -1066,16 +1062,16 @@ pub struct NodeInfo {
10661062

10671063
impl fmt::Display for NodeInfo {
10681064
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
1069-
write!(f, "lowest_inbound_channel_fees: {:?}, channels: {:?}, announcement_info: {:?}",
1070-
self.lowest_inbound_channel_fees, &self.channels[..], self.announcement_info)?;
1065+
write!(f, " channels: {:?}, announcement_info: {:?}",
1066+
&self.channels[..], self.announcement_info)?;
10711067
Ok(())
10721068
}
10731069
}
10741070

10751071
impl Writeable for NodeInfo {
10761072
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
10771073
write_tlv_fields!(writer, {
1078-
(0, self.lowest_inbound_channel_fees, option),
1074+
// Note that older versions of LDK wrote the lowest inbound fees here at type 0
10791075
(2, self.announcement_info, option),
10801076
(4, self.channels, vec_type),
10811077
});
@@ -1103,18 +1099,22 @@ impl MaybeReadable for NodeAnnouncementInfoDeserWrapper {
11031099

11041100
impl Readable for NodeInfo {
11051101
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
1106-
_init_tlv_field_var!(lowest_inbound_channel_fees, option);
1102+
// Historically, we tracked the lowest inbound fees for any node in order to use it as an
1103+
// A* heuristic when routing. Sadly, these days many, many nodes have at least one channel
1104+
// with zero inbound fees, causing that heuristic to provide little gain. Worse, because it
1105+
// requires additional complexity and lookups during routing, it ends up being a
1106+
// performance loss. Thus, we simply ignore the old field here and no longer track it.
1107+
let mut _lowest_inbound_channel_fees: Option<RoutingFees> = None;
11071108
let mut announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = None;
11081109
_init_tlv_field_var!(channels, vec_type);
11091110

11101111
read_tlv_fields!(reader, {
1111-
(0, lowest_inbound_channel_fees, option),
1112+
(0, _lowest_inbound_channel_fees, option),
11121113
(2, announcement_info_wrap, ignorable),
11131114
(4, channels, vec_type),
11141115
});
11151116

11161117
Ok(NodeInfo {
1117-
lowest_inbound_channel_fees: _init_tlv_based_struct_field!(lowest_inbound_channel_fees, option),
11181118
announcement_info: announcement_info_wrap.map(|w| w.0),
11191119
channels: _init_tlv_based_struct_field!(channels, vec_type),
11201120
})
@@ -1175,22 +1175,6 @@ impl<L: Deref> ReadableArgs<L> for NetworkGraph<L> where L::Target: Logger {
11751175
(1, last_rapid_gossip_sync_timestamp, option),
11761176
});
11771177

1178-
// Regenerate inbound fees for all channels. The live-updating of these has been broken in
1179-
// various ways historically, so this ensures that we have up-to-date limits.
1180-
for (node_id, node) in nodes.iter_mut() {
1181-
let mut best_fees = RoutingFees { base_msat: u32::MAX, proportional_millionths: u32::MAX };
1182-
for channel in node.channels.iter() {
1183-
if let Some(chan) = channels.get(channel) {
1184-
let dir_opt = if *node_id == chan.node_one { &chan.two_to_one } else { &chan.one_to_two };
1185-
if let Some(dir) = dir_opt {
1186-
best_fees.base_msat = cmp::min(best_fees.base_msat, dir.fees.base_msat);
1187-
best_fees.proportional_millionths = cmp::min(best_fees.proportional_millionths, dir.fees.proportional_millionths);
1188-
}
1189-
} else { return Err(DecodeError::InvalidValue); }
1190-
}
1191-
node.lowest_inbound_channel_fees = Some(best_fees);
1192-
}
1193-
11941178
Ok(NetworkGraph {
11951179
secp_ctx: Secp256k1::verification_only(),
11961180
genesis_hash,
@@ -1430,7 +1414,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
14301414
BtreeEntry::Vacant(node_entry) => {
14311415
node_entry.insert(NodeInfo {
14321416
channels: vec!(short_channel_id),
1433-
lowest_inbound_channel_fees: None,
14341417
announcement_info: None,
14351418
});
14361419
}
@@ -1731,9 +1714,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
17311714
}
17321715

17331716
fn update_channel_intern(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig: Option<&secp256k1::ecdsa::Signature>) -> Result<(), LightningError> {
1734-
let dest_node_id;
17351717
let chan_enabled = msg.flags & (1 << 1) != (1 << 1);
1736-
let chan_was_enabled;
17371718

17381719
#[cfg(all(feature = "std", not(test), not(feature = "_test_utils")))]
17391720
{
@@ -1781,9 +1762,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
17811762
} else if existing_chan_info.last_update == msg.timestamp {
17821763
return Err(LightningError{err: "Update had same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
17831764
}
1784-
chan_was_enabled = existing_chan_info.enabled;
1785-
} else {
1786-
chan_was_enabled = false;
17871765
}
17881766
}
17891767
}
@@ -1811,7 +1789,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18111789

18121790
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
18131791
if msg.flags & 1 == 1 {
1814-
dest_node_id = channel.node_one.clone();
18151792
check_update_latest!(channel.two_to_one);
18161793
if let Some(sig) = sig {
18171794
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_two.as_slice()).map_err(|_| LightningError{
@@ -1821,7 +1798,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18211798
}
18221799
channel.two_to_one = get_new_channel_info!();
18231800
} else {
1824-
dest_node_id = channel.node_two.clone();
18251801
check_update_latest!(channel.one_to_two);
18261802
if let Some(sig) = sig {
18271803
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_one.as_slice()).map_err(|_| LightningError{
@@ -1834,44 +1810,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18341810
}
18351811
}
18361812

1837-
let mut nodes = self.nodes.write().unwrap();
1838-
if chan_enabled {
1839-
let node = nodes.get_mut(&dest_node_id).unwrap();
1840-
let mut base_msat = msg.fee_base_msat;
1841-
let mut proportional_millionths = msg.fee_proportional_millionths;
1842-
if let Some(fees) = node.lowest_inbound_channel_fees {
1843-
base_msat = cmp::min(base_msat, fees.base_msat);
1844-
proportional_millionths = cmp::min(proportional_millionths, fees.proportional_millionths);
1845-
}
1846-
node.lowest_inbound_channel_fees = Some(RoutingFees {
1847-
base_msat,
1848-
proportional_millionths
1849-
});
1850-
} else if chan_was_enabled {
1851-
let node = nodes.get_mut(&dest_node_id).unwrap();
1852-
let mut lowest_inbound_channel_fees = None;
1853-
1854-
for chan_id in node.channels.iter() {
1855-
let chan = channels.get(chan_id).unwrap();
1856-
let chan_info_opt;
1857-
if chan.node_one == dest_node_id {
1858-
chan_info_opt = chan.two_to_one.as_ref();
1859-
} else {
1860-
chan_info_opt = chan.one_to_two.as_ref();
1861-
}
1862-
if let Some(chan_info) = chan_info_opt {
1863-
if chan_info.enabled {
1864-
let fees = lowest_inbound_channel_fees.get_or_insert(RoutingFees {
1865-
base_msat: u32::max_value(), proportional_millionths: u32::max_value() });
1866-
fees.base_msat = cmp::min(fees.base_msat, chan_info.fees.base_msat);
1867-
fees.proportional_millionths = cmp::min(fees.proportional_millionths, chan_info.fees.proportional_millionths);
1868-
}
1869-
}
1870-
}
1871-
1872-
node.lowest_inbound_channel_fees = lowest_inbound_channel_fees;
1873-
}
1874-
18751813
Ok(())
18761814
}
18771815

@@ -3291,7 +3229,6 @@ mod tests {
32913229
// 2. Check we can read a NodeInfo anyways, but set the NodeAnnouncementInfo to None if invalid
32923230
let valid_node_info = NodeInfo {
32933231
channels: Vec::new(),
3294-
lowest_inbound_channel_fees: None,
32953232
announcement_info: Some(valid_node_ann_info),
32963233
};
32973234

lightning/src/routing/router.rs

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,6 @@ impl_writeable_tlv_based!(RouteHintHop, {
582582
#[derive(Eq, PartialEq)]
583583
struct RouteGraphNode {
584584
node_id: NodeId,
585-
lowest_fee_to_peer_through_node: u64,
586585
lowest_fee_to_node: u64,
587586
total_cltv_delta: u32,
588587
// The maximum value a yet-to-be-constructed payment path might flow through this node.
@@ -603,9 +602,9 @@ struct RouteGraphNode {
603602

604603
impl cmp::Ord for RouteGraphNode {
605604
fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering {
606-
let other_score = cmp::max(other.lowest_fee_to_peer_through_node, other.path_htlc_minimum_msat)
605+
let other_score = cmp::max(other.lowest_fee_to_node, other.path_htlc_minimum_msat)
607606
.saturating_add(other.path_penalty_msat);
608-
let self_score = cmp::max(self.lowest_fee_to_peer_through_node, self.path_htlc_minimum_msat)
607+
let self_score = cmp::max(self.lowest_fee_to_node, self.path_htlc_minimum_msat)
609608
.saturating_add(self.path_penalty_msat);
610609
other_score.cmp(&self_score).then_with(|| other.node_id.cmp(&self.node_id))
611610
}
@@ -729,8 +728,6 @@ struct PathBuildingHop<'a> {
729728
candidate: CandidateRouteHop<'a>,
730729
fee_msat: u64,
731730

732-
/// Minimal fees required to route to the source node of the current hop via any of its inbound channels.
733-
src_lowest_inbound_fees: RoutingFees,
734731
/// All the fees paid *after* this channel on the way to the destination
735732
next_hops_fee_msat: u64,
736733
/// Fee paid for the use of the current channel (see candidate.fees()).
@@ -1007,9 +1004,8 @@ where L::Target: Logger {
10071004
// 8. If our maximum channel saturation limit caused us to pick two identical paths, combine
10081005
// them so that we're not sending two HTLCs along the same path.
10091006

1010-
// As for the actual search algorithm,
1011-
// we do a payee-to-payer pseudo-Dijkstra's sorting by each node's distance from the payee
1012-
// plus the minimum per-HTLC fee to get from it to another node (aka "shitty pseudo-A*").
1007+
// As for the actual search algorithm, we do a payee-to-payer Dijkstra's sorting by each node's
1008+
// distance from the payee
10131009
//
10141010
// We are not a faithful Dijkstra's implementation because we can change values which impact
10151011
// earlier nodes while processing later nodes. Specifically, if we reach a channel with a lower
@@ -1044,10 +1040,6 @@ where L::Target: Logger {
10441040
// runtime for little gain. Specifically, the current algorithm rather efficiently explores the
10451041
// graph for candidate paths, calculating the maximum value which can realistically be sent at
10461042
// the same time, remaining generic across different payment values.
1047-
//
1048-
// TODO: There are a few tweaks we could do, including possibly pre-calculating more stuff
1049-
// to use as the A* heuristic beyond just the cost to get one node further than the current
1050-
// one.
10511043

10521044
let network_channels = network_graph.channels();
10531045
let network_nodes = network_graph.nodes();
@@ -1097,7 +1089,7 @@ where L::Target: Logger {
10971089
}
10981090
}
10991091

1100-
// The main heap containing all candidate next-hops sorted by their score (max(A* fee,
1092+
// The main heap containing all candidate next-hops sorted by their score (max(fee,
11011093
// htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
11021094
// adding duplicate entries when we find a better path to a given node.
11031095
let mut targets: BinaryHeap<RouteGraphNode> = BinaryHeap::new();
@@ -1273,20 +1265,10 @@ where L::Target: Logger {
12731265
// semi-dummy record just to compute the fees to reach the source node.
12741266
// This will affect our decision on selecting short_channel_id
12751267
// as a way to reach the $dest_node_id.
1276-
let mut fee_base_msat = 0;
1277-
let mut fee_proportional_millionths = 0;
1278-
if let Some(Some(fees)) = network_nodes.get(&$src_node_id).map(|node| node.lowest_inbound_channel_fees) {
1279-
fee_base_msat = fees.base_msat;
1280-
fee_proportional_millionths = fees.proportional_millionths;
1281-
}
12821268
PathBuildingHop {
12831269
node_id: $dest_node_id.clone(),
12841270
candidate: $candidate.clone(),
12851271
fee_msat: 0,
1286-
src_lowest_inbound_fees: RoutingFees {
1287-
base_msat: fee_base_msat,
1288-
proportional_millionths: fee_proportional_millionths,
1289-
},
12901272
next_hops_fee_msat: u64::max_value(),
12911273
hop_use_fee_msat: u64::max_value(),
12921274
total_fee_msat: u64::max_value(),
@@ -1321,24 +1303,6 @@ where L::Target: Logger {
13211303
Some(fee_msat) => {
13221304
hop_use_fee_msat = fee_msat;
13231305
total_fee_msat += hop_use_fee_msat;
1324-
// When calculating the lowest inbound fees to a node, we
1325-
// calculate fees here not based on the actual value we think
1326-
// will flow over this channel, but on the minimum value that
1327-
// we'll accept flowing over it. The minimum accepted value
1328-
// is a constant through each path collection run, ensuring
1329-
// consistent basis. Otherwise we may later find a
1330-
// different path to the source node that is more expensive,
1331-
// but which we consider to be cheaper because we are capacity
1332-
// constrained and the relative fee becomes lower.
1333-
match compute_fees(minimal_value_contribution_msat, old_entry.src_lowest_inbound_fees)
1334-
.map(|a| a.checked_add(total_fee_msat)) {
1335-
Some(Some(v)) => {
1336-
total_fee_msat = v;
1337-
},
1338-
_ => {
1339-
total_fee_msat = u64::max_value();
1340-
}
1341-
};
13421306
}
13431307
}
13441308
}
@@ -1355,8 +1319,7 @@ where L::Target: Logger {
13551319
.saturating_add(channel_penalty_msat);
13561320
let new_graph_node = RouteGraphNode {
13571321
node_id: $src_node_id,
1358-
lowest_fee_to_peer_through_node: total_fee_msat,
1359-
lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat,
1322+
lowest_fee_to_node: total_fee_msat,
13601323
total_cltv_delta: hop_total_cltv_delta,
13611324
value_contribution_msat,
13621325
path_htlc_minimum_msat,

0 commit comments

Comments
 (0)