Skip to content

Commit 2e3763f

Browse files
Implement pathfinding for onion messages
We *could* reuse router::get_route, but it's better to start from scratch because get_route includes a lot of payment-specific computations that impact performance.
1 parent 31a7294 commit 2e3763f

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

lightning/src/routing/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
//! Structs and impls for receiving messages about the network and storing the topology live here.
1111
1212
pub mod gossip;
13+
#[allow(unused)]
14+
// Keep this module private until scoring is added.
15+
mod onion_message;
1316
pub mod router;
1417
pub mod scoring;
1518
#[cfg(test)]
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Onion message pathfinding lives here.
11+
12+
use bitcoin::secp256k1::{self, PublicKey};
13+
14+
use ln::channelmanager::ChannelDetails;
15+
use routing::gossip::{NetworkGraph, NodeId};
16+
use util::logger::Logger;
17+
18+
use alloc::collections::BinaryHeap;
19+
use core::cmp;
20+
use core::ops::Deref;
21+
use prelude::*;
22+
23+
/// Finds a route from us to the given `destination` node.
24+
/// If we have private channels, it may be useful to fill in `first_hops` with the results from
25+
/// [`ChannelManager::list_usable_channels`].
26+
///
27+
/// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels
28+
pub fn find_path<L: Deref, GL: Deref>(
29+
our_node_pubkey: &PublicKey, destination: &PublicKey, network_graph: &NetworkGraph<GL>, first_hops: Option<&[&ChannelDetails]>, logger: L
30+
) -> Result<Vec<PublicKey>, Error> where L::Target: Logger, GL::Target: Logger
31+
{
32+
log_trace!(logger, "Searching for an onion message path from origin {} to destination {} and {} first hops {}overriding the network graph",
33+
our_node_pubkey, destination, first_hops.map(|hops| hops.len()).unwrap_or(0),
34+
if first_hops.is_some() { "" } else { "not " });
35+
let graph_lock = network_graph.read_only();
36+
let network_channels = graph_lock.channels();
37+
let network_nodes = graph_lock.nodes();
38+
let our_node_id = NodeId::from_pubkey(our_node_pubkey);
39+
let dest_node_id = NodeId::from_pubkey(destination);
40+
41+
// Add our start and first-hops to `frontier`.
42+
let start = NodeId::from_pubkey(&our_node_pubkey);
43+
let mut frontier = BinaryHeap::new();
44+
frontier.push(PathBuildingHop { cost: 0, node_id: start, parent_node_id: start });
45+
if let Some(first_hops) = first_hops {
46+
for hop in first_hops {
47+
if !hop.counterparty.features.supports_onion_messages() { continue; }
48+
let node_id = NodeId::from_pubkey(&hop.counterparty.node_id);
49+
frontier.push(PathBuildingHop { cost: 1, node_id, parent_node_id: start });
50+
}
51+
}
52+
53+
let mut visited = HashMap::new();
54+
while let Some(PathBuildingHop { cost, node_id, parent_node_id }) = frontier.pop() {
55+
match visited.entry(node_id) {
56+
hash_map::Entry::Occupied(_) => continue,
57+
hash_map::Entry::Vacant(e) => e.insert(parent_node_id),
58+
};
59+
if node_id == dest_node_id {
60+
return Ok(reverse_path(visited, our_node_id, dest_node_id, logger)?)
61+
}
62+
if let Some(node_info) = network_nodes.get(&node_id) {
63+
for scid in &node_info.channels {
64+
if let Some(chan_info) = network_channels.get(&scid) {
65+
if let Some((_, successor)) = chan_info.as_directed_from(&node_id) {
66+
// We may push a given successor multiple times, but the heap should sort its best entry
67+
// to the top. We do this because there is no way to adjust the priority of an existing
68+
// entry in `BinaryHeap`.
69+
frontier.push(PathBuildingHop {
70+
cost: cost + 1,
71+
node_id: *successor,
72+
parent_node_id: node_id,
73+
});
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
Err(Error::PathNotFound)
81+
}
82+
83+
#[derive(Debug, PartialEq)]
84+
/// Errored running [`find_path`].
85+
pub enum Error {
86+
/// We failed to find a path to the destination.
87+
PathNotFound,
88+
/// We failed to convert this node id into a [`PublicKey`].
89+
InvalidNodeId(secp256k1::Error),
90+
}
91+
92+
#[derive(Eq, PartialEq)]
93+
struct PathBuildingHop {
94+
cost: u64,
95+
node_id: NodeId,
96+
parent_node_id: NodeId,
97+
}
98+
99+
impl PartialOrd for PathBuildingHop {
100+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
101+
// We need a min-heap, whereas `BinaryHeap`s are a max-heap, so compare the costs in reverse.
102+
other.cost.partial_cmp(&self.cost)
103+
}
104+
}
105+
106+
impl Ord for PathBuildingHop {
107+
fn cmp(&self, other: &Self) -> cmp::Ordering {
108+
self.partial_cmp(other).unwrap()
109+
}
110+
}
111+
112+
fn reverse_path<L: Deref>(
113+
parents: HashMap<NodeId, NodeId>, our_node_id: NodeId, destination: NodeId, logger: L
114+
)-> Result<Vec<PublicKey>, Error> where L::Target: Logger
115+
{
116+
let mut path = Vec::new();
117+
let mut curr = destination;
118+
loop {
119+
match PublicKey::from_slice(curr.as_slice()) {
120+
Ok(pk) => path.push(pk),
121+
Err(e) => return Err(Error::InvalidNodeId(e))
122+
}
123+
match parents.get(&curr) {
124+
None => return Err(Error::PathNotFound),
125+
Some(parent) => {
126+
if *parent == our_node_id { break; }
127+
curr = *parent;
128+
}
129+
}
130+
}
131+
132+
path.reverse();
133+
log_info!(logger, "Got route to {:?}: {:?}", destination, path);
134+
Ok(path)
135+
}
136+
137+
#[cfg(test)]
138+
mod tests {
139+
use routing::test_utils;
140+
141+
use sync::Arc;
142+
143+
#[test]
144+
fn one_hop() {
145+
let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph();
146+
let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx);
147+
148+
let path = super::find_path(&our_id, &node_pks[0], &network_graph, None, Arc::clone(&logger)).unwrap();
149+
assert_eq!(path.len(), 1);
150+
assert!(path[0] == node_pks[0]);
151+
}
152+
153+
#[test]
154+
fn two_hops() {
155+
let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph();
156+
let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx);
157+
158+
let path = super::find_path(&our_id, &node_pks[2], &network_graph, None, Arc::clone(&logger)).unwrap();
159+
assert_eq!(path.len(), 2);
160+
// See test_utils::build_graph ASCII graph, the first hop can be any of these
161+
assert!(path[0] == node_pks[1] || path[0] == node_pks[7] || path[0] == node_pks[0]);
162+
assert_eq!(path[1], node_pks[2]);
163+
}
164+
165+
#[test]
166+
fn three_hops() {
167+
let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph();
168+
let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx);
169+
170+
let mut path = super::find_path(&our_id, &node_pks[5], &network_graph, None, Arc::clone(&logger)).unwrap();
171+
assert_eq!(path.len(), 3);
172+
assert!(path[0] == node_pks[1] || path[0] == node_pks[7] || path[0] == node_pks[0]);
173+
path.remove(0);
174+
assert_eq!(path, vec![node_pks[2], node_pks[5]]);
175+
}
176+
177+
#[test]
178+
fn long_path() {
179+
let (secp_ctx, network_graph, _, _, logger) = test_utils::build_line_graph();
180+
let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx);
181+
182+
let path = super::find_path(&our_id, &node_pks[18], &network_graph, None, Arc::clone(&logger)).unwrap();
183+
assert_eq!(path.len(), 19);
184+
}
185+
}

0 commit comments

Comments
 (0)