Skip to content

Commit bd24563

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 8b99b2a commit bd24563

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

lightning/src/routing/mod.rs

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

0 commit comments

Comments
 (0)