Merge pull request #2221 from TheBlueMatt/2023-04-bp-exit-fast
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Mon, 24 Apr 2023 16:47:47 +0000 (16:47 +0000)
committerGitHub <noreply@github.com>
Mon, 24 Apr 2023 16:47:47 +0000 (16:47 +0000)
Check for `background-processor` exit condition before+after sleep

36 files changed:
fuzz/src/chanmon_consistency.rs
fuzz/src/invoice_request_deser.rs
fuzz/src/refund_deser.rs
lightning-background-processor/src/lib.rs
lightning/src/blinded_path/mod.rs [new file with mode: 0644]
lightning/src/blinded_path/utils.rs [new file with mode: 0644]
lightning/src/events/mod.rs
lightning/src/lib.rs
lightning/src/ln/chanmon_update_fail_tests.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/onion_route_tests.rs
lightning/src/ln/onion_utils.rs
lightning/src/ln/outbound_payment.rs
lightning/src/ln/payment_tests.rs
lightning/src/ln/priv_short_conf_tests.rs
lightning/src/ln/reload_tests.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs
lightning/src/offers/test_utils.rs
lightning/src/onion_message/blinded_path.rs [deleted file]
lightning/src/onion_message/functional_tests.rs
lightning/src/onion_message/messenger.rs
lightning/src/onion_message/mod.rs
lightning/src/onion_message/packet.rs
lightning/src/onion_message/utils.rs [deleted file]
lightning/src/routing/router.rs
lightning/src/routing/scoring.rs
lightning/src/util/macro_logger.rs
lightning/src/util/ser.rs
lightning/src/util/test_utils.rs
pending_changelog/2146.txt [new file with mode: 0644]

index a00404497fa1c4146d42abfb20a8cb2474de3220..0461c75772c7a39fa5176ca37bbc5f55878f4c7d 100644 (file)
@@ -50,7 +50,7 @@ use lightning::util::errors::APIError;
 use lightning::util::logger::Logger;
 use lightning::util::config::UserConfig;
 use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer};
-use lightning::routing::router::{InFlightHtlcs, Route, RouteHop, RouteParameters, Router};
+use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RouteParameters, Router};
 
 use crate::utils::test_logger::{self, Output};
 use crate::utils::test_persister::TestPersister;
@@ -353,14 +353,14 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
        payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes());
        *payment_idx += 1;
        if let Err(err) = source.send_payment_with_route(&Route {
-               paths: vec![vec![RouteHop {
+               paths: vec![Path { hops: vec![RouteHop {
                        pubkey: dest.get_our_node_id(),
                        node_features: dest.node_features(),
                        short_channel_id: dest_chan_id,
                        channel_features: dest.channel_features(),
                        fee_msat: amt,
                        cltv_expiry_delta: 200,
-               }]],
+               }], blinded_tail: None }],
                payment_params: None,
        }, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
                check_payment_err(err);
@@ -375,7 +375,7 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
        payment_id[0..8].copy_from_slice(&payment_idx.to_ne_bytes());
        *payment_idx += 1;
        if let Err(err) = source.send_payment_with_route(&Route {
-               paths: vec![vec![RouteHop {
+               paths: vec![Path { hops: vec![RouteHop {
                        pubkey: middle.get_our_node_id(),
                        node_features: middle.node_features(),
                        short_channel_id: middle_chan_id,
@@ -389,7 +389,7 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
                        channel_features: dest.channel_features(),
                        fee_msat: amt,
                        cltv_expiry_delta: 200,
-               }]],
+               }], blinded_tail: None }],
                payment_params: None,
        }, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
                check_payment_err(err);
index aa3045ccb247e36d82990dc314caf15ecc5f0e5e..e885884891501514862d94230eb8958eb29f951f 100644 (file)
 use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self};
 use crate::utils::test_logger;
 use core::convert::{Infallible, TryFrom};
+use lightning::blinded_path::BlindedPath;
 use lightning::chain::keysinterface::EntropySource;
 use lightning::ln::PaymentHash;
 use lightning::ln::features::BlindedHopFeatures;
 use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
 use lightning::offers::invoice_request::InvoiceRequest;
 use lightning::offers::parse::SemanticError;
-use lightning::onion_message::BlindedPath;
 use lightning::util::ser::Writeable;
 
 #[inline]
@@ -74,8 +74,8 @@ fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
 ) -> Result<UnsignedInvoice<'a>, SemanticError> {
        let entropy_source = Randomness {};
        let paths = vec![
-               BlindedPath::new(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
-               BlindedPath::new(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
+               BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
+               BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
        ];
 
        let payinfo = vec![
index 9adaa3e953cb63c97152eef3ea3ce8f339968f26..d76607c03b02a53c5745a9a10e1d78792598f370 100644 (file)
 use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
 use crate::utils::test_logger;
 use core::convert::{Infallible, TryFrom};
+use lightning::blinded_path::BlindedPath;
 use lightning::chain::keysinterface::EntropySource;
 use lightning::ln::PaymentHash;
 use lightning::ln::features::BlindedHopFeatures;
 use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
 use lightning::offers::parse::SemanticError;
 use lightning::offers::refund::Refund;
-use lightning::onion_message::BlindedPath;
 use lightning::util::ser::Writeable;
 
 #[inline]
@@ -63,8 +63,8 @@ fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
 ) -> Result<UnsignedInvoice<'a>, SemanticError> {
        let entropy_source = Randomness {};
        let paths = vec![
-               BlindedPath::new(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
-               BlindedPath::new(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
+               BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
+               BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
        ];
 
        let payinfo = vec![
index c6706636433e2862380b7719237a1d1b0465cb72..940da6d2f2cab5f48af273d489a27346c65bf568 100644 (file)
@@ -241,26 +241,21 @@ fn update_scorer<'a, S: 'static + Deref<Target = SC> + Send + Sync, SC: 'a + Wri
        let mut score = scorer.lock();
        match event {
                Event::PaymentPathFailed { ref path, short_channel_id: Some(scid), .. } => {
-                       let path = path.iter().collect::<Vec<_>>();
-                       score.payment_path_failed(&path, *scid);
+                       score.payment_path_failed(path, *scid);
                },
                Event::PaymentPathFailed { ref path, payment_failed_permanently: true, .. } => {
                        // Reached if the destination explicitly failed it back. We treat this as a successful probe
                        // because the payment made it all the way to the destination with sufficient liquidity.
-                       let path = path.iter().collect::<Vec<_>>();
-                       score.probe_successful(&path);
+                       score.probe_successful(path);
                },
                Event::PaymentPathSuccessful { path, .. } => {
-                       let path = path.iter().collect::<Vec<_>>();
-                       score.payment_path_successful(&path);
+                       score.payment_path_successful(path);
                },
                Event::ProbeSuccessful { path, .. } => {
-                       let path = path.iter().collect::<Vec<_>>();
-                       score.probe_successful(&path);
+                       score.probe_successful(path);
                },
                Event::ProbeFailed { path, short_channel_id: Some(scid), .. } => {
-                       let path = path.iter().collect::<Vec<_>>();
-                       score.probe_failed(&path, *scid);
+                       score.probe_failed(path, *scid);
                },
                _ => {},
        }
@@ -774,7 +769,7 @@ mod tests {
        use lightning::ln::msgs::{ChannelMessageHandler, Init};
        use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
        use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
-       use lightning::routing::router::{DefaultRouter, RouteHop};
+       use lightning::routing::router::{DefaultRouter, Path, RouteHop};
        use lightning::routing::scoring::{ChannelUsage, Score};
        use lightning::util::config::UserConfig;
        use lightning::util::ser::Writeable;
@@ -917,10 +912,10 @@ mod tests {
 
        #[derive(Debug)]
        enum TestResult {
-               PaymentFailure { path: Vec<RouteHop>, short_channel_id: u64 },
-               PaymentSuccess { path: Vec<RouteHop> },
-               ProbeFailure { path: Vec<RouteHop> },
-               ProbeSuccess { path: Vec<RouteHop> },
+               PaymentFailure { path: Path, short_channel_id: u64 },
+               PaymentSuccess { path: Path },
+               ProbeFailure { path: Path },
+               ProbeSuccess { path: Path },
        }
 
        impl TestScorer {
@@ -942,11 +937,11 @@ mod tests {
                        &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage
                ) -> u64 { unimplemented!(); }
 
-               fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) {
+               fn payment_path_failed(&mut self, actual_path: &Path, actual_short_channel_id: u64) {
                        if let Some(expectations) = &mut self.event_expectations {
                                match expectations.pop_front().unwrap() {
                                        TestResult::PaymentFailure { path, short_channel_id } => {
-                                               assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+                                               assert_eq!(actual_path, &path);
                                                assert_eq!(actual_short_channel_id, short_channel_id);
                                        },
                                        TestResult::PaymentSuccess { path } => {
@@ -962,14 +957,14 @@ mod tests {
                        }
                }
 
-               fn payment_path_successful(&mut self, actual_path: &[&RouteHop]) {
+               fn payment_path_successful(&mut self, actual_path: &Path) {
                        if let Some(expectations) = &mut self.event_expectations {
                                match expectations.pop_front().unwrap() {
                                        TestResult::PaymentFailure { path, .. } => {
                                                panic!("Unexpected payment path failure: {:?}", path)
                                        },
                                        TestResult::PaymentSuccess { path } => {
-                                               assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+                                               assert_eq!(actual_path, &path);
                                        },
                                        TestResult::ProbeFailure { path } => {
                                                panic!("Unexpected probe failure: {:?}", path)
@@ -981,7 +976,7 @@ mod tests {
                        }
                }
 
-               fn probe_failed(&mut self, actual_path: &[&RouteHop], _: u64) {
+               fn probe_failed(&mut self, actual_path: &Path, _: u64) {
                        if let Some(expectations) = &mut self.event_expectations {
                                match expectations.pop_front().unwrap() {
                                        TestResult::PaymentFailure { path, .. } => {
@@ -991,7 +986,7 @@ mod tests {
                                                panic!("Unexpected payment path success: {:?}", path)
                                        },
                                        TestResult::ProbeFailure { path } => {
-                                               assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+                                               assert_eq!(actual_path, &path);
                                        },
                                        TestResult::ProbeSuccess { path } => {
                                                panic!("Unexpected probe success: {:?}", path)
@@ -999,7 +994,7 @@ mod tests {
                                }
                        }
                }
-               fn probe_successful(&mut self, actual_path: &[&RouteHop]) {
+               fn probe_successful(&mut self, actual_path: &Path) {
                        if let Some(expectations) = &mut self.event_expectations {
                                match expectations.pop_front().unwrap() {
                                        TestResult::PaymentFailure { path, .. } => {
@@ -1012,7 +1007,7 @@ mod tests {
                                                panic!("Unexpected probe failure: {:?}", path)
                                        },
                                        TestResult::ProbeSuccess { path } => {
-                                               assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+                                               assert_eq!(actual_path, &path);
                                        }
                                }
                        }
@@ -1540,14 +1535,14 @@ mod tests {
                        let node_1_privkey = SecretKey::from_slice(&[42; 32]).unwrap();
                        let node_1_id = PublicKey::from_secret_key(&secp_ctx, &node_1_privkey);
 
-                       let path = vec![RouteHop {
+                       let path = Path { hops: vec![RouteHop {
                                pubkey: node_1_id,
                                node_features: NodeFeatures::empty(),
                                short_channel_id: scored_scid,
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 0,
                                cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
-                       }];
+                       }], blinded_tail: None };
 
                        $nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
                        $nodes[0].node.push_pending_event(Event::PaymentPathFailed {
diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs
new file mode 100644 (file)
index 0000000..2cd03b8
--- /dev/null
@@ -0,0 +1,233 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Creating blinded paths and related utilities live here.
+
+pub(crate) mod utils;
+
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
+
+use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
+use crate::onion_message::ControlTlvs;
+use crate::ln::msgs::DecodeError;
+use crate::ln::onion_utils;
+use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
+use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
+
+use core::mem;
+use core::ops::Deref;
+use crate::io::{self, Cursor};
+use crate::prelude::*;
+
+/// Onion messages and payments can be sent and received to blinded paths, which serve to hide the
+/// identity of the recipient.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct BlindedPath {
+       /// To send to a blinded path, the sender first finds a route to the unblinded
+       /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
+       /// message or payment's next hop and forward it along.
+       ///
+       /// [`encrypted_payload`]: BlindedHop::encrypted_payload
+       pub(crate) introduction_node_id: PublicKey,
+       /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
+       /// message or payment.
+       ///
+       /// [`encrypted_payload`]: BlindedHop::encrypted_payload
+       pub(crate) blinding_point: PublicKey,
+       /// The hops composing the blinded path.
+       pub(crate) blinded_hops: Vec<BlindedHop>,
+}
+
+/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified
+/// by outside observers and thus can be used to hide the identity of the recipient.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct BlindedHop {
+       /// The blinded node id of this hop in a blinded path.
+       pub(crate) blinded_node_id: PublicKey,
+       /// The encrypted payload intended for this hop in a blinded path.
+       // The node sending to this blinded path will later encode this payload into the onion packet for
+       // this hop.
+       pub(crate) encrypted_payload: Vec<u8>,
+}
+
+impl BlindedPath {
+       /// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node
+       /// pubkey in `node_pks` will be the destination node.
+       ///
+       /// Errors if less than two hops are provided or if `node_pk`(s) are invalid.
+       //  TODO: make all payloads the same size with padding + add dummy hops
+       pub fn new_for_message<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>
+               (node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
+       {
+               if node_pks.len() < 2 { return Err(()) }
+               let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
+               let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
+               let introduction_node_id = node_pks[0];
+
+               Ok(BlindedPath {
+                       introduction_node_id,
+                       blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
+                       blinded_hops: blinded_message_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
+               })
+       }
+
+       // Advance the blinded onion message path by one hop, so make the second hop into the new
+       // introduction node.
+       pub(super) fn advance_message_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>
+               (&mut self, node_signer: &NS, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
+               where NS::Target: NodeSigner
+       {
+               let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?;
+               let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
+               let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
+               let mut s = Cursor::new(&encrypted_control_tlvs);
+               let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
+               match ChaChaPolyReadAdapter::read(&mut reader, rho) {
+                       Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
+                               mut next_node_id, next_blinding_override,
+                       })}) => {
+                               let mut new_blinding_point = match next_blinding_override {
+                                       Some(blinding_point) => blinding_point,
+                                       None => {
+                                               let blinding_factor = {
+                                                       let mut sha = Sha256::engine();
+                                                       sha.input(&self.blinding_point.serialize()[..]);
+                                                       sha.input(control_tlvs_ss.as_ref());
+                                                       Sha256::from_engine(sha).into_inner()
+                                               };
+                                               self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
+                                                       .map_err(|_| ())?
+                                       }
+                               };
+                               mem::swap(&mut self.blinding_point, &mut new_blinding_point);
+                               mem::swap(&mut self.introduction_node_id, &mut next_node_id);
+                               Ok(())
+                       },
+                       _ => Err(())
+               }
+       }
+}
+
+/// Construct blinded onion message hops for the given `unblinded_path`.
+fn blinded_message_hops<T: secp256k1::Signing + secp256k1::Verification>(
+       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
+) -> Result<Vec<BlindedHop>, secp256k1::Error> {
+       let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
+
+       let mut prev_ss_and_blinded_node_id = None;
+       utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
+               if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
+                       if let Some(pk) = unblinded_pk {
+                               let payload = ForwardTlvs {
+                                       next_node_id: pk,
+                                       next_blinding_override: None,
+                               };
+                               blinded_hops.push(BlindedHop {
+                                       blinded_node_id: prev_blinded_node_id,
+                                       encrypted_payload: encrypt_payload(payload, prev_ss),
+                               });
+                       } else { debug_assert!(false); }
+               }
+               prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
+       })?;
+
+       if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
+               let final_payload = ReceiveTlvs { path_id: None };
+               blinded_hops.push(BlindedHop {
+                       blinded_node_id: final_blinded_node_id,
+                       encrypted_payload: encrypt_payload(final_payload, final_ss),
+               });
+       } else { debug_assert!(false) }
+
+       Ok(blinded_hops)
+}
+
+/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
+fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
+       let mut writer = VecWriter(Vec::new());
+       let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
+       write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
+       writer.0
+}
+
+impl Writeable for BlindedPath {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               self.introduction_node_id.write(w)?;
+               self.blinding_point.write(w)?;
+               (self.blinded_hops.len() as u8).write(w)?;
+               for hop in &self.blinded_hops {
+                       hop.write(w)?;
+               }
+               Ok(())
+       }
+}
+
+impl Readable for BlindedPath {
+       fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+               let introduction_node_id = Readable::read(r)?;
+               let blinding_point = Readable::read(r)?;
+               let num_hops: u8 = Readable::read(r)?;
+               if num_hops == 0 { return Err(DecodeError::InvalidValue) }
+               let mut blinded_hops: Vec<BlindedHop> = Vec::with_capacity(num_hops.into());
+               for _ in 0..num_hops {
+                       blinded_hops.push(Readable::read(r)?);
+               }
+               Ok(BlindedPath {
+                       introduction_node_id,
+                       blinding_point,
+                       blinded_hops,
+               })
+       }
+}
+
+impl_writeable!(BlindedHop, {
+       blinded_node_id,
+       encrypted_payload
+});
+
+/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
+/// route, they are encoded into [`BlindedHop::encrypted_payload`].
+pub(crate) struct ForwardTlvs {
+       /// The node id of the next hop in the onion message's path.
+       pub(super) next_node_id: PublicKey,
+       /// Senders to a blinded path use this value to concatenate the route they find to the
+       /// introduction node with the blinded path.
+       pub(super) next_blinding_override: Option<PublicKey>,
+}
+
+/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
+pub(crate) struct ReceiveTlvs {
+       /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
+       /// sending to. This is useful for receivers to check that said blinded path is being used in
+       /// the right context.
+       pub(super) path_id: Option<[u8; 32]>,
+}
+
+impl Writeable for ForwardTlvs {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               // TODO: write padding
+               encode_tlv_stream!(writer, {
+                       (4, self.next_node_id, required),
+                       (8, self.next_blinding_override, option)
+               });
+               Ok(())
+       }
+}
+
+impl Writeable for ReceiveTlvs {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               // TODO: write padding
+               encode_tlv_stream!(writer, {
+                       (6, self.path_id, option),
+               });
+               Ok(())
+       }
+}
diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs
new file mode 100644 (file)
index 0000000..1993ad9
--- /dev/null
@@ -0,0 +1,98 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Onion message utility methods live here.
+
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::hmac::{Hmac, HmacEngine};
+use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar};
+use bitcoin::secp256k1::ecdh::SharedSecret;
+
+use super::BlindedPath;
+use crate::ln::onion_utils;
+use crate::onion_message::Destination;
+
+use crate::prelude::*;
+
+// TODO: DRY with onion_utils::construct_onion_keys_callback
+#[inline]
+pub(crate) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
+       FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
+       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
+       session_priv: &SecretKey, mut callback: FType
+) -> Result<(), secp256k1::Error> {
+       let mut msg_blinding_point_priv = session_priv.clone();
+       let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
+       let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
+       let mut onion_packet_pubkey = msg_blinding_point.clone();
+
+       macro_rules! build_keys {
+               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {{
+                       let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
+
+                       let blinded_hop_pk = if $blinded { $pk } else {
+                               let hop_pk_blinding_factor = {
+                                       let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
+                                       hmac.input(encrypted_data_ss.as_ref());
+                                       Hmac::from_engine(hmac).into_inner()
+                               };
+                               $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())?
+                       };
+                       let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv);
+
+                       let rho = onion_utils::gen_rho_from_shared_secret(encrypted_data_ss.as_ref());
+                       let unblinded_pk_opt = if $blinded { None } else { Some($pk) };
+                       callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt, $encrypted_payload);
+                       (encrypted_data_ss, onion_packet_ss)
+               }}
+       }
+
+       macro_rules! build_keys_in_loop {
+               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {
+                       let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload);
+
+                       let msg_blinding_point_blinding_factor = {
+                               let mut sha = Sha256::engine();
+                               sha.input(&msg_blinding_point.serialize()[..]);
+                               sha.input(encrypted_data_ss.as_ref());
+                               Sha256::from_engine(sha).into_inner()
+                       };
+
+                       msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?;
+                       msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
+
+                       let onion_packet_pubkey_blinding_factor = {
+                               let mut sha = Sha256::engine();
+                               sha.input(&onion_packet_pubkey.serialize()[..]);
+                               sha.input(onion_packet_ss.as_ref());
+                               Sha256::from_engine(sha).into_inner()
+                       };
+                       onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?;
+                       onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv);
+               };
+       }
+
+       for pk in unblinded_path {
+               build_keys_in_loop!(*pk, false, None);
+       }
+       if let Some(dest) = destination {
+               match dest {
+                       Destination::Node(pk) => {
+                               build_keys!(pk, false, None);
+                       },
+                       Destination::BlindedPath(BlindedPath { blinded_hops, .. }) => {
+                               for hop in blinded_hops {
+                                       build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload));
+                               }
+                       },
+               }
+       }
+       Ok(())
+}
index 1bd1214596de769154fab9cd37e41df1b3748e7d..110d56cfe4aa327318c1e1dc4cde505b7e8d3df6 100644 (file)
@@ -30,7 +30,7 @@ use crate::routing::gossip::NetworkUpdate;
 use crate::util::errors::APIError;
 use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength};
 use crate::util::string::UntrustedString;
-use crate::routing::router::{RouteHop, RouteParameters};
+use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
 
 use bitcoin::{PackedLockTime, Transaction, OutPoint};
 #[cfg(anchors)]
@@ -503,7 +503,7 @@ pub enum Event {
                /// The payment path that was successful.
                ///
                /// May contain a closed channel if the HTLC sent along the path was fulfilled on chain.
-               path: Vec<RouteHop>,
+               path: Path,
        },
        /// Indicates an outbound HTLC we sent failed, likely due to an intermediary node being unable to
        /// handle the HTLC.
@@ -535,7 +535,7 @@ pub enum Event {
                /// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph
                failure: PathFailure,
                /// The payment path that failed.
-               path: Vec<RouteHop>,
+               path: Path,
                /// The channel responsible for the failed payment path.
                ///
                /// Note that for route hints or for the first hop in a path this may be an SCID alias and
@@ -561,7 +561,7 @@ pub enum Event {
                /// [`ChannelManager::send_probe`]: crate::ln::channelmanager::ChannelManager::send_probe
                payment_hash: PaymentHash,
                /// The payment path that was successful.
-               path: Vec<RouteHop>,
+               path: Path,
        },
        /// Indicates that a probe payment we sent failed at an intermediary node on the path.
        ProbeFailed {
@@ -574,7 +574,7 @@ pub enum Event {
                /// [`ChannelManager::send_probe`]: crate::ln::channelmanager::ChannelManager::send_probe
                payment_hash: PaymentHash,
                /// The payment path that failed.
-               path: Vec<RouteHop>,
+               path: Path,
                /// The channel responsible for the failed probe.
                ///
                /// Note that for route hints or for the first hop in a path this may be an SCID alias and
@@ -884,7 +884,8 @@ impl Writeable for Event {
                                        (1, None::<NetworkUpdate>, option), // network_update in LDK versions prior to 0.0.114
                                        (2, payment_failed_permanently, required),
                                        (3, false, required), // all_paths_failed in LDK versions prior to 0.0.114
-                                       (5, *path, vec_type),
+                                       (4, path.blinded_tail, option),
+                                       (5, path.hops, vec_type),
                                        (7, short_channel_id, option),
                                        (9, None::<RouteParameters>, option), // retry in LDK versions prior to 0.0.115
                                        (11, payment_id, option),
@@ -952,7 +953,8 @@ impl Writeable for Event {
                                write_tlv_fields!(writer, {
                                        (0, payment_id, required),
                                        (2, payment_hash, option),
-                                       (4, *path, vec_type)
+                                       (4, path.hops, vec_type),
+                                       (6, path.blinded_tail, option),
                                })
                        },
                        &Event::PaymentFailed { ref payment_id, ref payment_hash, ref reason } => {
@@ -982,7 +984,8 @@ impl Writeable for Event {
                                write_tlv_fields!(writer, {
                                        (0, payment_id, required),
                                        (2, payment_hash, required),
-                                       (4, *path, vec_type)
+                                       (4, path.hops, vec_type),
+                                       (6, path.blinded_tail, option),
                                })
                        },
                        &Event::ProbeFailed { ref payment_id, ref payment_hash, ref path, ref short_channel_id } => {
@@ -990,8 +993,9 @@ impl Writeable for Event {
                                write_tlv_fields!(writer, {
                                        (0, payment_id, required),
                                        (2, payment_hash, required),
-                                       (4, *path, vec_type),
+                                       (4, path.hops, vec_type),
                                        (6, short_channel_id, option),
+                                       (8, path.blinded_tail, option),
                                })
                        },
                        &Event::HTLCHandlingFailed { ref prev_channel_id, ref failed_next_destination } => {
@@ -1122,6 +1126,7 @@ impl MaybeReadable for Event {
                                        let mut payment_hash = PaymentHash([0; 32]);
                                        let mut payment_failed_permanently = false;
                                        let mut network_update = None;
+                                       let mut blinded_tail: Option<BlindedTail> = None;
                                        let mut path: Option<Vec<RouteHop>> = Some(vec![]);
                                        let mut short_channel_id = None;
                                        let mut payment_id = None;
@@ -1130,6 +1135,7 @@ impl MaybeReadable for Event {
                                                (0, payment_hash, required),
                                                (1, network_update, upgradable_option),
                                                (2, payment_failed_permanently, required),
+                                               (4, blinded_tail, option),
                                                (5, path, vec_type),
                                                (7, short_channel_id, option),
                                                (11, payment_id, option),
@@ -1141,7 +1147,7 @@ impl MaybeReadable for Event {
                                                payment_hash,
                                                payment_failed_permanently,
                                                failure,
-                                               path: path.unwrap(),
+                                               path: Path { hops: path.unwrap(), blinded_tail },
                                                short_channel_id,
                                                #[cfg(test)]
                                                error_code,
@@ -1244,18 +1250,16 @@ impl MaybeReadable for Event {
                        },
                        13u8 => {
                                let f = || {
-                                       let mut payment_id = PaymentId([0; 32]);
-                                       let mut payment_hash = None;
-                                       let mut path: Option<Vec<RouteHop>> = Some(vec![]);
-                                       read_tlv_fields!(reader, {
+                                       _init_and_read_tlv_fields!(reader, {
                                                (0, payment_id, required),
                                                (2, payment_hash, option),
                                                (4, path, vec_type),
+                                               (6, blinded_tail, option),
                                        });
                                        Ok(Some(Event::PaymentPathSuccessful {
-                                               payment_id,
+                                               payment_id: payment_id.0.unwrap(),
                                                payment_hash,
-                                               path: path.unwrap(),
+                                               path: Path { hops: path.unwrap(), blinded_tail },
                                        }))
                                };
                                f()
@@ -1305,38 +1309,33 @@ impl MaybeReadable for Event {
                        },
                        21u8 => {
                                let f = || {
-                                       let mut payment_id = PaymentId([0; 32]);
-                                       let mut payment_hash = PaymentHash([0; 32]);
-                                       let mut path: Option<Vec<RouteHop>> = Some(vec![]);
-                                       read_tlv_fields!(reader, {
+                                       _init_and_read_tlv_fields!(reader, {
                                                (0, payment_id, required),
                                                (2, payment_hash, required),
                                                (4, path, vec_type),
+                                               (6, blinded_tail, option),
                                        });
                                        Ok(Some(Event::ProbeSuccessful {
-                                               payment_id,
-                                               payment_hash,
-                                               path: path.unwrap(),
+                                               payment_id: payment_id.0.unwrap(),
+                                               payment_hash: payment_hash.0.unwrap(),
+                                               path: Path { hops: path.unwrap(), blinded_tail },
                                        }))
                                };
                                f()
                        },
                        23u8 => {
                                let f = || {
-                                       let mut payment_id = PaymentId([0; 32]);
-                                       let mut payment_hash = PaymentHash([0; 32]);
-                                       let mut path: Option<Vec<RouteHop>> = Some(vec![]);
-                                       let mut short_channel_id = None;
-                                       read_tlv_fields!(reader, {
+                                       _init_and_read_tlv_fields!(reader, {
                                                (0, payment_id, required),
                                                (2, payment_hash, required),
                                                (4, path, vec_type),
                                                (6, short_channel_id, option),
+                                               (8, blinded_tail, option),
                                        });
                                        Ok(Some(Event::ProbeFailed {
-                                               payment_id,
-                                               payment_hash,
-                                               path: path.unwrap(),
+                                               payment_id: payment_id.0.unwrap(),
+                                               payment_hash: payment_hash.0.unwrap(),
+                                               path: Path { hops: path.unwrap(), blinded_tail },
                                                short_channel_id,
                                        }))
                                };
index ad16914c35c3476992a1e855e7c8385e9811a7ff..668f752e6c261c455f36be6114f1597c70bb204b 100644 (file)
@@ -81,6 +81,7 @@ pub mod ln;
 pub mod offers;
 pub mod routing;
 pub mod onion_message;
+pub mod blinded_path;
 pub mod events;
 
 #[cfg(feature = "std")]
index 01bb7dde49f01d0a834e6f7f9f116f2bbc2f19f2..3f210cb46c535dfb433575d558740882f4446cd3 100644 (file)
@@ -2000,12 +2000,12 @@ fn test_path_paused_mpp() {
        // Set us up to take multiple routes, one 0 -> 1 -> 3 and one 0 -> 2 -> 3:
        let path = route.paths[0].clone();
        route.paths.push(path);
-       route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-       route.paths[0][0].short_channel_id = chan_1_id;
-       route.paths[0][1].short_channel_id = chan_3_id;
-       route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-       route.paths[1][0].short_channel_id = chan_2_ann.contents.short_channel_id;
-       route.paths[1][1].short_channel_id = chan_4_id;
+       route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+       route.paths[0].hops[0].short_channel_id = chan_1_id;
+       route.paths[0].hops[1].short_channel_id = chan_3_id;
+       route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+       route.paths[1].hops[0].short_channel_id = chan_2_ann.contents.short_channel_id;
+       route.paths[1].hops[1].short_channel_id = chan_4_id;
 
        // Set it so that the first monitor update (for the path 0 -> 1 -> 3) succeeds, but the second
        // (for the path 0 -> 2 -> 3) fails.
index dd553c1ff8604c75c03063fffb1e073fc64ae7fe..8b5e89d9441ace4510bddd65f877fc7355f94604 100644 (file)
@@ -7024,6 +7024,7 @@ mod tests {
        use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
        use crate::chain::keysinterface::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
        use crate::chain::transaction::OutPoint;
+       use crate::routing::router::Path;
        use crate::util::config::UserConfig;
        use crate::util::enforcing_trait_impls::EnforcingSigner;
        use crate::util::errors::APIError;
@@ -7201,7 +7202,7 @@ mod tests {
                        cltv_expiry: 200000000,
                        state: OutboundHTLCState::Committed,
                        source: HTLCSource::OutboundRoute {
-                               path: Vec::new(),
+                               path: Path { hops: Vec::new(), blinded_tail: None },
                                session_priv: SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
                                first_hop_htlc_msat: 548,
                                payment_id: PaymentId([42; 32]),
index d019cd6fd2a561741bba7da9efe01259160da98e..498813de7a11900e2dbdcffa9adf8de9fd037d6c 100644 (file)
@@ -45,7 +45,7 @@ use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, No
 #[cfg(any(feature = "_test_utils", test))]
 use crate::ln::features::InvoiceFeatures;
 use crate::routing::gossip::NetworkGraph;
-use crate::routing::router::{DefaultRouter, InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
+use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters, Router};
 use crate::routing::scoring::ProbabilisticScorer;
 use crate::ln::msgs;
 use crate::ln::onion_utils;
@@ -282,7 +282,7 @@ impl_writeable_tlv_based_enum!(SentHTLCId,
 pub(crate) enum HTLCSource {
        PreviousHopData(HTLCPreviousHopData),
        OutboundRoute {
-               path: Vec<RouteHop>,
+               path: Path,
                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
@@ -313,7 +313,7 @@ impl HTLCSource {
        #[cfg(test)]
        pub fn dummy() -> Self {
                HTLCSource::OutboundRoute {
-                       path: Vec::new(),
+                       path: Path { hops: Vec::new(), blinded_tail: None },
                        session_priv: SecretKey::from_slice(&[1; 32]).unwrap(),
                        first_hop_htlc_msat: 0,
                        payment_id: PaymentId([2; 32]),
@@ -2659,16 +2659,16 @@ where
        }
 
        #[cfg(test)]
-       pub(crate) fn test_send_payment_along_path(&self, path: &Vec<RouteHop>, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
+       pub(crate) fn test_send_payment_along_path(&self, path: &Path, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
                let _lck = self.total_consistency_lock.read().unwrap();
                self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv_bytes)
        }
 
-       fn send_payment_along_path(&self, path: &Vec<RouteHop>, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
+       fn send_payment_along_path(&self, path: &Path, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
                // The top-level caller should hold the total_consistency_lock read lock.
                debug_assert!(self.total_consistency_lock.try_write().is_err());
 
-               log_trace!(self.logger, "Attempting to send payment for path with next hop {}", path.first().unwrap().short_channel_id);
+               log_trace!(self.logger, "Attempting to send payment for path with next hop {}", path.hops.first().unwrap().short_channel_id);
                let prng_seed = self.entropy_source.get_secure_random_bytes();
                let session_priv = SecretKey::from_slice(&session_priv_bytes[..]).expect("RNG is busted");
 
@@ -2681,7 +2681,7 @@ where
                let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash);
 
                let err: Result<(), _> = loop {
-                       let (counterparty_node_id, id) = match self.short_to_chan_info.read().unwrap().get(&path.first().unwrap().short_channel_id) {
+                       let (counterparty_node_id, id) = match self.short_to_chan_info.read().unwrap().get(&path.hops.first().unwrap().short_channel_id) {
                                None => return Err(APIError::ChannelUnavailable{err: "No channel available with first hop!".to_owned()}),
                                Some((cp_id, chan_id)) => (cp_id.clone(), chan_id.clone()),
                        };
@@ -2732,7 +2732,7 @@ where
                        return Ok(());
                };
 
-               match handle_error!(self, err, path.first().unwrap().pubkey) {
+               match handle_error!(self, err, path.hops.first().unwrap().pubkey) {
                        Ok(_) => unreachable!(),
                        Err(e) => {
                                Err(APIError::ChannelUnavailable { err: e.err })
@@ -2902,10 +2902,10 @@ where
        /// Send a payment that is probing the given route for liquidity. We calculate the
        /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
        /// us to easily discern them from real payments.
-       pub fn send_probe(&self, hops: Vec<RouteHop>) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
+       pub fn send_probe(&self, path: Path) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
                let best_block_height = self.best_block.read().unwrap().height();
                let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
-               self.pending_outbound_payments.send_probe(hops, self.probing_cookie_secret, &self.entropy_source, &self.node_signer, best_block_height,
+               self.pending_outbound_payments.send_probe(path, self.probing_cookie_secret, &self.entropy_source, &self.node_signer, best_block_height,
                        |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
                        self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
        }
@@ -7025,28 +7025,30 @@ impl Readable for HTLCSource {
                        0 => {
                                let mut session_priv: crate::util::ser::RequiredWrapper<SecretKey> = crate::util::ser::RequiredWrapper(None);
                                let mut first_hop_htlc_msat: u64 = 0;
-                               let mut path: Option<Vec<RouteHop>> = Some(Vec::new());
+                               let mut path_hops: Option<Vec<RouteHop>> = Some(Vec::new());
                                let mut payment_id = None;
                                let mut payment_params: Option<PaymentParameters> = None;
+                               let mut blinded_tail: Option<BlindedTail> = None;
                                read_tlv_fields!(reader, {
                                        (0, session_priv, required),
                                        (1, payment_id, option),
                                        (2, first_hop_htlc_msat, required),
-                                       (4, path, vec_type),
+                                       (4, path_hops, vec_type),
                                        (5, payment_params, (option: ReadableArgs, 0)),
+                                       (6, blinded_tail, option),
                                });
                                if payment_id.is_none() {
                                        // For backwards compat, if there was no payment_id written, use the session_priv bytes
                                        // instead.
                                        payment_id = Some(PaymentId(*session_priv.0.unwrap().as_ref()));
                                }
-                               if path.is_none() || path.as_ref().unwrap().is_empty() {
+                               let path = Path { hops: path_hops.ok_or(DecodeError::InvalidValue)?, blinded_tail };
+                               if path.hops.len() == 0 {
                                        return Err(DecodeError::InvalidValue);
                                }
-                               let path = path.unwrap();
                                if let Some(params) = payment_params.as_mut() {
                                        if params.final_cltv_expiry_delta == 0 {
-                                               params.final_cltv_expiry_delta = path.last().unwrap().cltv_expiry_delta;
+                                               params.final_cltv_expiry_delta = path.final_cltv_expiry_delta().ok_or(DecodeError::InvalidValue)?;
                                        }
                                }
                                Ok(HTLCSource::OutboundRoute {
@@ -7073,8 +7075,9 @@ impl Writeable for HTLCSource {
                                        (1, payment_id_opt, option),
                                        (2, first_hop_htlc_msat, required),
                                        // 3 was previously used to write a PaymentSecret for the payment.
-                                       (4, *path, vec_type),
+                                       (4, path.hops, vec_type),
                                        (5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
+                                       (6, path.blinded_tail, option),
                                 });
                        }
                        HTLCSource::PreviousHopData(ref field) => {
@@ -7741,12 +7744,12 @@ where
                                if id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id()).is_none() {
                                        for (htlc_source, (htlc, _)) in monitor.get_pending_or_resolved_outbound_htlcs() {
                                                if let HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } = htlc_source {
-                                                       if path.is_empty() {
+                                                       if path.hops.is_empty() {
                                                                log_error!(args.logger, "Got an empty path for a pending payment");
                                                                return Err(DecodeError::InvalidValue);
                                                        }
 
-                                                       let path_amt = path.last().unwrap().fee_msat;
+                                                       let path_amt = path.final_value_msat();
                                                        let mut session_priv_bytes = [0; 32];
                                                        session_priv_bytes[..].copy_from_slice(&session_priv[..]);
                                                        match pending_outbounds.pending_outbound_payments.lock().unwrap().entry(payment_id) {
@@ -7756,7 +7759,7 @@ where
                                                                                if newly_added { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), log_bytes!(htlc.payment_hash.0));
                                                                },
                                                                hash_map::Entry::Vacant(entry) => {
-                                                                       let path_fee = path.get_path_fees();
+                                                                       let path_fee = path.fee_msat();
                                                                        entry.insert(PendingOutboundPayment::Retryable {
                                                                                retry_strategy: None,
                                                                                attempts: PaymentAttempts::new(),
@@ -8502,12 +8505,12 @@ mod tests {
                let (mut route, payment_hash, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[3], 100000);
                let path = route.paths[0].clone();
                route.paths.push(path);
-               route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-               route.paths[0][0].short_channel_id = chan_1_id;
-               route.paths[0][1].short_channel_id = chan_3_id;
-               route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-               route.paths[1][0].short_channel_id = chan_2_id;
-               route.paths[1][1].short_channel_id = chan_4_id;
+               route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+               route.paths[0].hops[0].short_channel_id = chan_1_id;
+               route.paths[0].hops[1].short_channel_id = chan_3_id;
+               route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+               route.paths[1].hops[0].short_channel_id = chan_2_id;
+               route.paths[1].hops[1].short_channel_id = chan_4_id;
 
                match nodes[0].node.send_payment_with_route(&route, payment_hash,
                        RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0))
index ca6c1eb29538c5b9291080178ebf362403b4da7f..dc7f32666ed0c5c1faaed08f22d188168073b04f 100644 (file)
@@ -2276,8 +2276,8 @@ pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route:
                .with_features(expected_route.last().unwrap().node.invoice_features());
        let route = get_route(origin_node, &payment_params, recv_value, TEST_FINAL_CLTV).unwrap();
        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!(route.paths[0].hops.len(), expected_route.len());
+       for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
                assert_eq!(hop.pubkey, node.node.get_our_node_id());
        }
 
@@ -2297,8 +2297,8 @@ pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_rou
                &origin_node.node.get_our_node_id(), &payment_params, &network_graph,
                None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer, &random_seed_bytes).unwrap();
        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!(route.paths[0].hops.len(), expected_route.len());
+       for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
                assert_eq!(hop.pubkey, node.node.get_our_node_id());
        }
 
@@ -2404,7 +2404,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
                                        assert_eq!(payment_hash, our_payment_hash);
                                        assert!(payment_failed_permanently);
                                        for (idx, hop) in expected_route.iter().enumerate() {
-                                               assert_eq!(hop.node.get_our_node_id(), path[idx].pubkey);
+                                               assert_eq!(hop.node.get_our_node_id(), path.hops[idx].pubkey);
                                        }
                                        payment_id.unwrap()
                                },
index 53c056ee62c58bf0ebebd9d88c9dd51c0bb87f87..2b8c8b8e3414af084180668fcc70b0ad59409ee8 100644 (file)
@@ -26,7 +26,7 @@ use crate::ln::channel::{Channel, ChannelError};
 use crate::ln::{chan_utils, onion_utils};
 use crate::ln::chan_utils::{OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
 use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
-use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
+use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
 use crate::ln::features::{ChannelFeatures, NodeFeatures};
 use crate::ln::msgs;
 use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
@@ -1044,7 +1044,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 { paths: vec![hops], payment_params: None }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
+       let payment_preimage_1 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
 
        let mut hops = Vec::with_capacity(3);
        hops.push(RouteHop {
@@ -1073,7 +1073,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 { paths: vec![hops], payment_params: None }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
+       let payment_hash_2 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &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);
@@ -1831,8 +1831,8 @@ fn test_channel_reserve_holding_cell_htlcs() {
                let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
                        .with_features(nodes[2].node.invoice_features()).with_max_channel_saturation_power_of_half(0);
                let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, recv_value_0, TEST_FINAL_CLTV);
-               route.paths[0].last_mut().unwrap().fee_msat += 1;
-               assert!(route.paths[0].iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
+               route.paths[0].hops.last_mut().unwrap().fee_msat += 1;
+               assert!(route.paths[0].hops.iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
 
                unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
                                RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)
@@ -5749,7 +5749,7 @@ fn test_fail_holding_cell_htlc_upon_free() {
                        assert_eq!(PaymentId(our_payment_hash.0), *payment_id.as_ref().unwrap());
                        assert_eq!(our_payment_hash.clone(), *payment_hash);
                        assert_eq!(*payment_failed_permanently, false);
-                       assert_eq!(*short_channel_id, Some(route.paths[0][0].short_channel_id));
+                       assert_eq!(*short_channel_id, Some(route.paths[0].hops[0].short_channel_id));
                },
                _ => panic!("Unexpected event"),
        }
@@ -5840,7 +5840,7 @@ fn test_free_and_fail_holding_cell_htlcs() {
                        assert_eq!(payment_id_2, *payment_id.as_ref().unwrap());
                        assert_eq!(payment_hash_2.clone(), *payment_hash);
                        assert_eq!(*payment_failed_permanently, false);
-                       assert_eq!(*short_channel_id, Some(route_2.paths[0][0].short_channel_id));
+                       assert_eq!(*short_channel_id, Some(route_2.paths[0].hops[0].short_channel_id));
                },
                _ => panic!("Unexpected event"),
        }
@@ -6037,7 +6037,7 @@ fn test_update_add_htlc_bolt2_sender_value_below_minimum_msat() {
        let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 95000000);
 
        let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100000);
-       route.paths[0][0].fee_msat = 100;
+       route.paths[0].hops[0].fee_msat = 100;
 
        unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
                        RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)
@@ -6057,7 +6057,7 @@ fn test_update_add_htlc_bolt2_sender_zero_value_msat() {
        let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 95000000);
 
        let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100000);
-       route.paths[0][0].fee_msat = 0;
+       route.paths[0].hops[0].fee_msat = 0;
        unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
                        RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)),
                true, APIError::ChannelUnavailable { ref err },
@@ -6103,7 +6103,7 @@ fn test_update_add_htlc_bolt2_sender_cltv_expiry_too_high() {
        let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 0)
                .with_features(nodes[1].node.invoice_features());
        let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_params, 100000000, 0);
-       route.paths[0].last_mut().unwrap().cltv_expiry_delta = 500000001;
+       route.paths[0].hops.last_mut().unwrap().cltv_expiry_delta = 500000001;
        unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
                        RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)
                ), true, APIError::InvalidRoute { ref err },
@@ -6172,7 +6172,7 @@ fn test_update_add_htlc_bolt2_sender_exceed_max_htlc_value_in_flight() {
        let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], max_in_flight);
        // Manually create a route over our max in flight (which our router normally automatically
        // limits us to.
-       route.paths[0][0].fee_msat =  max_in_flight + 1;
+       route.paths[0].hops[0].fee_msat =  max_in_flight + 1;
        unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
                        RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)
                ), true, APIError::ChannelUnavailable { ref err },
@@ -7671,7 +7671,7 @@ fn test_pending_claimed_htlc_no_balance_underflow() {
        // almost-claimed HTLC as available balance.
        let (mut route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 10_000);
        route.payment_params = None; // This is all wrong, but unnecessary
-       route.paths[0][0].pubkey = nodes[0].node.get_our_node_id();
+       route.paths[0].hops[0].pubkey = nodes[0].node.get_our_node_id();
        let (_, payment_hash_2, payment_secret_2) = get_payment_preimage_hash!(nodes[0]);
        nodes[1].node.send_payment_with_route(&route, payment_hash_2,
                RecipientOnionFields::secret_only(payment_secret_2), PaymentId(payment_hash_2.0)).unwrap();
@@ -8043,19 +8043,19 @@ fn test_onion_value_mpp_set_calculation() {
        let sample_path = route.paths.pop().unwrap();
 
        let mut path_1 = sample_path.clone();
-       path_1[0].pubkey = nodes[1].node.get_our_node_id();
-       path_1[0].short_channel_id = chan_1_id;
-       path_1[1].pubkey = nodes[3].node.get_our_node_id();
-       path_1[1].short_channel_id = chan_3_id;
-       path_1[1].fee_msat = 100_000;
+       path_1.hops[0].pubkey = nodes[1].node.get_our_node_id();
+       path_1.hops[0].short_channel_id = chan_1_id;
+       path_1.hops[1].pubkey = nodes[3].node.get_our_node_id();
+       path_1.hops[1].short_channel_id = chan_3_id;
+       path_1.hops[1].fee_msat = 100_000;
        route.paths.push(path_1);
 
        let mut path_2 = sample_path.clone();
-       path_2[0].pubkey = nodes[2].node.get_our_node_id();
-       path_2[0].short_channel_id = chan_2_id;
-       path_2[1].pubkey = nodes[3].node.get_our_node_id();
-       path_2[1].short_channel_id = chan_4_id;
-       path_2[1].fee_msat = 1_000;
+       path_2.hops[0].pubkey = nodes[2].node.get_our_node_id();
+       path_2.hops[0].short_channel_id = chan_2_id;
+       path_2.hops[1].pubkey = nodes[3].node.get_our_node_id();
+       path_2.hops[1].short_channel_id = chan_4_id;
+       path_2.hops[1].fee_msat = 1_000;
        route.paths.push(path_2);
 
        // Send payment
@@ -8152,11 +8152,11 @@ fn do_test_overshoot_mpp(msat_amounts: &[u64], total_msat: u64) {
        for i in 0..routing_node_count {
                let routing_node = 2 + i;
                let mut path = sample_path.clone();
-               path[0].pubkey = nodes[routing_node].node.get_our_node_id();
-               path[0].short_channel_id = src_chan_ids[i];
-               path[1].pubkey = nodes[dst_idx].node.get_our_node_id();
-               path[1].short_channel_id = dst_chan_ids[i];
-               path[1].fee_msat = msat_amounts[i];
+               path.hops[0].pubkey = nodes[routing_node].node.get_our_node_id();
+               path.hops[0].short_channel_id = src_chan_ids[i];
+               path.hops[1].pubkey = nodes[dst_idx].node.get_our_node_id();
+               path.hops[1].short_channel_id = dst_chan_ids[i];
+               path.hops[1].fee_msat = msat_amounts[i];
                route.paths.push(path);
        }
 
@@ -8205,12 +8205,12 @@ fn test_simple_mpp() {
        let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[3], 100000);
        let path = route.paths[0].clone();
        route.paths.push(path);
-       route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-       route.paths[0][0].short_channel_id = chan_1_id;
-       route.paths[0][1].short_channel_id = chan_3_id;
-       route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-       route.paths[1][0].short_channel_id = chan_2_id;
-       route.paths[1][1].short_channel_id = chan_4_id;
+       route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+       route.paths[0].hops[0].short_channel_id = chan_1_id;
+       route.paths[0].hops[1].short_channel_id = chan_3_id;
+       route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+       route.paths[1].hops[0].short_channel_id = chan_2_id;
+       route.paths[1].hops[1].short_channel_id = chan_4_id;
        send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], 200_000, payment_hash, payment_secret);
        claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_preimage);
 }
@@ -9414,7 +9414,7 @@ fn test_inconsistent_mpp_params() {
        assert_eq!(route.paths.len(), 2);
        route.paths.sort_by(|path_a, _| {
                // Sort the path so that the path through nodes[1] comes first
-               if path_a[0].pubkey == nodes[1].node.get_our_node_id() {
+               if path_a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
                        core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
        });
 
@@ -9590,7 +9590,7 @@ fn test_double_partial_claim() {
        assert_eq!(route.paths.len(), 2);
        route.paths.sort_by(|path_a, _| {
                // Sort the path so that the path through nodes[1] comes first
-               if path_a[0].pubkey == nodes[1].node.get_our_node_id() {
+               if path_a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
                        core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
        });
 
index aa3b3b7e8f69dac9ae897152ece49ebf813bab50..d70016cf4ac1e854998a86e48513665238665f21 100644 (file)
@@ -391,7 +391,7 @@ fn test_onion_failure() {
                let session_priv = SecretKey::from_slice(&[3; 32]).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.as_ref(), NODE|2, &[0;0]);
-       }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: false}), Some(route.paths[0][0].short_channel_id));
+       }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
@@ -401,7 +401,7 @@ fn test_onion_failure() {
                msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash);
-       }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false}), Some(route.paths[0][1].short_channel_id));
+       }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id));
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
 
        // intermediate node failure
@@ -411,7 +411,7 @@ fn test_onion_failure() {
                let session_priv = SecretKey::from_slice(&[3; 32]).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.as_ref(), PERM|NODE|2, &[0;0]);
-       }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id));
+       }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
@@ -420,7 +420,7 @@ fn test_onion_failure() {
                msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash);
-       }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}), Some(route.paths[0][1].short_channel_id));
+       }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id));
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
 
        // intermediate node failure
@@ -432,7 +432,7 @@ fn test_onion_failure() {
                msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash);
-       }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id));
+       }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id));
 
        // final node failure
        run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
@@ -441,7 +441,7 @@ fn test_onion_failure() {
                msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0]);
        }, ||{
                nodes[2].node.fail_htlc_backwards(&payment_hash);
-       }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}), Some(route.paths[0][1].short_channel_id));
+       }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id));
        let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
 
        // Our immediate peer sent UpdateFailMalformedHTLC because it couldn't understand the onion in
@@ -502,8 +502,8 @@ fn test_onion_failure() {
        }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id));
 
        let mut bogus_route = route.clone();
-       bogus_route.paths[0][1].short_channel_id -= 1;
-       let short_channel_id = bogus_route.paths[0][1].short_channel_id;
+       bogus_route.paths[0].hops[1].short_channel_id -= 1;
+       let short_channel_id = bogus_route.paths[0].hops[1].short_channel_id;
        run_onion_failure_test("unknown_next_peer", 0, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(PERM|10),
          Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id));
 
@@ -512,8 +512,8 @@ fn test_onion_failure() {
                .unwrap().lock().unwrap().channel_by_id.get(&channels[1].2).unwrap()
                .get_counterparty_htlc_minimum_msat() - 1;
        let mut bogus_route = route.clone();
-       let route_len = bogus_route.paths[0].len();
-       bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward;
+       let route_len = bogus_route.paths[0].hops.len();
+       bogus_route.paths[0].hops[route_len-1].fee_msat = amt_to_forward;
        run_onion_failure_test("amount_below_minimum", 0, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(UPDATE|11), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id));
 
        // Clear pending payments so that the following positive test has the correct payment hash.
@@ -522,7 +522,7 @@ fn test_onion_failure() {
        }
 
        // Test a positive test-case with one extra msat, meeting the minimum.
-       bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward + 1;
+       bogus_route.paths[0].hops[route_len-1].fee_msat = amt_to_forward + 1;
        let preimage = send_along_route(&nodes[0], bogus_route, &[&nodes[1], &nodes[2]], amt_to_forward+1).0;
        claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], preimage);
 
@@ -603,14 +603,14 @@ fn test_onion_failure() {
                let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
                let mut route = route.clone();
                let height = nodes[2].best_block_info().1;
-               route.paths[0][1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0][0].cltv_expiry_delta + 1;
+               route.paths[0].hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0].hops[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], 40000, RecipientOnionFields::spontaneous_empty(), height, &None).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;
-       }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id));
+       }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id));
 
        run_onion_failure_test_with_fail_intercept("mpp_timeout", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
                // Tamper returning error message
@@ -864,7 +864,7 @@ fn test_always_create_tlv_format_onion_payloads() {
                .with_features(InvoiceFeatures::empty());
        let (route, _payment_hash, _payment_preimage, _payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000, TEST_FINAL_CLTV);
 
-       let hops = &route.paths[0];
+       let hops = &route.paths[0].hops;
        // Asserts that the first hop to `node[1]` signals no support for variable length onions.
        assert!(!hops[0].node_features.supports_variable_length_onion());
        // Asserts that the first hop to `node[1]` signals no support for variable length onions.
@@ -1206,7 +1206,7 @@ fn test_phantom_failure_too_low_cltv() {
        let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel);
 
        // Modify the route to have a too-low cltv.
-       route.paths[0][1].cltv_expiry_delta = 5;
+       route.paths[0].hops[1].cltv_expiry_delta = 5;
 
        // Route the HTLC through to the destination.
        nodes[0].node.send_payment_with_route(&route, payment_hash,
@@ -1446,7 +1446,7 @@ fn test_phantom_failure_reject_payment() {
        nodes[1].node.process_pending_htlc_forwards();
        expect_pending_htlcs_forwardable_ignore!(nodes[1]);
        nodes[1].node.process_pending_htlc_forwards();
-       expect_payment_claimable!(nodes[1], payment_hash, payment_secret, recv_amt_msat, None, route.paths[0].last().unwrap().pubkey);
+       expect_payment_claimable!(nodes[1], payment_hash, payment_secret, recv_amt_msat, None, route.paths[0].hops.last().unwrap().pubkey);
        nodes[1].node.fail_htlc_backwards(&payment_hash);
        expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]);
        nodes[1].node.process_pending_htlc_forwards();
index b9f36bbd8dadf94592c9082a303ef378879a4819..36abad71f2d5187be95638d53b9f70c7c9c925e0 100644 (file)
@@ -12,7 +12,7 @@ use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
 use crate::ln::msgs;
 use crate::ln::wire::Encode;
 use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::RouteHop;
+use crate::routing::router::{Path, RouteHop};
 use crate::util::chacha20::{ChaCha20, ChaChaReader};
 use crate::util::errors::{self, APIError};
 use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
@@ -128,10 +128,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>, path: &Vec<RouteHop>, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
-       let mut res = Vec::with_capacity(path.len());
+pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
+       let mut res = Vec::with_capacity(path.hops.len());
 
-       construct_onion_keys_callback(secp_ctx, path, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| {
+       construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| {
                let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref());
 
                res.push(OnionKeys {
@@ -149,13 +149,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(path: &Vec<RouteHop>, total_msat: u64, mut recipient_onion: RecipientOnionFields, starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
+pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields, starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>) -> 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(path.len());
+       let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(path.hops.len());
 
-       for (idx, hop) in path.iter().rev().enumerate() {
+       for (idx, hop) in path.hops.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).
@@ -403,7 +403,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
                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, path, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| {
+               construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| {
                        if res.is_some() { return; }
 
                        let amt_to_forward = htlc_msat - route_hop.fee_msat;
@@ -419,8 +419,8 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
 
                        // The failing hop includes either the inbound channel to the recipient or the outbound
                        // channel from the current hop (i.e., the next hop's inbound channel).
-                       is_from_final_node = route_hop_idx + 1 == path.len();
-                       let failing_route_hop = if is_from_final_node { route_hop } else { &path[route_hop_idx + 1] };
+                       is_from_final_node = route_hop_idx + 1 == path.hops.len();
+                       let failing_route_hop = if is_from_final_node { route_hop } else { &path.hops[route_hop_idx + 1] };
 
                        if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
                                let um = gen_um_from_shared_secret(shared_secret.as_ref());
@@ -726,7 +726,7 @@ impl HTLCFailReason {
                                // generally ignores its view of our own channels as we provide them via
                                // ChannelDetails.
                                if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source {
-                                       (None, Some(path.first().unwrap().short_channel_id), true, Some(*failure_code), Some(data.clone()))
+                                       (None, Some(path.hops[0].short_channel_id), true, Some(*failure_code), Some(data.clone()))
                                } else { unreachable!(); }
                        }
                }
@@ -883,7 +883,7 @@ mod tests {
        use crate::prelude::*;
        use crate::ln::PaymentHash;
        use crate::ln::features::{ChannelFeatures, NodeFeatures};
-       use crate::routing::router::{Route, RouteHop};
+       use crate::routing::router::{Path, Route, RouteHop};
        use crate::ln::msgs;
        use crate::util::ser::{Writeable, Writer, VecWriter};
 
@@ -903,7 +903,7 @@ mod tests {
                let secp_ctx = Secp256k1::new();
 
                let route = Route {
-                       paths: vec![vec![
+                       paths: vec![Path { hops: vec![
                                        RouteHop {
                                                pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
@@ -929,12 +929,12 @@ mod tests {
                                                channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
                                                short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
                                        },
-                       ]],
+                       ], blinded_tail: None }],
                        payment_params: None,
                };
 
                let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()).unwrap();
-               assert_eq!(onion_keys.len(), route.paths[0].len());
+               assert_eq!(onion_keys.len(), route.paths[0].hops.len());
                onion_keys
        }
 
index 33f4762bbfa3fc8295cfd56f81d7eae7084859a0..5270ed35d8835d6f240e8ae0fcd90b12e0851685 100644 (file)
@@ -18,7 +18,7 @@ use crate::events::{self, PaymentFailureReason};
 use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
 use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
 use crate::ln::onion_utils::HTLCFailReason;
-use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
+use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
 use crate::util::errors::APIError;
 use crate::util::logger::Logger;
 use crate::util::time::Time;
@@ -26,7 +26,6 @@ use crate::util::time::Time;
 use crate::util::time::tests::SinceEpoch;
 use crate::util::ser::ReadableArgs;
 
-use core::cmp;
 use core::fmt::{self, Display, Formatter};
 use core::ops::Deref;
 
@@ -161,7 +160,7 @@ impl PendingOutboundPayment {
        }
 
        /// panics if path is None and !self.is_fulfilled
-       fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Vec<RouteHop>>) -> bool {
+       fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Path>) -> bool {
                let remove_res = match self {
                        PendingOutboundPayment::Legacy { session_privs } |
                                PendingOutboundPayment::Retryable { session_privs, .. } |
@@ -173,17 +172,16 @@ impl PendingOutboundPayment {
                if remove_res {
                        if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
                                let path = path.expect("Fulfilling a payment should always come with a path");
-                               let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
-                               *pending_amt_msat -= path_last_hop.fee_msat;
+                               *pending_amt_msat -= path.final_value_msat();
                                if let Some(fee_msat) = pending_fee_msat.as_mut() {
-                                       *fee_msat -= path.get_path_fees();
+                                       *fee_msat -= path.fee_msat();
                                }
                        }
                }
                remove_res
        }
 
-       pub(super) fn insert(&mut self, session_priv: [u8; 32], path: &Vec<RouteHop>) -> bool {
+       pub(super) fn insert(&mut self, session_priv: [u8; 32], path: &Path) -> bool {
                let insert_res = match self {
                        PendingOutboundPayment::Legacy { session_privs } |
                                PendingOutboundPayment::Retryable { session_privs, .. } => {
@@ -194,10 +192,9 @@ impl PendingOutboundPayment {
                };
                if insert_res {
                        if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
-                               let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
-                               *pending_amt_msat += path_last_hop.fee_msat;
+                               *pending_amt_msat += path.final_value_msat();
                                if let Some(fee_msat) = pending_fee_msat.as_mut() {
-                                       *fee_msat += path.get_path_fees();
+                                       *fee_msat += path.fee_msat();
                                }
                        }
                }
@@ -498,7 +495,7 @@ impl OutboundPayments {
                NS::Target: NodeSigner,
                L::Target: Logger,
                IH: Fn() -> InFlightHtlcs,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
        {
                self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy,
@@ -514,7 +511,7 @@ impl OutboundPayments {
        where
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
-               F: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, None, route, None, None, entropy_source, best_block_height)?;
@@ -536,7 +533,7 @@ impl OutboundPayments {
                NS::Target: NodeSigner,
                L::Target: Logger,
                IH: Fn() -> InFlightHtlcs,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
        {
                let preimage = payment_preimage
@@ -556,7 +553,7 @@ impl OutboundPayments {
        where
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
-               F: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                let preimage = payment_preimage
@@ -585,7 +582,7 @@ impl OutboundPayments {
                R::Target: Router,
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
                IH: Fn() -> InFlightHtlcs,
                FH: Fn() -> Vec<ChannelDetails>,
@@ -656,7 +653,7 @@ impl OutboundPayments {
                NS::Target: NodeSigner,
                L::Target: Logger,
                IH: Fn() -> InFlightHtlcs,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                #[cfg(feature = "std")] {
@@ -697,7 +694,7 @@ impl OutboundPayments {
                NS::Target: NodeSigner,
                L::Target: Logger,
                IH: Fn() -> InFlightHtlcs,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                #[cfg(feature = "std")] {
@@ -721,8 +718,8 @@ impl OutboundPayments {
                        }
                };
                for path in route.paths.iter() {
-                       if path.len() == 0 {
-                               log_error!(logger, "length-0 path in route");
+                       if path.hops.len() == 0 {
+                               log_error!(logger, "Unusable path in route (path.hops.len() must be at least 1");
                                self.abandon_payment(payment_id, PaymentFailureReason::UnexpectedError, pending_events);
                                return
                        }
@@ -757,7 +754,7 @@ impl OutboundPayments {
                                                PendingOutboundPayment::Retryable {
                                                        total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, ..
                                                } => {
-                                                       let retry_amt_msat: u64 = route.paths.iter().map(|path| path.last().unwrap().fee_msat).sum();
+                                                       let retry_amt_msat = route.get_total_amount();
                                                        if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
                                                                log_error!(logger, "retry_amt_msat of {} will put pending_amt_msat (currently: {}) more than 10% over total_payment_amt_msat of {}", retry_amt_msat, pending_amt_msat, total_msat);
                                                                abandon_with_entry!(payment, PaymentFailureReason::UnexpectedError);
@@ -819,7 +816,7 @@ impl OutboundPayments {
                NS::Target: NodeSigner,
                L::Target: Logger,
                IH: Fn() -> InFlightHtlcs,
-               SP: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                match err {
@@ -854,7 +851,7 @@ impl OutboundPayments {
 
        fn push_path_failed_evs_and_scids<I: ExactSizeIterator + Iterator<Item = Result<(), APIError>>, L: Deref>(
                payment_id: PaymentId, payment_hash: PaymentHash, route_params: &mut RouteParameters,
-               paths: Vec<Vec<RouteHop>>, path_results: I, logger: &L, pending_events: &Mutex<Vec<events::Event>>
+               paths: Vec<Path>, path_results: I, logger: &L, pending_events: &Mutex<Vec<events::Event>>
        ) where L::Target: Logger {
                let mut events = pending_events.lock().unwrap();
                debug_assert_eq!(paths.len(), path_results.len());
@@ -864,7 +861,7 @@ impl OutboundPayments {
                                log_error!(logger, "Failed to send along path due to error: {:?}", e);
                                let mut failed_scid = None;
                                if let APIError::ChannelUnavailable { .. } = e {
-                                       let scid = path[0].short_channel_id;
+                                       let scid = path.hops[0].short_channel_id;
                                        failed_scid = Some(scid);
                                        route_params.payment_params.previously_failed_channels.push(scid);
                                }
@@ -885,26 +882,26 @@ impl OutboundPayments {
        }
 
        pub(super) fn send_probe<ES: Deref, NS: Deref, F>(
-               &self, hops: Vec<RouteHop>, probing_cookie_secret: [u8; 32], entropy_source: &ES,
-               node_signer: &NS, best_block_height: u32, send_payment_along_path: F
+               &self, path: Path, probing_cookie_secret: [u8; 32], entropy_source: &ES, node_signer: &NS,
+               best_block_height: u32, send_payment_along_path: F
        ) -> Result<(PaymentHash, PaymentId), PaymentSendFailure>
        where
                ES::Target: EntropySource,
                NS::Target: NodeSigner,
-               F: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                let payment_id = PaymentId(entropy_source.get_secure_random_bytes());
 
                let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret);
 
-               if hops.len() < 2 {
+               if path.hops.len() < 2 && path.blinded_tail.is_none() {
                        return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
                                err: "No need probing a path with less than two hops".to_string()
                        }))
                }
 
-               let route = Route { paths: vec![hops], payment_params: None };
+               let route = Route { paths: vec![path], payment_params: None };
                let onion_session_privs = self.add_new_pending_payment(payment_hash,
                        RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None,
                        entropy_source, best_block_height)?;
@@ -986,7 +983,7 @@ impl OutboundPayments {
        ) -> Result<(), PaymentSendFailure>
        where
                NS::Target: NodeSigner,
-               F: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                if route.paths.len() < 1 {
@@ -999,17 +996,23 @@ impl OutboundPayments {
                let our_node_id = node_signer.get_node_id(Recipient::Node).unwrap(); // TODO no unwrap
                let mut path_errs = Vec::with_capacity(route.paths.len());
                'path_check: for path in route.paths.iter() {
-                       if path.len() < 1 || path.len() > 20 {
+                       if path.hops.len() < 1 || path.hops.len() > 20 {
                                path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
                                continue 'path_check;
                        }
-                       for (idx, hop) in path.iter().enumerate() {
-                               if idx != path.len() - 1 && hop.pubkey == our_node_id {
+                       if path.blinded_tail.is_some() {
+                               path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
+                               continue 'path_check;
+                       }
+                       let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
+                               usize::max_value() } else { path.hops.len() - 1 };
+                       for (idx, hop) in path.hops.iter().enumerate() {
+                               if idx != dest_hop_idx && hop.pubkey == our_node_id {
                                        path_errs.push(Err(APIError::InvalidRoute{err: "Path went through us but wasn't a simple rebalance loop to us".to_owned()}));
                                        continue 'path_check;
                                }
                        }
-                       total_value += path.last().unwrap().fee_msat;
+                       total_value += path.final_value_msat();
                        path_errs.push(Ok(()));
                }
                if path_errs.iter().any(|e| e.is_err()) {
@@ -1048,7 +1051,6 @@ impl OutboundPayments {
                let mut has_ok = false;
                let mut has_err = false;
                let mut pending_amt_unsent = 0;
-               let mut max_unsent_cltv_delta = 0;
                for (res, path) in results.iter().zip(route.paths.iter()) {
                        if res.is_ok() { has_ok = true; }
                        if res.is_err() { has_err = true; }
@@ -1058,8 +1060,7 @@ impl OutboundPayments {
                                has_err = true;
                                has_ok = true;
                        } else if res.is_err() {
-                               pending_amt_unsent += path.last().unwrap().fee_msat;
-                               max_unsent_cltv_delta = cmp::max(max_unsent_cltv_delta, path.last().unwrap().cltv_expiry_delta);
+                               pending_amt_unsent += path.final_value_msat();
                        }
                }
                if has_err && has_ok {
@@ -1091,7 +1092,7 @@ impl OutboundPayments {
        ) -> Result<(), PaymentSendFailure>
        where
                NS::Target: NodeSigner,
-               F: Fn(&Vec<RouteHop>, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
+               F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
                        &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
        {
                self.pay_route_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id,
@@ -1111,7 +1112,7 @@ impl OutboundPayments {
 
        pub(super) fn claim_htlc<L: Deref>(
                &self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey,
-               path: Vec<RouteHop>, from_onchain: bool, pending_events: &Mutex<Vec<events::Event>>, logger: &L
+               path: Path, from_onchain: bool, pending_events: &Mutex<Vec<events::Event>>, logger: &L
        ) where L::Target: Logger {
                let mut session_priv_bytes = [0; 32];
                session_priv_bytes.copy_from_slice(&session_priv[..]);
@@ -1220,9 +1221,8 @@ impl OutboundPayments {
        // Returns a bool indicating whether a PendingHTLCsForwardable event should be generated.
        pub(super) fn fail_htlc<L: Deref>(
                &self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason,
-               path: &Vec<RouteHop>, session_priv: &SecretKey, payment_id: &PaymentId,
-               probing_cookie_secret: [u8; 32], secp_ctx: &Secp256k1<secp256k1::All>,
-               pending_events: &Mutex<Vec<events::Event>>, logger: &L
+               path: &Path, session_priv: &SecretKey, payment_id: &PaymentId, probing_cookie_secret: [u8; 32],
+               secp_ctx: &Secp256k1<secp256k1::All>, pending_events: &Mutex<Vec<events::Event>>, logger: &L
        ) -> bool where L::Target: Logger {
                #[cfg(test)]
                let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
@@ -1430,7 +1430,7 @@ mod tests {
        use crate::ln::msgs::{ErrorAction, LightningError};
        use crate::ln::outbound_payment::{OutboundPayments, Retry, RetryableSendFailure};
        use crate::routing::gossip::NetworkGraph;
-       use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters};
+       use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters};
        use crate::sync::{Arc, Mutex};
        use crate::util::errors::APIError;
        use crate::util::test_utils;
@@ -1551,14 +1551,14 @@ mod tests {
                };
                let failed_scid = 42;
                let route = Route {
-                       paths: vec![vec![RouteHop {
+                       paths: vec![Path { hops: vec![RouteHop {
                                pubkey: receiver_pk,
                                node_features: NodeFeatures::empty(),
                                short_channel_id: failed_scid,
                                channel_features: ChannelFeatures::empty(),
                                fee_msat: 0,
                                cltv_expiry_delta: 0,
-                       }]],
+                       }], blinded_tail: None }],
                        payment_params: Some(payment_params),
                };
                router.expect_find_route(route_params.clone(), Ok(route.clone()));
index 0487fc95a724bb2fd5e84175560dfb5f404fb6f3..69ae4bb81f57d1ab303dd1e04280e72497ead6cd 100644 (file)
@@ -23,7 +23,7 @@ use crate::ln::msgs;
 use crate::ln::msgs::ChannelMessageHandler;
 use crate::ln::outbound_payment::Retry;
 use crate::routing::gossip::{EffectiveCapacity, RoutingFees};
-use crate::routing::router::{get_route, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters};
+use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters};
 use crate::routing::scoring::ChannelUsage;
 use crate::util::test_utils;
 use crate::util::errors::APIError;
@@ -59,12 +59,12 @@ fn mpp_failure() {
        let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[3], 100000);
        let path = route.paths[0].clone();
        route.paths.push(path);
-       route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-       route.paths[0][0].short_channel_id = chan_1_id;
-       route.paths[0][1].short_channel_id = chan_3_id;
-       route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-       route.paths[1][0].short_channel_id = chan_2_id;
-       route.paths[1][1].short_channel_id = chan_4_id;
+       route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+       route.paths[0].hops[0].short_channel_id = chan_1_id;
+       route.paths[0].hops[1].short_channel_id = chan_3_id;
+       route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+       route.paths[1].hops[0].short_channel_id = chan_2_id;
+       route.paths[1].hops[1].short_channel_id = chan_4_id;
        send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], 200_000, payment_hash, payment_secret);
        fail_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_hash);
 }
@@ -87,12 +87,12 @@ fn mpp_retry() {
        let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[3], amt_msat);
        let path = route.paths[0].clone();
        route.paths.push(path);
-       route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-       route.paths[0][0].short_channel_id = chan_1_update.contents.short_channel_id;
-       route.paths[0][1].short_channel_id = chan_3_update.contents.short_channel_id;
-       route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-       route.paths[1][0].short_channel_id = chan_2_update.contents.short_channel_id;
-       route.paths[1][1].short_channel_id = chan_4_update.contents.short_channel_id;
+       route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+       route.paths[0].hops[0].short_channel_id = chan_1_update.contents.short_channel_id;
+       route.paths[0].hops[1].short_channel_id = chan_3_update.contents.short_channel_id;
+       route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+       route.paths[1].hops[0].short_channel_id = chan_2_update.contents.short_channel_id;
+       route.paths[1].hops[1].short_channel_id = chan_4_update.contents.short_channel_id;
 
        // Initiate the MPP payment.
        let payment_id = PaymentId(payment_hash.0);
@@ -177,12 +177,12 @@ fn do_mpp_receive_timeout(send_partial_mpp: bool) {
        let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[3], 100_000);
        let path = route.paths[0].clone();
        route.paths.push(path);
-       route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
-       route.paths[0][0].short_channel_id = chan_1_update.contents.short_channel_id;
-       route.paths[0][1].short_channel_id = chan_3_update.contents.short_channel_id;
-       route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
-       route.paths[1][0].short_channel_id = chan_2_update.contents.short_channel_id;
-       route.paths[1][1].short_channel_id = chan_4_update.contents.short_channel_id;
+       route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
+       route.paths[0].hops[0].short_channel_id = chan_1_update.contents.short_channel_id;
+       route.paths[0].hops[1].short_channel_id = chan_3_update.contents.short_channel_id;
+       route.paths[1].hops[0].pubkey = nodes[2].node.get_our_node_id();
+       route.paths[1].hops[0].short_channel_id = chan_2_update.contents.short_channel_id;
+       route.paths[1].hops[1].short_channel_id = chan_4_update.contents.short_channel_id;
 
        // Initiate the MPP payment.
        nodes[0].node.send_payment_with_route(&route, payment_hash,
@@ -437,7 +437,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
                let mut new_config = channel.config();
                new_config.forwarding_fee_base_msat += 100_000;
                channel.update_config(&new_config);
-               new_route.paths[0][0].fee_msat += 100_000;
+               new_route.paths[0].hops[0].fee_msat += 100_000;
        }
 
        // Force expiration of the channel's previous config.
@@ -454,7 +454,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
        assert_eq!(events.len(), 1);
        pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000, payment_hash, Some(payment_secret), events.pop().unwrap(), true, None);
        do_claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
-       expect_payment_sent!(nodes[0], payment_preimage, Some(new_route.paths[0][0].fee_msat));
+       expect_payment_sent!(nodes[0], payment_preimage, Some(new_route.paths[0].hops[0].fee_msat));
 }
 
 #[test]
@@ -1839,56 +1839,56 @@ fn auto_retry_partial_failure() {
        // Configure the initial send, retry1 and retry2's paths.
        let send_route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_id,
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: amt_msat / 2,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_2_id,
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: amt_msat / 2,
                                cltv_expiry_delta: 100,
-                       }],
+                       }], blinded_tail: None },
                ],
                payment_params: Some(route_params.payment_params.clone()),
        };
        let retry_1_route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_id,
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: amt_msat / 4,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_3_id,
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: amt_msat / 4,
                                cltv_expiry_delta: 100,
-                       }],
+                       }], blinded_tail: None },
                ],
                payment_params: Some(route_params.payment_params.clone()),
        };
        let retry_2_route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_id,
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: amt_msat / 4,
                                cltv_expiry_delta: 100,
-                       }],
+                       }], blinded_tail: None },
                ],
                payment_params: Some(route_params.payment_params.clone()),
        };
@@ -2128,29 +2128,29 @@ fn retry_multi_path_single_failed_payment() {
        let chans = nodes[0].node.list_usable_channels();
        let mut route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chans[0].short_channel_id.unwrap(),
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: 10_000,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chans[1].short_channel_id.unwrap(),
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
                                cltv_expiry_delta: 100,
-                       }],
+                       }], blinded_tail: None },
                ],
                payment_params: Some(payment_params),
        };
        nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
        // On retry, split the payment across both channels.
-       route.paths[0][0].fee_msat = 50_000_001;
-       route.paths[1][0].fee_msat = 50_000_000;
+       route.paths[0].hops[0].fee_msat = 50_000_001;
+       route.paths[1].hops[0].fee_msat = 50_000_000;
        let mut pay_params = route.payment_params.clone().unwrap();
        pay_params.previously_failed_channels.push(chans[1].short_channel_id.unwrap());
        nodes[0].router.expect_find_route(RouteParameters {
@@ -2180,7 +2180,7 @@ fn retry_multi_path_single_failed_payment() {
                        short_channel_id: Some(expected_scid), .. } =>
                {
                        assert_eq!(payment_hash, ev_payment_hash);
-                       assert_eq!(expected_scid, route.paths[1][0].short_channel_id);
+                       assert_eq!(expected_scid, route.paths[1].hops[0].short_channel_id);
                        assert!(err_msg.contains("max HTLC"));
                },
                _ => panic!("Unexpected event"),
@@ -2222,23 +2222,23 @@ fn immediate_retry_on_failure() {
        let chans = nodes[0].node.list_usable_channels();
        let mut route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chans[0].short_channel_id.unwrap(),
                                channel_features: nodes[1].node.channel_features(),
                                fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
                                cltv_expiry_delta: 100,
-                       }],
+                       }], blinded_tail: None },
                ],
                payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)),
        };
        nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
        // On retry, split the payment across both channels.
        route.paths.push(route.paths[0].clone());
-       route.paths[0][0].short_channel_id = chans[1].short_channel_id.unwrap();
-       route.paths[0][0].fee_msat = 50_000_000;
-       route.paths[1][0].fee_msat = 50_000_001;
+       route.paths[0].hops[0].short_channel_id = chans[1].short_channel_id.unwrap();
+       route.paths[0].hops[0].fee_msat = 50_000_000;
+       route.paths[1].hops[0].fee_msat = 50_000_001;
        let mut pay_params = route_params.payment_params.clone();
        pay_params.previously_failed_channels.push(chans[0].short_channel_id.unwrap());
        nodes[0].router.expect_find_route(RouteParameters {
@@ -2255,7 +2255,7 @@ fn immediate_retry_on_failure() {
                        short_channel_id: Some(expected_scid), .. } =>
                {
                        assert_eq!(payment_hash, ev_payment_hash);
-                       assert_eq!(expected_scid, route.paths[1][0].short_channel_id);
+                       assert_eq!(expected_scid, route.paths[1].hops[0].short_channel_id);
                        assert!(err_msg.contains("max HTLC"));
                },
                _ => panic!("Unexpected event"),
@@ -2310,7 +2310,7 @@ fn no_extra_retries_on_back_to_back_fail() {
 
        let mut route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_scid,
@@ -2324,8 +2324,8 @@ fn no_extra_retries_on_back_to_back_fail() {
                                channel_features: nodes[2].node.channel_features(),
                                fee_msat: 100_000_000,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_scid,
@@ -2339,7 +2339,7 @@ fn no_extra_retries_on_back_to_back_fail() {
                                channel_features: nodes[2].node.channel_features(),
                                fee_msat: 100_000_000,
                                cltv_expiry_delta: 100,
-                       }]
+                       }], blinded_tail: None }
                ],
                payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
        };
@@ -2348,7 +2348,7 @@ fn no_extra_retries_on_back_to_back_fail() {
        second_payment_params.previously_failed_channels = vec![chan_2_scid, chan_2_scid];
        // On retry, we'll only return one path
        route.paths.remove(1);
-       route.paths[0][1].fee_msat = amt_msat;
+       route.paths[0].hops[1].fee_msat = amt_msat;
        nodes[0].router.expect_find_route(RouteParameters {
                        payment_params: second_payment_params,
                        final_value_msat: amt_msat,
@@ -2512,7 +2512,7 @@ fn test_simple_partial_retry() {
 
        let mut route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_scid,
@@ -2526,8 +2526,8 @@ fn test_simple_partial_retry() {
                                channel_features: nodes[2].node.channel_features(),
                                fee_msat: 100_000_000,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_scid,
@@ -2541,7 +2541,7 @@ fn test_simple_partial_retry() {
                                channel_features: nodes[2].node.channel_features(),
                                fee_msat: 100_000_000,
                                cltv_expiry_delta: 100,
-                       }]
+                       }], blinded_tail: None }
                ],
                payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
        };
@@ -2678,7 +2678,7 @@ fn test_threaded_payment_retries() {
 
        let mut route = Route {
                paths: vec![
-                       vec![RouteHop {
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[1].node.get_our_node_id(),
                                node_features: nodes[1].node.node_features(),
                                short_channel_id: chan_1_scid,
@@ -2692,8 +2692,8 @@ fn test_threaded_payment_retries() {
                                channel_features: nodes[2].node.channel_features(),
                                fee_msat: amt_msat / 1000,
                                cltv_expiry_delta: 100,
-                       }],
-                       vec![RouteHop {
+                       }], blinded_tail: None },
+                       Path { hops: vec![RouteHop {
                                pubkey: nodes[2].node.get_our_node_id(),
                                node_features: nodes[2].node.node_features(),
                                short_channel_id: chan_3_scid,
@@ -2707,7 +2707,7 @@ fn test_threaded_payment_retries() {
                                channel_features: nodes[3].node.channel_features(),
                                fee_msat: amt_msat - amt_msat / 1000,
                                cltv_expiry_delta: 100,
-                       }]
+                       }], blinded_tail: None }
                ],
                payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
        };
@@ -2765,9 +2765,9 @@ fn test_threaded_payment_retries() {
                // we should still ultimately fail for the same reason - because we're trying to send too
                // many HTLCs at once.
                let mut new_route_params = route_params.clone();
-               previously_failed_channels.push(route.paths[0][1].short_channel_id);
+               previously_failed_channels.push(route.paths[0].hops[1].short_channel_id);
                new_route_params.payment_params.previously_failed_channels = previously_failed_channels.clone();
-               route.paths[0][1].short_channel_id += 1;
+               route.paths[0].hops[1].short_channel_id += 1;
                nodes[0].router.expect_find_route(new_route_params, Ok(route.clone()));
 
                let bs_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
@@ -2913,13 +2913,13 @@ fn do_claim_from_closed_chan(fail_payment: bool) {
        let mut route = nodes[0].router.find_route(&nodes[0].node.get_our_node_id(), &route_params,
                None, &nodes[0].node.compute_inflight_htlcs()).unwrap();
        // Make sure the route is ordered as the B->D path before C->D
-       route.paths.sort_by(|a, _| if a[0].pubkey == nodes[1].node.get_our_node_id() {
+       route.paths.sort_by(|a, _| if a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
                std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater });
 
        // Note that we add an extra 1 in the send pipeline to compensate for any blocks found while
        // the HTLC is being relayed.
-       route.paths[0][1].cltv_expiry_delta = TEST_FINAL_CLTV + 8;
-       route.paths[1][1].cltv_expiry_delta = TEST_FINAL_CLTV + 12;
+       route.paths[0].hops[1].cltv_expiry_delta = TEST_FINAL_CLTV + 8;
+       route.paths[1].hops[1].cltv_expiry_delta = TEST_FINAL_CLTV + 12;
        let final_cltv = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 8 + 1;
 
        nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
index 6ca37203d32635bd95d01cb51a7017736697a981..7bb1fd44f2fe1f6d42698a222d47039f431d91be 100644 (file)
@@ -239,7 +239,7 @@ fn test_routed_scid_alias() {
                .with_features(nodes[2].node.invoice_features())
                .with_route_hints(hop_hints);
        let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000, 42);
-       assert_eq!(route.paths[0][1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
+       assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
        nodes[0].node.send_payment_with_route(&route, payment_hash,
                RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
        check_added_monitors!(nodes[0], 1);
@@ -405,7 +405,7 @@ fn test_inbound_scid_privacy() {
                .with_features(nodes[2].node.invoice_features())
                .with_route_hints(hop_hints.clone());
        let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000, 42);
-       assert_eq!(route.paths[0][1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
+       assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
        nodes[0].node.send_payment_with_route(&route, payment_hash,
                RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
        check_added_monitors!(nodes[0], 1);
@@ -421,7 +421,7 @@ fn test_inbound_scid_privacy() {
                .with_features(nodes[2].node.invoice_features())
                .with_route_hints(hop_hints);
        let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params_2, 100_000, 42);
-       assert_eq!(route_2.paths[0][1].short_channel_id, last_hop[0].short_channel_id.unwrap());
+       assert_eq!(route_2.paths[0].hops[1].short_channel_id, last_hop[0].short_channel_id.unwrap());
        nodes[0].node.send_payment_with_route(&route_2, payment_hash_2,
                RecipientOnionFields::secret_only(payment_secret_2), PaymentId(payment_hash_2.0)).unwrap();
        check_added_monitors!(nodes[0], 1);
@@ -473,9 +473,9 @@ fn test_scid_alias_returned() {
                .with_features(nodes[2].node.invoice_features())
                .with_route_hints(hop_hints);
        let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000, 42);
-       assert_eq!(route.paths[0][1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap());
+       assert_eq!(route.paths[0].hops[1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap());
 
-       route.paths[0][1].fee_msat = 10_000_000; // Overshoot the last channel's value
+       route.paths[0].hops[1].fee_msat = 10_000_000; // Overshoot the last channel's value
 
        // Route the HTLC through to the destination.
        nodes[0].node.send_payment_with_route(&route, payment_hash,
@@ -518,8 +518,8 @@ fn test_scid_alias_returned() {
                PaymentFailedConditions::new().blamed_scid(last_hop[0].inbound_scid_alias.unwrap())
                        .blamed_chan_closed(false).expected_htlc_error_data(0x1000|7, &err_data));
 
-       route.paths[0][1].fee_msat = 10_000; // Reset to the correct payment amount
-       route.paths[0][0].fee_msat = 0; // But set fee paid to the middle hop to 0
+       route.paths[0].hops[1].fee_msat = 10_000; // Reset to the correct payment amount
+       route.paths[0].hops[0].fee_msat = 0; // But set fee paid to the middle hop to 0
 
        // Route the HTLC through to the destination.
        nodes[0].node.send_payment_with_route(&route, payment_hash,
@@ -839,7 +839,7 @@ fn test_0conf_channel_reorg() {
        assert_eq!(nodes[1].node.list_usable_channels()[0].short_channel_id.unwrap(), real_scid);
 
        let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 10_000);
-       assert_eq!(route.paths[0][0].short_channel_id, real_scid);
+       assert_eq!(route.paths[0].hops[0].short_channel_id, real_scid);
        send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1]]], 10_000, payment_hash, payment_secret);
        claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
 
index 3bd8b711c116edee37fd6ea603cda0aeb1d47ffd..67a48012789c1e30d6e56e519984f21eefb84eee 100644 (file)
@@ -699,7 +699,7 @@ fn do_test_partial_claim_before_restart(persist_both_monitors: bool) {
        assert_eq!(route.paths.len(), 2);
        route.paths.sort_by(|path_a, _| {
                // Sort the path so that the path through nodes[1] comes first
-               if path_a[0].pubkey == nodes[1].node.get_our_node_id() {
+               if path_a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
                        core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
        });
 
@@ -856,7 +856,7 @@ fn do_forwarded_payment_no_manager_persistence(use_cs_commitment: bool, claim_ht
        let (mut route, payment_hash, payment_preimage, payment_secret) =
                get_route_and_payment_hash!(nodes[0], nodes[2], 1_000_000);
        if use_intercept {
-               route.paths[0][1].short_channel_id = intercept_scid;
+               route.paths[0].hops[1].short_channel_id = intercept_scid;
        }
        let payment_id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes());
        let htlc_expiry = nodes[0].best_block_info().1 + TEST_FINAL_CLTV;
index 6bf155a230324a48c29c5b5c5d0b9cb06ed04a97..05b2b5d010ec7a8cf36306f95ce481d6c148dd58 100644 (file)
@@ -29,7 +29,7 @@
 //!
 //! # use lightning::ln::PaymentHash;
 //! # use lightning::offers::invoice::BlindedPayInfo;
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! #
 //! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
 //! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
@@ -104,6 +104,7 @@ use bitcoin::util::schnorr::TweakedPublicKey;
 use core::convert::{Infallible, TryFrom};
 use core::time::Duration;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
 use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
 use crate::ln::inbound_payment::ExpandedKey;
@@ -115,7 +116,6 @@ use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
 use crate::offers::signer;
-use crate::onion_message::BlindedPath;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
@@ -748,7 +748,7 @@ type BlindedPayInfoIter<'a> = core::iter::Map<
 >;
 
 /// Information needed to route a payment across a [`BlindedPath`].
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
 pub struct BlindedPayInfo {
        /// Base fee charged (in millisatoshi) for the entire blinded path.
        pub fee_base_msat: u32,
@@ -942,6 +942,7 @@ mod tests {
        use bitcoin::util::schnorr::TweakedPublicKey;
        use core::convert::TryFrom;
        use core::time::Duration;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::chain::keysinterface::KeyMaterial;
        use crate::ln::features::Bolt12InvoiceFeatures;
        use crate::ln::inbound_payment::ExpandedKey;
@@ -953,7 +954,6 @@ mod tests {
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
        use crate::offers::test_utils::*;
-       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Iterable, Writeable};
        use crate::util::string::PrintableString;
 
index 92fabd6fdf0c7ab5f49c46cfd0793e02a8886774..e8aeb2c827b68e03a6f24c804532d903b53893c3 100644 (file)
@@ -60,6 +60,7 @@ use core::convert::{Infallible, TryFrom};
 use core::ops::Deref;
 use crate::chain::keysinterface::EntropySource;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
@@ -70,7 +71,6 @@ use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamR
 use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::signer::{Metadata, MetadataMaterial};
-use crate::onion_message::BlindedPath;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
index 192317240f94df2e61ffbe7269f97cd3d4d22701..887318c0c0ea8dccffad71d61ecfdaaf045da6df 100644 (file)
@@ -27,7 +27,7 @@
 //! use lightning::offers::parse::ParseError;
 //! use lightning::util::ser::{Readable, Writeable};
 //!
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! # #[cfg(feature = "std")]
 //! # use std::time::SystemTime;
 //! #
@@ -76,6 +76,7 @@ use core::str::FromStr;
 use core::time::Duration;
 use crate::chain::keysinterface::EntropySource;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::features::OfferFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::MAX_VALUE_MSAT;
@@ -83,7 +84,6 @@ use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceReq
 use crate::offers::merkle::TlvStream;
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
 use crate::offers::signer::{Metadata, MetadataMaterial, self};
-use crate::onion_message::BlindedPath;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
@@ -836,13 +836,13 @@ mod tests {
        use core::convert::TryFrom;
        use core::num::NonZeroU64;
        use core::time::Duration;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::chain::keysinterface::KeyMaterial;
        use crate::ln::features::OfferFeatures;
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::test_utils::*;
-       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
index eac7a3754edf54444d3b87b6c3b31fd5ccf4f3dd..d5d80198ef29c8045f9097ddec5645fa1c47ffd7 100644 (file)
@@ -32,7 +32,7 @@
 //! use lightning::offers::refund::{Refund, RefundBuilder};
 //! use lightning::util::ser::{Readable, Writeable};
 //!
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! # #[cfg(feature = "std")]
 //! # use std::time::SystemTime;
 //! #
@@ -80,6 +80,7 @@ use core::str::FromStr;
 use core::time::Duration;
 use crate::chain::keysinterface::EntropySource;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
 use crate::ln::features::InvoiceRequestFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
@@ -90,7 +91,6 @@ use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::signer::{Metadata, MetadataMaterial, self};
-use crate::onion_message::BlindedPath;
 use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
@@ -701,6 +701,7 @@ mod tests {
        use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
        use core::convert::TryFrom;
        use core::time::Duration;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::chain::keysinterface::KeyMaterial;
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
@@ -710,7 +711,6 @@ mod tests {
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::test_utils::*;
-       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
index 43664079dbd55580b224f1926e017525c85c9249..8ded4a66e37b0dec874e56e51b9880a02a5303f5 100644 (file)
@@ -13,11 +13,11 @@ use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
 use bitcoin::secp256k1::schnorr::Signature;
 use core::convert::Infallible;
 use core::time::Duration;
+use crate::blinded_path::{BlindedHop, BlindedPath};
 use crate::chain::keysinterface::EntropySource;
 use crate::ln::PaymentHash;
 use crate::ln::features::BlindedHopFeatures;
 use crate::offers::invoice::BlindedPayInfo;
-use crate::onion_message::{BlindedHop, BlindedPath};
 
 pub(super) fn payer_keys() -> KeyPair {
        let secp_ctx = Secp256k1::new();
diff --git a/lightning/src/onion_message/blinded_path.rs b/lightning/src/onion_message/blinded_path.rs
deleted file mode 100644 (file)
index 946e5e7..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Creating blinded paths and related utilities live here.
-
-use bitcoin::hashes::{Hash, HashEngine};
-use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
-
-use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
-use super::packet::ControlTlvs;
-use super::utils;
-use crate::ln::msgs::DecodeError;
-use crate::ln::onion_utils;
-use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
-use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
-
-use core::mem;
-use core::ops::Deref;
-use crate::io::{self, Cursor};
-use crate::prelude::*;
-
-/// Onion messages can be sent and received to blinded paths, which serve to hide the identity of
-/// the recipient.
-#[derive(Clone, Debug, PartialEq)]
-pub struct BlindedPath {
-       /// To send to a blinded path, the sender first finds a route to the unblinded
-       /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
-       /// message's next hop and forward it along.
-       ///
-       /// [`encrypted_payload`]: BlindedHop::encrypted_payload
-       pub(crate) introduction_node_id: PublicKey,
-       /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
-       /// message.
-       ///
-       /// [`encrypted_payload`]: BlindedHop::encrypted_payload
-       pub(crate) blinding_point: PublicKey,
-       /// The hops composing the blinded path.
-       pub(crate) blinded_hops: Vec<BlindedHop>,
-}
-
-/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified
-/// by outside observers and thus can be used to hide the identity of the recipient.
-#[derive(Clone, Debug, PartialEq)]
-pub struct BlindedHop {
-       /// The blinded node id of this hop in a blinded path.
-       pub(crate) blinded_node_id: PublicKey,
-       /// The encrypted payload intended for this hop in a blinded path.
-       // The node sending to this blinded path will later encode this payload into the onion packet for
-       // this hop.
-       pub(crate) encrypted_payload: Vec<u8>,
-}
-
-impl BlindedPath {
-       /// Create a blinded path to be forwarded along `node_pks`. The last node pubkey in `node_pks`
-       /// will be the destination node.
-       ///
-       /// Errors if less than two hops are provided or if `node_pk`(s) are invalid.
-       //  TODO: make all payloads the same size with padding + add dummy hops
-       pub fn new<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>
-               (node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
-       {
-               if node_pks.len() < 2 { return Err(()) }
-               let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
-               let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
-               let introduction_node_id = node_pks[0];
-
-               Ok(BlindedPath {
-                       introduction_node_id,
-                       blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
-                       blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
-               })
-       }
-
-       // Advance the blinded path by one hop, so make the second hop into the new introduction node.
-       pub(super) fn advance_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>
-               (&mut self, node_signer: &NS, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
-               where NS::Target: NodeSigner
-       {
-               let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?;
-               let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
-               let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
-               let mut s = Cursor::new(&encrypted_control_tlvs);
-               let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
-               match ChaChaPolyReadAdapter::read(&mut reader, rho) {
-                       Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
-                               mut next_node_id, next_blinding_override,
-                       })}) => {
-                               let mut new_blinding_point = match next_blinding_override {
-                                       Some(blinding_point) => blinding_point,
-                                       None => {
-                                               let blinding_factor = {
-                                                       let mut sha = Sha256::engine();
-                                                       sha.input(&self.blinding_point.serialize()[..]);
-                                                       sha.input(control_tlvs_ss.as_ref());
-                                                       Sha256::from_engine(sha).into_inner()
-                                               };
-                                               self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
-                                                       .map_err(|_| ())?
-                                       }
-                               };
-                               mem::swap(&mut self.blinding_point, &mut new_blinding_point);
-                               mem::swap(&mut self.introduction_node_id, &mut next_node_id);
-                               Ok(())
-                       },
-                       _ => Err(())
-               }
-       }
-}
-
-/// Construct blinded hops for the given `unblinded_path`.
-fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
-       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
-) -> Result<Vec<BlindedHop>, secp256k1::Error> {
-       let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
-
-       let mut prev_ss_and_blinded_node_id = None;
-       utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
-               if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
-                       if let Some(pk) = unblinded_pk {
-                               let payload = ForwardTlvs {
-                                       next_node_id: pk,
-                                       next_blinding_override: None,
-                               };
-                               blinded_hops.push(BlindedHop {
-                                       blinded_node_id: prev_blinded_node_id,
-                                       encrypted_payload: encrypt_payload(payload, prev_ss),
-                               });
-                       } else { debug_assert!(false); }
-               }
-               prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
-       })?;
-
-       if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
-               let final_payload = ReceiveTlvs { path_id: None };
-               blinded_hops.push(BlindedHop {
-                       blinded_node_id: final_blinded_node_id,
-                       encrypted_payload: encrypt_payload(final_payload, final_ss),
-               });
-       } else { debug_assert!(false) }
-
-       Ok(blinded_hops)
-}
-
-/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
-fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
-       let mut writer = VecWriter(Vec::new());
-       let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
-       write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
-       writer.0
-}
-
-impl Writeable for BlindedPath {
-       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
-               self.introduction_node_id.write(w)?;
-               self.blinding_point.write(w)?;
-               (self.blinded_hops.len() as u8).write(w)?;
-               for hop in &self.blinded_hops {
-                       hop.write(w)?;
-               }
-               Ok(())
-       }
-}
-
-impl Readable for BlindedPath {
-       fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
-               let introduction_node_id = Readable::read(r)?;
-               let blinding_point = Readable::read(r)?;
-               let num_hops: u8 = Readable::read(r)?;
-               if num_hops == 0 { return Err(DecodeError::InvalidValue) }
-               let mut blinded_hops: Vec<BlindedHop> = Vec::with_capacity(num_hops.into());
-               for _ in 0..num_hops {
-                       blinded_hops.push(Readable::read(r)?);
-               }
-               Ok(BlindedPath {
-                       introduction_node_id,
-                       blinding_point,
-                       blinded_hops,
-               })
-       }
-}
-
-impl_writeable!(BlindedHop, {
-       blinded_node_id,
-       encrypted_payload
-});
-
-/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
-/// route, they are encoded into [`BlindedHop::encrypted_payload`].
-pub(crate) struct ForwardTlvs {
-       /// The node id of the next hop in the onion message's path.
-       pub(super) next_node_id: PublicKey,
-       /// Senders to a blinded path use this value to concatenate the route they find to the
-       /// introduction node with the blinded path.
-       pub(super) next_blinding_override: Option<PublicKey>,
-}
-
-/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
-pub(crate) struct ReceiveTlvs {
-       /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
-       /// sending to. This is useful for receivers to check that said blinded path is being used in
-       /// the right context.
-       pub(super) path_id: Option<[u8; 32]>,
-}
-
-impl Writeable for ForwardTlvs {
-       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
-               // TODO: write padding
-               encode_tlv_stream!(writer, {
-                       (4, self.next_node_id, required),
-                       (8, self.next_blinding_override, option)
-               });
-               Ok(())
-       }
-}
-
-impl Writeable for ReceiveTlvs {
-       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
-               // TODO: write padding
-               encode_tlv_stream!(writer, {
-                       (6, self.path_id, option),
-               });
-               Ok(())
-       }
-}
index 89582305c2107baabcce522937ab13f793b0efbc..991b4e9e7df7666eabca6273926702ea435d2a9b 100644 (file)
@@ -9,10 +9,11 @@
 
 //! Onion message testing and test utilities live here.
 
+use crate::blinded_path::BlindedPath;
 use crate::chain::keysinterface::{NodeSigner, Recipient};
 use crate::ln::features::InitFeatures;
 use crate::ln::msgs::{self, DecodeError, OnionMessageHandler};
-use super::{BlindedPath, CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError};
+use super::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError};
 use crate::util::ser::{Writeable, Writer};
 use crate::util::test_utils;
 
@@ -135,7 +136,7 @@ fn two_unblinded_two_blinded() {
        let test_msg = OnionMessageContents::Custom(TestCustomMessage {});
 
        let secp_ctx = Secp256k1::new();
-       let blinded_path = BlindedPath::new(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap();
+       let blinded_path = BlindedPath::new_for_message(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap();
 
        nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedPath(blinded_path), test_msg, None).unwrap();
        pass_along_path(&nodes, None);
@@ -147,7 +148,7 @@ fn three_blinded_hops() {
        let test_msg = OnionMessageContents::Custom(TestCustomMessage {});
 
        let secp_ctx = Secp256k1::new();
-       let blinded_path = BlindedPath::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
+       let blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
 
        nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), test_msg, None).unwrap();
        pass_along_path(&nodes, None);
@@ -173,13 +174,13 @@ fn we_are_intro_node() {
        let test_msg = TestCustomMessage {};
 
        let secp_ctx = Secp256k1::new();
-       let blinded_path = BlindedPath::new(&[nodes[0].get_node_pk(), nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
+       let blinded_path = BlindedPath::new_for_message(&[nodes[0].get_node_pk(), nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
 
        nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
        pass_along_path(&nodes, None);
 
        // Try with a two-hop blinded path where we are the introduction node.
-       let blinded_path = BlindedPath::new(&[nodes[0].get_node_pk(), nodes[1].get_node_pk()], &*nodes[1].keys_manager, &secp_ctx).unwrap();
+       let blinded_path = BlindedPath::new_for_message(&[nodes[0].get_node_pk(), nodes[1].get_node_pk()], &*nodes[1].keys_manager, &secp_ctx).unwrap();
        nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), OnionMessageContents::Custom(test_msg), None).unwrap();
        nodes.remove(2);
        pass_along_path(&nodes, None);
@@ -193,13 +194,13 @@ fn invalid_blinded_path_error() {
 
        // 0 hops
        let secp_ctx = Secp256k1::new();
-       let mut blinded_path = BlindedPath::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
+       let mut blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
        blinded_path.blinded_hops.clear();
        let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), OnionMessageContents::Custom(test_msg.clone()), None).unwrap_err();
        assert_eq!(err, SendError::TooFewBlindedHops);
 
        // 1 hop
-       let mut blinded_path = BlindedPath::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
+       let mut blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
        blinded_path.blinded_hops.remove(0);
        assert_eq!(blinded_path.blinded_hops.len(), 1);
        let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), OnionMessageContents::Custom(test_msg), None).unwrap_err();
@@ -213,7 +214,7 @@ fn reply_path() {
        let secp_ctx = Secp256k1::new();
 
        // Destination::Node
-       let reply_path = BlindedPath::new(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
+       let reply_path = BlindedPath::new_for_message(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
        nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::Node(nodes[3].get_node_pk()), OnionMessageContents::Custom(test_msg.clone()), Some(reply_path)).unwrap();
        pass_along_path(&nodes, None);
        // Make sure the last node successfully decoded the reply path.
@@ -222,8 +223,8 @@ fn reply_path() {
                &format!("Received an onion message with path_id None and a reply_path"), 1);
 
        // Destination::BlindedPath
-       let blinded_path = BlindedPath::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
-       let reply_path = BlindedPath::new(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
+       let blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
+       let reply_path = BlindedPath::new_for_message(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
 
        nodes[0].messenger.send_onion_message(&[], Destination::BlindedPath(blinded_path), OnionMessageContents::Custom(test_msg), Some(reply_path)).unwrap();
        pass_along_path(&nodes, None);
index b9269c7cf2c058dc9297b451f029fb068fa06793..b50282433b18a128df1185711861a98ea0b44aa4 100644 (file)
@@ -15,16 +15,15 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
 use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
 
+use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs, utils};
 use crate::chain::keysinterface::{EntropySource, KeysManager, NodeSigner, Recipient};
 use crate::events::OnionMessageProvider;
 use crate::ln::features::{InitFeatures, NodeFeatures};
 use crate::ln::msgs::{self, OnionMessageHandler};
 use crate::ln::onion_utils;
 use crate::ln::peer_handler::IgnoringMessageHandler;
-use super::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs};
 pub use super::packet::{CustomOnionMessageContents, OnionMessageContents};
 use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
-use super::utils;
 use crate::util::logger::Logger;
 use crate::util::ser::Writeable;
 
@@ -43,9 +42,10 @@ use crate::prelude::*;
 /// # extern crate bitcoin;
 /// # use bitcoin::hashes::_export::_core::time::Duration;
 /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+/// # use lightning::blinded_path::BlindedPath;
 /// # use lightning::chain::keysinterface::KeysManager;
 /// # use lightning::ln::peer_handler::IgnoringMessageHandler;
-/// # use lightning::onion_message::{BlindedPath, CustomOnionMessageContents, Destination, OnionMessageContents, OnionMessenger};
+/// # use lightning::onion_message::{CustomOnionMessageContents, Destination, OnionMessageContents, OnionMessenger};
 /// # use lightning::util::logger::{Logger, Record};
 /// # use lightning::util::ser::{Writeable, Writer};
 /// # use lightning::io;
@@ -91,7 +91,7 @@ use crate::prelude::*;
 /// // Create a blinded path to yourself, for someone to send an onion message to.
 /// # let your_node_id = hop_node_id1;
 /// let hops = [hop_node_id3, hop_node_id4, your_node_id];
-/// let blinded_path = BlindedPath::new(&hops, &keys_manager, &secp_ctx).unwrap();
+/// let blinded_path = BlindedPath::new_for_message(&hops, &keys_manager, &secp_ctx).unwrap();
 ///
 /// // Send a custom onion message to a blinded path.
 /// # let intermediate_hops = [hop_node_id1, hop_node_id2];
@@ -226,7 +226,7 @@ impl<ES: Deref, NS: Deref, L: Deref, CMH: Deref> OnionMessenger<ES, NS, L, CMH>
                                let our_node_id = self.node_signer.get_node_id(Recipient::Node)
                                        .map_err(|()| SendError::GetNodeIdFailed)?;
                                if blinded_path.introduction_node_id == our_node_id {
-                                       blinded_path.advance_by_one(&self.node_signer, &self.secp_ctx)
+                                       blinded_path.advance_message_path_by_one(&self.node_signer, &self.secp_ctx)
                                                .map_err(|()| SendError::BlindedPathAdvanceFailed)?;
                                }
                        }
index e735b428ea03dea6a26018bf23ac8ff4670e7c33..713b83c62d67d3e0a2fdcc39fd5a004d8a744e6c 100644 (file)
 //! information on its usage.
 //!
 //! [offers]: <https://github.com/lightning/bolts/pull/798>
-//! [blinded paths]: crate::onion_message::BlindedPath
+//! [blinded paths]: crate::blinded_path::BlindedPath
 
-mod blinded_path;
 mod messenger;
 mod packet;
-mod utils;
 #[cfg(test)]
 mod functional_tests;
 
 // Re-export structs so they can be imported with just the `onion_message::` module prefix.
-pub use self::blinded_path::{BlindedPath, BlindedHop};
 pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
-pub(crate) use self::packet::Packet;
+pub(crate) use self::packet::{ControlTlvs, Packet};
index d22ff0682da285ed5d717158060c72144f70b533..2fb2407dbdd093bdbe4ae260e31f12fbe4722d8a 100644 (file)
@@ -12,9 +12,9 @@
 use bitcoin::secp256k1::PublicKey;
 use bitcoin::secp256k1::ecdh::SharedSecret;
 
+use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs};
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils;
-use super::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs};
 use super::messenger::CustomOnionMessageHandler;
 use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
 use crate::util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer};
@@ -149,7 +149,7 @@ pub(super) enum ForwardControlTlvs {
        Blinded(Vec<u8>),
        /// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
        /// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
-       /// them into an intermediate Vec. See [`super::blinded_path::ForwardTlvs`] for more info.
+       /// them into an intermediate Vec. See [`crate::blinded_path::ForwardTlvs`] for more info.
        Unblinded(ForwardTlvs),
 }
 
@@ -157,7 +157,7 @@ pub(super) enum ForwardControlTlvs {
 pub(super) enum ReceiveControlTlvs {
        /// See [`ForwardControlTlvs::Blinded`].
        Blinded(Vec<u8>),
-       /// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_path::ReceiveTlvs`].
+       /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::ReceiveTlvs`].
        Unblinded(ReceiveTlvs),
 }
 
@@ -255,7 +255,7 @@ impl<H: CustomOnionMessageHandler> ReadableArgs<(SharedSecret, &H)> for Payload<
 /// When reading a packet off the wire, we don't know a priori whether the packet is to be forwarded
 /// or received. Thus we read a ControlTlvs rather than reading a ForwardControlTlvs or
 /// ReceiveControlTlvs directly.
-pub(super) enum ControlTlvs {
+pub(crate) enum ControlTlvs {
        /// This onion message is intended to be forwarded.
        Forward(ForwardTlvs),
        /// This onion message is intended to be received.
diff --git a/lightning/src/onion_message/utils.rs b/lightning/src/onion_message/utils.rs
deleted file mode 100644 (file)
index ae9e0a7..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Onion message utility methods live here.
-
-use bitcoin::hashes::{Hash, HashEngine};
-use bitcoin::hashes::hmac::{Hmac, HmacEngine};
-use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar};
-use bitcoin::secp256k1::ecdh::SharedSecret;
-
-use crate::ln::onion_utils;
-use super::blinded_path::BlindedPath;
-use super::messenger::Destination;
-
-use crate::prelude::*;
-
-// TODO: DRY with onion_utils::construct_onion_keys_callback
-#[inline]
-pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
-       FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
-       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
-       session_priv: &SecretKey, mut callback: FType
-) -> Result<(), secp256k1::Error> {
-       let mut msg_blinding_point_priv = session_priv.clone();
-       let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
-       let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
-       let mut onion_packet_pubkey = msg_blinding_point.clone();
-
-       macro_rules! build_keys {
-               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {{
-                       let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
-
-                       let blinded_hop_pk = if $blinded { $pk } else {
-                               let hop_pk_blinding_factor = {
-                                       let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
-                                       hmac.input(encrypted_data_ss.as_ref());
-                                       Hmac::from_engine(hmac).into_inner()
-                               };
-                               $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())?
-                       };
-                       let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv);
-
-                       let rho = onion_utils::gen_rho_from_shared_secret(encrypted_data_ss.as_ref());
-                       let unblinded_pk_opt = if $blinded { None } else { Some($pk) };
-                       callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt, $encrypted_payload);
-                       (encrypted_data_ss, onion_packet_ss)
-               }}
-       }
-
-       macro_rules! build_keys_in_loop {
-               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {
-                       let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload);
-
-                       let msg_blinding_point_blinding_factor = {
-                               let mut sha = Sha256::engine();
-                               sha.input(&msg_blinding_point.serialize()[..]);
-                               sha.input(encrypted_data_ss.as_ref());
-                               Sha256::from_engine(sha).into_inner()
-                       };
-
-                       msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?;
-                       msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
-
-                       let onion_packet_pubkey_blinding_factor = {
-                               let mut sha = Sha256::engine();
-                               sha.input(&onion_packet_pubkey.serialize()[..]);
-                               sha.input(onion_packet_ss.as_ref());
-                               Sha256::from_engine(sha).into_inner()
-                       };
-                       onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?;
-                       onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv);
-               };
-       }
-
-       for pk in unblinded_path {
-               build_keys_in_loop!(*pk, false, None);
-       }
-       if let Some(dest) = destination {
-               match dest {
-                       Destination::Node(pk) => {
-                               build_keys!(pk, false, None);
-                       },
-                       Destination::BlindedPath(BlindedPath { blinded_hops, .. }) => {
-                               for hop in blinded_hops {
-                                       build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload));
-                               }
-                       },
-               }
-       }
-       Ok(())
-}
index 6f131fc56d6caa6aa8517cdb6cf19bad9b5dcf0a..b26298391b2f9cb2fe3561b2b5786927a27ef6a2 100644 (file)
@@ -13,10 +13,12 @@ use bitcoin::secp256k1::PublicKey;
 use bitcoin::hashes::Hash;
 use bitcoin::hashes::sha256::Hash as Sha256;
 
+use crate::blinded_path::{BlindedHop, BlindedPath};
 use crate::ln::PaymentHash;
 use crate::ln::channelmanager::{ChannelDetails, PaymentId};
 use crate::ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
 use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
+use crate::offers::invoice::BlindedPayInfo;
 use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
 use crate::routing::scoring::{ChannelUsage, LockableScore, Score};
 use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
@@ -135,19 +137,19 @@ impl<'a, S: Score> Score for ScorerAccountingForInFlightHtlcs<'a, S> {
                }
        }
 
-       fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.scorer.payment_path_failed(path, short_channel_id)
        }
 
-       fn payment_path_successful(&mut self, path: &[&RouteHop]) {
+       fn payment_path_successful(&mut self, path: &Path) {
                self.scorer.payment_path_successful(path)
        }
 
-       fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.scorer.probe_failed(path, short_channel_id)
        }
 
-       fn probe_successful(&mut self, path: &[&RouteHop]) {
+       fn probe_successful(&mut self, path: &Path) {
                self.scorer.probe_successful(path)
        }
 }
@@ -168,22 +170,27 @@ impl InFlightHtlcs {
        pub fn new() -> Self { InFlightHtlcs(HashMap::new()) }
 
        /// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`.
-       pub fn process_path(&mut self, path: &[RouteHop], payer_node_id: PublicKey) {
-               if path.is_empty() { return };
+       pub fn process_path(&mut self, path: &Path, payer_node_id: PublicKey) {
+               if path.hops.is_empty() { return };
+
+               let mut cumulative_msat = 0;
+               if let Some(tail) = &path.blinded_tail {
+                       cumulative_msat += tail.final_value_msat;
+               }
+
                // total_inflight_map needs to be direction-sensitive when keeping track of the HTLC value
                // that is held up. However, the `hops` array, which is a path returned by `find_route` in
                // the router excludes the payer node. In the following lines, the payer's information is
                // hardcoded with an inflight value of 0 so that we can correctly represent the first hop
                // in our sliding window of two.
-               let reversed_hops_with_payer = path.iter().rev().skip(1)
+               let reversed_hops_with_payer = path.hops.iter().rev().skip(1)
                        .map(|hop| hop.pubkey)
                        .chain(core::iter::once(payer_node_id));
-               let mut cumulative_msat = 0;
 
                // Taking the reversed vector from above, we zip it with just the reversed hops list to
                // work "backwards" of the given path, since the last hop's `fee_msat` actually represents
                // the total amount sent.
-               for (next_hop, prev_hop) in path.iter().rev().zip(reversed_hops_with_payer) {
+               for (next_hop, prev_hop) in path.hops.iter().rev().zip(reversed_hops_with_payer) {
                        cumulative_msat += next_hop.fee_msat;
                        self.0
                                .entry((next_hop.short_channel_id, NodeId::from_pubkey(&prev_hop) < NodeId::from_pubkey(&next_hop.pubkey)))
@@ -210,7 +217,8 @@ impl Readable for InFlightHtlcs {
        }
 }
 
-/// A hop in a route
+/// A hop in a route, and additional metadata about it. "Hop" is defined as a node and the channel
+/// that leads to it.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct RouteHop {
        /// The node_id of the node at this hop.
@@ -224,11 +232,18 @@ pub struct RouteHop {
        /// to reach this node.
        pub channel_features: ChannelFeatures,
        /// The fee taken on this hop (for paying for the use of the *next* channel in the path).
-       /// For the last hop, this should be the full value of the payment (might be more than
-       /// requested if we had to match htlc_minimum_msat).
+       /// If this is the last hop in [`Path::hops`]:
+       /// * if we're sending to a [`BlindedPath`], this is the fee paid for use of the entire blinded path
+       /// * otherwise, this is the full value of this [`Path`]'s part of the payment
+       ///
+       /// [`BlindedPath`]: crate::blinded_path::BlindedPath
        pub fee_msat: u64,
-       /// The CLTV delta added for this hop. For the last hop, this should be the full CLTV value
-       /// expected at the destination, in excess of the current block height.
+       /// The CLTV delta added for this hop.
+       /// If this is the last hop in [`Path::hops`]:
+       /// * if we're sending to a [`BlindedPath`], this is the CLTV delta for the entire blinded path
+       /// * otherwise, this is the CLTV delta expected at the destination
+       ///
+       /// [`BlindedPath`]: crate::blinded_path::BlindedPath
        pub cltv_expiry_delta: u32,
 }
 
@@ -241,16 +256,82 @@ impl_writeable_tlv_based!(RouteHop, {
        (10, cltv_expiry_delta, required),
 });
 
+/// The blinded portion of a [`Path`], if we're routing to a recipient who provided blinded paths in
+/// their BOLT12 [`Invoice`].
+///
+/// [`Invoice`]: crate::offers::invoice::Invoice
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct BlindedTail {
+       /// The hops of the [`BlindedPath`] provided by the recipient.
+       ///
+       /// [`BlindedPath`]: crate::blinded_path::BlindedPath
+       pub hops: Vec<BlindedHop>,
+       /// The blinding point of the [`BlindedPath`] provided by the recipient.
+       ///
+       /// [`BlindedPath`]: crate::blinded_path::BlindedPath
+       pub blinding_point: PublicKey,
+       /// Excess CLTV delta added to the recipient's CLTV expiry to deter intermediate nodes from
+       /// inferring the destination. May be 0.
+       pub excess_final_cltv_expiry_delta: u32,
+       /// The total amount paid on this [`Path`], excluding the fees.
+       pub final_value_msat: u64,
+}
+
+impl_writeable_tlv_based!(BlindedTail, {
+       (0, hops, vec_type),
+       (2, blinding_point, required),
+       (4, excess_final_cltv_expiry_delta, required),
+       (6, final_value_msat, required),
+});
+
+/// A path in a [`Route`] to the payment recipient. Must always be at least length one.
+/// If no [`Path::blinded_tail`] is present, then [`Path::hops`] length may be up to 19.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct Path {
+       /// The list of unblinded hops in this [`Path`]. Must be at least length one.
+       pub hops: Vec<RouteHop>,
+       /// The blinded path at which this path terminates, if we're sending to one, and its metadata.
+       pub blinded_tail: Option<BlindedTail>,
+}
+
+impl Path {
+       /// Gets the fees for a given path, excluding any excess paid to the recipient.
+       pub fn fee_msat(&self) -> u64 {
+               match &self.blinded_tail {
+                       Some(_) => self.hops.iter().map(|hop| hop.fee_msat).sum::<u64>(),
+                       None => {
+                               // Do not count last hop of each path since that's the full value of the payment
+                               self.hops.split_last().map_or(0,
+                                       |(_, path_prefix)| path_prefix.iter().map(|hop| hop.fee_msat).sum())
+                       }
+               }
+       }
+
+       /// Gets the total amount paid on this [`Path`], excluding the fees.
+       pub fn final_value_msat(&self) -> u64 {
+               match &self.blinded_tail {
+                       Some(blinded_tail) => blinded_tail.final_value_msat,
+                       None => self.hops.last().map_or(0, |hop| hop.fee_msat)
+               }
+       }
+
+       /// Gets the final hop's CLTV expiry delta.
+       pub fn final_cltv_expiry_delta(&self) -> Option<u32> {
+               match &self.blinded_tail {
+                       Some(_) => None,
+                       None => self.hops.last().map(|hop| hop.cltv_expiry_delta)
+               }
+       }
+}
+
 /// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,
 /// it can take multiple paths. Each path is composed of one or more hops through the network.
 #[derive(Clone, Hash, PartialEq, Eq)]
 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 less or equal to 19 should currently ensure it is viable.
-       pub paths: Vec<Vec<RouteHop>>,
+       /// The list of [`Path`]s taken for a single (potentially-)multi-part payment. If no
+       /// [`BlindedTail`]s are present, then the pubkey of the last [`RouteHop`] in each path must be
+       /// the same.
+       pub paths: Vec<Path>,
        /// The `payment_params` parameter passed to [`find_route`].
        /// This is used by `ChannelManager` to track information which may be required for retries,
        /// provided back to you via [`Event::PaymentPathFailed`].
@@ -259,33 +340,19 @@ pub struct Route {
        pub payment_params: Option<PaymentParameters>,
 }
 
-pub(crate) trait RoutePath {
-       /// Gets the fees for a given path, excluding any excess paid to the recipient.
-       fn get_path_fees(&self) -> u64;
-}
-impl RoutePath for Vec<RouteHop> {
-       fn get_path_fees(&self) -> u64 {
-               // Do not count last hop of each path since that's the full value of the payment
-               self.split_last().map(|(_, path_prefix)| path_prefix).unwrap_or(&[])
-                       .iter().map(|hop| &hop.fee_msat)
-                       .sum()
-       }
-}
-
 impl Route {
        /// Returns the total amount of fees paid on this [`Route`].
        ///
        /// This doesn't include any extra payment made to the recipient, which can happen in excess of
        /// the amount passed to [`find_route`]'s `params.final_value_msat`.
        pub fn get_total_fees(&self) -> u64 {
-               self.paths.iter().map(|path| path.get_path_fees()).sum()
+               self.paths.iter().map(|path| path.fee_msat()).sum()
        }
 
-       /// Returns the total amount paid on this [`Route`], excluding the fees.
+       /// Returns the total amount paid on this [`Route`], excluding the fees. Might be more than
+       /// requested if we had to reach htlc_minimum_msat.
        pub fn get_total_amount(&self) -> u64 {
-               return self.paths.iter()
-                       .map(|path| path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0))
-                       .sum();
+               self.paths.iter().map(|path| path.final_value_msat()).sum()
        }
 }
 
@@ -296,14 +363,25 @@ impl Writeable for Route {
        fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
                write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
                (self.paths.len() as u64).write(writer)?;
-               for hops in self.paths.iter() {
-                       (hops.len() as u8).write(writer)?;
-                       for hop in hops.iter() {
+               let mut blinded_tails = Vec::new();
+               for path in self.paths.iter() {
+                       (path.hops.len() as u8).write(writer)?;
+                       for (idx, hop) in path.hops.iter().enumerate() {
                                hop.write(writer)?;
+                               if let Some(blinded_tail) = &path.blinded_tail {
+                                       if blinded_tails.is_empty() {
+                                               blinded_tails = Vec::with_capacity(path.hops.len());
+                                               for _ in 0..idx {
+                                                       blinded_tails.push(None);
+                                               }
+                                       }
+                                       blinded_tails.push(Some(blinded_tail));
+                               } else if !blinded_tails.is_empty() { blinded_tails.push(None); }
                        }
                }
                write_tlv_fields!(writer, {
                        (1, self.payment_params, option),
+                       (2, blinded_tails, optional_vec),
                });
                Ok(())
        }
@@ -325,12 +403,19 @@ impl Readable for Route {
                        if hops.is_empty() { return Err(DecodeError::InvalidValue); }
                        min_final_cltv_expiry_delta =
                                cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta);
-                       paths.push(hops);
+                       paths.push(Path { hops, blinded_tail: None });
                }
-               let mut payment_params = None;
-               read_tlv_fields!(reader, {
+               _init_and_read_tlv_fields!(reader, {
                        (1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
+                       (2, blinded_tails, optional_vec),
                });
+               let blinded_tails = blinded_tails.unwrap_or(Vec::new());
+               if blinded_tails.len() != 0 {
+                       if blinded_tails.len() != paths.len() { return Err(DecodeError::InvalidValue) }
+                       for (mut path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) {
+                               path.blinded_tail = blinded_tail_opt;
+                       }
+               }
                Ok(Route { paths, payment_params })
        }
 }
@@ -420,7 +505,7 @@ pub struct PaymentParameters {
        pub features: Option<InvoiceFeatures>,
 
        /// Hints for routing to the payee, containing channels connecting the payee to public nodes.
-       pub route_hints: Vec<RouteHint>,
+       pub route_hints: Hints,
 
        /// Expiration of a payment to the payee, in seconds relative to the UNIX epoch.
        pub expiry_time: Option<u64>,
@@ -459,15 +544,22 @@ pub struct PaymentParameters {
 
 impl Writeable for PaymentParameters {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               let mut clear_hints = &vec![];
+               let mut blinded_hints = &vec![];
+               match &self.route_hints {
+                       Hints::Clear(hints) => clear_hints = hints,
+                       Hints::Blinded(hints) => blinded_hints = hints,
+               }
                write_tlv_fields!(writer, {
                        (0, self.payee_pubkey, required),
                        (1, self.max_total_cltv_expiry_delta, required),
                        (2, self.features, option),
                        (3, self.max_path_count, required),
-                       (4, self.route_hints, vec_type),
+                       (4, *clear_hints, vec_type),
                        (5, self.max_channel_saturation_power_of_half, required),
                        (6, self.expiry_time, option),
                        (7, self.previously_failed_channels, vec_type),
+                       (8, *blinded_hints, optional_vec),
                        (9, self.final_cltv_expiry_delta, required),
                });
                Ok(())
@@ -485,14 +577,23 @@ impl ReadableArgs<u32> for PaymentParameters {
                        (5, max_channel_saturation_power_of_half, (default_value, 2)),
                        (6, expiry_time, option),
                        (7, previously_failed_channels, vec_type),
+                       (8, blinded_route_hints, optional_vec),
                        (9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
                });
+               let clear_route_hints = route_hints.unwrap_or(vec![]);
+               let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
+               let route_hints = if blinded_route_hints.len() != 0 {
+                       if clear_route_hints.len() != 0 { return Err(DecodeError::InvalidValue) }
+                       Hints::Blinded(blinded_route_hints)
+               } else {
+                       Hints::Clear(clear_route_hints)
+               };
                Ok(Self {
                        payee_pubkey: _init_tlv_based_struct_field!(payee_pubkey, required),
                        max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)),
                        features,
                        max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)),
-                       route_hints: route_hints.unwrap_or(Vec::new()),
+                       route_hints,
                        max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)),
                        expiry_time,
                        previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()),
@@ -511,7 +612,7 @@ impl PaymentParameters {
                Self {
                        payee_pubkey,
                        features: None,
-                       route_hints: vec![],
+                       route_hints: Hints::Clear(vec![]),
                        expiry_time: None,
                        max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
                        max_path_count: DEFAULT_MAX_PATH_COUNT,
@@ -540,7 +641,7 @@ impl PaymentParameters {
        ///
        /// This is not exported to bindings users since bindings don't support move semantics
        pub fn with_route_hints(self, route_hints: Vec<RouteHint>) -> Self {
-               Self { route_hints, ..self }
+               Self { route_hints: Hints::Clear(route_hints), ..self }
        }
 
        /// Includes a payment expiration in seconds relative to the UNIX epoch.
@@ -572,6 +673,16 @@ impl PaymentParameters {
        }
 }
 
+/// Routing hints for the tail of the route.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub enum Hints {
+       /// The recipient provided blinded paths and payinfo to reach them. The blinded paths themselves
+       /// will be included in the final [`Route`].
+       Blinded(Vec<(BlindedPayInfo, BlindedPath)>),
+       /// The recipient included these route hints in their BOLT11 invoice.
+       Clear(Vec<RouteHint>),
+}
+
 /// A list of hops along a payment path terminating with a channel to the recipient.
 #[derive(Clone, Debug, Hash, Eq, PartialEq)]
 pub struct RouteHint(pub Vec<RouteHintHop>);
@@ -1021,12 +1132,18 @@ where L::Target: Logger {
                return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
        }
 
-       for route in payment_params.route_hints.iter() {
-               for hop in &route.0 {
-                       if hop.src_node_id == payment_params.payee_pubkey {
-                               return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError});
+       match &payment_params.route_hints {
+               Hints::Clear(hints) => {
+                       for route in hints.iter() {
+                               for hop in &route.0 {
+                                       if hop.src_node_id == payment_params.payee_pubkey {
+                                               return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError});
+                                       }
+                               }
                        }
-               }
+               },
+               _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
+
        }
        if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
                return Err(LightningError{err: "Can't find a route where the maximum total CLTV expiry delta is below the final CLTV expiry.".to_owned(), action: ErrorAction::IgnoreError});
@@ -1551,7 +1668,11 @@ where L::Target: Logger {
                // If a caller provided us with last hops, add them to routing targets. Since this happens
                // earlier than general path finding, they will be somewhat prioritized, although currently
                // it matters only if the fees are exactly the same.
-               for route in payment_params.route_hints.iter().filter(|route| !route.0.is_empty()) {
+               let route_hints = match &payment_params.route_hints {
+                       Hints::Clear(hints) => hints,
+                       _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
+               };
+               for route in route_hints.iter().filter(|route| !route.0.is_empty()) {
                        let first_hop_in_route = &(route.0)[0];
                        let have_hop_src_in_graph =
                                // Only add the hops in this route to our candidate set if either
@@ -1966,8 +2087,14 @@ where L::Target: Logger {
                }
        }
 
+       let mut paths: Vec<Path> = Vec::new();
+       for results_vec in selected_paths {
+               let mut hops = Vec::with_capacity(results_vec.len());
+               for res in results_vec { hops.push(res?); }
+               paths.push(Path { hops, blinded_tail: None });
+       }
        let route = Route {
-               paths: selected_paths.into_iter().map(|path| path.into_iter().collect()).collect::<Result<Vec<_>, _>>()?,
+               paths,
                payment_params: Some(payment_params.clone()),
        };
        log_info!(logger, "Got route to {}: {}", payment_params.payee_pubkey, log_route!(route));
@@ -1989,14 +2116,14 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
 
                // Remember the last three nodes of the random walk and avoid looping back on them.
                // Init with the last three nodes from the actual path, if possible.
-               let mut nodes_to_avoid: [NodeId; 3] = [NodeId::from_pubkey(&path.last().unwrap().pubkey),
-                       NodeId::from_pubkey(&path.get(path.len().saturating_sub(2)).unwrap().pubkey),
-                       NodeId::from_pubkey(&path.get(path.len().saturating_sub(3)).unwrap().pubkey)];
+               let mut nodes_to_avoid: [NodeId; 3] = [NodeId::from_pubkey(&path.hops.last().unwrap().pubkey),
+                       NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(2)).unwrap().pubkey),
+                       NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(3)).unwrap().pubkey)];
 
                // Choose the last publicly known node as the starting point for the random walk.
                let mut cur_hop: Option<NodeId> = None;
                let mut path_nonce = [0u8; 12];
-               if let Some(starting_hop) = path.iter().rev()
+               if let Some(starting_hop) = path.hops.iter().rev()
                        .find(|h| network_nodes.contains_key(&NodeId::from_pubkey(&h.pubkey))) {
                                cur_hop = Some(NodeId::from_pubkey(&starting_hop.pubkey));
                                path_nonce.copy_from_slice(&cur_hop.unwrap().as_slice()[..12]);
@@ -2045,7 +2172,7 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
 
                // Limit the offset so we never exceed the max_total_cltv_expiry_delta. To improve plausibility,
                // we choose the limit to be the largest possible multiple of MEDIAN_HOP_CLTV_EXPIRY_DELTA.
-               let path_total_cltv_expiry_delta: u32 = path.iter().map(|h| h.cltv_expiry_delta).sum();
+               let path_total_cltv_expiry_delta: u32 = path.hops.iter().map(|h| h.cltv_expiry_delta).sum();
                let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta;
                max_path_offset = cmp::max(
                        max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA),
@@ -2053,7 +2180,11 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
                shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);
 
                // Add 'shadow' CLTV offset to the final hop
-               if let Some(last_hop) = path.last_mut() {
+               if let Some(tail) = path.blinded_tail.as_mut() {
+                       tail.excess_final_cltv_expiry_delta = tail.excess_final_cltv_expiry_delta
+                               .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(tail.excess_final_cltv_expiry_delta);
+               }
+               if let Some(last_hop) = path.hops.last_mut() {
                        last_hop.cltv_expiry_delta = last_hop.cltv_expiry_delta
                                .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(last_hop.cltv_expiry_delta);
                }
@@ -2107,13 +2238,13 @@ fn build_route_from_hops_internal<L: Deref>(
                        u64::max_value()
                }
 
-               fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+               fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
 
-               fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+               fn payment_path_successful(&mut self, _path: &Path) {}
 
-               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+               fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
 
-               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
+               fn probe_successful(&mut self, _path: &Path) {}
        }
 
        impl<'a> Writeable for HopScorer {
@@ -2141,10 +2272,11 @@ fn build_route_from_hops_internal<L: Deref>(
 
 #[cfg(test)]
 mod tests {
+       use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity};
        use crate::routing::utxo::UtxoResult;
        use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
-               PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
+               BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
                DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
        use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, Score, ProbabilisticScorer, ProbabilisticScoringParameters};
        use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
@@ -2156,8 +2288,9 @@ mod tests {
        use crate::util::config::UserConfig;
        use crate::util::test_utils as ln_test_utils;
        use crate::util::chacha20::ChaCha20;
+       use crate::util::ser::{Readable, Writeable};
        #[cfg(c_bindings)]
-       use crate::util::ser::{Writeable, Writer};
+       use crate::util::ser::Writer;
 
        use bitcoin::hashes::Hash;
        use bitcoin::network::constants::Network;
@@ -2171,6 +2304,7 @@ mod tests {
        use bitcoin::secp256k1::{PublicKey,SecretKey};
        use bitcoin::secp256k1::Secp256k1;
 
+       use crate::io::Cursor;
        use crate::prelude::*;
        use crate::sync::Arc;
 
@@ -2228,21 +2362,21 @@ mod tests {
                } else { panic!(); }
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
 
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
 
-               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-               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));
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
        }
 
        #[test]
@@ -2264,7 +2398,7 @@ mod tests {
                } else { panic!(); }
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
        }
 
        #[test]
@@ -2391,7 +2525,7 @@ mod tests {
 
                // A payment above the minimum should pass
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
        }
 
        #[test]
@@ -2474,7 +2608,7 @@ mod tests {
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                // Overpay fees to hit htlc_minimum_msat.
-               let overpaid_fees = route.paths[0][0].fee_msat + route.paths[1][0].fee_msat;
+               let overpaid_fees = route.paths[0].hops[0].fee_msat + route.paths[1].hops[0].fee_msat;
                // TODO: this could be better balanced to overpay 10k and not 15k.
                assert_eq!(overpaid_fees, 15_000);
 
@@ -2520,16 +2654,16 @@ mod tests {
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                // Fine to overpay for htlc_minimum_msat if it allows us to save fee.
                assert_eq!(route.paths.len(), 1);
-               assert_eq!(route.paths[0][0].short_channel_id, 12);
-               let fees = route.paths[0][0].fee_msat;
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
+               let fees = route.paths[0].hops[0].fee_msat;
                assert_eq!(fees, 5_000);
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                // Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on
                // the other channel.
                assert_eq!(route.paths.len(), 1);
-               assert_eq!(route.paths[0][0].short_channel_id, 2);
-               let fees = route.paths[0][0].fee_msat;
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               let fees = route.paths[0].hops[0].fee_msat;
                assert_eq!(fees, 5_000);
        }
 
@@ -2576,21 +2710,21 @@ mod tests {
                // If we specify a channel to node7, that overrides our local channel view and that gets used
                let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
 
-               assert_eq!(route.paths[0][0].pubkey, nodes[7]);
-               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 << 4) | 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::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
 
-               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-               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));
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
        }
 
        #[test]
@@ -2617,21 +2751,21 @@ mod tests {
                // If we specify a channel to node7, that overrides our local channel view and that gets used
                let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[7]);
-               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 << 4) | 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::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
-
-               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-               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));
+               assert_eq!(route.paths[0].hops.len(), 2);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
 
                // Note that we don't test disabling node 3 and failing to route to it, as we (somewhat
                // naively) assume that the user checked the feature bits on the invoice, which override
@@ -2649,48 +2783,48 @@ mod tests {
                // Route to 1 via 2 and 3 because our channel to 1 is disabled
                let payment_params = PaymentParameters::from_node_id(nodes[0], 42);
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 3);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[0]);
-               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));
+               assert_eq!(route.paths[0].hops.len(), 3);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (3 << 4) | 2);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[0]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 3);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(1));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(3));
 
                // If we specify a channel to node7, that overrides our local channel view and that gets used
                let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
                let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
 
-               assert_eq!(route.paths[0][0].pubkey, nodes[7]);
-               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 << 4) | 1);
-               assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
-               assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
 
-               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-               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));
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
        }
 
        fn last_hops(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
@@ -2805,44 +2939,44 @@ mod tests {
 
                let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_multi_private_channels(&nodes));
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 5);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[4]);
-               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 << 4) | 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, nodes[3]);
-               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 << 4) | 1);
+               assert_eq!(route.paths[0].hops.len(), 5);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 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.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].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
 
-               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[4].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        fn empty_last_hop(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
@@ -2881,44 +3015,44 @@ mod tests {
                // Test handling of an empty RouteHint passed in Invoice.
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 5);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[4]);
-               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 << 4) | 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, nodes[3]);
-               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 << 4) | 1);
+               assert_eq!(route.paths[0].hops.len(), 5);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 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.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].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
 
-               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[4].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        /// Builds a trivial last-hop hint that passes through the two nodes given, with channel 0xff00
@@ -2987,35 +3121,35 @@ mod tests {
                });
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 4);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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, 65);
-               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, nodes[2]);
-               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, 81);
-               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, nodes[3]);
-               assert_eq!(route.paths[0][2].short_channel_id, last_hops[0].0[0].short_channel_id);
-               assert_eq!(route.paths[0][2].fee_msat, 0);
-               assert_eq!(route.paths[0][2].cltv_expiry_delta, 129);
-               assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags(4));
-               assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
-
-               assert_eq!(route.paths[0][3].pubkey, nodes[6]);
-               assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops.len(), 4);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, 65);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 81);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, last_hops[0].0[0].short_channel_id);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 129);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        #[test]
@@ -3059,35 +3193,35 @@ mod tests {
                });
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &[42u8; 32]).unwrap();
-               assert_eq!(route.paths[0].len(), 4);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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, 65);
-               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, nodes[2]);
-               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, 81);
-               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, non_announced_pubkey);
-               assert_eq!(route.paths[0][2].short_channel_id, last_hops[0].0[0].short_channel_id);
-               assert_eq!(route.paths[0][2].fee_msat, 0);
-               assert_eq!(route.paths[0][2].cltv_expiry_delta, 129);
-               assert_eq!(route.paths[0][2].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
-
-               assert_eq!(route.paths[0][3].pubkey, nodes[6]);
-               assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops.len(), 4);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, 65);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 81);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, non_announced_pubkey);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, last_hops[0].0[0].short_channel_id);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 129);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        fn last_hops_with_public_channel(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
@@ -3141,44 +3275,44 @@ mod tests {
                // which would be handled in the same manner.
 
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 5);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[4]);
-               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 << 4) | 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, nodes[3]);
-               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 << 4) | 1);
+               assert_eq!(route.paths[0].hops.len(), 5);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 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.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].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
 
-               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[4].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        #[test]
@@ -3194,99 +3328,99 @@ mod tests {
                let mut last_hops = last_hops(&nodes);
                let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone());
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 2);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[3]);
-               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 << 4) | 1);
-               assert_eq!(route.paths[0][0].node_features.le_flags(), &vec![0b11]);
-               assert_eq!(route.paths[0][0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
-
-               assert_eq!(route.paths[0][1].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops.len(), 2);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
 
                last_hops[0].0[0].fees.base_msat = 1000;
 
                // Revert to via 6 as the fee on 8 goes up
                let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops);
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 4);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[5]);
-               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 << 4) | 1);
+               assert_eq!(route.paths[0].hops.len(), 4);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 200); // fee increased as its % of value transferred across node
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (7 << 4) | 1);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[5]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 7);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (10 << 4) | 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.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.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(6));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(7));
 
-               assert_eq!(route.paths[0][3].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, 10);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 100);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::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 = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 2000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               assert_eq!(route.paths[0].len(), 5);
-
-               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-               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 << 4) | 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, nodes[2]);
-               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 << 4) | 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, nodes[4]);
-               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 << 4) | 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, nodes[3]);
-               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 << 4) | 1);
+               assert_eq!(route.paths[0].hops.len(), 5);
+
+               assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 3000);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
+               assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
+               assert_eq!(route.paths[0].hops[3].fee_msat, 1000);
+               assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 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.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].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
 
-               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
-               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(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[4].fee_msat, 2000);
+               assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
        fn do_unannounced_path_test(last_hop_htlc_max: Option<u64>, last_hop_fee_prop: u32, outbound_capacity_msat: u64, route_val: u64) -> Result<Route, LightningError> {
@@ -3327,21 +3461,21 @@ mod tests {
 
                let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
                let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
-               assert_eq!(route.paths[0].len(), 2);
+               assert_eq!(route.paths[0].hops.len(), 2);
 
-               assert_eq!(route.paths[0][0].pubkey, middle_node_id);
-               assert_eq!(route.paths[0][0].short_channel_id, 42);
-               assert_eq!(route.paths[0][0].fee_msat, 1001);
-               assert_eq!(route.paths[0][0].cltv_expiry_delta, (8 << 4) | 1);
-               assert_eq!(route.paths[0][0].node_features.le_flags(), &[0b11]);
-               assert_eq!(route.paths[0][0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[0].pubkey, middle_node_id);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 1001);
+               assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
+               assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &[0b11]);
+               assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
 
-               assert_eq!(route.paths[0][1].pubkey, target_node_id);
-               assert_eq!(route.paths[0][1].short_channel_id, 8);
-               assert_eq!(route.paths[0][1].fee_msat, 1000000);
-               assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
-               assert_eq!(route.paths[0][1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
-               assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
+               assert_eq!(route.paths[0].hops[1].pubkey, target_node_id);
+               assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
+               assert_eq!(route.paths[0].hops[1].fee_msat, 1000000);
+               assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
        }
 
        #[test]
@@ -3446,9 +3580,9 @@ mod tests {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
                        let path = route.paths.last().unwrap();
-                       assert_eq!(path.len(), 2);
-                       assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                       assert_eq!(path.last().unwrap().fee_msat, 250_000_000);
+                       assert_eq!(path.hops.len(), 2);
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       assert_eq!(path.final_value_msat(), 250_000_000);
                }
 
                // Check that setting next_outbound_htlc_limit_msat in first_hops limits the channels.
@@ -3482,9 +3616,9 @@ mod tests {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
                        let path = route.paths.last().unwrap();
-                       assert_eq!(path.len(), 2);
-                       assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                       assert_eq!(path.last().unwrap().fee_msat, 200_000_000);
+                       assert_eq!(path.hops.len(), 2);
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       assert_eq!(path.final_value_msat(), 200_000_000);
                }
 
                // Enable channel #1 back.
@@ -3529,9 +3663,9 @@ mod tests {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
                        let path = route.paths.last().unwrap();
-                       assert_eq!(path.len(), 2);
-                       assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                       assert_eq!(path.last().unwrap().fee_msat, 15_000);
+                       assert_eq!(path.hops.len(), 2);
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       assert_eq!(path.final_value_msat(), 15_000);
                }
 
                // Now let's see if routing works if we know only capacity from the UTXO.
@@ -3600,9 +3734,9 @@ mod tests {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
                        let path = route.paths.last().unwrap();
-                       assert_eq!(path.len(), 2);
-                       assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                       assert_eq!(path.last().unwrap().fee_msat, 15_000);
+                       assert_eq!(path.hops.len(), 2);
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       assert_eq!(path.final_value_msat(), 15_000);
                }
 
                // Now let's see if routing chooses htlc_maximum_msat over UTXO capacity.
@@ -3632,9 +3766,9 @@ mod tests {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 10_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
                        let path = route.paths.last().unwrap();
-                       assert_eq!(path.len(), 2);
-                       assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                       assert_eq!(path.last().unwrap().fee_msat, 10_000);
+                       assert_eq!(path.hops.len(), 2);
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       assert_eq!(path.final_value_msat(), 10_000);
                }
        }
 
@@ -3745,9 +3879,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 1);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 4);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[3]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 4);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 49_000);
                }
@@ -3758,9 +3892,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 1);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 4);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[3]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 4);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 50_000);
                }
@@ -3806,9 +3940,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 1);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 2);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 2);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 50_000);
                }
@@ -3952,9 +4086,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 2);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 2);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 250_000);
                }
@@ -3966,9 +4100,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 2);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 2);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 290_000);
                }
@@ -4131,8 +4265,8 @@ mod tests {
 
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.last().unwrap().pubkey, nodes[3]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 300_000);
                }
@@ -4293,9 +4427,9 @@ mod tests {
                        let mut total_value_transferred_msat = 0;
                        let mut total_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.last().unwrap().pubkey, nodes[3]);
-                               total_value_transferred_msat += path.last().unwrap().fee_msat;
-                               for hop in path {
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
+                               total_value_transferred_msat += path.final_value_msat();
+                               for hop in &path.hops {
                                        total_paid_msat += hop.fee_msat;
                                }
                        }
@@ -4470,8 +4604,8 @@ mod tests {
 
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.last().unwrap().pubkey, nodes[3]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 200_000);
                        assert_eq!(route.get_total_fees(), 150_000);
@@ -4567,16 +4701,16 @@ mod tests {
                // overpay at all.
                let mut route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                assert_eq!(route.paths.len(), 2);
-               route.paths.sort_by_key(|path| path[0].short_channel_id);
+               route.paths.sort_by_key(|path| path.hops[0].short_channel_id);
                // Paths are manually ordered ordered by SCID, so:
                // * the first is channel 1 (0 fee, but 99 sat maximum) -> channel 3 -> channel 42
                // * the second is channel 2 (1 msat fee) -> channel 4 -> channel 42
-               assert_eq!(route.paths[0][0].short_channel_id, 1);
-               assert_eq!(route.paths[0][0].fee_msat, 0);
-               assert_eq!(route.paths[0][2].fee_msat, 99_000);
-               assert_eq!(route.paths[1][0].short_channel_id, 2);
-               assert_eq!(route.paths[1][0].fee_msat, 1);
-               assert_eq!(route.paths[1][2].fee_msat, 1_000);
+               assert_eq!(route.paths[0].hops[0].short_channel_id, 1);
+               assert_eq!(route.paths[0].hops[0].fee_msat, 0);
+               assert_eq!(route.paths[0].hops[2].fee_msat, 99_000);
+               assert_eq!(route.paths[1].hops[0].short_channel_id, 2);
+               assert_eq!(route.paths[1].hops[0].fee_msat, 1);
+               assert_eq!(route.paths[1].hops[2].fee_msat, 1_000);
                assert_eq!(route.get_total_fees(), 1);
                assert_eq!(route.get_total_amount(), 100_000);
        }
@@ -4696,9 +4830,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 2);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 2);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 125_000);
                }
@@ -4709,9 +4843,9 @@ mod tests {
                        assert_eq!(route.paths.len(), 2);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.len(), 2);
-                               assert_eq!(path.last().unwrap().pubkey, nodes[2]);
-                               total_amount_paid_msat += path.last().unwrap().fee_msat;
+                               assert_eq!(path.hops.len(), 2);
+                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 90_000);
                }
@@ -4846,28 +4980,28 @@ mod tests {
                        // Now ensure the route flows simply over nodes 1 and 4 to 6.
                        let route = get_route(&our_id, &payment_params, &network.read_only(), None, 10_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
-                       assert_eq!(route.paths[0].len(), 3);
-
-                       assert_eq!(route.paths[0][0].pubkey, nodes[1]);
-                       assert_eq!(route.paths[0][0].short_channel_id, 6);
-                       assert_eq!(route.paths[0][0].fee_msat, 100);
-                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (5 << 4) | 0);
-                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(1));
-                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(6));
-
-                       assert_eq!(route.paths[0][1].pubkey, nodes[4]);
-                       assert_eq!(route.paths[0][1].short_channel_id, 5);
-                       assert_eq!(route.paths[0][1].fee_msat, 0);
-                       assert_eq!(route.paths[0][1].cltv_expiry_delta, (1 << 4) | 0);
-                       assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(4));
-                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(5));
-
-                       assert_eq!(route.paths[0][2].pubkey, nodes[6]);
-                       assert_eq!(route.paths[0][2].short_channel_id, 1);
-                       assert_eq!(route.paths[0][2].fee_msat, 10_000);
-                       assert_eq!(route.paths[0][2].cltv_expiry_delta, 42);
-                       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(1));
+                       assert_eq!(route.paths[0].hops.len(), 3);
+
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
+                       assert_eq!(route.paths[0].hops[0].short_channel_id, 6);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 100);
+                       assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (5 << 4) | 0);
+                       assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(1));
+                       assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(6));
+
+                       assert_eq!(route.paths[0].hops[1].pubkey, nodes[4]);
+                       assert_eq!(route.paths[0].hops[1].short_channel_id, 5);
+                       assert_eq!(route.paths[0].hops[1].fee_msat, 0);
+                       assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (1 << 4) | 0);
+                       assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(4));
+                       assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(5));
+
+                       assert_eq!(route.paths[0].hops[2].pubkey, nodes[6]);
+                       assert_eq!(route.paths[0].hops[2].short_channel_id, 1);
+                       assert_eq!(route.paths[0].hops[2].fee_msat, 10_000);
+                       assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(6));
+                       assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(1));
                }
        }
 
@@ -4917,21 +5051,21 @@ mod tests {
                        // 200% fee charged channel 13 in the 1-to-2 direction.
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
-                       assert_eq!(route.paths[0].len(), 2);
-
-                       assert_eq!(route.paths[0][0].pubkey, nodes[7]);
-                       assert_eq!(route.paths[0][0].short_channel_id, 12);
-                       assert_eq!(route.paths[0][0].fee_msat, 90_000*2);
-                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 4) | 1);
-                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(8));
-                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(12));
-
-                       assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-                       assert_eq!(route.paths[0][1].short_channel_id, 13);
-                       assert_eq!(route.paths[0][1].fee_msat, 90_000);
-                       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));
+                       assert_eq!(route.paths[0].hops.len(), 2);
+
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
+                       assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2);
+                       assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
+                       assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8));
+                       assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12));
+
+                       assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+                       assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
+                       assert_eq!(route.paths[0].hops[1].fee_msat, 90_000);
+                       assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
+                       assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
                }
        }
 
@@ -4983,21 +5117,21 @@ mod tests {
                        // expensive) channels 12-13 path.
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
-                       assert_eq!(route.paths[0].len(), 2);
-
-                       assert_eq!(route.paths[0][0].pubkey, nodes[7]);
-                       assert_eq!(route.paths[0][0].short_channel_id, 12);
-                       assert_eq!(route.paths[0][0].fee_msat, 90_000*2);
-                       assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 4) | 1);
-                       assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(8));
-                       assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(12));
-
-                       assert_eq!(route.paths[0][1].pubkey, nodes[2]);
-                       assert_eq!(route.paths[0][1].short_channel_id, 13);
-                       assert_eq!(route.paths[0][1].fee_msat, 90_000);
-                       assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
-                       assert_eq!(route.paths[0][1].node_features.le_flags(), channelmanager::provided_invoice_features(&config).le_flags());
-                       assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(13));
+                       assert_eq!(route.paths[0].hops.len(), 2);
+
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
+                       assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2);
+                       assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
+                       assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8));
+                       assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12));
+
+                       assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
+                       assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
+                       assert_eq!(route.paths[0].hops[1].fee_msat, 90_000);
+                       assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
+                       assert_eq!(route.paths[0].hops[1].node_features.le_flags(), channelmanager::provided_invoice_features(&config).le_flags());
+                       assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
                }
        }
 
@@ -5025,11 +5159,11 @@ mod tests {
                                &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000),
                        ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
-                       assert_eq!(route.paths[0].len(), 1);
+                       assert_eq!(route.paths[0].hops.len(), 1);
 
-                       assert_eq!(route.paths[0][0].pubkey, nodes[0]);
-                       assert_eq!(route.paths[0][0].short_channel_id, 3);
-                       assert_eq!(route.paths[0][0].fee_msat, 100_000);
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
+                       assert_eq!(route.paths[0].hops[0].short_channel_id, 3);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
                }
                {
                        let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
@@ -5037,17 +5171,17 @@ mod tests {
                                &get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
                        ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 2);
-                       assert_eq!(route.paths[0].len(), 1);
-                       assert_eq!(route.paths[1].len(), 1);
+                       assert_eq!(route.paths[0].hops.len(), 1);
+                       assert_eq!(route.paths[1].hops.len(), 1);
 
-                       assert!((route.paths[0][0].short_channel_id == 3 && route.paths[1][0].short_channel_id == 2) ||
-                               (route.paths[0][0].short_channel_id == 2 && route.paths[1][0].short_channel_id == 3));
+                       assert!((route.paths[0].hops[0].short_channel_id == 3 && route.paths[1].hops[0].short_channel_id == 2) ||
+                               (route.paths[0].hops[0].short_channel_id == 2 && route.paths[1].hops[0].short_channel_id == 3));
 
-                       assert_eq!(route.paths[0][0].pubkey, nodes[0]);
-                       assert_eq!(route.paths[0][0].fee_msat, 50_000);
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 50_000);
 
-                       assert_eq!(route.paths[1][0].pubkey, nodes[0]);
-                       assert_eq!(route.paths[1][0].fee_msat, 50_000);
+                       assert_eq!(route.paths[1].hops[0].pubkey, nodes[0]);
+                       assert_eq!(route.paths[1].hops[0].fee_msat, 50_000);
                }
 
                {
@@ -5069,11 +5203,11 @@ mod tests {
                                &get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000),
                        ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 1);
-                       assert_eq!(route.paths[0].len(), 1);
+                       assert_eq!(route.paths[0].hops.len(), 1);
 
-                       assert_eq!(route.paths[0][0].pubkey, nodes[0]);
-                       assert_eq!(route.paths[0][0].short_channel_id, 6);
-                       assert_eq!(route.paths[0][0].fee_msat, 100_000);
+                       assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
+                       assert_eq!(route.paths[0].hops[0].short_channel_id, 6);
+                       assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
                }
        }
 
@@ -5091,7 +5225,7 @@ mod tests {
                        &our_id, &payment_params, &network_graph.read_only(), None, 100, 42,
                        Arc::clone(&logger), &scorer, &random_seed_bytes
                ).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
 
                assert_eq!(route.get_total_fees(), 100);
                assert_eq!(route.get_total_amount(), 100);
@@ -5104,7 +5238,7 @@ mod tests {
                        &our_id, &payment_params, &network_graph.read_only(), None, 100, 42,
                        Arc::clone(&logger), &scorer, &random_seed_bytes
                ).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
 
                assert_eq!(route.get_total_fees(), 300);
                assert_eq!(route.get_total_amount(), 100);
@@ -5124,10 +5258,10 @@ mod tests {
                        if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
                }
 
-               fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
-               fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
-               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
-               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
+               fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
+               fn payment_path_successful(&mut self, _path: &Path) {}
+               fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
+               fn probe_successful(&mut self, _path: &Path) {}
        }
 
        struct BadNodeScorer {
@@ -5144,10 +5278,10 @@ mod tests {
                        if *target == self.node_id { u64::max_value() } else { 0 }
                }
 
-               fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
-               fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
-               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
-               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
+               fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
+               fn payment_path_successful(&mut self, _path: &Path) {}
+               fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
+               fn probe_successful(&mut self, _path: &Path) {}
        }
 
        #[test]
@@ -5165,7 +5299,7 @@ mod tests {
                        &our_id, &payment_params, &network_graph, None, 100, 42,
                        Arc::clone(&logger), &scorer, &random_seed_bytes
                ).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
 
                assert_eq!(route.get_total_fees(), 100);
                assert_eq!(route.get_total_amount(), 100);
@@ -5177,7 +5311,7 @@ mod tests {
                        &our_id, &payment_params, &network_graph, None, 100, 42,
                        Arc::clone(&logger), &scorer, &random_seed_bytes
                ).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
 
                assert_eq!(route.get_total_fees(), 300);
                assert_eq!(route.get_total_amount(), 100);
@@ -5199,7 +5333,7 @@ mod tests {
        #[test]
        fn total_fees_single_path() {
                let route = Route {
-                       paths: vec![vec![
+                       paths: vec![Path { hops: vec![
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
@@ -5215,7 +5349,7 @@ mod tests {
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
                                        short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
                                },
-                       ]],
+                       ], blinded_tail: None }],
                        payment_params: None,
                };
 
@@ -5226,7 +5360,7 @@ mod tests {
        #[test]
        fn total_fees_multi_path() {
                let route = Route {
-                       paths: vec![vec![
+                       paths: vec![Path { hops: vec![
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
@@ -5237,7 +5371,7 @@ mod tests {
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
                                        short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
                                },
-                       ],vec![
+                       ], blinded_tail: None }, Path { hops: vec![
                                RouteHop {
                                        pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
@@ -5248,7 +5382,7 @@ mod tests {
                                        channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
                                        short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
                                },
-                       ]],
+                       ], blinded_tail: None }],
                        payment_params: None,
                };
 
@@ -5282,7 +5416,7 @@ mod tests {
                let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
                let random_seed_bytes = keys_manager.get_secure_random_bytes();
                let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, 0, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
                assert_ne!(path.len(), 0);
 
                // But not if we exclude all paths on the basis of their accumulated CLTV delta
@@ -5317,12 +5451,12 @@ mod tests {
                assert!(get_route(&our_id, &payment_params, &network_graph, None, 100, 0, Arc::clone(&logger), &scorer, &random_seed_bytes).is_ok());
                loop {
                        if let Ok(route) = get_route(&our_id, &payment_params, &network_graph, None, 100, 0, Arc::clone(&logger), &scorer, &random_seed_bytes) {
-                               for chan in route.paths[0].iter() {
+                               for chan in route.paths[0].hops.iter() {
                                        assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
                                }
                                let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) as usize)
-                                       % route.paths[0].len();
-                               payment_params.previously_failed_channels.push(route.paths[0][victim].short_channel_id);
+                                       % route.paths[0].hops.len();
+                               payment_params.previously_failed_channels.push(route.paths[0].hops[victim].short_channel_id);
                        } else { break; }
                }
        }
@@ -5341,7 +5475,7 @@ mod tests {
                let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
                let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, 0,
                        Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
-               let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
+               let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
                assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into());
 
                // But we can't create a path surpassing the MAX_PATH_LENGTH_ESTIMATE limit.
@@ -5369,12 +5503,12 @@ mod tests {
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                assert_eq!(route.paths.len(), 1);
 
-               let cltv_expiry_deltas_before = route.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+               let cltv_expiry_deltas_before = route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
 
                // Check whether the offset added to the last hop by default is in [1 .. DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA]
                let mut route_default = route.clone();
                add_random_cltv_offset(&mut route_default, &payment_params, &network_graph.read_only(), &random_seed_bytes);
-               let cltv_expiry_deltas_default = route_default.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+               let cltv_expiry_deltas_default = route_default.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
                assert_eq!(cltv_expiry_deltas_before.split_last().unwrap().1, cltv_expiry_deltas_default.split_last().unwrap().1);
                assert!(cltv_expiry_deltas_default.last() > cltv_expiry_deltas_before.last());
                assert!(cltv_expiry_deltas_default.last().unwrap() <= &DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA);
@@ -5384,7 +5518,7 @@ mod tests {
                let limited_max_total_cltv_expiry_delta = cltv_expiry_deltas_before.iter().sum();
                let limited_payment_params = payment_params.with_max_total_cltv_expiry_delta(limited_max_total_cltv_expiry_delta);
                add_random_cltv_offset(&mut route_limited, &limited_payment_params, &network_graph.read_only(), &random_seed_bytes);
-               let cltv_expiry_deltas_limited = route_limited.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+               let cltv_expiry_deltas_limited = route_limited.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
                assert_eq!(cltv_expiry_deltas_before, cltv_expiry_deltas_limited);
        }
 
@@ -5412,11 +5546,11 @@ mod tests {
                        let mut random_bytes = [0u8; ::core::mem::size_of::<usize>()];
 
                        prng.process_in_place(&mut random_bytes);
-                       let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.len());
-                       let observation_point = NodeId::from_pubkey(&p.get(random_path_index).unwrap().pubkey);
+                       let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.hops.len());
+                       let observation_point = NodeId::from_pubkey(&p.hops.get(random_path_index).unwrap().pubkey);
 
                        // 2. Calculate what CLTV expiry delta we would observe there
-                       let observed_cltv_expiry_delta: u32 = p[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum();
+                       let observed_cltv_expiry_delta: u32 = p.hops[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum();
 
                        // 3. Starting from the observation point, find candidate paths
                        let mut candidates: VecDeque<(NodeId, Vec<u32>)> = VecDeque::new();
@@ -5467,8 +5601,8 @@ mod tests {
                let hops = [nodes[1], nodes[2], nodes[4], nodes[3]];
                let route = build_route_from_hops_internal(&our_id, &hops, &payment_params,
                         &network_graph, 100, 0, Arc::clone(&logger), &random_seed_bytes).unwrap();
-               let route_hop_pubkeys = route.paths[0].iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
-               assert_eq!(hops.len(), route.paths[0].len());
+               let route_hop_pubkeys = route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
+               assert_eq!(hops.len(), route.paths[0].hops.len());
                for (idx, hop_pubkey) in hops.iter().enumerate() {
                        assert!(*hop_pubkey == route_hop_pubkeys[idx]);
                }
@@ -5515,8 +5649,8 @@ mod tests {
                // 100,000 sats is less than the available liquidity on each channel, set above.
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                assert_eq!(route.paths.len(), 2);
-               assert!((route.paths[0][1].short_channel_id == 4 && route.paths[1][1].short_channel_id == 13) ||
-                       (route.paths[1][1].short_channel_id == 4 && route.paths[0][1].short_channel_id == 13));
+               assert!((route.paths[0].hops[1].short_channel_id == 4 && route.paths[1].hops[1].short_channel_id == 13) ||
+                       (route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
        }
 
        #[cfg(not(feature = "no-std"))]
@@ -5641,6 +5775,151 @@ mod tests {
                let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes);
                assert!(route.is_ok());
        }
+
+       #[test]
+       fn blinded_route_ser() {
+               let blinded_path_1 = BlindedPath {
+                       introduction_node_id: ln_test_utils::pubkey(42),
+                       blinding_point: ln_test_utils::pubkey(43),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() },
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }
+                       ],
+               };
+               let blinded_path_2 = BlindedPath {
+                       introduction_node_id: ln_test_utils::pubkey(46),
+                       blinding_point: ln_test_utils::pubkey(47),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() },
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }
+                       ],
+               };
+               // (De)serialize a Route with 1 blinded path out of two total paths.
+               let mut route = Route { paths: vec![Path {
+                       hops: vec![RouteHop {
+                               pubkey: ln_test_utils::pubkey(50),
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 42,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 100,
+                               cltv_expiry_delta: 0,
+                       }],
+                       blinded_tail: Some(BlindedTail {
+                               hops: blinded_path_1.blinded_hops,
+                               blinding_point: blinded_path_1.blinding_point,
+                               excess_final_cltv_expiry_delta: 40,
+                               final_value_msat: 100,
+                       })}, Path {
+                       hops: vec![RouteHop {
+                               pubkey: ln_test_utils::pubkey(51),
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 43,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 100,
+                               cltv_expiry_delta: 0,
+                       }], blinded_tail: None }],
+                       payment_params: None,
+               };
+               let encoded_route = route.encode();
+               let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
+               assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
+               assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
+
+               // (De)serialize a Route with two paths, each containing a blinded tail.
+               route.paths[1].blinded_tail = Some(BlindedTail {
+                       hops: blinded_path_2.blinded_hops,
+                       blinding_point: blinded_path_2.blinding_point,
+                       excess_final_cltv_expiry_delta: 41,
+                       final_value_msat: 101,
+               });
+               let encoded_route = route.encode();
+               let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
+               assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
+               assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
+       }
+
+       #[test]
+       fn blinded_path_inflight_processing() {
+               // Ensure we'll score the channel that's inbound to a blinded path's introduction node, and
+               // account for the blinded tail's final amount_msat.
+               let mut inflight_htlcs = InFlightHtlcs::new();
+               let blinded_path = BlindedPath {
+                       introduction_node_id: ln_test_utils::pubkey(43),
+                       blinding_point: ln_test_utils::pubkey(48),
+                       blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }],
+               };
+               let path = Path {
+                       hops: vec![RouteHop {
+                               pubkey: ln_test_utils::pubkey(42),
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 42,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 100,
+                               cltv_expiry_delta: 0,
+                       },
+                       RouteHop {
+                               pubkey: blinded_path.introduction_node_id,
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 43,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 1,
+                               cltv_expiry_delta: 0,
+                       }],
+                       blinded_tail: Some(BlindedTail {
+                               hops: blinded_path.blinded_hops,
+                               blinding_point: blinded_path.blinding_point,
+                               excess_final_cltv_expiry_delta: 0,
+                               final_value_msat: 200,
+                       }),
+               };
+               inflight_htlcs.process_path(&path, ln_test_utils::pubkey(44));
+               assert_eq!(*inflight_htlcs.0.get(&(42, true)).unwrap(), 301);
+               assert_eq!(*inflight_htlcs.0.get(&(43, false)).unwrap(), 201);
+       }
+
+       #[test]
+       fn blinded_path_cltv_shadow_offset() {
+               // Make sure we add a shadow offset when sending to blinded paths.
+               let blinded_path = BlindedPath {
+                       introduction_node_id: ln_test_utils::pubkey(43),
+                       blinding_point: ln_test_utils::pubkey(44),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() },
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(46), encrypted_payload: Vec::new() }
+                       ],
+               };
+               let mut route = Route { paths: vec![Path {
+                       hops: vec![RouteHop {
+                               pubkey: ln_test_utils::pubkey(42),
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 42,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 100,
+                               cltv_expiry_delta: 0,
+                       },
+                       RouteHop {
+                               pubkey: blinded_path.introduction_node_id,
+                               node_features: NodeFeatures::empty(),
+                               short_channel_id: 43,
+                               channel_features: ChannelFeatures::empty(),
+                               fee_msat: 1,
+                               cltv_expiry_delta: 0,
+                       }
+                       ],
+                       blinded_tail: Some(BlindedTail {
+                               hops: blinded_path.blinded_hops,
+                               blinding_point: blinded_path.blinding_point,
+                               excess_final_cltv_expiry_delta: 0,
+                               final_value_msat: 200,
+                       }),
+               }], payment_params: None};
+
+               let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
+               let (_, network_graph, _, _, _) = build_line_graph();
+               add_random_cltv_offset(&mut route, &payment_params, &network_graph.read_only(), &[0; 32]);
+               assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40);
+               assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40);
+       }
 }
 
 #[cfg(all(test, not(feature = "no-std")))]
@@ -5811,12 +6090,12 @@ mod benches {
                        let amount = route.get_total_amount();
                        if amount < 250_000 {
                                for path in route.paths {
-                                       scorer.payment_path_successful(&path.iter().collect::<Vec<_>>());
+                                       scorer.payment_path_successful(&path);
                                }
                        } else if amount > 750_000 {
                                for path in route.paths {
-                                       let short_channel_id = path[path.len() / 2].short_channel_id;
-                                       scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), short_channel_id);
+                                       let short_channel_id = path.hops[path.hops.len() / 2].short_channel_id;
+                                       scorer.payment_path_failed(&path, short_channel_id);
                                }
                        }
                }
index 4d342562bea75afbe091af9244584dcbb2124ffb..e60e4879b3d5e25ca121ed6d1a543b120317282f 100644 (file)
@@ -56,7 +56,7 @@
 
 use crate::ln::msgs::DecodeError;
 use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
-use crate::routing::router::RouteHop;
+use crate::routing::router::Path;
 use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
 use crate::util::logger::Logger;
 use crate::util::time::Time;
@@ -99,16 +99,16 @@ pub trait Score $(: $supertrait)* {
        ) -> u64;
 
        /// Handles updating channel penalties after failing to route through a channel.
-       fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
+       fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64);
 
        /// Handles updating channel penalties after successfully routing along a path.
-       fn payment_path_successful(&mut self, path: &[&RouteHop]);
+       fn payment_path_successful(&mut self, path: &Path);
 
        /// Handles updating channel penalties after a probe over the given path failed.
-       fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
+       fn probe_failed(&mut self, path: &Path, short_channel_id: u64);
 
        /// Handles updating channel penalties after a probe over the given path succeeded.
-       fn probe_successful(&mut self, path: &[&RouteHop]);
+       fn probe_successful(&mut self, path: &Path);
 }
 
 impl<S: Score, T: DerefMut<Target=S> $(+ $supertrait)*> Score for T {
@@ -118,19 +118,19 @@ impl<S: Score, T: DerefMut<Target=S> $(+ $supertrait)*> Score for T {
                self.deref().channel_penalty_msat(short_channel_id, source, target, usage)
        }
 
-       fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.deref_mut().payment_path_failed(path, short_channel_id)
        }
 
-       fn payment_path_successful(&mut self, path: &[&RouteHop]) {
+       fn payment_path_successful(&mut self, path: &Path) {
                self.deref_mut().payment_path_successful(path)
        }
 
-       fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.deref_mut().probe_failed(path, short_channel_id)
        }
 
-       fn probe_successful(&mut self, path: &[&RouteHop]) {
+       fn probe_successful(&mut self, path: &Path) {
                self.deref_mut().probe_successful(path)
        }
 }
@@ -195,16 +195,16 @@ impl<'a, T: Score + 'a> Score for MultiThreadedScoreLock<'a, T> {
        fn channel_penalty_msat(&self, scid: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage) -> u64 {
                self.0.channel_penalty_msat(scid, source, target, usage)
        }
-       fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.0.payment_path_failed(path, short_channel_id)
        }
-       fn payment_path_successful(&mut self, path: &[&RouteHop]) {
+       fn payment_path_successful(&mut self, path: &Path) {
                self.0.payment_path_successful(path)
        }
-       fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.0.probe_failed(path, short_channel_id)
        }
-       fn probe_successful(&mut self, path: &[&RouteHop]) {
+       fn probe_successful(&mut self, path: &Path) {
                self.0.probe_successful(path)
        }
 }
@@ -290,13 +290,13 @@ impl Score for FixedPenaltyScorer {
                self.penalty_msat
        }
 
-       fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+       fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
 
-       fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+       fn payment_path_successful(&mut self, _path: &Path) {}
 
-       fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+       fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
 
-       fn probe_successful(&mut self, _path: &[&RouteHop]) {}
+       fn probe_successful(&mut self, _path: &Path) {}
 }
 
 impl Writeable for FixedPenaltyScorer {
@@ -1233,11 +1233,11 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for Probabilis
                        .saturating_add(base_penalty_msat)
        }
 
-       fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
-               let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0);
+       fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
+               let amount_msat = path.final_value_msat();
                log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat);
                let network_graph = self.network_graph.read_only();
-               for (hop_idx, hop) in path.iter().enumerate() {
+               for (hop_idx, hop) in path.hops.iter().enumerate() {
                        let target = NodeId::from_pubkey(&hop.pubkey);
                        let channel_directed_from_source = network_graph.channels()
                                .get(&hop.short_channel_id)
@@ -1272,12 +1272,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for Probabilis
                }
        }
 
-       fn payment_path_successful(&mut self, path: &[&RouteHop]) {
-               let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0);
+       fn payment_path_successful(&mut self, path: &Path) {
+               let amount_msat = path.final_value_msat();
                log_trace!(self.logger, "Scoring path through SCID {} as having succeeded at {} msat.",
-                       path.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat);
+                       path.hops.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat);
                let network_graph = self.network_graph.read_only();
-               for hop in path {
+               for hop in &path.hops {
                        let target = NodeId::from_pubkey(&hop.pubkey);
                        let channel_directed_from_source = network_graph.channels()
                                .get(&hop.short_channel_id)
@@ -1298,11 +1298,11 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for Probabilis
                }
        }
 
-       fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+       fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
                self.payment_path_failed(path, short_channel_id)
        }
 
-       fn probe_successful(&mut self, path: &[&RouteHop]) {
+       fn probe_successful(&mut self, path: &Path) {
                self.payment_path_failed(path, u64::max_value())
        }
 }
@@ -1702,6 +1702,7 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
 #[cfg(test)]
 mod tests {
        use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringParameters, ProbabilisticScorerUsingTime};
+       use crate::blinded_path::{BlindedHop, BlindedPath};
        use crate::util::config::UserConfig;
        use crate::util::time::Time;
        use crate::util::time::tests::SinceEpoch;
@@ -1709,10 +1710,10 @@ mod tests {
        use crate::ln::channelmanager;
        use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate};
        use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
-       use crate::routing::router::RouteHop;
+       use crate::routing::router::{BlindedTail, Path, RouteHop};
        use crate::routing::scoring::{ChannelUsage, Score};
        use crate::util::ser::{ReadableArgs, Writeable};
-       use crate::util::test_utils::TestLogger;
+       use crate::util::test_utils::{self, TestLogger};
 
        use bitcoin::blockdata::constants::genesis_block;
        use bitcoin::hashes::Hash;
@@ -1858,12 +1859,14 @@ mod tests {
                }
        }
 
-       fn payment_path_for_amount(amount_msat: u64) -> Vec<RouteHop> {
-               vec![
-                       path_hop(source_pubkey(), 41, 1),
-                       path_hop(target_pubkey(), 42, 2),
-                       path_hop(recipient_pubkey(), 43, amount_msat),
-               ]
+       fn payment_path_for_amount(amount_msat: u64) -> Path {
+               Path {
+                       hops: vec![
+                               path_hop(source_pubkey(), 41, 1),
+                               path_hop(target_pubkey(), 42, 2),
+                               path_hop(recipient_pubkey(), 43, amount_msat),
+                       ], blinded_tail: None,
+               }
        }
 
        #[test]
@@ -2161,10 +2164,10 @@ mod tests {
 
                assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
 
-               scorer.payment_path_failed(&failed_path.iter().collect::<Vec<_>>(), 41);
+               scorer.payment_path_failed(&failed_path, 41);
                assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
 
-               scorer.payment_path_successful(&successful_path.iter().collect::<Vec<_>>());
+               scorer.payment_path_successful(&successful_path);
                assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
        }
 
@@ -2192,7 +2195,7 @@ mod tests {
                let usage = ChannelUsage { amount_msat: 750, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602);
 
-               scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&path, 43);
 
                let usage = ChannelUsage { amount_msat: 250, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
@@ -2227,7 +2230,7 @@ mod tests {
                let usage = ChannelUsage { amount_msat: 750, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602);
 
-               scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&path, 42);
 
                let usage = ChannelUsage { amount_msat: 250, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
@@ -2287,7 +2290,7 @@ mod tests {
                assert_eq!(scorer.channel_penalty_msat(43, &node_b, &node_c, usage), 128);
                assert_eq!(scorer.channel_penalty_msat(44, &node_c, &node_d, usage), 128);
 
-               scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43);
 
                assert_eq!(scorer.channel_penalty_msat(42, &node_a, &node_b, usage), 80);
                // Note that a default liquidity bound is used for B -> C as no channel exists
@@ -2313,13 +2316,12 @@ mod tests {
                        inflight_htlc_msat: 0,
                        effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
                };
-               let path = payment_path_for_amount(500);
 
                assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 128);
                assert_eq!(scorer.channel_penalty_msat(43, &target, &recipient, usage), 128);
 
-               scorer.payment_path_successful(&path.iter().collect::<Vec<_>>());
+               scorer.payment_path_successful(&payment_path_for_amount(500));
 
                assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
@@ -2349,8 +2351,8 @@ mod tests {
                let usage = ChannelUsage { amount_msat: 1_023, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
 
-               scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
-               scorer.payment_path_failed(&payment_path_for_amount(128).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(768), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(128), 43);
 
                let usage = ChannelUsage { amount_msat: 128, ..usage };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
@@ -2425,7 +2427,7 @@ mod tests {
                };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125);
 
-               scorer.payment_path_failed(&payment_path_for_amount(512).iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(512), 42);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 281);
 
                // An unchecked right shift 64 bits or more in DirectedChannelLiquidity::decayed_offset_msat
@@ -2458,8 +2460,8 @@ mod tests {
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
 
                // More knowledge gives higher confidence (256, 768), meaning a lower penalty.
-               scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
-               scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(768), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(256), 43);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 281);
 
                // Decaying knowledge gives less confidence (128, 896), meaning a higher penalty.
@@ -2468,12 +2470,12 @@ mod tests {
 
                // Reducing the upper bound gives more confidence (128, 832) that the payment amount (512)
                // is closer to the upper bound, meaning a higher penalty.
-               scorer.payment_path_successful(&payment_path_for_amount(64).iter().collect::<Vec<_>>());
+               scorer.payment_path_successful(&payment_path_for_amount(64));
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 331);
 
                // Increasing the lower bound gives more confidence (256, 832) that the payment amount (512)
                // is closer to the lower bound, meaning a lower penalty.
-               scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(256), 43);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 245);
 
                // Further decaying affects the lower bound more than the upper bound (128, 928).
@@ -2500,13 +2502,13 @@ mod tests {
                        effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
                };
 
-               scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(500), 42);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
 
                SinceEpoch::advance(Duration::from_secs(10));
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 473);
 
-               scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(250), 43);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
 
                let mut serialized_scorer = Vec::new();
@@ -2537,7 +2539,7 @@ mod tests {
                        effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
                };
 
-               scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(500), 42);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
 
                let mut serialized_scorer = Vec::new();
@@ -2550,7 +2552,7 @@ mod tests {
                        <ProbabilisticScorer>::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap();
                assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 473);
 
-               scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(250), 43);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
 
                SinceEpoch::advance(Duration::from_secs(10));
@@ -2773,7 +2775,7 @@ mod tests {
                assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
                        None);
 
-               scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(1), 42);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048);
                // The "it failed" increment is 32, where the probability should lie fully in the first
                // octile.
@@ -2782,7 +2784,7 @@ mod tests {
 
                // Even after we tell the scorer we definitely have enough available liquidity, it will
                // still remember that there was some failure in the past, and assign a non-0 penalty.
-               scorer.payment_path_failed(&payment_path_for_amount(1000).iter().collect::<Vec<_>>(), 43);
+               scorer.payment_path_failed(&payment_path_for_amount(1000), 43);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 198);
                // The first octile should be decayed just slightly and the last octile has a new point.
                assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
@@ -2802,7 +2804,7 @@ mod tests {
                        inflight_htlc_msat: 1024,
                        effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
                };
-               scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&payment_path_for_amount(1), 42);
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 409);
 
                let usage = ChannelUsage {
@@ -2822,7 +2824,7 @@ mod tests {
                        path_hop(source_pubkey(), 42, 1),
                        path_hop(sender_pubkey(), 41, 0),
                ];
-               scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 42);
+               scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 42);
        }
 
        #[test]
@@ -2869,4 +2871,54 @@ mod tests {
                };
                assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
        }
+
+       #[test]
+       fn scores_with_blinded_path() {
+               // Make sure we'll account for a blinded path's final_value_msat in scoring
+               let logger = TestLogger::new();
+               let network_graph = network_graph(&logger);
+               let params = ProbabilisticScoringParameters {
+                       liquidity_penalty_multiplier_msat: 1_000,
+                       liquidity_offset_half_life: Duration::from_secs(10),
+                       ..ProbabilisticScoringParameters::zero_penalty()
+               };
+               let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
+               let source = source_node_id();
+               let target = target_node_id();
+               let usage = ChannelUsage {
+                       amount_msat: 512,
+                       inflight_htlc_msat: 0,
+                       effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 },
+               };
+               assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
+
+               let mut path = payment_path_for_amount(768);
+               let recipient_hop = path.hops.pop().unwrap();
+               let blinded_path = BlindedPath {
+                       introduction_node_id: path.hops.last().as_ref().unwrap().pubkey,
+                       blinding_point: test_utils::pubkey(42),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() }
+                       ],
+               };
+               path.blinded_tail = Some(BlindedTail {
+                       hops: blinded_path.blinded_hops,
+                       blinding_point: blinded_path.blinding_point,
+                       excess_final_cltv_expiry_delta: recipient_hop.cltv_expiry_delta,
+                       final_value_msat: recipient_hop.fee_msat,
+               });
+
+               // Check the liquidity before and after scoring payment failures to ensure the blinded path's
+               // final value is taken into account.
+               assert!(scorer.channel_liquidities.get(&42).is_none());
+
+               scorer.payment_path_failed(&path, 42);
+               path.blinded_tail.as_mut().unwrap().final_value_msat = 256;
+               scorer.payment_path_failed(&path, 43);
+
+               let liquidity = scorer.channel_liquidities.get(&42).unwrap()
+                       .as_directed(&source, &target, 0, 1_000, &scorer.params);
+               assert_eq!(liquidity.min_liquidity_msat(), 256);
+               assert_eq!(liquidity.max_liquidity_msat(), 768);
+       }
 }
index 6e98272f3171d2bc47082c4171b6e4013800e911..a9018f3da90c89f412a4bb55ca438c55f8abec77 100644 (file)
@@ -65,9 +65,10 @@ impl<'a> core::fmt::Display for DebugRoute<'a> {
        fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
                for (idx, p) in self.0.paths.iter().enumerate() {
                        writeln!(f, "path {}:", idx)?;
-                       for h in p.iter() {
+                       for h in p.hops.iter() {
                                writeln!(f, " node_id: {}, short_channel_id: {}, fee_msat: {}, cltv_expiry_delta: {}", log_pubkey!(h.pubkey), h.short_channel_id, h.fee_msat, h.cltv_expiry_delta)?;
                        }
+                       writeln!(f, " blinded_tail: {:?}", p.blinded_tail)?;
                }
                Ok(())
        }
index 8056f3bed35569e952c6897a4b999b6e2bf3b1fd..77ee33c4fa099de78ef6bac9f7c1cfd210586f73 100644 (file)
@@ -749,7 +749,7 @@ where T: Readable + Eq + Hash
 }
 
 // Vectors
-macro_rules! impl_for_vec {
+macro_rules! impl_writeable_for_vec {
        ($ty: ty $(, $name: ident)*) => {
                impl<$($name : Writeable),*> Writeable for Vec<$ty> {
                        #[inline]
@@ -761,7 +761,10 @@ macro_rules! impl_for_vec {
                                Ok(())
                        }
                }
-
+       }
+}
+macro_rules! impl_readable_for_vec {
+       ($ty: ty $(, $name: ident)*) => {
                impl<$($name : Readable),*> Readable for Vec<$ty> {
                        #[inline]
                        fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
@@ -777,6 +780,12 @@ macro_rules! impl_for_vec {
                }
        }
 }
+macro_rules! impl_for_vec {
+       ($ty: ty $(, $name: ident)*) => {
+               impl_writeable_for_vec!($ty $(, $name)*);
+               impl_readable_for_vec!($ty $(, $name)*);
+       }
+}
 
 impl Writeable for Vec<u8> {
        #[inline]
@@ -805,6 +814,8 @@ impl Readable for Vec<u8> {
 impl_for_vec!(ecdsa::Signature);
 impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction);
 impl_for_vec!((A, B), A, B);
+impl_writeable_for_vec!(&crate::routing::router::BlindedTail);
+impl_readable_for_vec!(crate::routing::router::BlindedTail);
 
 impl Writeable for Script {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
index 5be32a87f726d9fa486da514d237c96f3148b5e9..67f6fd6b2888dfecd4f6acb3928302e2ce41ea79 100644 (file)
@@ -25,7 +25,7 @@ use crate::ln::msgs::LightningError;
 use crate::ln::script::ShutdownScript;
 use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
 use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
-use crate::routing::router::{find_route, InFlightHtlcs, Route, RouteHop, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
+use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
 use crate::routing::scoring::{ChannelUsage, Score};
 use crate::util::config::UserConfig;
 use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
@@ -60,6 +60,15 @@ use crate::chain::keysinterface::{InMemorySigner, Recipient, EntropySource, Node
 use std::time::{SystemTime, UNIX_EPOCH};
 use bitcoin::Sequence;
 
+pub fn pubkey(byte: u8) -> PublicKey {
+       let secp_ctx = Secp256k1::new();
+       PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
+}
+
+pub fn privkey(byte: u8) -> SecretKey {
+       SecretKey::from_slice(&[byte; 32]).unwrap()
+}
+
 pub struct TestVecWriter(pub Vec<u8>);
 impl Writer for TestVecWriter {
        fn write_all(&mut self, buf: &[u8]) -> Result<(), io::Error> {
@@ -106,7 +115,7 @@ impl<'a> Router for TestRouter<'a> {
                                let scorer = ScorerAccountingForInFlightHtlcs::new(locked_scorer, inflight_htlcs);
                                for path in &route.paths {
                                        let mut aggregate_msat = 0u64;
-                                       for (idx, hop) in path.iter().rev().enumerate() {
+                                       for (idx, hop) in path.hops.iter().rev().enumerate() {
                                                aggregate_msat += hop.fee_msat;
                                                let usage = ChannelUsage {
                                                        amount_msat: aggregate_msat,
@@ -116,11 +125,11 @@ impl<'a> Router for TestRouter<'a> {
 
                                                // Since the path is reversed, the last element in our iteration is the first
                                                // hop.
-                                               if idx == path.len() - 1 {
+                                               if idx == path.hops.len() - 1 {
                                                        scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(payer), &NodeId::from_pubkey(&hop.pubkey), usage);
                                                } else {
-                                                       let curr_hop_path_idx = path.len() - 1 - idx;
-                                                       scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(&path[curr_hop_path_idx - 1].pubkey), &NodeId::from_pubkey(&hop.pubkey), usage);
+                                                       let curr_hop_path_idx = path.hops.len() - 1 - idx;
+                                                       scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(&path.hops[curr_hop_path_idx - 1].pubkey), &NodeId::from_pubkey(&hop.pubkey), usage);
                                                }
                                        }
                                }
@@ -974,13 +983,13 @@ impl Score for TestScorer {
                0
        }
 
-       fn payment_path_failed(&mut self, _actual_path: &[&RouteHop], _actual_short_channel_id: u64) {}
+       fn payment_path_failed(&mut self, _actual_path: &Path, _actual_short_channel_id: u64) {}
 
-       fn payment_path_successful(&mut self, _actual_path: &[&RouteHop]) {}
+       fn payment_path_successful(&mut self, _actual_path: &Path) {}
 
-       fn probe_failed(&mut self, _actual_path: &[&RouteHop], _: u64) {}
+       fn probe_failed(&mut self, _actual_path: &Path, _: u64) {}
 
-       fn probe_successful(&mut self, _actual_path: &[&RouteHop]) {}
+       fn probe_successful(&mut self, _actual_path: &Path) {}
 }
 
 impl Drop for TestScorer {
diff --git a/pending_changelog/2146.txt b/pending_changelog/2146.txt
new file mode 100644 (file)
index 0000000..12e3be3
--- /dev/null
@@ -0,0 +1,4 @@
+## Backwards Compatibility
+
+* Routes manually constructed with `Path::blinded_tail` present will not be readable by prior versions of LDK
+* `PaymentParameters` manually constructed with `Hints::Blinded` will not be readable by prior versions of LDK