]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Expand the Route object to include multiple paths.
authorMatt Corallo <git@bluematt.me>
Sat, 4 Jan 2020 00:31:40 +0000 (19:31 -0500)
committerMatt Corallo <git@bluematt.me>
Tue, 14 Apr 2020 23:54:17 +0000 (19:54 -0400)
Rather big diff, but its all mechanical and doesn't introduce any
new features.

fuzz/src/chanmon_consistency.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/onion_utils.rs
lightning/src/ln/router.rs
lightning/src/util/macro_logger.rs

index 4981833baa3de5787d6d98ef3e8b7ed43e03e905..5f4fc1333406a008a7048db3347e22969e5ab562 100644 (file)
@@ -409,14 +409,14 @@ pub fn do_test(data: &[u8]) {
                                let payment_hash = Sha256::hash(&[payment_id; 1]);
                                payment_id = payment_id.wrapping_add(1);
                                if let Err(_) = $source.send_payment(Route {
-                                       hops: vec![RouteHop {
+                                       paths: vec![vec![RouteHop {
                                                pubkey: $dest.0.get_our_node_id(),
                                                node_features: NodeFeatures::empty(),
                                                short_channel_id: $dest.1,
                                                channel_features: ChannelFeatures::empty(),
                                                fee_msat: 5000000,
                                                cltv_expiry_delta: 200,
-                                       }],
+                                       }]],
                                }, PaymentHash(payment_hash.into_inner()), &None) {
                                        // Probably ran out of funds
                                        test_return!();
@@ -426,7 +426,7 @@ pub fn do_test(data: &[u8]) {
                                let payment_hash = Sha256::hash(&[payment_id; 1]);
                                payment_id = payment_id.wrapping_add(1);
                                if let Err(_) = $source.send_payment(Route {
-                                       hops: vec![RouteHop {
+                                       paths: vec![vec![RouteHop {
                                                pubkey: $middle.0.get_our_node_id(),
                                                node_features: NodeFeatures::empty(),
                                                short_channel_id: $middle.1,
@@ -440,7 +440,7 @@ pub fn do_test(data: &[u8]) {
                                                channel_features: ChannelFeatures::empty(),
                                                fee_msat: 5000000,
                                                cltv_expiry_delta: 200,
-                                       }],
+                                       }]],
                                }, PaymentHash(payment_hash.into_inner()), &None) {
                                        // Probably ran out of funds
                                        test_return!();
index efcf2159a068fa21b6f18e9674c98960a0f2f501..d1395e1271c3d5bc130d53c2cb29e561d4dc7f16 100644 (file)
@@ -30,7 +30,7 @@ use chain::transaction::OutPoint;
 use ln::channel::{Channel, ChannelError};
 use ln::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateErr, ManyChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
 use ln::features::{InitFeatures, NodeFeatures};
-use ln::router::Route;
+use ln::router::{Route, RouteHop};
 use ln::msgs;
 use ln::onion_utils;
 use ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
@@ -136,7 +136,7 @@ struct ClaimableHTLC {
 pub(super) enum HTLCSource {
        PreviousHopData(HTLCPreviousHopData),
        OutboundRoute {
-               route: Route,
+               path: Vec<RouteHop>,
                session_priv: SecretKey,
                /// Technically we can recalculate this from the route, but we cache it here to avoid
                /// doing a double-pass on route when we get a failure back
@@ -147,7 +147,7 @@ pub(super) enum HTLCSource {
 impl HTLCSource {
        pub fn dummy() -> Self {
                HTLCSource::OutboundRoute {
-                       route: Route { hops: Vec::new() },
+                       path: Vec::new(),
                        session_priv: SecretKey::from_slice(&[1; 32]).unwrap(),
                        first_hop_htlc_msat: 0,
                }
@@ -1231,13 +1231,16 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
        /// bit set (either as required or as available). If multiple paths are present in the Route,
        /// we assume the invoice had the basic_mpp feature set.
        pub fn send_payment(&self, route: Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>) -> Result<(), APIError> {
-               if route.hops.len() < 1 || route.hops.len() > 20 {
-                       return Err(APIError::RouteError{err: "Route didn't go anywhere/had bogus size"});
+               if route.paths.len() < 1 || route.paths.len() > 1 {
+                       return Err(APIError::RouteError{err: "We currently don't support MPP, and we need at least one path"});
+               }
+               if route.paths[0].len() < 1 || route.paths[0].len() > 20 {
+                       return Err(APIError::RouteError{err: "Path didn't go anywhere/had bogus size"});
                }
                let our_node_id = self.get_our_node_id();
-               for (idx, hop) in route.hops.iter().enumerate() {
-                       if idx != route.hops.len() - 1 && hop.pubkey == our_node_id {
-                               return Err(APIError::RouteError{err: "Route went through us but wasn't a simple rebalance loop to us"});
+               for (idx, hop) in route.paths[0].iter().enumerate() {
+                       if idx != route.paths[0].len() - 1 && hop.pubkey == our_node_id {
+                               return Err(APIError::RouteError{err: "Path went through us but wasn't a simple rebalance loop to us"});
                        }
                }
 
@@ -1245,9 +1248,9 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
 
                let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
 
-               let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
+               let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route.paths[0], &session_priv),
                                APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
-               let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, payment_secret, cur_height)?;
+               let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], payment_secret, cur_height)?;
                if onion_utils::route_size_insane(&onion_payloads) {
                        return Err(APIError::RouteError{err: "Route size too large considering onion data"});
                }
@@ -1257,7 +1260,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
 
                let err: Result<(), _> = loop {
                        let mut channel_lock = self.channel_state.lock().unwrap();
-                       let id = match channel_lock.short_to_id.get(&route.hops.first().unwrap().short_channel_id) {
+                       let id = match channel_lock.short_to_id.get(&route.paths[0].first().unwrap().short_channel_id) {
                                None => return Err(APIError::ChannelUnavailable{err: "No channel available with first hop!"}),
                                Some(id) => id.clone(),
                        };
@@ -1265,14 +1268,14 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                        let channel_state = &mut *channel_lock;
                        if let hash_map::Entry::Occupied(mut chan) = channel_state.by_id.entry(id) {
                                match {
-                                       if chan.get().get_their_node_id() != route.hops.first().unwrap().pubkey {
+                                       if chan.get().get_their_node_id() != route.paths[0].first().unwrap().pubkey {
                                                return Err(APIError::RouteError{err: "Node ID mismatch on first hop!"});
                                        }
                                        if !chan.get().is_live() {
                                                return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected/pending monitor update!"});
                                        }
                                        break_chan_entry!(self, chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
-                                               route: route.clone(),
+                                               path: route.paths[0].clone(),
                                                session_priv: session_priv.clone(),
                                                first_hop_htlc_msat: htlc_msat,
                                        }, onion_packet), channel_state, chan)
@@ -1288,7 +1291,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                                                }
 
                                                channel_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
-                                                       node_id: route.hops.first().unwrap().pubkey,
+                                                       node_id: route.paths[0].first().unwrap().pubkey,
                                                        updates: msgs::CommitmentUpdate {
                                                                update_add_htlcs: vec![update_add],
                                                                update_fulfill_htlcs: Vec::new(),
@@ -1305,7 +1308,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                        return Ok(());
                };
 
-               match handle_error!(self, err, route.hops.first().unwrap().pubkey) {
+               match handle_error!(self, err, route.paths[0].first().unwrap().pubkey) {
                        Ok(_) => unreachable!(),
                        Err(e) => { Err(APIError::ChannelUnavailable { err: e.err }) }
                }
@@ -1750,7 +1753,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                //between the branches here. We should make this async and move it into the forward HTLCs
                //timer handling.
                match source {
-                       HTLCSource::OutboundRoute { ref route, .. } => {
+                       HTLCSource::OutboundRoute { ref path, .. } => {
                                log_trace!(self, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
                                mem::drop(channel_state_lock);
                                match &onion_error {
@@ -1792,7 +1795,7 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                                                self.pending_events.lock().unwrap().push(
                                                        events::Event::PaymentFailed {
                                                                payment_hash: payment_hash.clone(),
-                                                               rejected_by_dest: route.hops.len() == 1,
+                                                               rejected_by_dest: path.len() == 1,
 #[cfg(test)]
                                                                error_code: Some(*failure_code),
                                                        }
@@ -1856,9 +1859,19 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                let mut channel_state = Some(self.channel_state.lock().unwrap());
                let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(payment_hash, *payment_secret));
                if let Some(mut sources) = removed_source {
+                       assert!(!sources.is_empty());
+                       let valid_mpp_amount = if let &Some(ref data) = &sources[0].payment_data {
+                               assert!(payment_secret.is_some());
+                               data.total_msat == expected_amount
+                       } else {
+                               assert!(payment_secret.is_none());
+                               false
+                       };
+
+                       let mut claimed_any_htlcs = false;
                        for htlc in sources.drain(..) {
                                if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
-                               if htlc.value < expected_amount || htlc.value > expected_amount * 2 {
+                               if !valid_mpp_amount && (htlc.value < expected_amount || htlc.value > expected_amount * 2) {
                                        let mut htlc_msat_data = byte_utils::be64_to_array(htlc.value).to_vec();
                                        let mut height_data = byte_utils::be32_to_array(self.latest_block_height.load(Ordering::Acquire) as u32).to_vec();
                                        htlc_msat_data.append(&mut height_data);
@@ -1867,9 +1880,10 @@ impl<ChanSigner: ChannelKeys, M: Deref, T: Deref, K: Deref, F: Deref> ChannelMan
                                                                         HTLCFailReason::Reason { failure_code: 0x4000|15, data: htlc_msat_data });
                                } else {
                                        self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc.prev_hop), payment_preimage);
+                                       claimed_any_htlcs = true;
                                }
                        }
-                       true
+                       claimed_any_htlcs
                } else { false }
        }
        fn claim_funds_internal(&self, mut channel_state_lock: MutexGuard<ChannelHolder<ChanSigner>>, source: HTLCSource, payment_preimage: PaymentPreimage) {
@@ -3271,9 +3285,9 @@ impl Writeable for HTLCSource {
                                0u8.write(writer)?;
                                hop_data.write(writer)?;
                        },
-                       &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } => {
+                       &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } => {
                                1u8.write(writer)?;
-                               route.write(writer)?;
+                               path.write(writer)?;
                                session_priv.write(writer)?;
                                first_hop_htlc_msat.write(writer)?;
                        }
@@ -3287,7 +3301,7 @@ impl Readable for HTLCSource {
                match <u8 as Readable>::read(reader)? {
                        0 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
                        1 => Ok(HTLCSource::OutboundRoute {
-                               route: Readable::read(reader)?,
+                               path: Readable::read(reader)?,
                                session_priv: Readable::read(reader)?,
                                first_hop_htlc_msat: Readable::read(reader)?,
                        }),
index 4b73ad0ad058f3dd9c6b538e4d6a404c7d5d03e6..5a655f6cc5abab161e5d6fe9c49350ca3ef9f1b1 100644 (file)
@@ -897,8 +897,9 @@ pub const TEST_FINAL_CLTV: u32 = 32;
 
 pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash) {
        let route = origin_node.router.get_route(&expected_route.last().unwrap().node.get_our_node_id(), None, &Vec::new(), recv_value, TEST_FINAL_CLTV).unwrap();
-       assert_eq!(route.hops.len(), expected_route.len());
-       for (node, hop) in expected_route.iter().zip(route.hops.iter()) {
+       assert_eq!(route.paths.len(), 1);
+       assert_eq!(route.paths[0].len(), expected_route.len());
+       for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
                assert_eq!(hop.pubkey, node.node.get_our_node_id());
        }
 
@@ -907,8 +908,9 @@ pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route:
 
 pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64)  {
        let route = origin_node.router.get_route(&expected_route.last().unwrap().node.get_our_node_id(), None, &Vec::new(), recv_value, TEST_FINAL_CLTV).unwrap();
-       assert_eq!(route.hops.len(), expected_route.len());
-       for (node, hop) in expected_route.iter().zip(route.hops.iter()) {
+       assert_eq!(route.paths.len(), 1);
+       assert_eq!(route.paths[0].len(), expected_route.len());
+       for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
                assert_eq!(hop.pubkey, node.node.get_our_node_id());
        }
 
index da6999ff847bd2714662413666dbe13058b7d7ef..5ac00e6e7a592be3371894404e05dcd40379b5a2 100644 (file)
@@ -1226,7 +1226,7 @@ fn fake_network_test() {
        });
        hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
        hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
-       let payment_preimage_1 = send_along_route(&nodes[1], Route { hops }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
+       let payment_preimage_1 = send_along_route(&nodes[1], Route { paths: vec![hops] }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
 
        let mut hops = Vec::with_capacity(3);
        hops.push(RouteHop {
@@ -1255,7 +1255,7 @@ fn fake_network_test() {
        });
        hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
        hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
-       let payment_hash_2 = send_along_route(&nodes[1], Route { hops }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
+       let payment_hash_2 = send_along_route(&nodes[1], Route { paths: vec![hops] }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
 
        // Claim the rebalances...
        fail_payment(&nodes[1], &vec!(&nodes[3], &nodes[2], &nodes[1])[..], payment_hash_2);
@@ -1562,7 +1562,7 @@ fn do_channel_reserve_test(test_recv: bool) {
        // attempt to send amt_msat > their_max_htlc_value_in_flight_msat
        {
                let (route, our_payment_hash, _) = get_route_and_payment_hash!(recv_value_0 + 1);
-               assert!(route.hops.iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
+               assert!(route.paths[0].iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
                let err = nodes[0].node.send_payment(route, our_payment_hash, &None).err().unwrap();
                match err {
                        APIError::ChannelUnavailable{err} => assert_eq!(err, "Cannot send value that would put us over the max HTLC value in flight our peer will accept"),
@@ -1651,8 +1651,8 @@ fn do_channel_reserve_test(test_recv: bool) {
                }).expect("RNG is bad!");
 
                let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
-               let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
-               let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
+               let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
                let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
                let msg = msgs::UpdateAddHTLC {
                        channel_id: chan_1.2,
@@ -3038,8 +3038,8 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
                };
 
                let current_height = nodes[1].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
-               let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(&route, &None, current_height).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
+               let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(&route.paths[0], &None, current_height).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
                let onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
 
                // Send a 0-msat update_add_htlc to fail the channel.
@@ -5470,8 +5470,8 @@ fn test_onion_failure() {
        run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
                let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
-               let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+               let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
                let mut new_payloads = Vec::new();
                for payload in onion_payloads.drain(..) {
                        new_payloads.push(BogusOnionHopData::new(payload));
@@ -5486,8 +5486,8 @@ fn test_onion_failure() {
        run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
                let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
-               let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+               let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
                let mut new_payloads = Vec::new();
                for payload in onion_payloads.drain(..) {
                        new_payloads.push(BogusOnionHopData::new(payload));
@@ -5507,57 +5507,57 @@ fn test_onion_failure() {
        }, |msg| {
                // and tamper returning error message
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], NODE|2, &[0;0]);
-       }, ||{}, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: false}));
+       }, ||{}, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: false}));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
                // and tamper returning error message
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], NODE|2, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
-       }, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: false}));
+       }, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false}));
 
        // intermediate node failure
        run_onion_failure_test_with_fail_intercept("permanent_node_failure", 100, &nodes, &route, &payment_hash, |msg| {
                msg.amount_msat -= 1;
        }, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|2, &[0;0]);
-       }, ||{}, true, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: true}));
+       }, ||{}, true, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|2, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
-       }, false, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: true}));
+       }, false, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
 
        // intermediate node failure
        run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 100, &nodes, &route, &payment_hash, |msg| {
                msg.amount_msat -= 1;
        }, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|3, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
-       }, true, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[0].pubkey, is_permanent: true}));
+       }, true, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, |_msg| {}, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|3, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
-       }, false, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.hops[1].pubkey, is_permanent: true}));
+       }, false, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
 
        run_onion_failure_test("invalid_onion_version", 0, &nodes, &route, &payment_hash, |msg| { msg.onion_routing_packet.version = 1; }, ||{}, true,
                Some(BADONION|PERM|4), None);
@@ -5572,7 +5572,7 @@ fn test_onion_failure() {
                msg.amount_msat -= 1;
        }, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], UPDATE|7, &ChannelUpdate::dummy().encode_with_len()[..]);
        }, ||{}, true, Some(UPDATE|7), Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()}));
 
@@ -5580,7 +5580,7 @@ fn test_onion_failure() {
                msg.amount_msat -= 1;
        }, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|8, &[0;0]);
                // short_channel_id from the processing node
        }, ||{}, true, Some(PERM|8), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
@@ -5589,20 +5589,20 @@ fn test_onion_failure() {
                msg.amount_msat -= 1;
        }, |msg| {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
                msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|9, &[0;0]);
                // short_channel_id from the processing node
        }, ||{}, true, Some(PERM|9), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
 
        let mut bogus_route = route.clone();
-       bogus_route.hops[1].short_channel_id -= 1;
+       bogus_route.paths[0][1].short_channel_id -= 1;
        run_onion_failure_test("unknown_next_peer", 0, &nodes, &bogus_route, &payment_hash, |_| {}, ||{}, true, Some(PERM|10),
-         Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: bogus_route.hops[1].short_channel_id, is_permanent:true}));
+         Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: bogus_route.paths[0][1].short_channel_id, is_permanent:true}));
 
        let amt_to_forward = nodes[1].node.channel_state.lock().unwrap().by_id.get(&channels[1].2).unwrap().get_their_htlc_minimum_msat() - 1;
        let mut bogus_route = route.clone();
-       let route_len = bogus_route.hops.len();
-       bogus_route.hops[route_len-1].fee_msat = amt_to_forward;
+       let route_len = bogus_route.paths[0].len();
+       bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward;
        run_onion_failure_test("amount_below_minimum", 0, &nodes, &bogus_route, &payment_hash, |_| {}, ||{}, true, Some(UPDATE|11), Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()}));
 
        //TODO: with new config API, we will be able to generate both valid and
@@ -5670,9 +5670,9 @@ fn test_onion_failure() {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
                let mut route = route.clone();
                let height = 1;
-               route.hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.hops[0].cltv_expiry_delta + 1;
-               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
-               let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, height).unwrap();
+               route.paths[0][1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0][0].cltv_expiry_delta + 1;
+               let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+               let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, height).unwrap();
                let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
                msg.cltv_expiry = htlc_cltv;
                msg.onion_routing_packet = onion_packet;
@@ -5762,7 +5762,7 @@ fn test_update_add_htlc_bolt2_sender_value_below_minimum_msat() {
        let mut route = nodes[0].router.get_route(&nodes[1].node.get_our_node_id(), None, &[], 100000, TEST_FINAL_CLTV).unwrap();
        let (_, our_payment_hash) = get_payment_preimage_hash!(nodes[0]);
 
-       route.hops[0].fee_msat = 100;
+       route.paths[0][0].fee_msat = 100;
 
        let err = nodes[0].node.send_payment(route, our_payment_hash, &None);
 
@@ -5786,7 +5786,7 @@ fn test_update_add_htlc_bolt2_sender_zero_value_msat() {
        let mut route = nodes[0].router.get_route(&nodes[1].node.get_our_node_id(), None, &[], 100000, TEST_FINAL_CLTV).unwrap();
        let (_, our_payment_hash) = get_payment_preimage_hash!(nodes[0]);
 
-       route.hops[0].fee_msat = 0;
+       route.paths[0][0].fee_msat = 0;
 
        let err = nodes[0].node.send_payment(route, our_payment_hash, &None);
 
@@ -5992,8 +5992,8 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
        }).expect("RNG is bad!");
 
        let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
-       let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route, &session_priv).unwrap();
-       let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, &None, cur_height).unwrap();
+       let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap();
+       let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], &None, cur_height).unwrap();
        let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
 
        let mut msg = msgs::UpdateAddHTLC {
index ebea1334a8489bc81000b0df1e272ad4557fab73..ef0d6d2e8a0b6b5617615438fa11f055e45558d7 100644 (file)
@@ -1,6 +1,6 @@
 use ln::channelmanager::{PaymentHash, PaymentSecret, HTLCSource};
 use ln::msgs;
-use ln::router::{Route,RouteHop};
+use ln::router::RouteHop;
 use util::byte_utils;
 use util::chacha20::ChaCha20;
 use util::errors::{self, APIError};
@@ -63,11 +63,11 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
 
 // can only fail if an intermediary hop has an invalid public key or session_priv is invalid
 #[inline]
-pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop)> (secp_ctx: &Secp256k1<T>, route: &Route, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
+pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
        let mut blinded_priv = session_priv.clone();
        let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
 
-       for hop in route.hops.iter() {
+       for hop in path.iter() {
                let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv);
 
                let mut sha = Sha256::engine();
@@ -87,10 +87,10 @@ pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(
 }
 
 // can only fail if an intermediary hop has an invalid public key or session_priv is invalid
-pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, route: &Route, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
-       let mut res = Vec::with_capacity(route.hops.len());
+pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
+       let mut res = Vec::with_capacity(path.len());
 
-       construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _| {
+       construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _| {
                let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret[..]);
 
                res.push(OnionKeys {
@@ -108,13 +108,13 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T
 }
 
 /// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
-pub(super) fn build_onion_payloads(route: &Route, payment_secret_option: &Option<PaymentSecret>, starting_htlc_offset: u32) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
+pub(super) fn build_onion_payloads(path: &Vec<RouteHop>, payment_secret_option: &Option<PaymentSecret>, starting_htlc_offset: u32) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
        let mut cur_value_msat = 0u64;
        let mut cur_cltv = starting_htlc_offset;
        let mut last_short_channel_id = 0;
-       let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(route.hops.len());
+       let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(path.len());
 
-       for (idx, hop) in route.hops.iter().rev().enumerate() {
+       for (idx, hop) in path.iter().rev().enumerate() {
                // First hop gets special values so that it can check, on receipt, that everything is
                // exactly as it should be (and the next hop isn't trying to probe to find out if we're
                // the intended recipient).
@@ -318,7 +318,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
 /// OutboundRoute).
 /// Returns update, a boolean indicating that the payment itself failed, and the error code.
 pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, logger: &Arc<Logger>, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<msgs::HTLCFailChannelUpdate>, bool, Option<u16>) {
-       if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
+       if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
                let mut res = None;
                let mut htlc_msat = *first_hop_htlc_msat;
                let mut error_code_ret = None;
@@ -326,7 +326,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
                let mut is_from_final_node = false;
 
                // Handle packed channel/node updates for passing back for the route handler
-               construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| {
+               construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _, _, route_hop| {
                        next_route_hop_ix += 1;
                        if res.is_some() { return; }
 
@@ -341,7 +341,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
                        chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
                        packet_decrypted = decryption_tmp;
 
-                       is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey;
+                       is_from_final_node = path.last().unwrap().pubkey == route_hop.pubkey;
 
                        if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
                                let um = gen_um_from_shared_secret(&shared_secret[..]);
@@ -374,7 +374,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing>(secp_ctx: &Secp256k1<
                                                }
                                                else if error_code & PERM == PERM {
                                                        fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
-                                                               short_channel_id: route.hops[next_route_hop_ix - if next_route_hop_ix == route.hops.len() { 1 } else { 0 }].short_channel_id,
+                                                               short_channel_id: path[next_route_hop_ix - if next_route_hop_ix == path.len() { 1 } else { 0 }].short_channel_id,
                                                                is_permanent: true,
                                                        })};
                                                }
@@ -485,7 +485,7 @@ mod tests {
                let secp_ctx = Secp256k1::new();
 
                let route = Route {
-                       hops: vec!(
+                       paths: vec![vec![
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
@@ -511,13 +511,13 @@ mod tests {
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
                                                short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
                                        },
-                       ),
+                       ]],
                };
 
                let session_priv = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap();
 
-               let onion_keys = super::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
-               assert_eq!(onion_keys.len(), route.hops.len());
+               let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
+               assert_eq!(onion_keys.len(), route.paths[0].len());
                onion_keys
        }
 
index 950072504e37b81d4021c6abf08050ced32638fc..c7ebe86d7eb0d9b607c68b3831a990e76e61468e 100644 (file)
@@ -47,19 +47,10 @@ pub struct RouteHop {
        pub cltv_expiry_delta: u32,
 }
 
-/// A route from us through the network to a destination
-#[derive(Clone, PartialEq)]
-pub struct Route {
-       /// The list of hops, NOT INCLUDING our own, where the last hop is the destination. Thus, this
-       /// must always be at least length one. By protocol rules, this may not currently exceed 20 in
-       /// length.
-       pub hops: Vec<RouteHop>,
-}
-
-impl Writeable for Route {
+impl Writeable for Vec<RouteHop> {
        fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
-               (self.hops.len() as u8).write(writer)?;
-               for hop in self.hops.iter() {
+               (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)?;
@@ -71,8 +62,8 @@ impl Writeable for Route {
        }
 }
 
-impl Readable for Route {
-       fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
+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 {
@@ -85,9 +76,41 @@ impl Readable for Route {
                                cltv_expiry_delta: Readable::read(reader)?,
                        });
                }
-               Ok(Route {
-                       hops
-               })
+               Ok(hops)
+       }
+}
+
+/// 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.
+#[derive(Clone, PartialEq)]
+pub struct Route {
+       /// The list of routes taken for a single (potentially-)multi-part payment. The pubkey of the
+       /// last RouteHop in each path must be the same.
+       /// Each entry represents a list of hops, NOT INCLUDING our own, where the last hop is the
+       /// destination. Thus, this must always be at least length one. While the maximum length of any
+       /// given path is variable, keeping the length of any path to less than 20 should currently
+       /// ensure it is viable.
+       pub paths: Vec<Vec<RouteHop>>,
+}
+
+impl Writeable for Route {
+       fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+               (self.paths.len() as u64).write(writer)?;
+               for hops in self.paths.iter() {
+                       hops.write(writer)?;
+               }
+               Ok(())
+       }
+}
+
+impl Readable for Route {
+       fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
+               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)?);
+               }
+               Ok(Route { paths })
        }
 }
 
@@ -868,14 +891,14 @@ impl Router {
                                let short_channel_id = chan.short_channel_id.expect("first_hops should be filled in with usable channels, not pending ones");
                                if chan.remote_network_id == *target {
                                        return Ok(Route {
-                                               hops: vec![RouteHop {
+                                               paths: vec![vec![RouteHop {
                                                        pubkey: chan.remote_network_id,
                                                        node_features: NodeFeatures::with_known_relevant_init_flags(&chan.counterparty_features),
                                                        short_channel_id,
                                                        channel_features: ChannelFeatures::with_known_relevant_init_flags(&chan.counterparty_features),
                                                        fee_msat: final_value_msat,
                                                        cltv_expiry_delta: final_cltv,
-                                               }],
+                                               }]],
                                        });
                                }
                                first_hop_targets.insert(chan.remote_network_id, (short_channel_id, chan.counterparty_features.clone()));
@@ -1032,7 +1055,7 @@ impl Router {
                                }
                                res.last_mut().unwrap().fee_msat = final_value_msat;
                                res.last_mut().unwrap().cltv_expiry_delta = final_cltv;
-                               let route = Route { hops: res };
+                               let route = Route { paths: vec![res] };
                                log_trace!(self, "Got route: {}", log_route!(route));
                                return Ok(route);
                        }
@@ -1497,21 +1520,21 @@ mod tests {
 
                { // Simple route to 3 via 2
                        let route = router.get_route(&node3, None, &Vec::new(), 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 2);
+                       assert_eq!(route.paths[0].len(), 2);
 
-                       assert_eq!(route.hops[0].pubkey, node2);
-                       assert_eq!(route.hops[0].short_channel_id, 2);
-                       assert_eq!(route.hops[0].fee_msat, 100);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].pubkey, node2);
+                       assert_eq!(route.paths[0][0].short_channel_id, 2);
+                       assert_eq!(route.paths[0][0].fee_msat, 100);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
 
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 4);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 4);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
                }
 
                { // Disable channels 4 and 12 by requiring unknown feature bits
@@ -1539,21 +1562,21 @@ mod tests {
                                is_live: true,
                        }];
                        let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 2);
+                       assert_eq!(route.paths[0].len(), 2);
 
-                       assert_eq!(route.hops[0].pubkey, node8);
-                       assert_eq!(route.hops[0].short_channel_id, 42);
-                       assert_eq!(route.hops[0].fee_msat, 200);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+                       assert_eq!(route.paths[0][0].pubkey, node8);
+                       assert_eq!(route.paths[0][0].short_channel_id, 42);
+                       assert_eq!(route.paths[0][0].fee_msat, 200);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
 
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 13);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 13);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
                }
 
                { // Re-enable channels 4 and 12 by wiping the unknown feature bits
@@ -1588,21 +1611,21 @@ mod tests {
                                is_live: true,
                        }];
                        let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 2);
+                       assert_eq!(route.paths[0].len(), 2);
 
-                       assert_eq!(route.hops[0].pubkey, node8);
-                       assert_eq!(route.hops[0].short_channel_id, 42);
-                       assert_eq!(route.hops[0].fee_msat, 200);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+                       assert_eq!(route.paths[0][0].pubkey, node8);
+                       assert_eq!(route.paths[0][0].short_channel_id, 42);
+                       assert_eq!(route.paths[0][0].fee_msat, 200);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
 
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 13);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 13);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
                }
 
                { // Re-enable nodes 1, 2, and 8
@@ -1618,28 +1641,28 @@ mod tests {
 
                { // Route to 1 via 2 and 3 because our channel to 1 is disabled
                        let route = router.get_route(&node1, None, &Vec::new(), 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 3);
-
-                       assert_eq!(route.hops[0].pubkey, node2);
-                       assert_eq!(route.hops[0].short_channel_id, 2);
-                       assert_eq!(route.hops[0].fee_msat, 200);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 4);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, (3 << 8) | 2);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
-                       assert_eq!(route.hops[2].pubkey, node1);
-                       assert_eq!(route.hops[2].short_channel_id, 3);
-                       assert_eq!(route.hops[2].fee_msat, 100);
-                       assert_eq!(route.hops[2].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(1));
-                       assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0].len(), 3);
+
+                       assert_eq!(route.paths[0][0].pubkey, node2);
+                       assert_eq!(route.paths[0][0].short_channel_id, 2);
+                       assert_eq!(route.paths[0][0].fee_msat, 200);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 4);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, (3 << 8) | 2);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+                       assert_eq!(route.paths[0][2].pubkey, node1);
+                       assert_eq!(route.paths[0][2].short_channel_id, 3);
+                       assert_eq!(route.paths[0][2].fee_msat, 100);
+                       assert_eq!(route.paths[0][2].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(1));
+                       assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(3));
                }
 
                { // If we specify a channel to node8, that overrides our local channel view and that gets used
@@ -1655,21 +1678,21 @@ mod tests {
                                is_live: true,
                        }];
                        let route = router.get_route(&node3, Some(&our_chans), &Vec::new(), 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 2);
+                       assert_eq!(route.paths[0].len(), 2);
 
-                       assert_eq!(route.hops[0].pubkey, node8);
-                       assert_eq!(route.hops[0].short_channel_id, 42);
-                       assert_eq!(route.hops[0].fee_msat, 200);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (13 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]);
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+                       assert_eq!(route.paths[0][0].pubkey, node8);
+                       assert_eq!(route.paths[0][0].short_channel_id, 42);
+                       assert_eq!(route.paths[0][0].fee_msat, 200);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
 
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 13);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(13));
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 13);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(13));
                }
 
                let mut last_hops = vec!(RouteHint {
@@ -1697,44 +1720,44 @@ mod tests {
 
                { // Simple test across 2, 3, 5, and 4 via a last_hop channel
                        let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 5);
-
-                       assert_eq!(route.hops[0].pubkey, node2);
-                       assert_eq!(route.hops[0].short_channel_id, 2);
-                       assert_eq!(route.hops[0].fee_msat, 100);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 4);
-                       assert_eq!(route.hops[1].fee_msat, 0);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
-                       assert_eq!(route.hops[2].pubkey, node5);
-                       assert_eq!(route.hops[2].short_channel_id, 6);
-                       assert_eq!(route.hops[2].fee_msat, 0);
-                       assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1);
-                       assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(5));
-                       assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6));
-
-                       assert_eq!(route.hops[3].pubkey, node4);
-                       assert_eq!(route.hops[3].short_channel_id, 11);
-                       assert_eq!(route.hops[3].fee_msat, 0);
-                       assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1);
+                       assert_eq!(route.paths[0].len(), 5);
+
+                       assert_eq!(route.paths[0][0].pubkey, node2);
+                       assert_eq!(route.paths[0][0].short_channel_id, 2);
+                       assert_eq!(route.paths[0][0].fee_msat, 100);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 4);
+                       assert_eq!(route.paths[0][1].fee_msat, 0);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+                       assert_eq!(route.paths[0][2].pubkey, node5);
+                       assert_eq!(route.paths[0][2].short_channel_id, 6);
+                       assert_eq!(route.paths[0][2].fee_msat, 0);
+                       assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+                       assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5));
+                       assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6));
+
+                       assert_eq!(route.paths[0][3].pubkey, node4);
+                       assert_eq!(route.paths[0][3].short_channel_id, 11);
+                       assert_eq!(route.paths[0][3].fee_msat, 0);
+                       assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
                        // If we have a peer in the node map, we'll use their features here since we don't have
                        // a way of figuring out their features from the invoice:
-                       assert_eq!(route.hops[3].node_features.le_flags(), &id_to_feature_flags!(4));
-                       assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11));
+                       assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4));
+                       assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11));
 
-                       assert_eq!(route.hops[4].pubkey, node7);
-                       assert_eq!(route.hops[4].short_channel_id, 8);
-                       assert_eq!(route.hops[4].fee_msat, 100);
-                       assert_eq!(route.hops[4].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
-                       assert_eq!(route.hops[4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+                       assert_eq!(route.paths[0][4].pubkey, node7);
+                       assert_eq!(route.paths[0][4].short_channel_id, 8);
+                       assert_eq!(route.paths[0][4].fee_msat, 100);
+                       assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+                       assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
                }
 
                { // Simple test with outbound channel to 4 to test that last_hops and first_hops connect
@@ -1750,100 +1773,100 @@ mod tests {
                                is_live: true,
                        }];
                        let route = router.get_route(&node7, Some(&our_chans), &last_hops, 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 2);
+                       assert_eq!(route.paths[0].len(), 2);
 
-                       assert_eq!(route.hops[0].pubkey, node4);
-                       assert_eq!(route.hops[0].short_channel_id, 42);
-                       assert_eq!(route.hops[0].fee_msat, 0);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (8 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &vec![0b11]);
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
+                       assert_eq!(route.paths[0][0].pubkey, node4);
+                       assert_eq!(route.paths[0][0].short_channel_id, 42);
+                       assert_eq!(route.paths[0][0].fee_msat, 0);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::new()); // No feature flags will meet the relevant-to-channel conversion
 
-                       assert_eq!(route.hops[1].pubkey, node7);
-                       assert_eq!(route.hops[1].short_channel_id, 8);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+                       assert_eq!(route.paths[0][1].pubkey, node7);
+                       assert_eq!(route.paths[0][1].short_channel_id, 8);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
+                       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].fee_base_msat = 1000;
 
                { // Revert to via 6 as the fee on 8 goes up
                        let route = router.get_route(&node7, None, &last_hops, 100, 42).unwrap();
-                       assert_eq!(route.hops.len(), 4);
-
-                       assert_eq!(route.hops[0].pubkey, node2);
-                       assert_eq!(route.hops[0].short_channel_id, 2);
-                       assert_eq!(route.hops[0].fee_msat, 200); // fee increased as its % of value transferred across node
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 4);
-                       assert_eq!(route.hops[1].fee_msat, 100);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, (7 << 8) | 1);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
-                       assert_eq!(route.hops[2].pubkey, node6);
-                       assert_eq!(route.hops[2].short_channel_id, 7);
-                       assert_eq!(route.hops[2].fee_msat, 0);
-                       assert_eq!(route.hops[2].cltv_expiry_delta, (10 << 8) | 1);
+                       assert_eq!(route.paths[0].len(), 4);
+
+                       assert_eq!(route.paths[0][0].pubkey, node2);
+                       assert_eq!(route.paths[0][0].short_channel_id, 2);
+                       assert_eq!(route.paths[0][0].fee_msat, 200); // fee increased as its % of value transferred across node
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 4);
+                       assert_eq!(route.paths[0][1].fee_msat, 100);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, (7 << 8) | 1);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+                       assert_eq!(route.paths[0][2].pubkey, node6);
+                       assert_eq!(route.paths[0][2].short_channel_id, 7);
+                       assert_eq!(route.paths[0][2].fee_msat, 0);
+                       assert_eq!(route.paths[0][2].cltv_expiry_delta, (10 << 8) | 1);
                        // If we have a peer in the node map, we'll use their features here since we don't have
                        // a way of figuring out their features from the invoice:
-                       assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(6));
-                       assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(7));
+                       assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(6));
+                       assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(7));
 
-                       assert_eq!(route.hops[3].pubkey, node7);
-                       assert_eq!(route.hops[3].short_channel_id, 10);
-                       assert_eq!(route.hops[3].fee_msat, 100);
-                       assert_eq!(route.hops[3].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
-                       assert_eq!(route.hops[3].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+                       assert_eq!(route.paths[0][3].pubkey, node7);
+                       assert_eq!(route.paths[0][3].short_channel_id, 10);
+                       assert_eq!(route.paths[0][3].fee_msat, 100);
+                       assert_eq!(route.paths[0][3].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+                       assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
                }
 
                { // ...but still use 8 for larger payments as 6 has a variable feerate
                        let route = router.get_route(&node7, None, &last_hops, 2000, 42).unwrap();
-                       assert_eq!(route.hops.len(), 5);
-
-                       assert_eq!(route.hops[0].pubkey, node2);
-                       assert_eq!(route.hops[0].short_channel_id, 2);
-                       assert_eq!(route.hops[0].fee_msat, 3000);
-                       assert_eq!(route.hops[0].cltv_expiry_delta, (4 << 8) | 1);
-                       assert_eq!(route.hops[0].node_features.le_flags(), &id_to_feature_flags!(2));
-                       assert_eq!(route.hops[0].channel_features.le_flags(), &id_to_feature_flags!(2));
-
-                       assert_eq!(route.hops[1].pubkey, node3);
-                       assert_eq!(route.hops[1].short_channel_id, 4);
-                       assert_eq!(route.hops[1].fee_msat, 0);
-                       assert_eq!(route.hops[1].cltv_expiry_delta, (6 << 8) | 1);
-                       assert_eq!(route.hops[1].node_features.le_flags(), &id_to_feature_flags!(3));
-                       assert_eq!(route.hops[1].channel_features.le_flags(), &id_to_feature_flags!(4));
-
-                       assert_eq!(route.hops[2].pubkey, node5);
-                       assert_eq!(route.hops[2].short_channel_id, 6);
-                       assert_eq!(route.hops[2].fee_msat, 0);
-                       assert_eq!(route.hops[2].cltv_expiry_delta, (11 << 8) | 1);
-                       assert_eq!(route.hops[2].node_features.le_flags(), &id_to_feature_flags!(5));
-                       assert_eq!(route.hops[2].channel_features.le_flags(), &id_to_feature_flags!(6));
-
-                       assert_eq!(route.hops[3].pubkey, node4);
-                       assert_eq!(route.hops[3].short_channel_id, 11);
-                       assert_eq!(route.hops[3].fee_msat, 1000);
-                       assert_eq!(route.hops[3].cltv_expiry_delta, (8 << 8) | 1);
+                       assert_eq!(route.paths[0].len(), 5);
+
+                       assert_eq!(route.paths[0][0].pubkey, node2);
+                       assert_eq!(route.paths[0][0].short_channel_id, 2);
+                       assert_eq!(route.paths[0][0].fee_msat, 3000);
+                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags!(2));
+                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags!(2));
+
+                       assert_eq!(route.paths[0][1].pubkey, node3);
+                       assert_eq!(route.paths[0][1].short_channel_id, 4);
+                       assert_eq!(route.paths[0][1].fee_msat, 0);
+                       assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags!(3));
+                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags!(4));
+
+                       assert_eq!(route.paths[0][2].pubkey, node5);
+                       assert_eq!(route.paths[0][2].short_channel_id, 6);
+                       assert_eq!(route.paths[0][2].fee_msat, 0);
+                       assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+                       assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags!(5));
+                       assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags!(6));
+
+                       assert_eq!(route.paths[0][3].pubkey, node4);
+                       assert_eq!(route.paths[0][3].short_channel_id, 11);
+                       assert_eq!(route.paths[0][3].fee_msat, 1000);
+                       assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
                        // If we have a peer in the node map, we'll use their features here since we don't have
                        // a way of figuring out their features from the invoice:
-                       assert_eq!(route.hops[3].node_features.le_flags(), &id_to_feature_flags!(4));
-                       assert_eq!(route.hops[3].channel_features.le_flags(), &id_to_feature_flags!(11));
-
-                       assert_eq!(route.hops[4].pubkey, node7);
-                       assert_eq!(route.hops[4].short_channel_id, 8);
-                       assert_eq!(route.hops[4].fee_msat, 2000);
-                       assert_eq!(route.hops[4].cltv_expiry_delta, 42);
-                       assert_eq!(route.hops[4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
-                       assert_eq!(route.hops[4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
+                       assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags!(4));
+                       assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags!(11));
+
+                       assert_eq!(route.paths[0][4].pubkey, node7);
+                       assert_eq!(route.paths[0][4].short_channel_id, 8);
+                       assert_eq!(route.paths[0][4].fee_msat, 2000);
+                       assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::new()); // We dont pass flags in from invoices yet
+                       assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::new()); // We can't learn any flags from invoices, sadly
                }
 
                { // Test Router serialization/deserialization
index d16bd48aebb53a77ca39dbb935b36fb4104560fc..1f9cb1ad27e62d21f916272be115deca7d26710d 100644 (file)
@@ -73,8 +73,11 @@ macro_rules! log_funding_info {
 pub(crate) struct DebugRoute<'a>(pub &'a Route);
 impl<'a> std::fmt::Display for DebugRoute<'a> {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
-               for h in self.0.hops.iter() {
-                       write!(f, "node_id: {}, short_channel_id: {}, fee_msat: {}, cltv_expiry_delta: {}\n", log_pubkey!(h.pubkey), h.short_channel_id, h.fee_msat, h.cltv_expiry_delta)?;
+               for (idx, p) in self.0.paths.iter().enumerate() {
+                       write!(f, "path {}:\n", idx)?;
+                       for h in p.iter() {
+                               write!(f, " node_id: {}, short_channel_id: {}, fee_msat: {}, cltv_expiry_delta: {}\n", log_pubkey!(h.pubkey), h.short_channel_id, h.fee_msat, h.cltv_expiry_delta)?;
+                       }
                }
                Ok(())
        }