diff --git a/lightning/src/routing/mod.rs b/lightning/src/routing/mod.rs index 7fff856345c..d80a50ca085 100644 --- a/lightning/src/routing/mod.rs +++ b/lightning/src/routing/mod.rs @@ -11,6 +11,9 @@ pub mod utxo; pub mod gossip; +#[allow(unused)] +// Keep this module private until scoring is added. +mod onion_message; pub mod router; pub mod scoring; #[cfg(test)] diff --git a/lightning/src/routing/onion_message.rs b/lightning/src/routing/onion_message.rs new file mode 100644 index 00000000000..7a13261ee06 --- /dev/null +++ b/lightning/src/routing/onion_message.rs @@ -0,0 +1,383 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! [Onion message] pathfinding lives here. +//! +//! Finding paths for onion messages is necessary for retrieving invoices and fulfilling invoice +//! requests from [offers]. It differs from payment pathfinding in that channel liquidity, fees, and +//! knobs such as `htlc_maximum_msat` do not factor into path selection -- onion messages require a +//! peer connection and nothing more. However, we still use the network graph because onion messages +//! between channel peers are likely to be prioritized over those between non-channel peers. +//! +//! [Onion message]: crate::onion_message +//! [offers]: + +use bitcoin::secp256k1::{self, PublicKey}; + +use crate::ln::channelmanager::ChannelDetails; +use crate::routing::gossip::{NetworkGraph, NodeId}; +use crate::util::logger::Logger; + +use alloc::collections::BinaryHeap; +use core::cmp; +use core::fmt; +use core::ops::Deref; +use crate::prelude::*; + +/// Finds a route from us to the given `destination` node. +/// If we have private channels, it may be useful to fill in `first_hops` with the results from +/// [`ChannelManager::list_usable_channels`]. If `first_hops` is not filled in, connected peers +/// without public channels will not be forwarded over. +/// +/// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels +pub fn find_path( + our_node_pubkey: &PublicKey, destination: &PublicKey, network_graph: &NetworkGraph, first_hops: Option<&[&ChannelDetails]>, logger: L +) -> Result, Error> where L::Target: Logger, GL::Target: Logger +{ + 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 " }); + let graph_lock = network_graph.read_only(); + let network_channels = graph_lock.channels(); + let network_nodes = graph_lock.nodes(); + let our_node_id = NodeId::from_pubkey(our_node_pubkey); + let dest_node_id = NodeId::from_pubkey(destination); + if our_node_id == dest_node_id { return Err(Error::InvalidDestination) } + + // Add our start and first-hops to `frontier`, which is the set of hops that we'll next explore. + let start = NodeId::from_pubkey(&our_node_pubkey); + let mut valid_first_hops = HashSet::new(); + let mut frontier = BinaryHeap::new(); + let mut visited = HashMap::new(); + if let Some(first_hops) = first_hops { + for hop in first_hops { + if &hop.counterparty.node_id == destination { return Ok(vec![*destination]) } + if hop.counterparty.node_id == *our_node_pubkey { return Err(Error::InvalidFirstHop) } + #[cfg(not(feature = "_bench_unstable"))] + if !hop.counterparty.features.supports_onion_messages() { continue; } + let node_id = NodeId::from_pubkey(&hop.counterparty.node_id); + match visited.entry(node_id) { + hash_map::Entry::Occupied(_) => continue, + hash_map::Entry::Vacant(e) => { e.insert(start); }, + }; + if let Some(node_info) = network_nodes.get(&node_id) { + for scid in &node_info.channels { + if let Some(chan_info) = network_channels.get(&scid) { + if let Some((directed_channel, successor)) = chan_info.as_directed_from(&node_id) { + if *successor == start { continue } // TODO: test + if directed_channel.direction().enabled { + frontier.push(PathBuildingHop { + cost: 1, scid: *scid, one_to_two: chan_info.node_one == node_id, + }); + } + } + } + } + } + valid_first_hops.insert(node_id); + } + } + if frontier.is_empty() { + if let Some(node_info) = network_nodes.get(&start) { + for scid in &node_info.channels { + if let Some(chan_info) = network_channels.get(&scid) { + if let Some((directed_channel, successor)) = chan_info.as_directed_from(&start) { + if directed_channel.direction().enabled { + frontier.push(PathBuildingHop { + cost: 1, scid: *scid, one_to_two: chan_info.node_one == start, + }); + } + } + } + } + } + } + + while let Some(PathBuildingHop { cost, scid, one_to_two }) = frontier.pop() { + if let Some(chan_info) = network_channels.get(&scid) { + let directed_from_node_id = if one_to_two { chan_info.node_one } else { chan_info.node_two }; + let directed_to_node_id = if one_to_two { chan_info.node_two } else { chan_info.node_one }; + match visited.entry(directed_to_node_id) { + hash_map::Entry::Occupied(_) => continue, + hash_map::Entry::Vacant(e) => e.insert(directed_from_node_id), + }; + if directed_to_node_id == dest_node_id { + let path = reverse_path(visited, our_node_id, dest_node_id)?; + log_info!(logger, "Got route to {:?}: {:?}", destination, path); + return Ok(path) + } + if let Some(node_info) = network_nodes.get(&directed_to_node_id) { + // Only consider the network graph if first_hops does not override it. + if valid_first_hops.contains(&directed_to_node_id) || directed_to_node_id == our_node_id { + } else if let Some(node_ann) = &node_info.announcement_info { + #[cfg(not(feature = "_bench_unstable"))] + if !node_ann.features.supports_onion_messages() || node_ann.features.requires_unknown_bits() + { continue; } + } else { continue; } + for scid_to_push in &node_info.channels { + if let Some(chan_info) = network_channels.get(&scid_to_push) { + if let Some((directed_channel, successor)) = chan_info.as_directed_from(&directed_to_node_id) { + if directed_channel.direction().enabled { + let one_to_two = if let Some(chan_info) = network_channels.get(&scid_to_push) { + directed_to_node_id == chan_info.node_one + } else { continue }; + frontier.push(PathBuildingHop { + cost: cost + 1, scid: *scid_to_push, one_to_two, + }); + } + } + } + } + } + } + } + + Err(Error::PathNotFound) +} + +#[derive(Debug, PartialEq)] +/// Errors that might occur running [`find_path`]. +pub enum Error { + /// We failed to find a path to the destination. + PathNotFound, + /// We failed to convert this node id into a [`PublicKey`]. + InvalidNodeId(secp256k1::Error), + /// We attempted to generate a path to ourselves, which is not allowed. + InvalidDestination, + /// First hops cannot have our node id as a counterparty node id. + InvalidFirstHop, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::PathNotFound => write!(f, "Failed to find a path to the destination"), + Error::InvalidNodeId(e) => + write!(f, "Failed to convert a node id into a PublicKey with error: {}", e), + Error::InvalidDestination => write!(f, "Cannot generate a route to ourselves"), + Error::InvalidFirstHop => write!(f, "First hops cannot have our node id as a counterparty node id"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +#[derive(Eq, PartialEq)] +struct PathBuildingHop { + cost: u64, + scid: u64, + one_to_two: bool, +} + +impl PartialOrd for PathBuildingHop { + fn partial_cmp(&self, other: &Self) -> Option { + // We need a min-heap, whereas `BinaryHeap`s are a max-heap, so compare the costs in reverse. + other.cost.partial_cmp(&self.cost) + } +} + +impl Ord for PathBuildingHop { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +fn reverse_path( + parents: HashMap, our_node_id: NodeId, destination: NodeId +)-> Result, Error> +{ + let mut path = Vec::new(); + let mut curr = destination; + loop { + match PublicKey::from_slice(curr.as_slice()) { + Ok(pk) => path.push(pk), + Err(e) => return Err(Error::InvalidNodeId(e)) + } + match parents.get(&curr) { + None => return Err(Error::PathNotFound), + Some(parent) => { + if *parent == our_node_id { break; } + curr = *parent; + } + } + } + + path.reverse(); + Ok(path) +} + +#[cfg(test)] +mod tests { + use crate::ln::features::{InitFeatures, NodeFeatures}; + use crate::routing::test_utils::*; + + use crate::sync::Arc; + + #[test] + fn three_hops() { + let mut features = NodeFeatures::empty(); + features.set_onion_messages_optional(); + let (secp_ctx, network_graph, _, _, logger) = build_graph_with_features(features); + let (_, our_id, _, node_pks) = get_nodes(&secp_ctx); + + let mut path = super::find_path(&our_id, &node_pks[5], &network_graph, None, Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 3); + assert!(path[0] == node_pks[1] || path[0] == node_pks[7] || path[0] == node_pks[0]); + path.remove(0); + assert_eq!(path, vec![node_pks[2], node_pks[5]]); + } + + #[test] + fn long_path() { + let mut features = NodeFeatures::empty(); + features.set_onion_messages_optional(); + let (secp_ctx, network_graph, _, _, logger) = build_line_graph_with_features(features); + let (_, our_id, _, node_pks) = get_nodes(&secp_ctx); + + let path = super::find_path(&our_id, &node_pks[18], &network_graph, None, Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 19); + } + + #[test] + fn disable_nodes_test() { + // Check that we won't route over nodes that require unknown feature bits. + let mut features = InitFeatures::empty(); + features.set_onion_messages_optional(); + let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph_with_features(features.to_context()); + let (_, our_id, privkeys, node_pks) = get_nodes(&secp_ctx); + + // Disable nodes 1, 2, and 8 by requiring unknown feature bits + let mut unknown_features = NodeFeatures::empty(); + unknown_features.set_unknown_feature_required(); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], unknown_features.clone(), 1); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], unknown_features.clone(), 1); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], unknown_features.clone(), 1); + + // If all nodes require some features we don't understand, route should fail + let err = super::find_path(&our_id, &node_pks[2], &network_graph, None, Arc::clone(&logger)).unwrap_err(); + assert_eq!(err, super::Error::PathNotFound); + + // If we specify a channel to node7, that overrides our local channel view and that gets used + let our_chans = vec![get_channel_details(Some(42), node_pks[7].clone(), features, 250_000_000)]; + let path = super::find_path(&our_id, &node_pks[2], &network_graph, Some(&our_chans.iter().collect::>()), Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 2); + assert_eq!(path[0], node_pks[7]); + assert_eq!(path[1], node_pks[2]); + } + + #[test] + fn disabled_channels_test() { + // Check that we won't attempt to route over nodes where the channel is disabled from their + // direction (implying the peer is offline). + let mut features = InitFeatures::empty(); + features.set_onion_messages_optional(); + let (secp_ctx, network_graph, _, _, logger) = build_graph_with_features(features.to_context()); + let (_, our_id, _, node_pks) = get_nodes(&secp_ctx); + + // Route to 1 via 2 and 3 because our channel to 1 is disabled + let path = super::find_path(&our_id, &node_pks[0], &network_graph, None, Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 3); + assert!((path[0] == node_pks[1]) || (path[0] == node_pks[7])); + assert_eq!(path[1], node_pks[2]); + assert_eq!(path[2], node_pks[0]); + + // If we specify a channel to node1, that overrides our local channel view and that gets used + let our_chans = vec![get_channel_details(Some(42), node_pks[0].clone(), features, 250_000_000)]; + let path = super::find_path(&our_id, &node_pks[0], &network_graph, Some(&our_chans.iter().collect::>()), Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 1); + assert_eq!(path[0], node_pks[0]); + } + + #[test] + fn invalid_first_hop() { + // Check that we can't generate a path if first_hops contains a counterparty node id that + // is equal to our node id. + let mut features = InitFeatures::empty(); + features.set_onion_messages_optional(); + let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph_with_features(features.to_context()); + let (_, our_id, privkeys, node_pks) = get_nodes(&secp_ctx); + + let bad_first_hop = vec![get_channel_details(Some(2), our_id, features, 100000)]; + let err = super::find_path(&our_id, &node_pks[2], &network_graph, Some(&bad_first_hop.iter().collect::>()), Arc::clone(&logger)).unwrap_err(); + assert_eq!(err, super::Error::InvalidFirstHop); + + let path = super::find_path(&our_id, &node_pks[2], &network_graph, None, Arc::clone(&logger)).unwrap(); + assert_eq!(path.len(), 2); + } +} + +#[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))] +mod benches { + use super::*; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use crate::routing::gossip::NetworkGraph; + use crate::routing::router::bench_utils; + use crate::test::Bencher; + use crate::util::logger::{Logger, Record}; + use crate::util::ser::ReadableArgs; + + struct DummyLogger {} + impl Logger for DummyLogger { + fn log(&self, _record: &Record) {} + } + + fn read_network_graph(logger: &DummyLogger) -> NetworkGraph<&DummyLogger> { + let mut d = bench_utils::get_route_file().unwrap(); + NetworkGraph::read(&mut d, logger).unwrap() + } + + #[bench] + fn generate_simple_routes(bench: &mut Bencher) { + let logger = DummyLogger {}; + let network_graph = read_network_graph(&logger); + generate_routes(bench, &network_graph); + } + + fn payer_pubkey() -> PublicKey { + let secp_ctx = Secp256k1::new(); + PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) + } + + fn generate_routes( + bench: &mut Bencher, graph: &NetworkGraph<&DummyLogger>, + ) { + let nodes = graph.read_only().nodes().clone(); + let payer = payer_pubkey(); + + // Get 100 (source, destination) pairs for which route-getting actually succeeds... + let mut routes = Vec::new(); + let mut route_endpoints = Vec::new(); + let mut seed: usize = 0xdeadbeef; + 'load_endpoints: for _ in 0..150 { + loop { + seed *= 0xdeadbeef; + let src = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + let first_hop = bench_utils::first_hop(src); + seed *= 0xdeadbeef; + let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); + if let Ok(route) = find_path(&payer, &dst, &graph, Some(&[&first_hop]), &DummyLogger{}) { + routes.push(route); + route_endpoints.push((first_hop, dst)); + continue 'load_endpoints; + } + } + } + route_endpoints.truncate(100); + assert_eq!(route_endpoints.len(), 100); + + // Benchmark finding paths between the nodes we learned. + let mut idx = 0; + bench.iter(|| { + let (first_hop, dst) = &route_endpoints[idx % route_endpoints.len()]; + assert!(find_path(&payer, &dst, &graph, Some(&[first_hop]), &DummyLogger{}).is_ok()); + idx += 1; + }); + } +} diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 9682503ee45..ed89c3acff9 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -2147,8 +2147,7 @@ mod tests { PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE}; use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, Score, ProbabilisticScorer, ProbabilisticScoringParameters}; - use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel}; - use crate::chain::transaction::OutPoint; + use crate::routing::test_utils::*; use crate::chain::keysinterface::EntropySource; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; @@ -2159,7 +2158,6 @@ mod tests { #[cfg(c_bindings)] use crate::util::ser::{Writeable, Writer}; - use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::script::Builder; @@ -2176,41 +2174,6 @@ mod tests { use core::convert::TryInto; - fn get_channel_details(short_channel_id: Option, node_id: PublicKey, - features: InitFeatures, outbound_capacity_msat: u64) -> channelmanager::ChannelDetails { - channelmanager::ChannelDetails { - channel_id: [0; 32], - counterparty: channelmanager::ChannelCounterparty { - features, - node_id, - unspendable_punishment_reserve: 0, - forwarding_info: None, - outbound_htlc_minimum_msat: None, - outbound_htlc_maximum_msat: None, - }, - funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), - channel_type: None, - short_channel_id, - outbound_scid_alias: None, - inbound_scid_alias: None, - channel_value_satoshis: 0, - user_channel_id: 0, - balance_msat: 0, - outbound_capacity_msat, - next_outbound_htlc_limit_msat: outbound_capacity_msat, - inbound_capacity_msat: 42, - unspendable_punishment_reserve: None, - confirmations_required: None, - confirmations: None, - force_close_spend_delay: None, - is_outbound: true, is_channel_ready: true, - is_usable: true, is_public: true, - inbound_htlc_minimum_msat: None, - inbound_htlc_maximum_msat: None, - config: None, - } - } - #[test] fn simple_route_test() { let (secp_ctx, network_graph, _, _, logger) = build_graph(); @@ -5328,7 +5291,8 @@ mod tests { #[test] fn limits_path_length() { - let (secp_ctx, network, _, _, logger) = build_line_graph(); + let features = NodeFeatures::from_le_bytes(id_to_feature_flags(1)); + let (secp_ctx, network, _, _, logger) = build_line_graph_with_features(features); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); let network_graph = network.read_only(); @@ -5606,7 +5570,8 @@ mod tests { #[test] fn honors_manual_penalties() { - let (secp_ctx, network_graph, _, _, logger) = build_line_graph(); + let features = NodeFeatures::from_le_bytes(id_to_feature_flags(1)); + let (secp_ctx, network_graph, _, _, logger) = build_line_graph_with_features(features); let (_, our_id, _, nodes) = get_nodes(&secp_ctx); let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet); @@ -5644,7 +5609,11 @@ mod tests { #[cfg(all(test, not(feature = "no-std")))] pub(crate) mod bench_utils { + #[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))] + use bitcoin::secp256k1::PublicKey; + use std::fs::File; + /// Tries to open a network graph file, or panics with a URL to fetch it. pub(crate) fn get_route_file() -> Result { let res = File::open("net_graph-2023-01-18.bin") // By default we're run in RL/lightning @@ -5667,42 +5636,15 @@ pub(crate) mod bench_utils { #[cfg(not(require_route_graph_test))] return res; } -} - -#[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))] -mod benches { - use super::*; - use bitcoin::hashes::Hash; - use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; - use crate::chain::transaction::OutPoint; - use crate::chain::keysinterface::{EntropySource, KeysManager}; - use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails}; - use crate::ln::features::InvoiceFeatures; - use crate::routing::gossip::NetworkGraph; - use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters}; - use crate::util::config::UserConfig; - use crate::util::logger::{Logger, Record}; - use crate::util::ser::ReadableArgs; - - use test::Bencher; - - struct DummyLogger {} - impl Logger for DummyLogger { - fn log(&self, _record: &Record) {} - } - - fn read_network_graph(logger: &DummyLogger) -> NetworkGraph<&DummyLogger> { - let mut d = bench_utils::get_route_file().unwrap(); - NetworkGraph::read(&mut d, logger).unwrap() - } - - fn payer_pubkey() -> PublicKey { - let secp_ctx = Secp256k1::new(); - PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) - } #[inline] - fn first_hop(node_id: PublicKey) -> ChannelDetails { + #[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))] + pub(crate) fn first_hop(node_id: PublicKey) -> crate::ln::channelmanager::ChannelDetails { + use bitcoin::hashes::Hash; + use crate::chain::transaction::OutPoint; + use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails}; + use crate::util::config::UserConfig; + ChannelDetails { channel_id: [0; 32], counterparty: ChannelCounterparty { @@ -5739,6 +5681,37 @@ mod benches { config: None, } } +} + +#[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))] +mod benches { + use super::*; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use crate::chain::keysinterface::{EntropySource, KeysManager}; + use crate::ln::channelmanager; + use crate::ln::features::InvoiceFeatures; + use crate::routing::gossip::NetworkGraph; + use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters}; + use crate::util::config::UserConfig; + use crate::util::logger::{Logger, Record}; + use crate::util::ser::ReadableArgs; + + use test::Bencher; + + struct DummyLogger {} + impl Logger for DummyLogger { + fn log(&self, _record: &Record) {} + } + + fn read_network_graph(logger: &DummyLogger) -> NetworkGraph<&DummyLogger> { + let mut d = bench_utils::get_route_file().unwrap(); + NetworkGraph::read(&mut d, logger).unwrap() + } + + fn payer_pubkey() -> PublicKey { + let secp_ctx = Secp256k1::new(); + PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) + } #[bench] fn generate_routes_with_zero_penalty_scorer(bench: &mut Bencher) { @@ -5794,7 +5767,7 @@ mod benches { seed *= 0xdeadbeef; let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap(); let params = PaymentParameters::from_node_id(dst, 42).with_features(features.clone()); - let first_hop = first_hop(src); + let first_hop = bench_utils::first_hop(src); let amt = seed as u64 % 1_000_000; if let Ok(route) = get_route(&payer, ¶ms, &graph.read_only(), Some(&[&first_hop]), amt, 42, &DummyLogger{}, &scorer, &random_seed_bytes) { routes.push(route); diff --git a/lightning/src/routing/test_utils.rs b/lightning/src/routing/test_utils.rs index 68264207f58..6740c2ae134 100644 --- a/lightning/src/routing/test_utils.rs +++ b/lightning/src/routing/test_utils.rs @@ -7,8 +7,10 @@ // You may not use this file except in accordance with one or both of these // licenses. +use crate::chain::transaction::OutPoint; use crate::routing::gossip::{NetworkGraph, P2PGossipSync}; -use crate::ln::features::{ChannelFeatures, NodeFeatures}; +use crate::ln::channelmanager::{ChannelCounterparty, ChannelDetails}; +use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::msgs::{UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler, NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate, MAX_VALUE_MSAT}; use crate::util::test_utils; @@ -29,6 +31,41 @@ use crate::sync::{self, Arc}; use crate::routing::gossip::NodeId; +pub(super) fn get_channel_details(short_channel_id: Option, node_id: PublicKey, + features: InitFeatures, outbound_capacity_msat: u64) -> ChannelDetails { + ChannelDetails { + channel_id: [0; 32], + counterparty: ChannelCounterparty { + features, + node_id, + unspendable_punishment_reserve: 0, + forwarding_info: None, + outbound_htlc_minimum_msat: None, + outbound_htlc_maximum_msat: None, + }, + funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), + channel_type: None, + short_channel_id, + outbound_scid_alias: None, + inbound_scid_alias: None, + channel_value_satoshis: 0, + user_channel_id: 0, + balance_msat: 0, + outbound_capacity_msat, + next_outbound_htlc_limit_msat: outbound_capacity_msat, + inbound_capacity_msat: 42, + unspendable_punishment_reserve: None, + confirmations_required: None, + confirmations: None, + force_close_spend_delay: None, + is_outbound: true, is_channel_ready: true, + is_usable: true, is_public: true, + inbound_htlc_minimum_msat: None, + inbound_htlc_maximum_msat: None, + config: None, + } +} + // Using the same keys for LN and BTC ids pub(super) fn add_channel( gossip_sync: &P2PGossipSync>>, Arc, Arc>, @@ -133,7 +170,7 @@ pub(super) fn id_to_feature_flags(id: u8) -> Vec { } } -pub(super) fn build_line_graph() -> ( +pub(super) fn build_line_graph_with_features(node_features: NodeFeatures) -> ( Secp256k1, sync::Arc>>, P2PGossipSync>>, sync::Arc, sync::Arc>, sync::Arc, sync::Arc, @@ -178,7 +215,7 @@ pub(super) fn build_line_graph() -> ( excess_data: Vec::new() }); add_or_update_node(&gossip_sync, &secp_ctx, &next_privkey, - NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); + node_features.clone(), 0); } (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) @@ -190,6 +227,26 @@ pub(super) fn build_graph() -> ( P2PGossipSync>>, sync::Arc, sync::Arc>, sync::Arc, sync::Arc, +) { + build_graph_inner(None) +} + +pub(super) fn build_graph_with_features(features: NodeFeatures) -> ( + Secp256k1, + sync::Arc>>, + P2PGossipSync>>, sync::Arc, sync::Arc>, + sync::Arc, + sync::Arc, +) { + build_graph_inner(Some(features)) +} + +fn build_graph_inner(features: Option) -> ( + Secp256k1, + sync::Arc>>, + P2PGossipSync>>, sync::Arc, sync::Arc>, + sync::Arc, + sync::Arc, ) { let secp_ctx = Secp256k1::new(); let logger = Arc::new(test_utils::TestLogger::new()); @@ -271,7 +328,7 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(1))), 0); add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2); update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { @@ -299,7 +356,7 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], NodeFeatures::from_le_bytes(id_to_feature_flags(2)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(2))), 0); add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[7], ChannelFeatures::from_le_bytes(id_to_feature_flags(12)), 12); update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate { @@ -327,7 +384,7 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], NodeFeatures::from_le_bytes(id_to_feature_flags(8)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(8))), 0); add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3); update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate { @@ -407,7 +464,7 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], NodeFeatures::from_le_bytes(id_to_feature_flags(3)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(3))), 0); add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6); update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { @@ -461,9 +518,9 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], NodeFeatures::from_le_bytes(id_to_feature_flags(5)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(5))), 0); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], NodeFeatures::from_le_bytes(id_to_feature_flags(4)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(4))), 0); add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[5], ChannelFeatures::from_le_bytes(id_to_feature_flags(7)), 7); update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate { @@ -491,7 +548,7 @@ pub(super) fn build_graph() -> ( excess_data: Vec::new() }); - add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[5], NodeFeatures::from_le_bytes(id_to_feature_flags(6)), 0); + add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[5], features.clone().unwrap_or_else(|| NodeFeatures::from_le_bytes(id_to_feature_flags(6))), 0); (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) }