|
| 1 | +use bitcoin::secp256k1::PublicKey; |
| 2 | + |
| 3 | +use ln::msgs::{ErrorAction, LightningError}; |
| 4 | +use routing::gossip::{NetworkGraph, NodeId}; |
| 5 | +use util::logger::{Level, Logger}; |
| 6 | + |
| 7 | +use alloc::collections::BinaryHeap; |
| 8 | +use core::hash::Hash; |
| 9 | +use core::ops::Deref; |
| 10 | +use prelude::*; |
| 11 | + |
| 12 | +/// Find a path for sending an onion message. |
| 13 | +pub fn find_path<L: Deref, GL: Deref>( |
| 14 | + our_node_pubkey: &PublicKey, receiver_pubkey: &PublicKey, network_graph: &NetworkGraph<GL>, first_hops: Option<&[&PublicKey]>, logger: L |
| 15 | +) -> Result<Vec<PublicKey>, LightningError> where L::Target: Logger, GL::Target: Logger |
| 16 | +{ |
| 17 | + let graph_lock = network_graph.read_only(); |
| 18 | + let network_channels = graph_lock.channels(); |
| 19 | + let network_nodes = graph_lock.nodes(); |
| 20 | + let our_node_id = NodeId::from_pubkey(our_node_pubkey); |
| 21 | + |
| 22 | + let mut for_each_successor = |node_id, callback: &mut FnMut(&NodeId, u64)| { |
| 23 | + // TODO: in this method, check if OM forwarding feature bit is supported |
| 24 | + if node_id == our_node_id && first_hops.is_some() { |
| 25 | + if let Some(first_hops) = first_hops { |
| 26 | + for hop in first_hops { |
| 27 | + callback(&NodeId::from_pubkey(hop), 1); |
| 28 | + } |
| 29 | + } |
| 30 | + } else if let Some(node_info) = network_nodes.get(&node_id) { |
| 31 | + for scid in &node_info.channels { |
| 32 | + if let Some(chan_info) = network_channels.get(&scid) { |
| 33 | + let successor_node_id = if chan_info.node_one == node_id { |
| 34 | + &chan_info.node_two |
| 35 | + } else { |
| 36 | + debug_assert!(chan_info.node_two == node_id); |
| 37 | + &chan_info.node_one |
| 38 | + }; |
| 39 | + callback(successor_node_id, 1); // Use a fixed cost for each hop until scoring is added |
| 40 | + } |
| 41 | + } |
| 42 | + } |
| 43 | + }; |
| 44 | + |
| 45 | + let mut invalid_final_hop_pk = None; |
| 46 | + let mut convert_final_hop = |node_id: &NodeId| { |
| 47 | + match PublicKey::from_slice(node_id.as_slice()) { |
| 48 | + Ok(pk) => Ok(pk), |
| 49 | + Err(e) => { |
| 50 | + invalid_final_hop_pk = Some(*node_id); |
| 51 | + Err(()) |
| 52 | + }, |
| 53 | + } |
| 54 | + }; |
| 55 | + |
| 56 | + let receiver_node_id = NodeId::from_pubkey(receiver_pubkey); |
| 57 | + match dijkstra(our_node_id, &mut for_each_successor, |node_id| node_id == &receiver_node_id, &mut convert_final_hop) { |
| 58 | + Ok(p) => Ok(p), |
| 59 | + Err(Error::PathNotFound) => Err(LightningError { |
| 60 | + err: "Failed to find a path to the given destination".to_owned(), |
| 61 | + action: ErrorAction::IgnoreError, |
| 62 | + }), |
| 63 | + Err(Error::FinalHopConversion) => { |
| 64 | + debug_assert!(invalid_final_hop_pk.is_some()); |
| 65 | + Err(LightningError { |
| 66 | + err: format!("Public key {:?} is invalid", invalid_final_hop_pk), |
| 67 | + action: ErrorAction::IgnoreAndLog(Level::Trace) |
| 68 | + }) |
| 69 | + } |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +#[derive(Debug, PartialEq)] |
| 74 | +/// Errored running `dijkstra`. |
| 75 | +enum Error { |
| 76 | + /// No path exists to the destination. |
| 77 | + PathNotFound, |
| 78 | + /// Converting the processing hop type to the final hop type failed, see `dijkstra`'s |
| 79 | + /// `convert_final_hop` parameter. |
| 80 | + FinalHopConversion, |
| 81 | +} |
| 82 | + |
| 83 | +// Heavily adapted from https://github.com/samueltardieu/pathfinding/blob/master/src/directed/dijkstra.rs |
| 84 | +// TODO: how2credit the repo (is that necessary?)? |
| 85 | +/// Run Dijkstra's from `start` until `found_target` indicates that we've found the destination. |
| 86 | +/// `successor_callback` must invoke the callback that it is provided on each of a given node's |
| 87 | +/// next-hop peers. `convert_final_hop` may be used to convert an intermediate processing hop type |
| 88 | +/// (`N`) to a final path hop type (`H`). |
| 89 | +fn dijkstra<N, H, FN, FS, FC>(start: N, successor_callback: &mut FN, found_target: FS, |
| 90 | + convert_final_hop: &mut FC) -> Result<Vec<H>, Error> |
| 91 | + where N: Eq + Hash + Copy + Ord, |
| 92 | + FN: FnMut(N, &mut FnMut(&N, u64)), |
| 93 | + FS: Fn(&N) -> bool, |
| 94 | + FC: FnMut(&N) -> Result<H, ()>, |
| 95 | +{ |
| 96 | + let mut to_see = BinaryHeap::new(); |
| 97 | + to_see.push((start, 0)); |
| 98 | + let mut parents: HashMap<N, (N, u64)> = HashMap::new(); |
| 99 | + parents.insert(start, (start, 0)); |
| 100 | + |
| 101 | + let mut target_reached = None; |
| 102 | + while let Some((node, cost)) = to_see.pop() { |
| 103 | + let &(_, c) = parents.get(&node).unwrap(); |
| 104 | + if found_target(&node) { |
| 105 | + target_reached = Some(node); |
| 106 | + break; |
| 107 | + } |
| 108 | + // We may have inserted a node several times into the binary heap if we found a better way to |
| 109 | + // access it. Ensure that we are currently dealing with the best path and discard the others. |
| 110 | + if cost > c { |
| 111 | + continue; |
| 112 | + } |
| 113 | + successor_callback(node, &mut |successor, move_cost| { |
| 114 | + let new_cost = cost + move_cost; |
| 115 | + match parents.entry(*successor) { |
| 116 | + hash_map::Entry::Vacant(e) => { |
| 117 | + e.insert((node, new_cost)); |
| 118 | + to_see.push((*successor, new_cost)); |
| 119 | + } |
| 120 | + hash_map::Entry::Occupied(mut e) => { |
| 121 | + if e.get().1 > new_cost { |
| 122 | + e.insert((node, new_cost)); |
| 123 | + to_see.push((e.get().0, new_cost)); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + }); |
| 128 | + } |
| 129 | + |
| 130 | + match target_reached { |
| 131 | + Some(t) => reverse_path(parents, t, convert_final_hop).map_err(|()| Error::FinalHopConversion), |
| 132 | + None => Err(Error::PathNotFound) |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +// Errors if `convert_path_hop` fails. |
| 137 | +fn reverse_path<N, H, FC>(parents: HashMap<N, (N, u64)>, start: N, convert_path_hop: &mut FC) -> Result<Vec<H>, ()> |
| 138 | + where N: Eq + Hash + Copy + Ord, |
| 139 | + FC: FnMut(&N) -> Result<H, ()>, |
| 140 | +{ |
| 141 | + let mut path = vec![convert_path_hop(&start)?]; |
| 142 | + let mut curr = start; |
| 143 | + loop { |
| 144 | + if let Some((parent_node_id, _)) = parents.get(&curr) { |
| 145 | + if parent_node_id != &curr { |
| 146 | + path.push(convert_path_hop(parent_node_id)?); |
| 147 | + curr = *parent_node_id; |
| 148 | + } else { break; } |
| 149 | + } else { break; } |
| 150 | + } |
| 151 | + path.reverse(); |
| 152 | + path.remove(0); |
| 153 | + Ok(path) |
| 154 | +} |
| 155 | + |
| 156 | +#[cfg(test)] |
| 157 | +mod tests { |
| 158 | + use routing::test_utils; |
| 159 | + use super::dijkstra; |
| 160 | + use super::Error; |
| 161 | + |
| 162 | + use sync::Arc; |
| 163 | + |
| 164 | + fn expected(target: u8) -> Result<Vec<u8>, Error> { |
| 165 | + match target { |
| 166 | + 0 => Ok(vec![0]), |
| 167 | + 1 => Ok(vec![]), |
| 168 | + 2 => Ok(vec![6, 2]), |
| 169 | + 3 => Ok(vec![0, 3]), |
| 170 | + 4 => Ok(vec![6, 4]), |
| 171 | + 5 => Ok(vec![6, 5]), |
| 172 | + 6 => Ok(vec![6]), |
| 173 | + 7 => Ok(vec![0, 3, 7]), |
| 174 | + 8 => Err(Error::PathNotFound), |
| 175 | + _ => panic!("no such node"), |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + #[test] |
| 180 | + fn dijkstra_ok() { |
| 181 | + let successors_lookup : Vec<Vec<(u8, usize)>> = vec![ |
| 182 | + vec![(1, 7), (2, 7), (3, 6)], |
| 183 | + vec![(0, 8), (6, 7)], |
| 184 | + vec![(5, 7)], |
| 185 | + vec![(7, 7)], |
| 186 | + vec![(4, 2)], |
| 187 | + vec![(1, 1)], |
| 188 | + vec![(2, 5), (4, 5), (5, 2)], |
| 189 | + vec![(5, 8)], |
| 190 | + vec![], |
| 191 | + ]; |
| 192 | + let mut successors = |node, callback: &mut FnMut(&u8, u64)| { |
| 193 | + for successor in &successors_lookup[node as usize] { |
| 194 | + callback(&successor.0, 1); |
| 195 | + } |
| 196 | + }; |
| 197 | + for target in 0..9 { |
| 198 | + assert_eq!( |
| 199 | + dijkstra(1, &mut successors, |&node| node == target, &mut |&node| Ok(node)), |
| 200 | + expected(target) |
| 201 | + ); |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + #[test] |
| 206 | + fn one_hop() { |
| 207 | + let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph(); |
| 208 | + let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx); |
| 209 | + |
| 210 | + let path = super::find_path(&our_id, &node_pks[0], &network_graph, None, Arc::clone(&logger)).unwrap(); |
| 211 | + assert_eq!(path.len(), 1); |
| 212 | + assert!(path[0] == node_pks[0]); |
| 213 | + } |
| 214 | + |
| 215 | + #[test] |
| 216 | + fn two_hops() { |
| 217 | + let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph(); |
| 218 | + let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx); |
| 219 | + |
| 220 | + let path = super::find_path(&our_id, &node_pks[2], &network_graph, None, Arc::clone(&logger)).unwrap(); |
| 221 | + assert_eq!(path.len(), 2); |
| 222 | + // See test_utils::build_graph ASCII graph, the first hop can be any of these |
| 223 | + assert!(path[0] == node_pks[1] || path[0] == node_pks[7] || path[0] == node_pks[0]); |
| 224 | + assert_eq!(path[1], node_pks[2]); |
| 225 | + } |
| 226 | + |
| 227 | + #[test] |
| 228 | + fn three_hops() { |
| 229 | + let (secp_ctx, network_graph, _, _, logger) = test_utils::build_graph(); |
| 230 | + let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx); |
| 231 | + |
| 232 | + let mut path = super::find_path(&our_id, &node_pks[5], &network_graph, None, Arc::clone(&logger)).unwrap(); |
| 233 | + assert_eq!(path.len(), 3); |
| 234 | + assert!(path[0] == node_pks[1] || path[0] == node_pks[7] || path[0] == node_pks[0]); |
| 235 | + path.remove(0); |
| 236 | + assert_eq!(path, vec![node_pks[2], node_pks[5]]); |
| 237 | + } |
| 238 | + |
| 239 | + #[test] |
| 240 | + fn long_path() { |
| 241 | + let (secp_ctx, network, _, _, logger) = test_utils::build_line_graph(); |
| 242 | + let (_, our_id, _, node_pks) = test_utils::get_nodes(&secp_ctx); |
| 243 | + let network_graph = network.read_only(); |
| 244 | + |
| 245 | + let path = super::find_path(&our_id, &node_pks[18], &network, None, Arc::clone(&logger)).unwrap(); |
| 246 | + assert_eq!(path.len(), 19); |
| 247 | + } |
| 248 | +} |
0 commit comments