X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Frouter.rs;h=aa1c3c1176c70e45d6db1b0a0ca903e2b7e470d0;hb=0882655680040a0226b1790b6558150324a288f4;hp=1f77681bf06b673de4d44e23f4050ceb6b1beefa;hpb=3d1c72e73fde6e4d58b087433acdf0e4b94ee0f6;p=rust-lightning diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 1f77681b..aa1c3c11 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -21,8 +21,9 @@ use routing::network_graph::{NetworkGraph, RoutingFees}; use util::ser::{Writeable, Readable}; use util::logger::Logger; +use prelude::*; +use alloc::collections::BinaryHeap; use core::cmp; -use std::collections::{HashMap, BinaryHeap}; use core::ops::Deref; /// A hop in a route @@ -47,40 +48,14 @@ pub struct RouteHop { pub cltv_expiry_delta: u32, } -/// (C-not exported) -impl Writeable for Vec { - fn write(&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 { - fn read(reader: &mut R) -> Result, 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, required), + (2, node_features, required), + (4, short_channel_id, required), + (6, channel_features, required), + (8, fee_msat, required), + (10, cltv_expiry_delta, required), +}); /// 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. @@ -103,9 +78,12 @@ impl Writeable for Route { 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, {}, {}); + write_tlv_fields!(writer, {}); Ok(()) } } @@ -116,14 +94,23 @@ impl Readable for Route { 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, {}, {}); + read_tlv_fields!(reader, {}); Ok(Route { paths }) } } -/// A channel descriptor which provides a last-hop route to get_route +/// 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); + +/// 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 @@ -181,7 +168,7 @@ struct DummyDirectionalChannelInfo { /// so that we can choose cheaper paths (as per Dijkstra's algorithm). /// Fee values should be updated only in the context of the whole path, see update_value_and_recompute_fees. /// These fee values are useful to choose hops as we traverse the graph "payee-to-payer". -#[derive(Clone)] +#[derive(Clone, Debug)] struct PathBuildingHop<'a> { // 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. @@ -345,8 +332,8 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { /// 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 @@ -360,7 +347,7 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { /// equal), however the enabled/disabled bit on such channels as well as the /// htlc_minimum_msat/htlc_maximum_msat *are* checked as they may change based on the receiving node. pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, payee: &PublicKey, payee_features: Option, first_hops: Option<&[&ChannelDetails]>, - last_hops: &[&RouteHintHop], final_value_msat: u64, final_cltv: u32, logger: L) -> Result where L::Target: Logger { + last_hops: &[&RouteHint], final_value_msat: u64, final_cltv: u32, logger: L) -> Result where L::Target: Logger { // TODO: Obviously *only* using total fee cost sucks. We should consider weighting by // uptime/success in using a node in the past. if *payee == *our_node_id { @@ -375,7 +362,8 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye 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::>(); + 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}); } @@ -518,6 +506,8 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye // - when we want to stop looking for new paths. let mut already_collected_value_msat = 0; + log_trace!(logger, "Building path from {} (payee) to {} (us/payer) for value {} msat.", payee, our_node_id, final_value_msat); + macro_rules! add_entry { // Adds entry which goes from $src_node_id to $dest_node_id // over the channel with id $chan_id with fees described in @@ -903,6 +893,8 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye } } + log_trace!(logger, "Starting main path collection loop with {} nodes pre-filled from first/last hops.", targets.len()); + // At this point, targets are filled with the data from first and // last hops communicated by the caller, and the payment receiver. let mut found_new_path = false; @@ -966,6 +958,9 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye ordered_hops.last_mut().unwrap().0.hop_use_fee_msat = 0; ordered_hops.last_mut().unwrap().0.cltv_expiry_delta = final_cltv; + log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: {:?}", + ordered_hops.len(), value_contribution_msat, ordered_hops); + let mut payment_path = PaymentPath {hops: ordered_hops}; // We could have possibly constructed a slightly inconsistent path: since we reduce @@ -1001,8 +996,9 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye // If we weren't capped by hitting a liquidity limit on a channel in the path, // we'll probably end up picking the same path again on the next iteration. // Decrease the available liquidity of a hop in the middle of the path. - let victim_liquidity = bookkeeped_channels_liquidity_available_msat.get_mut( - &payment_path.hops[(payment_path.hops.len() - 1) / 2].0.short_channel_id).unwrap(); + let victim_scid = payment_path.hops[(payment_path.hops.len() - 1) / 2].0.short_channel_id; + log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid); + let victim_liquidity = bookkeeped_channels_liquidity_available_msat.get_mut(&victim_scid).unwrap(); *victim_liquidity = 0; } @@ -1044,6 +1040,8 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye // In the latter case, making another path finding attempt won't help, // because we deterministically terminated the search due to low liquidity. if already_collected_value_msat >= recommended_value_msat || !found_new_path { + log_trace!(logger, "Have now collected {} msat (seeking {} msat) in paths. Last path loop {} a new path.", + already_collected_value_msat, recommended_value_msat, if found_new_path { "found" } else { "did not find" }); break 'paths_collection; } else if found_new_path && already_collected_value_msat == final_value_msat && payment_paths.len() == 1 { // Further, if this was our first walk of the graph, and we weren't limited by an @@ -1052,8 +1050,10 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye // potentially allowing us to pay fees to meet the htlc_minimum on the new path while // still keeping a lower total fee than this path. if !hit_minimum_limit { + log_trace!(logger, "Collected exactly our payment amount on the first pass, without hitting an htlc_minimum_msat limit, exiting."); break 'paths_collection; } + log_trace!(logger, "Collected our payment amount on the first pass, but running again to collect extra paths with a potentially higher limit."); path_value_msat = recommended_value_msat; } } @@ -1164,13 +1164,13 @@ pub fn get_route(our_node_id: &PublicKey, network: &NetworkGraph, paye } let route = Route { paths: selected_paths }; - log_trace!(logger, "Got route: {}", log_route!(route)); + log_info!(logger, "Got route to {}: {}", payee, log_route!(route)); Ok(route) } #[cfg(test)] mod tests { - use routing::router::{get_route, RouteHintHop, 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}; @@ -1193,6 +1193,7 @@ mod tests { 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 @@ -1641,6 +1642,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 100000, inbound_capacity_msat: 100000, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -1961,6 +1966,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 250_000_000, inbound_capacity_msat: 0, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -2011,6 +2020,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 250_000_000, inbound_capacity_msat: 0, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -2078,6 +2091,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 250_000_000, inbound_capacity_msat: 0, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -2100,19 +2117,19 @@ mod tests { assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(13)); } - fn last_hops(nodes: &Vec) -> Vec { + fn last_hops(nodes: &Vec) -> Vec { let zero_fees = RoutingFees { base_msat: 0, proportional_millionths: 0, }; - vec!(RouteHintHop { + 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, - }, RouteHintHop { + }]), RouteHint(vec![RouteHintHop { src_node_id: nodes[4].clone(), short_channel_id: 9, fees: RoutingFees { @@ -2122,14 +2139,14 @@ mod tests { cltv_expiry_delta: (9 << 8) | 1, htlc_minimum_msat: None, htlc_maximum_msat: None, - }, RouteHintHop { + }]), 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] @@ -2139,8 +2156,8 @@ mod tests { // 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 = RouteHintHop { + // 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 { @@ -2150,7 +2167,7 @@ mod tests { 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); @@ -2217,6 +2234,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 250_000_000, inbound_capacity_msat: 0, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -2239,7 +2260,7 @@ mod tests { assert_eq!(route.paths[0][1].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::::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::>(), 100, 42, Arc::clone(&logger)).unwrap(); @@ -2327,7 +2348,7 @@ mod tests { 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![RouteHintHop { + let last_hops = RouteHint(vec![RouteHintHop { src_node_id: middle_node_id, short_channel_id: 8, fees: RoutingFees { @@ -2337,7 +2358,7 @@ mod tests { 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 }), @@ -2348,11 +2369,15 @@ mod tests { user_id: 0, outbound_capacity_msat: 100000, inbound_capacity_msat: 100000, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, 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::>()), &last_hops.iter().collect::>(), 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![&last_hops], 100, 42, Arc::new(test_utils::TestLogger::new())).unwrap(); assert_eq!(route.paths[0].len(), 2); @@ -2482,6 +2507,10 @@ mod tests { user_id: 0, outbound_capacity_msat: 200_000_000, inbound_capacity_msat: 0, + to_self_reserve_satoshis: None, + to_remote_reserve_satoshis: 0, + confirmations_required: None, + spend_csv_on_our_commitment_funds: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, counterparty_forwarding_info: None, @@ -3853,30 +3882,7 @@ mod tests { } } - 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 { - 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 core::hash::{BuildHasher, Hasher}; @@ -3884,13 +3890,16 @@ mod tests { 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-05f0c5a0d772-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin"); + Err(e) => { + eprintln!("{}", e); return; }, }; @@ -3913,11 +3922,12 @@ mod tests { } #[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-05f0c5a0d772-2020-02-12.bin and place it at lightning/net_graph-2021-02-12.bin"); + Err(e) => { + eprintln!("{}", e); return; }, }; @@ -3940,7 +3950,34 @@ mod tests { } } -#[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 { + 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}; @@ -3954,8 +3991,7 @@ mod benches { #[bench] fn generate_routes(bench: &mut Bencher) { - let mut d = tests::get_route_file() - .expect("Please fetch https://bitcoin.ninja/ldk-net_graph-05f0c5a0d772-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... @@ -3986,8 +4022,7 @@ mod benches { #[bench] fn generate_mpp_routes(bench: &mut Bencher) { - let mut d = tests::get_route_file() - .expect("Please fetch https://bitcoin.ninja/ldk-net_graph-05f0c5a0d772-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...