Skip to content

Commit c88c71e

Browse files
committed
Allow building of a route from given hops.
Implements `build_route_from_hops`, which provides a simple way to build a route from us (payer) to the target node (payee) via the given hops (which should exclude the payer, but include the payee). This may be useful, e.g., for probing the chosen path.
1 parent aac3907 commit c88c71e

File tree

1 file changed

+104
-15
lines changed

1 file changed

+104
-15
lines changed

lightning/src/routing/router.rs

+104-15
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
1919
use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
2020
use routing::scoring::{ChannelUsage, Score};
2121
use routing::network_graph::{DirectedChannelInfoWithUpdate, EffectiveCapacity, NetworkGraph, ReadOnlyNetworkGraph, NodeId, RoutingFees};
22-
use util::ser::{Writeable, Readable};
22+
use util::ser::{Writeable, Readable, Writer};
2323
use util::logger::{Level, Logger};
2424
use util::chacha20::ChaCha20;
2525

@@ -151,8 +151,8 @@ impl Readable for Route {
151151

152152
/// Parameters needed to find a [`Route`].
153153
///
154-
/// Passed to [`find_route`] and also provided in [`Event::PaymentPathFailed`] for retrying a failed
155-
/// payment path.
154+
/// Passed to [`find_route`] and [`build_route_from_hops`], but also provided in
155+
/// [`Event::PaymentPathFailed`] for retrying a failed payment path.
156156
///
157157
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
158158
#[derive(Clone, Debug)]
@@ -676,16 +676,11 @@ pub fn find_route<L: Deref, S: Score>(
676676
) -> Result<Route, LightningError>
677677
where L::Target: Logger {
678678
let network_graph = network.read_only();
679-
match get_route(
680-
our_node_pubkey, &route_params.payment_params, &network_graph, first_hops, route_params.final_value_msat,
681-
route_params.final_cltv_expiry_delta, logger, scorer, random_seed_bytes
682-
) {
683-
Ok(mut route) => {
684-
add_random_cltv_offset(&mut route, &route_params.payment_params, &network_graph, random_seed_bytes);
685-
Ok(route)
686-
},
687-
Err(err) => Err(err),
688-
}
679+
let mut route = get_route(our_node_pubkey, &route_params.payment_params, &network_graph, first_hops,
680+
route_params.final_value_msat, route_params.final_cltv_expiry_delta, logger, scorer,
681+
random_seed_bytes)?;
682+
add_random_cltv_offset(&mut route, &route_params.payment_params, &network_graph, random_seed_bytes);
683+
Ok(route)
689684
}
690685

691686
pub(crate) fn get_route<L: Deref, S: Score>(
@@ -1703,7 +1698,9 @@ where L::Target: Logger {
17031698
// destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network
17041699
// graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the
17051700
// payment path by adding a randomized 'shadow route' offset to the final hop.
1706-
fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph, random_seed_bytes: &[u8; 32]) {
1701+
fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
1702+
network_graph: &ReadOnlyNetworkGraph, random_seed_bytes: &[u8; 32]
1703+
) {
17071704
let network_channels = network_graph.channels();
17081705
let network_nodes = network_graph.nodes();
17091706

@@ -1785,10 +1782,82 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
17851782
}
17861783
}
17871784

1785+
/// Construct a route from us (payer) to the target node (payee) via the given hops (which should
1786+
/// exclude the payer, but include the payee). This may be useful, e.g., for probing the chosen path.
1787+
///
1788+
/// Re-uses logic from `find_route`, so the restrictions described there also apply here.
1789+
pub fn build_route_from_hops<L: Deref>(
1790+
our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters, network: &NetworkGraph,
1791+
logger: L, random_seed_bytes: &[u8; 32]
1792+
) -> Result<Route, LightningError>
1793+
where L::Target: Logger {
1794+
let network_graph = network.read_only();
1795+
let mut route = build_route_from_hops_internal(
1796+
our_node_pubkey, hops, &route_params.payment_params, &network_graph,
1797+
route_params.final_value_msat, route_params.final_cltv_expiry_delta, logger, random_seed_bytes)?;
1798+
add_random_cltv_offset(&mut route, &route_params.payment_params, &network_graph, random_seed_bytes);
1799+
Ok(route)
1800+
}
1801+
1802+
fn build_route_from_hops_internal<L: Deref>(
1803+
our_node_pubkey: &PublicKey, hops: &[PublicKey], payment_params: &PaymentParameters,
1804+
network_graph: &ReadOnlyNetworkGraph, final_value_msat: u64, final_cltv_expiry_delta: u32,
1805+
logger: L, random_seed_bytes: &[u8; 32]
1806+
) -> Result<Route, LightningError> where L::Target: Logger {
1807+
1808+
struct HopScorer<'a> {
1809+
our_node_id: &'a NodeId,
1810+
hop_ids: &'a [Option<NodeId>],
1811+
}
1812+
1813+
impl Score for HopScorer<'_> {
1814+
fn channel_penalty_msat(&self, _short_channel_id: u64, source: &NodeId, target: &NodeId,
1815+
_usage: ChannelUsage) -> u64
1816+
{
1817+
let mut cur_id = self.our_node_id;
1818+
for i in 0..self.hop_ids.len() {
1819+
if let Some(ref next_id) = self.hop_ids[i] {
1820+
if *cur_id == *source && *next_id == *target {
1821+
return 0;
1822+
}
1823+
cur_id = next_id;
1824+
}
1825+
}
1826+
u64::max_value()
1827+
}
1828+
1829+
fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
1830+
1831+
fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
1832+
}
1833+
1834+
impl<'a> Writeable for HopScorer<'a> {
1835+
#[inline]
1836+
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> {
1837+
unreachable!();
1838+
}
1839+
}
1840+
1841+
if hops.len() > MAX_PATH_LENGTH_ESTIMATE.into() {
1842+
return Err(LightningError{err: "Cannot build a route exceeding the maximum path length.".to_owned(), action: ErrorAction::IgnoreError});
1843+
}
1844+
1845+
let our_node_id = NodeId::from_pubkey(our_node_pubkey);
1846+
let mut hop_ids = [None; MAX_PATH_LENGTH_ESTIMATE as usize];
1847+
for i in 0..hops.len() {
1848+
hop_ids[i] = Some(NodeId::from_pubkey(&hops[i]));
1849+
}
1850+
1851+
let scorer = HopScorer { our_node_id: &our_node_id, hop_ids: &hop_ids };
1852+
1853+
get_route(our_node_pubkey, payment_params, network_graph, None, final_value_msat,
1854+
final_cltv_expiry_delta, logger, &scorer, random_seed_bytes)
1855+
}
1856+
17881857
#[cfg(test)]
17891858
mod tests {
17901859
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId};
1791-
use routing::router::{get_route, add_random_cltv_offset, default_node_features,
1860+
use routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
17921861
PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
17931862
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
17941863
use routing::scoring::{ChannelUsage, Score};
@@ -5486,6 +5555,26 @@ mod tests {
54865555
assert!(path_plausibility.iter().all(|x| *x));
54875556
}
54885557

5558+
#[test]
5559+
fn builds_correct_path_from_hops() {
5560+
let (secp_ctx, network, _, _, logger) = build_graph();
5561+
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
5562+
let network_graph = network.read_only();
5563+
5564+
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
5565+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
5566+
5567+
let payment_params = PaymentParameters::from_node_id(nodes[3]);
5568+
let hops = [nodes[1], nodes[2], nodes[4], nodes[3]];
5569+
let route = build_route_from_hops_internal(&our_id, &hops, &payment_params,
5570+
&network_graph, 100, 0, Arc::clone(&logger), &random_seed_bytes).unwrap();
5571+
let route_hop_pubkeys = route.paths[0].iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
5572+
assert_eq!(hops.len(), route.paths[0].len());
5573+
for (idx, hop_pubkey) in hops.iter().enumerate() {
5574+
assert!(*hop_pubkey == route_hop_pubkeys[idx]);
5575+
}
5576+
}
5577+
54895578
#[cfg(not(feature = "no-std"))]
54905579
pub(super) fn random_init_seed() -> u64 {
54915580
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.

0 commit comments

Comments
 (0)