use util::ser::{Writeable, Readable};
use util::logger::Logger;
-use std::cmp;
-use std::collections::{HashMap, BinaryHeap};
-use std::ops::Deref;
+use prelude::*;
+use alloc::collections::BinaryHeap;
+use core::cmp;
+use core::ops::Deref;
/// A hop in a route
#[derive(Clone, PartialEq)]
pub cltv_expiry_delta: u32,
}
-/// (C-not exported)
-impl Writeable for Vec<RouteHop> {
- fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
- (self.len() as u8).write(writer)?;
- for hop in self.iter() {
- hop.pubkey.write(writer)?;
- hop.node_features.write(writer)?;
- hop.short_channel_id.write(writer)?;
- hop.channel_features.write(writer)?;
- hop.fee_msat.write(writer)?;
- hop.cltv_expiry_delta.write(writer)?;
- }
- Ok(())
- }
-}
-
-/// (C-not exported)
-impl Readable for Vec<RouteHop> {
- fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Vec<RouteHop>, DecodeError> {
- let hops_count: u8 = Readable::read(reader)?;
- let mut hops = Vec::with_capacity(hops_count as usize);
- for _ in 0..hops_count {
- hops.push(RouteHop {
- pubkey: Readable::read(reader)?,
- node_features: Readable::read(reader)?,
- short_channel_id: Readable::read(reader)?,
- channel_features: Readable::read(reader)?,
- fee_msat: Readable::read(reader)?,
- cltv_expiry_delta: Readable::read(reader)?,
- });
- }
- Ok(hops)
- }
-}
+impl_writeable_tlv_based!(RouteHop, {
+ (0, pubkey),
+ (2, node_features),
+ (4, short_channel_id),
+ (6, channel_features),
+ (8, fee_msat),
+ (10, cltv_expiry_delta),
+}, {}, {});
/// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,
/// it can take multiple paths. Each path is composed of one or more hops through the network.
pub paths: Vec<Vec<RouteHop>>,
}
+const SERIALIZATION_VERSION: u8 = 1;
+const MIN_SERIALIZATION_VERSION: u8 = 1;
+
impl Writeable for Route {
fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+ write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
(self.paths.len() as u64).write(writer)?;
for hops in self.paths.iter() {
- hops.write(writer)?;
+ (hops.len() as u8).write(writer)?;
+ for hop in hops.iter() {
+ hop.write(writer)?;
+ }
}
+ write_tlv_fields!(writer, {}, {});
Ok(())
}
}
impl Readable for Route {
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
+ let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
let path_count: u64 = Readable::read(reader)?;
let mut paths = Vec::with_capacity(cmp::min(path_count, 128) as usize);
for _ in 0..path_count {
- paths.push(Readable::read(reader)?);
+ let hop_count: u8 = Readable::read(reader)?;
+ let mut hops = Vec::with_capacity(hop_count as usize);
+ for _ in 0..hop_count {
+ hops.push(Readable::read(reader)?);
+ }
+ paths.push(hops);
}
+ read_tlv_fields!(reader, {}, {});
Ok(Route { paths })
}
}
-/// A channel descriptor which provides a last-hop route to get_route
-#[derive(Clone)]
-pub struct RouteHint {
+/// A list of hops along a payment path terminating with a channel to the recipient.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct RouteHint(pub Vec<RouteHintHop>);
+
+/// A channel descriptor for a hop along a payment path.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct RouteHintHop {
/// The node_id of the non-target end of the route
pub src_node_id: PublicKey,
/// The short_channel_id of this channel
/// These fee values are useful to choose hops as we traverse the graph "payee-to-payer".
#[derive(Clone)]
struct PathBuildingHop<'a> {
- // The RouteHint fields which will eventually be used if this hop is used in a final Route.
+ // The RouteHintHop fields which will eventually be used if this hop is used in a final Route.
// Note that node_features is calculated separately after our initial graph walk.
pubkey: PublicKey,
short_channel_id: u64,
/// If the payee provided features in their invoice, they should be provided via payee_features.
/// Without this, MPP will only be used if the payee's features are available in the network graph.
///
-/// Extra routing hops between known nodes and the target will be used if they are included in
-/// last_hops.
+/// Private routing paths between a public node and the target may be included in `last_hops`.
+/// Currently, only the last hop in each path is considered.
///
/// If some channels aren't announced, it may be useful to fill in a first_hops with the
/// results from a local ChannelManager::list_usable_channels() call. If it is filled in, our
return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
}
- for last_hop in last_hops {
+ let last_hops = last_hops.iter().filter_map(|hops| hops.0.last()).collect::<Vec<_>>();
+ for last_hop in last_hops.iter() {
if last_hop.src_node_id == *payee {
return Err(LightningError{err: "Last hop cannot have a payee as a source.".to_owned(), action: ErrorAction::IgnoreError});
}
#[cfg(test)]
mod tests {
- use routing::router::{get_route, RouteHint, RoutingFees};
+ use routing::router::{get_route, RouteHint, RouteHintHop, RoutingFees};
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler};
+ use chain::transaction::OutPoint;
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
use ln::msgs::{ErrorAction, LightningError, OptionalField, UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler,
NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate};
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
use bitcoin::secp256k1::{Secp256k1, All};
+ use prelude::*;
use std::sync::Arc;
// Using the same keys for LN and BTC ids
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(2),
remote_network_id: our_id,
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 100000,
inbound_capacity_msat: 100000,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: nodes[7].clone(),
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 250_000_000,
inbound_capacity_msat: 0,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2], None, Some(&our_chans.iter().collect::<Vec<_>>()), &Vec::new(), 100, 42, Arc::clone(&logger)).unwrap();
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: nodes[7].clone(),
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 250_000_000,
inbound_capacity_msat: 0,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2], None, Some(&our_chans.iter().collect::<Vec<_>>()), &Vec::new(), 100, 42, Arc::clone(&logger)).unwrap();
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: nodes[7].clone(),
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 250_000_000,
inbound_capacity_msat: 0,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2], None, Some(&our_chans.iter().collect::<Vec<_>>()), &Vec::new(), 100, 42, Arc::clone(&logger)).unwrap();
base_msat: 0,
proportional_millionths: 0,
};
- vec!(RouteHint {
+ vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[3].clone(),
short_channel_id: 8,
fees: zero_fees,
cltv_expiry_delta: (8 << 8) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- }, RouteHint {
+ }]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[4].clone(),
short_channel_id: 9,
fees: RoutingFees {
cltv_expiry_delta: (9 << 8) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- }, RouteHint {
+ }]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[5].clone(),
short_channel_id: 10,
fees: zero_fees,
cltv_expiry_delta: (10 << 8) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- })
+ }])]
}
#[test]
// Simple test across 2, 3, 5, and 4 via a last_hop channel
- // First check that lst hop can't have its source as the payee.
- let invalid_last_hop = RouteHint {
+ // First check that last hop can't have its source as the payee.
+ let invalid_last_hop = RouteHint(vec![RouteHintHop {
src_node_id: nodes[6],
short_channel_id: 8,
fees: RoutingFees {
cltv_expiry_delta: (8 << 8) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- };
+ }]);
let mut invalid_last_hops = last_hops(&nodes);
invalid_last_hops.push(invalid_last_hop);
// Simple test with outbound channel to 4 to test that last_hops and first_hops connect
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: nodes[3].clone(),
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 250_000_000,
inbound_capacity_msat: 0,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
let mut last_hops = last_hops(&nodes);
assert_eq!(route.paths[0][1].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
- last_hops[0].fees.base_msat = 1000;
+ last_hops[0].0[0].fees.base_msat = 1000;
// Revert to via 6 as the fee on 8 goes up
let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &last_hops.iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
// If we specify a channel to a middle hop, that overrides our local channel view and that gets used
- let last_hops = vec![RouteHint {
+ let last_hops = RouteHint(vec![RouteHintHop {
src_node_id: middle_node_id,
short_channel_id: 8,
fees: RoutingFees {
cltv_expiry_delta: (8 << 8) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- }];
+ }]);
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: middle_node_id,
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 100000,
inbound_capacity_msat: 100000,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
- let route = get_route(&source_node_id, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), &target_node_id, None, Some(&our_chans.iter().collect::<Vec<_>>()), &last_hops.iter().collect::<Vec<_>>(), 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap();
+ let route = get_route(&source_node_id, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), &target_node_id, None, Some(&our_chans.iter().collect::<Vec<_>>()), &vec![&last_hops], 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap();
assert_eq!(route.paths[0].len(), 2);
// Now, limit the first_hop by the outbound_capacity_msat of 200_000 sats.
let our_chans = vec![channelmanager::ChannelDetails {
channel_id: [0; 32],
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(42),
remote_network_id: nodes[0].clone(),
counterparty_features: InitFeatures::from_le_bytes(vec![0b11]),
user_id: 0,
outbound_capacity_msat: 200_000_000,
inbound_capacity_msat: 0,
- is_live: true,
+ is_outbound: true, is_funding_locked: true,
+ is_usable: true, is_public: true,
counterparty_forwarding_info: None,
}];
}
}
- use std::fs::File;
- use util::ser::Readable;
- /// Tries to open a network graph file, or panics with a URL to fetch it.
- pub(super) fn get_route_file() -> Result<std::fs::File, std::io::Error> {
- let res = File::open("net_graph-2021-02-12.bin") // By default we're run in RL/lightning
- .or_else(|_| File::open("lightning/net_graph-2021-02-12.bin")) // We may be run manually in RL/
- .or_else(|_| { // Fall back to guessing based on the binary location
- // path is likely something like .../rust-lightning/target/debug/deps/lightning-...
- let mut path = std::env::current_exe().unwrap();
- path.pop(); // lightning-...
- path.pop(); // deps
- path.pop(); // debug
- path.pop(); // target
- path.push("lightning");
- path.push("net_graph-2021-02-12.bin");
- eprintln!("{}", path.to_str().unwrap());
- File::open(path)
- });
- #[cfg(require_route_graph_test)]
- return Ok(res.expect("Didn't have route graph and was configured to require it"));
- #[cfg(not(require_route_graph_test))]
- return res;
- }
-
+ #[cfg(not(feature = "no_std"))]
pub(super) fn random_init_seed() -> u64 {
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
- use std::hash::{BuildHasher, Hasher};
+ use core::hash::{BuildHasher, Hasher};
let seed = std::collections::hash_map::RandomState::new().build_hasher().finish();
println!("Using seed of {}", seed);
seed
}
+ #[cfg(not(feature = "no_std"))]
+ use util::ser::Readable;
#[test]
+ #[cfg(not(feature = "no_std"))]
fn generate_routes() {
- let mut d = match get_route_file() {
+ let mut d = match super::test_utils::get_route_file() {
Ok(f) => f,
- Err(_) => {
- eprintln!("Please fetch https://bitcoin.ninja/ldk-net_graph-879e309c128-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin");
+ Err(e) => {
+ eprintln!("{}", e);
return;
},
};
}
#[test]
+ #[cfg(not(feature = "no_std"))]
fn generate_routes_mpp() {
- let mut d = match get_route_file() {
+ let mut d = match super::test_utils::get_route_file() {
Ok(f) => f,
- Err(_) => {
- eprintln!("Please fetch https://bitcoin.ninja/ldk-net_graph-879e309c128-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin");
+ Err(e) => {
+ eprintln!("{}", e);
return;
},
};
}
}
-#[cfg(all(test, feature = "unstable"))]
+#[cfg(all(test, not(feature = "no_std")))]
+pub(crate) mod test_utils {
+ 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<std::fs::File, &'static str> {
+ let res = File::open("net_graph-2021-05-31.bin") // By default we're run in RL/lightning
+ .or_else(|_| File::open("lightning/net_graph-2021-05-31.bin")) // We may be run manually in RL/
+ .or_else(|_| { // Fall back to guessing based on the binary location
+ // path is likely something like .../rust-lightning/target/debug/deps/lightning-...
+ let mut path = std::env::current_exe().unwrap();
+ path.pop(); // lightning-...
+ path.pop(); // deps
+ path.pop(); // debug
+ path.pop(); // target
+ path.push("lightning");
+ path.push("net_graph-2021-05-31.bin");
+ eprintln!("{}", path.to_str().unwrap());
+ File::open(path)
+ })
+ .map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.15-2021-05-31.bin and place it at lightning/net_graph-2021-05-31.bin");
+ #[cfg(require_route_graph_test)]
+ return Ok(res.unwrap());
+ #[cfg(not(require_route_graph_test))]
+ return res;
+ }
+}
+
+#[cfg(all(test, feature = "unstable", not(feature = "no_std")))]
mod benches {
use super::*;
use util::logger::{Logger, Record};
#[bench]
fn generate_routes(bench: &mut Bencher) {
- let mut d = tests::get_route_file()
- .expect("Please fetch https://bitcoin.ninja/ldk-net_graph-879e309c128-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin");
+ let mut d = test_utils::get_route_file().unwrap();
let graph = NetworkGraph::read(&mut d).unwrap();
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
#[bench]
fn generate_mpp_routes(bench: &mut Bencher) {
- let mut d = tests::get_route_file()
- .expect("Please fetch https://bitcoin.ninja/ldk-net_graph-879e309c128-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin");
+ let mut d = test_utils::get_route_file().unwrap();
let graph = NetworkGraph::read(&mut d).unwrap();
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...