Merge pull request #2209 from TheBlueMatt/2023-04-better-discon-err-msg
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Mon, 24 Apr 2023 21:22:06 +0000 (21:22 +0000)
committerGitHub <noreply@github.com>
Mon, 24 Apr 2023 21:22:06 +0000 (21:22 +0000)
Clarify the error message when we disconnect a peer

47 files changed:
README.md
ci/ci-tests.sh
fuzz/src/chanmon_consistency.rs
fuzz/src/invoice_request_deser.rs
fuzz/src/refund_deser.rs
fuzz/src/router.rs
lightning-background-processor/src/lib.rs
lightning-net-tokio/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/chain/chainmonitor.rs
lightning/src/chain/channelmonitor.rs
lightning/src/chain/onchaintx.rs
lightning/src/chain/package.rs
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/monitor_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/ln/reorg_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/gossip.rs
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 7f95427a6ab13bfb29d1fe1614e5238fb28b1bcb..a4ab59b53833afd6e672d1d67686a233b98f67ff 100644 (file)
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@ In general, `rust-lightning` does not provide (but LDK has implementations of):
   wanted to run your full Lightning node on a hardware wallet, you could, by
   piping the Lightning network messages over USB/serial and then sending them in
   a TCP socket from another machine.
-* private keys - again we have "default implementations", but users can chose to
+* private keys - again we have "default implementations", but users can choose to
   provide private keys to RL/LDK in any way they wish following a simple API. We
   even support a generic API for signing transactions, allowing users to run
   RL/LDK without any private keys in memory/putting private keys only on
index e0e66861fa98c05c7d38a5db081a6288e0323304..f948cc0d63e925f94b803f2738ed0475f01cbc2b 100755 (executable)
@@ -87,7 +87,7 @@ fi
 
 echo -e "\n\nTest futures builds"
 pushd lightning-background-processor
-cargo test --verbose --color always --no-default-features --features futures
+cargo test --verbose --color always --features futures
 popd
 
 if [ "$RUSTC_MINOR_VERSION" -gt 55 ]; then
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 568dcdf0250057c68e1cf946b4b543f97c8933c0..fe6f1647f4d20c182558938b2ce475d6b7d1037b 100644 (file)
@@ -227,7 +227,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
                        },
                        4 => {
                                let short_channel_id = slice_to_be64(get_slice!(8));
-                               net_graph.channel_failed(short_channel_id, false);
+                               net_graph.channel_failed_permanent(short_channel_id);
                        },
                        _ if node_pks.is_empty() => {},
                        _ => {
index 7d705bdcc3ae05630c53f1806e30b95d337ac919..940da6d2f2cab5f48af273d489a27346c65bf568 100644 (file)
@@ -64,8 +64,8 @@ use alloc::vec::Vec;
 /// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
 ///   writing it to disk/backups by invoking the callback given to it at startup.
 ///   [`ChannelManager`] persistence should be done in the background.
-/// * Calling [`ChannelManager::timer_tick_occurred`] and [`PeerManager::timer_tick_occurred`]
-///   at the appropriate intervals.
+/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
+///   and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
 /// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
 ///   [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
 ///
@@ -116,12 +116,17 @@ const FIRST_NETWORK_PRUNE_TIMER: u64 = 60;
 #[cfg(test)]
 const FIRST_NETWORK_PRUNE_TIMER: u64 = 1;
 
+#[cfg(not(test))]
+const REBROADCAST_TIMER: u64 = 30;
+#[cfg(test)]
+const REBROADCAST_TIMER: u64 = 1;
+
 #[cfg(feature = "futures")]
 /// core::cmp::min is not currently const, so we define a trivial (and equivalent) replacement
 const fn min_u64(a: u64, b: u64) -> u64 { if a < b { a } else { b } }
 #[cfg(feature = "futures")]
 const FASTEST_TIMER: u64 = min_u64(min_u64(FRESHNESS_TIMER, PING_TIMER),
-       min_u64(SCORER_PERSIST_TIMER, FIRST_NETWORK_PRUNE_TIMER));
+       min_u64(SCORER_PERSIST_TIMER, min_u64(FIRST_NETWORK_PRUNE_TIMER, REBROADCAST_TIMER)));
 
 /// Either [`P2PGossipSync`] or [`RapidGossipSync`].
 pub enum GossipSync<
@@ -236,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);
                },
                _ => {},
        }
@@ -270,11 +270,14 @@ macro_rules! define_run_body {
        => { {
                log_trace!($logger, "Calling ChannelManager's timer_tick_occurred on startup");
                $channel_manager.timer_tick_occurred();
+               log_trace!($logger, "Rebroadcasting monitor's pending claims on startup");
+               $chain_monitor.rebroadcast_pending_claims();
 
                let mut last_freshness_call = $get_timer(FRESHNESS_TIMER);
                let mut last_ping_call = $get_timer(PING_TIMER);
                let mut last_prune_call = $get_timer(FIRST_NETWORK_PRUNE_TIMER);
                let mut last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER);
+               let mut last_rebroadcast_call = $get_timer(REBROADCAST_TIMER);
                let mut have_pruned = false;
 
                loop {
@@ -294,6 +297,12 @@ macro_rules! define_run_body {
                        // persistence.
                        $peer_manager.process_events();
 
+                       // Exit the loop if the background processor was requested to stop.
+                       if $loop_exit_check {
+                               log_trace!($logger, "Terminating background processor.");
+                               break;
+                       }
+
                        // We wait up to 100ms, but track how long it takes to detect being put to sleep,
                        // see `await_start`'s use below.
                        let mut await_start = None;
@@ -301,16 +310,17 @@ macro_rules! define_run_body {
                        let updates_available = $await;
                        let await_slow = if $check_slow_await { $timer_elapsed(&mut await_start.unwrap(), 1) } else { false };
 
-                       if updates_available {
-                               log_trace!($logger, "Persisting ChannelManager...");
-                               $persister.persist_manager(&*$channel_manager)?;
-                               log_trace!($logger, "Done persisting ChannelManager.");
-                       }
                        // Exit the loop if the background processor was requested to stop.
                        if $loop_exit_check {
                                log_trace!($logger, "Terminating background processor.");
                                break;
                        }
+
+                       if updates_available {
+                               log_trace!($logger, "Persisting ChannelManager...");
+                               $persister.persist_manager(&*$channel_manager)?;
+                               log_trace!($logger, "Done persisting ChannelManager.");
+                       }
                        if $timer_elapsed(&mut last_freshness_call, FRESHNESS_TIMER) {
                                log_trace!($logger, "Calling ChannelManager's timer_tick_occurred");
                                $channel_manager.timer_tick_occurred();
@@ -342,7 +352,8 @@ macro_rules! define_run_body {
                        // falling back to our usual hourly prunes. This avoids short-lived clients never
                        // pruning their network graph. We run once 60 seconds after startup before
                        // continuing our normal cadence.
-                       if $timer_elapsed(&mut last_prune_call, if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER }) {
+                       let prune_timer = if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER };
+                       if $timer_elapsed(&mut last_prune_call, prune_timer) {
                                // The network graph must not be pruned while rapid sync completion is pending
                                if let Some(network_graph) = $gossip_sync.prunable_network_graph() {
                                        #[cfg(feature = "std")] {
@@ -360,7 +371,8 @@ macro_rules! define_run_body {
 
                                        have_pruned = true;
                                }
-                               last_prune_call = $get_timer(NETWORK_PRUNE_TIMER);
+                               let prune_timer = if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER };
+                               last_prune_call = $get_timer(prune_timer);
                        }
 
                        if $timer_elapsed(&mut last_scorer_persist_call, SCORER_PERSIST_TIMER) {
@@ -372,6 +384,12 @@ macro_rules! define_run_body {
                                }
                                last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER);
                        }
+
+                       if $timer_elapsed(&mut last_rebroadcast_call, REBROADCAST_TIMER) {
+                               log_trace!($logger, "Rebroadcasting monitor's pending claims");
+                               $chain_monitor.rebroadcast_pending_claims();
+                               last_rebroadcast_call = $get_timer(REBROADCAST_TIMER);
+                       }
                }
 
                // After we exit, ensure we persist the ChannelManager one final time - this avoids
@@ -751,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;
@@ -867,7 +885,10 @@ mod tests {
 
                        if key == "network_graph" {
                                if let Some(sender) = &self.graph_persistence_notifier {
-                                       sender.send(()).unwrap();
+                                       match sender.send(()) {
+                                               Ok(()) => {},
+                                               Err(std::sync::mpsc::SendError(())) => println!("Persister failed to notify as receiver went away."),
+                                       }
                                };
 
                                if let Some((error, message)) = self.graph_error {
@@ -891,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 {
@@ -916,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 } => {
@@ -936,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)
@@ -955,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, .. } => {
@@ -965,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)
@@ -973,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, .. } => {
@@ -986,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);
                                        }
                                }
                        }
@@ -1014,12 +1035,12 @@ mod tests {
        }
 
        fn create_nodes(num_nodes: usize, persist_dir: String) -> Vec<Node> {
+               let network = Network::Testnet;
                let mut nodes = Vec::new();
                for i in 0..num_nodes {
-                       let tx_broadcaster = Arc::new(test_utils::TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new()), blocks: Arc::new(Mutex::new(Vec::new()))});
+                       let tx_broadcaster = Arc::new(test_utils::TestBroadcaster::new(network));
                        let fee_estimator = Arc::new(test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) });
                        let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", i)));
-                       let network = Network::Testnet;
                        let genesis_block = genesis_block(network);
                        let network_graph = Arc::new(NetworkGraph::new(network, logger.clone()));
                        let scorer = Arc::new(Mutex::new(TestScorer::new()));
@@ -1189,8 +1210,9 @@ mod tests {
 
        #[test]
        fn test_timer_tick_called() {
-               // Test that ChannelManager's and PeerManager's `timer_tick_occurred` is called every
-               // `FRESHNESS_TIMER`.
+               // Test that `ChannelManager::timer_tick_occurred` is called every `FRESHNESS_TIMER`,
+               // `ChainMonitor::rebroadcast_pending_claims` is called every `REBROADCAST_TIMER`, and
+               // `PeerManager::timer_tick_occurred` every `PING_TIMER`.
                let nodes = create_nodes(1, "test_timer_tick_called".to_string());
                let data_dir = nodes[0].persister.get_data_dir();
                let persister = Arc::new(Persister::new(data_dir));
@@ -1198,10 +1220,12 @@ mod tests {
                let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
                loop {
                        let log_entries = nodes[0].logger.lines.lock().unwrap();
-                       let desired_log = "Calling ChannelManager's timer_tick_occurred".to_string();
-                       let second_desired_log = "Calling PeerManager's timer_tick_occurred".to_string();
-                       if log_entries.get(&("lightning_background_processor".to_string(), desired_log)).is_some() &&
-                                       log_entries.get(&("lightning_background_processor".to_string(), second_desired_log)).is_some() {
+                       let desired_log_1 = "Calling ChannelManager's timer_tick_occurred".to_string();
+                       let desired_log_2 = "Calling PeerManager's timer_tick_occurred".to_string();
+                       let desired_log_3 = "Rebroadcasting monitor's pending claims".to_string();
+                       if log_entries.get(&("lightning_background_processor".to_string(), desired_log_1)).is_some() &&
+                               log_entries.get(&("lightning_background_processor".to_string(), desired_log_2)).is_some() &&
+                               log_entries.get(&("lightning_background_processor".to_string(), desired_log_3)).is_some() {
                                break
                        }
                }
@@ -1480,10 +1504,9 @@ mod tests {
                                })
                        }, false,
                );
-               // TODO: Drop _local and simply spawn after #2003
-               let local_set = tokio::task::LocalSet::new();
-               local_set.spawn_local(bp_future);
-               local_set.spawn_local(async move {
+
+               let t1 = tokio::spawn(bp_future);
+               let t2 = tokio::spawn(async move {
                        do_test_not_pruning_network_graph_until_graph_sync_completion!(nodes, {
                                let mut i = 0;
                                loop {
@@ -1495,7 +1518,9 @@ mod tests {
                        }, tokio::time::sleep(Duration::from_millis(1)).await);
                        exit_sender.send(()).unwrap();
                });
-               local_set.await;
+               let (r1, r2) = tokio::join!(t1, t2);
+               r1.unwrap().unwrap();
+               r2.unwrap()
        }
 
        macro_rules! do_test_payment_path_scoring {
@@ -1510,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 {
@@ -1649,13 +1674,14 @@ mod tests {
                                })
                        }, false,
                );
-               // TODO: Drop _local and simply spawn after #2003
-               let local_set = tokio::task::LocalSet::new();
-               local_set.spawn_local(bp_future);
-               local_set.spawn_local(async move {
+               let t1 = tokio::spawn(bp_future);
+               let t2 = tokio::spawn(async move {
                        do_test_payment_path_scoring!(nodes, receiver.recv().await);
                        exit_sender.send(()).unwrap();
                });
-               local_set.await;
+
+               let (r1, r2) = tokio::join!(t1, t2);
+               r1.unwrap().unwrap();
+               r2.unwrap()
        }
 }
index 37c9ddad76204ee68dda7656a81772bb97ef6fca..48f1736d0c2296988c5d0f7c2c110421d5ca319f 100644 (file)
@@ -189,7 +189,7 @@ impl Connection {
                        // our timeslice to another task we may just spin on this peer, starving other peers
                        // and eventually disconnecting them for ping timeouts. Instead, we explicitly yield
                        // here.
-                       tokio::task::yield_now().await;
+                       let _ = tokio::task::yield_now().await;
                };
                let writer_option = us.lock().unwrap().writer.take();
                if let Some(mut writer) = writer_option {
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 fdaa25f69b8ea7df39dc6b246d2f062095071019..e7c2b0f18ec001d703eb0249964b90a8a3f1a5ed 100644 (file)
@@ -217,8 +217,15 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> Deref for LockedChannelMonitor<
 /// or used independently to monitor channels remotely. See the [module-level documentation] for
 /// details.
 ///
+/// Note that `ChainMonitor` should regularly trigger rebroadcasts/fee bumps of pending claims from
+/// a force-closed channel. This is crucial in preventing certain classes of pinning attacks,
+/// detecting substantial mempool feerate changes between blocks, and ensuring reliability if
+/// broadcasting fails. We recommend invoking this every 30 seconds, or lower if running in an
+/// environment with spotty connections, like on mobile.
+///
 /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
 /// [module-level documentation]: crate::chain::chainmonitor
+/// [`rebroadcast_pending_claims`]: Self::rebroadcast_pending_claims
 pub struct ChainMonitor<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
        where C::Target: chain::Filter,
         T::Target: BroadcasterInterface,
@@ -533,6 +540,20 @@ where C::Target: chain::Filter,
        pub fn get_update_future(&self) -> Future {
                self.event_notifier.get_future()
        }
+
+       /// Triggers rebroadcasts/fee-bumps of pending claims from a force-closed channel. This is
+       /// crucial in preventing certain classes of pinning attacks, detecting substantial mempool
+       /// feerate changes between blocks, and ensuring reliability if broadcasting fails. We recommend
+       /// invoking this every 30 seconds, or lower if running in an environment with spotty
+       /// connections, like on mobile.
+       pub fn rebroadcast_pending_claims(&self) {
+               let monitors = self.monitors.read().unwrap();
+               for (_, monitor_holder) in &*monitors {
+                       monitor_holder.monitor.rebroadcast_pending_claims(
+                               &*self.broadcaster, &*self.fee_estimator, &*self.logger
+                       )
+               }
+       }
 }
 
 impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
index 5ff297b1af7477c4fee3cff8d8b5db6a7f428a48..f2c6ea18249480d981337fd530bbf8874265af71 100644 (file)
@@ -1467,6 +1467,27 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
        pub fn current_best_block(&self) -> BestBlock {
                self.inner.lock().unwrap().best_block.clone()
        }
+
+       /// Triggers rebroadcasts/fee-bumps of pending claims from a force-closed channel. This is
+       /// crucial in preventing certain classes of pinning attacks, detecting substantial mempool
+       /// feerate changes between blocks, and ensuring reliability if broadcasting fails. We recommend
+       /// invoking this every 30 seconds, or lower if running in an environment with spotty
+       /// connections, like on mobile.
+       pub fn rebroadcast_pending_claims<B: Deref, F: Deref, L: Deref>(
+               &self, broadcaster: B, fee_estimator: F, logger: L,
+       )
+       where
+               B::Target: BroadcasterInterface,
+               F::Target: FeeEstimator,
+               L::Target: Logger,
+       {
+               let fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator);
+               let mut inner = self.inner.lock().unwrap();
+               let current_height = inner.best_block.height;
+               inner.onchain_tx_handler.rebroadcast_pending_claims(
+                       current_height, &broadcaster, &fee_estimator, &logger,
+               );
+       }
 }
 
 impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
@@ -4148,7 +4169,7 @@ mod tests {
                replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage: payment_preimage_1 });
                replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage: payment_preimage_2 });
 
-               let broadcaster = TestBroadcaster::new(Arc::clone(&nodes[1].blocks));
+               let broadcaster = TestBroadcaster::with_blocks(Arc::clone(&nodes[1].blocks));
                assert!(
                        pre_update_monitor.update_monitor(&replay_update, &&broadcaster, &chanmon_cfgs[1].fee_estimator, &nodes[1].logger)
                        .is_err());
@@ -4174,10 +4195,7 @@ mod tests {
        fn test_prune_preimages() {
                let secp_ctx = Secp256k1::new();
                let logger = Arc::new(TestLogger::new());
-               let broadcaster = Arc::new(TestBroadcaster {
-                       txn_broadcasted: Mutex::new(Vec::new()),
-                       blocks: Arc::new(Mutex::new(Vec::new()))
-               });
+               let broadcaster = Arc::new(TestBroadcaster::new(Network::Testnet));
                let fee_estimator = TestFeeEstimator { sat_per_kw: Mutex::new(253) };
 
                let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
index cd0cb08eab3522c00a057a7e9043c45c6a2a43a0..2d5c85cd477e56660f5a32cfd2a7b95318ba1c57 100644 (file)
@@ -481,6 +481,59 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                events.into_iter().map(|(_, event)| event).collect()
        }
 
+       /// Triggers rebroadcasts/fee-bumps of pending claims from a force-closed channel. This is
+       /// crucial in preventing certain classes of pinning attacks, detecting substantial mempool
+       /// feerate changes between blocks, and ensuring reliability if broadcasting fails. We recommend
+       /// invoking this every 30 seconds, or lower if running in an environment with spotty
+       /// connections, like on mobile.
+       pub(crate) fn rebroadcast_pending_claims<B: Deref, F: Deref, L: Deref>(
+               &mut self, current_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>,
+               logger: &L,
+       )
+       where
+               B::Target: BroadcasterInterface,
+               F::Target: FeeEstimator,
+               L::Target: Logger,
+       {
+               let mut bump_requests = Vec::with_capacity(self.pending_claim_requests.len());
+               for (package_id, request) in self.pending_claim_requests.iter() {
+                       let inputs = request.outpoints();
+                       log_info!(logger, "Triggering rebroadcast/fee-bump for request with inputs {:?}", inputs);
+                       bump_requests.push((*package_id, request.clone()));
+               }
+               for (package_id, request) in bump_requests {
+                       self.generate_claim(current_height, &request, false /* force_feerate_bump */, fee_estimator, logger)
+                               .map(|(_, new_feerate, claim)| {
+                                       let mut bumped_feerate = false;
+                                       if let Some(mut_request) = self.pending_claim_requests.get_mut(&package_id) {
+                                               bumped_feerate = request.previous_feerate() > new_feerate;
+                                               mut_request.set_feerate(new_feerate);
+                                       }
+                                       match claim {
+                                               OnchainClaim::Tx(tx) => {
+                                                       let log_start = if bumped_feerate { "Broadcasting RBF-bumped" } else { "Rebroadcasting" };
+                                                       log_info!(logger, "{} onchain {}", log_start, log_tx!(tx));
+                                                       broadcaster.broadcast_transaction(&tx);
+                                               },
+                                               #[cfg(anchors)]
+                                               OnchainClaim::Event(event) => {
+                                                       let log_start = if bumped_feerate { "Yielding fee-bumped" } else { "Replaying" };
+                                                       log_info!(logger, "{} onchain event to spend inputs {:?}", log_start,
+                                                               request.outpoints());
+                                                       #[cfg(debug_assertions)] {
+                                                               debug_assert!(request.requires_external_funding());
+                                                               let num_existing = self.pending_claim_events.iter()
+                                                                       .filter(|entry| entry.0 == package_id).count();
+                                                               assert!(num_existing == 0 || num_existing == 1);
+                                                       }
+                                                       self.pending_claim_events.retain(|event| event.0 != package_id);
+                                                       self.pending_claim_events.push((package_id, event));
+                                               }
+                                       }
+                               });
+               }
+       }
+
        /// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty
        /// onchain) lays on the assumption of claim transactions getting confirmed before timelock
        /// expiration (CSV or CLTV following cases). In case of high-fee spikes, claim tx may get stuck
@@ -489,9 +542,13 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
        ///
        /// Panics if there are signing errors, because signing operations in reaction to on-chain
        /// events are not expected to fail, and if they do, we may lose funds.
-       fn generate_claim<F: Deref, L: Deref>(&mut self, cur_height: u32, cached_request: &PackageTemplate, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u32, u64, OnchainClaim)>
-               where F::Target: FeeEstimator,
-                                       L::Target: Logger,
+       fn generate_claim<F: Deref, L: Deref>(
+               &mut self, cur_height: u32, cached_request: &PackageTemplate, force_feerate_bump: bool,
+               fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
+       ) -> Option<(u32, u64, OnchainClaim)>
+       where
+               F::Target: FeeEstimator,
+               L::Target: Logger,
        {
                let request_outpoints = cached_request.outpoints();
                if request_outpoints.is_empty() {
@@ -538,8 +595,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                        #[cfg(anchors)]
                        { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
                                if cached_request.requires_external_funding() {
-                                       let target_feerate_sat_per_1000_weight = cached_request
-                                               .compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority);
+                                       let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate(
+                                               fee_estimator, ConfirmationTarget::HighPriority, force_feerate_bump
+                                       );
                                        if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
                                                return Some((
                                                        new_timer,
@@ -558,7 +616,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
 
                        let predicted_weight = cached_request.package_weight(&self.destination_script);
                        if let Some((output_value, new_feerate)) = cached_request.compute_package_output(
-                               predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger,
+                               predicted_weight, self.destination_script.dust_value().to_sat(),
+                               force_feerate_bump, fee_estimator, logger,
                        ) {
                                assert!(new_feerate != 0);
 
@@ -601,7 +660,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                                                        // counterparty's latest commitment don't have any HTLCs present.
                                                        let conf_target = ConfirmationTarget::HighPriority;
                                                        let package_target_feerate_sat_per_1000_weight = cached_request
-                                                               .compute_package_feerate(fee_estimator, conf_target);
+                                                               .compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
                                                        Some((
                                                                new_timer,
                                                                package_target_feerate_sat_per_1000_weight as u64,
@@ -689,8 +748,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                        preprocessed_requests.push(req);
                }
 
-               // Claim everything up to and including cur_height + 1
-               let remaining_locked_packages = self.locktimed_packages.split_off(&(cur_height + 2));
+               // Claim everything up to and including `cur_height`
+               let remaining_locked_packages = self.locktimed_packages.split_off(&(cur_height + 1));
                for (pop_height, mut entry) in self.locktimed_packages.iter_mut() {
                        log_trace!(logger, "Restoring delayed claim of package(s) at their timelock at {}.", pop_height);
                        preprocessed_requests.append(&mut entry);
@@ -700,7 +759,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                // Generate claim transactions and track them to bump if necessary at
                // height timer expiration (i.e in how many blocks we're going to take action).
                for mut req in preprocessed_requests {
-                       if let Some((new_timer, new_feerate, claim)) = self.generate_claim(cur_height, &req, &*fee_estimator, &*logger) {
+                       if let Some((new_timer, new_feerate, claim)) = self.generate_claim(
+                               cur_height, &req, true /* force_feerate_bump */, &*fee_estimator, &*logger,
+                       ) {
                                req.set_timer(new_timer);
                                req.set_feerate(new_feerate);
                                let package_id = match claim {
@@ -893,7 +954,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                // Build, bump and rebroadcast tx accordingly
                log_trace!(logger, "Bumping {} candidates", bump_candidates.len());
                for (package_id, request) in bump_candidates.iter() {
-                       if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(cur_height, &request, &*fee_estimator, &*logger) {
+                       if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
+                               cur_height, &request, true /* force_feerate_bump */, &*fee_estimator, &*logger,
+                       ) {
                                match bump_claim {
                                        OnchainClaim::Tx(bump_tx) => {
                                                log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
@@ -973,7 +1036,11 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
                        }
                }
                for ((_package_id, _), ref mut request) in bump_candidates.iter_mut() {
-                       if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(height, &request, fee_estimator, &&*logger) {
+                       // `height` is the height being disconnected, so our `current_height` is 1 lower.
+                       let current_height = height - 1;
+                       if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
+                               current_height, &request, true /* force_feerate_bump */, fee_estimator, &&*logger
+                       ) {
                                request.set_timer(new_timer);
                                request.set_feerate(new_feerate);
                                match bump_claim {
index 786451ee50c834923f79e34bae478e7800f12c2b..7ea61dc244976c2257087b91ade479d5aa758832 100644 (file)
@@ -460,13 +460,13 @@ impl PackageSolvingData {
                }
        }
        fn absolute_tx_timelock(&self, current_height: u32) -> u32 {
-               // We use `current_height + 1` as our default locktime to discourage fee sniping and because
+               // We use `current_height` as our default locktime to discourage fee sniping and because
                // transactions with it always propagate.
                let absolute_timelock = match self {
-                       PackageSolvingData::RevokedOutput(_) => current_height + 1,
-                       PackageSolvingData::RevokedHTLCOutput(_) => current_height + 1,
-                       PackageSolvingData::CounterpartyOfferedHTLCOutput(_) => current_height + 1,
-                       PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => cmp::max(outp.htlc.cltv_expiry, current_height + 1),
+                       PackageSolvingData::RevokedOutput(_) => current_height,
+                       PackageSolvingData::RevokedHTLCOutput(_) => current_height,
+                       PackageSolvingData::CounterpartyOfferedHTLCOutput(_) => current_height,
+                       PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => cmp::max(outp.htlc.cltv_expiry, current_height),
                        // HTLC timeout/success transactions rely on a fixed timelock due to the counterparty's
                        // signature.
                        PackageSolvingData::HolderHTLCOutput(ref outp) => {
@@ -475,7 +475,7 @@ impl PackageSolvingData {
                                }
                                outp.cltv_expiry
                        },
-                       PackageSolvingData::HolderFundingOutput(_) => current_height + 1,
+                       PackageSolvingData::HolderFundingOutput(_) => current_height,
                };
                absolute_timelock
        }
@@ -554,6 +554,9 @@ impl PackageTemplate {
        pub(crate) fn aggregable(&self) -> bool {
                self.aggregable
        }
+       pub(crate) fn previous_feerate(&self) -> u64 {
+               self.feerate_previous
+       }
        pub(crate) fn set_feerate(&mut self, new_feerate: u64) {
                self.feerate_previous = new_feerate;
        }
@@ -762,16 +765,23 @@ impl PackageTemplate {
        /// Returns value in satoshis to be included as package outgoing output amount and feerate
        /// which was used to generate the value. Will not return less than `dust_limit_sats` for the
        /// value.
-       pub(crate) fn compute_package_output<F: Deref, L: Deref>(&self, predicted_weight: usize, dust_limit_sats: u64, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u64, u64)>
-               where F::Target: FeeEstimator,
-                     L::Target: Logger,
+       pub(crate) fn compute_package_output<F: Deref, L: Deref>(
+               &self, predicted_weight: usize, dust_limit_sats: u64, force_feerate_bump: bool,
+               fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
+       ) -> Option<(u64, u64)>
+       where
+               F::Target: FeeEstimator,
+               L::Target: Logger,
        {
                debug_assert!(self.malleability == PackageMalleability::Malleable, "The package output is fixed for non-malleable packages");
                let input_amounts = self.package_amount();
                assert!(dust_limit_sats as i64 > 0, "Output script must be broadcastable/have a 'real' dust limit.");
                // If old feerate is 0, first iteration of this claim, use normal fee calculation
                if self.feerate_previous != 0 {
-                       if let Some((new_fee, feerate)) = feerate_bump(predicted_weight, input_amounts, self.feerate_previous, fee_estimator, logger) {
+                       if let Some((new_fee, feerate)) = feerate_bump(
+                               predicted_weight, input_amounts, self.feerate_previous, force_feerate_bump,
+                               fee_estimator, logger,
+                       ) {
                                return Some((cmp::max(input_amounts as i64 - new_fee as i64, dust_limit_sats as i64) as u64, feerate));
                        }
                } else {
@@ -784,16 +794,19 @@ impl PackageTemplate {
 
        #[cfg(anchors)]
        /// Computes a feerate based on the given confirmation target. If a previous feerate was used,
-       /// and the new feerate is below it, we'll use a 25% increase of the previous feerate instead of
-       /// the new one.
+       /// the new feerate is below it, and `force_feerate_bump` is set, we'll use a 25% increase of
+       /// the previous feerate instead of the new feerate.
        pub(crate) fn compute_package_feerate<F: Deref>(
                &self, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
+               force_feerate_bump: bool,
        ) -> u32 where F::Target: FeeEstimator {
                let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
                if self.feerate_previous != 0 {
                        // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
                        if feerate_estimate as u64 > self.feerate_previous {
                                feerate_estimate
+                       } else if !force_feerate_bump {
+                               self.feerate_previous.try_into().unwrap_or(u32::max_value())
                        } else {
                                // ...else just increase the previous feerate by 25% (because that's a nice number)
                                (self.feerate_previous + (self.feerate_previous / 4)).try_into().unwrap_or(u32::max_value())
@@ -945,32 +958,47 @@ fn compute_fee_from_spent_amounts<F: Deref, L: Deref>(input_amounts: u64, predic
 
 /// Attempt to propose a bumping fee for a transaction from its spent output's values and predicted
 /// weight. If feerates proposed by the fee-estimator have been increasing since last fee-bumping
-/// attempt, use them. Otherwise, blindly bump the feerate by 25% of the previous feerate. We also
-/// verify that those bumping heuristics respect BIP125 rules 3) and 4) and if required adjust
-/// the new fee to meet the RBF policy requirement.
-fn feerate_bump<F: Deref, L: Deref>(predicted_weight: usize, input_amounts: u64, previous_feerate: u64, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u64, u64)>
-       where F::Target: FeeEstimator,
-             L::Target: Logger,
+/// attempt, use them. If `force_feerate_bump` is set, we bump the feerate by 25% of the previous
+/// feerate, or just use the previous feerate otherwise. If a feerate bump did happen, we also
+/// verify that those bumping heuristics respect BIP125 rules 3) and 4) and if required adjust the
+/// new fee to meet the RBF policy requirement.
+fn feerate_bump<F: Deref, L: Deref>(
+       predicted_weight: usize, input_amounts: u64, previous_feerate: u64, force_feerate_bump: bool,
+       fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
+) -> Option<(u64, u64)>
+where
+       F::Target: FeeEstimator,
+       L::Target: Logger,
 {
        // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
-       let new_fee = if let Some((new_fee, _)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, fee_estimator, logger) {
-               let updated_feerate = new_fee / (predicted_weight as u64 * 1000);
-               if updated_feerate > previous_feerate {
-                       new_fee
+       let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, fee_estimator, logger) {
+               if new_feerate > previous_feerate {
+                       (new_fee, new_feerate)
+               } else if !force_feerate_bump {
+                       let previous_fee = previous_feerate * (predicted_weight as u64) / 1000;
+                       (previous_fee, previous_feerate)
                } else {
                        // ...else just increase the previous feerate by 25% (because that's a nice number)
-                       let new_fee = previous_feerate * (predicted_weight as u64) / 750;
-                       if input_amounts <= new_fee {
+                       let bumped_feerate = previous_feerate + (previous_feerate / 4);
+                       let bumped_fee = bumped_feerate * (predicted_weight as u64) / 1000;
+                       if input_amounts <= bumped_fee {
                                log_warn!(logger, "Can't 25% bump new claiming tx, amount {} is too small", input_amounts);
                                return None;
                        }
-                       new_fee
+                       (bumped_fee, bumped_feerate)
                }
        } else {
                log_warn!(logger, "Can't new-estimation bump new claiming tx, amount {} is too small", input_amounts);
                return None;
        };
 
+       // Our feerates should never decrease. If it hasn't changed though, we just need to
+       // rebroadcast/re-sign the previous claim.
+       debug_assert!(new_feerate >= previous_feerate);
+       if new_feerate == previous_feerate {
+               return Some((new_fee, new_feerate));
+       }
+
        let previous_fee = previous_feerate * (predicted_weight as u64) / 1000;
        let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * (predicted_weight as u64) / 1000;
        // BIP 125 Opt-in Full Replace-by-Fee Signaling
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 3fa8fa5ba947080d52cb888f505f40b568adb2a1..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;
@@ -72,7 +72,7 @@ use core::{cmp, mem};
 use core::cell::RefCell;
 use crate::io::Read;
 use crate::sync::{Arc, Mutex, RwLock, RwLockReadGuard, FairRwLock, LockTestExt, LockHeldState};
-use core::sync::atomic::{AtomicUsize, Ordering};
+use core::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
 use core::time::Duration;
 use core::ops::Deref;
 
@@ -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]),
@@ -934,6 +934,8 @@ where
 
        /// See `ChannelManager` struct-level documentation for lock order requirements.
        pending_events: Mutex<Vec<events::Event>>,
+       /// A simple atomic flag to ensure only one task at a time can be processing events asynchronously.
+       pending_events_processor: AtomicBool,
        /// See `ChannelManager` struct-level documentation for lock order requirements.
        pending_background_events: Mutex<Vec<BackgroundEvent>>,
        /// Used when we have to take a BIG lock to make sure everything is self-consistent.
@@ -1696,30 +1698,47 @@ macro_rules! handle_new_monitor_update {
 
 macro_rules! process_events_body {
        ($self: expr, $event_to_handle: expr, $handle_event: expr) => {
-               // We'll acquire our total consistency lock until the returned future completes so that
-               // we can be sure no other persists happen while processing events.
-               let _read_guard = $self.total_consistency_lock.read().unwrap();
+               let mut processed_all_events = false;
+               while !processed_all_events {
+                       if $self.pending_events_processor.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
+                               return;
+                       }
 
-               let mut result = NotifyOption::SkipPersist;
+                       let mut result = NotifyOption::SkipPersist;
 
-               // TODO: This behavior should be documented. It's unintuitive that we query
-               // ChannelMonitors when clearing other events.
-               if $self.process_pending_monitor_events() {
-                       result = NotifyOption::DoPersist;
-               }
+                       {
+                               // We'll acquire our total consistency lock so that we can be sure no other
+                               // persists happen while processing monitor events.
+                               let _read_guard = $self.total_consistency_lock.read().unwrap();
+
+                               // TODO: This behavior should be documented. It's unintuitive that we query
+                               // ChannelMonitors when clearing other events.
+                               if $self.process_pending_monitor_events() {
+                                       result = NotifyOption::DoPersist;
+                               }
+                       }
 
-               let pending_events = mem::replace(&mut *$self.pending_events.lock().unwrap(), vec![]);
-               if !pending_events.is_empty() {
-                       result = NotifyOption::DoPersist;
-               }
+                       let pending_events = $self.pending_events.lock().unwrap().clone();
+                       let num_events = pending_events.len();
+                       if !pending_events.is_empty() {
+                               result = NotifyOption::DoPersist;
+                       }
 
-               for event in pending_events {
-                       $event_to_handle = event;
-                       $handle_event;
-               }
+                       for event in pending_events {
+                               $event_to_handle = event;
+                               $handle_event;
+                       }
 
-               if result == NotifyOption::DoPersist {
-                       $self.persistence_notifier.notify();
+                       {
+                               let mut pending_events = $self.pending_events.lock().unwrap();
+                               pending_events.drain(..num_events);
+                               processed_all_events = pending_events.is_empty();
+                               $self.pending_events_processor.store(false, Ordering::Release);
+                       }
+
+                       if result == NotifyOption::DoPersist {
+                               $self.persistence_notifier.notify();
+                       }
                }
        }
 }
@@ -1787,6 +1806,7 @@ where
                        per_peer_state: FairRwLock::new(HashMap::new()),
 
                        pending_events: Mutex::new(Vec::new()),
+                       pending_events_processor: AtomicBool::new(false),
                        pending_background_events: Mutex::new(Vec::new()),
                        total_consistency_lock: RwLock::new(()),
                        persistence_notifier: Notifier::new(),
@@ -2639,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");
 
@@ -2661,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()),
                        };
@@ -2712,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 })
@@ -2882,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))
        }
@@ -3006,10 +3026,11 @@ where
                }
                {
                        let height = self.best_block.read().unwrap().height();
-                       // Transactions are evaluated as final by network mempools at the next block. However, the modules
-                       // constituting our Lightning node might not have perfect sync about their blockchain views. Thus, if
-                       // the wallet module is in advance on the LDK view, allow one more block of headroom.
-                       if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && LockTime::from(funding_transaction.lock_time).is_block_height() && funding_transaction.lock_time.0 > height + 2 {
+                       // Transactions are evaluated as final by network mempools if their locktime is strictly
+                       // lower than the next block height. However, the modules constituting our Lightning
+                       // node might not have perfect sync about their blockchain views. Thus, if the wallet
+                       // module is ahead of LDK, only allow one more block of headroom.
+                       if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && LockTime::from(funding_transaction.lock_time).is_block_height() && funding_transaction.lock_time.0 > height + 1 {
                                return Err(APIError::APIMisuseError {
                                        err: "Funding transaction absolute timelock is non-final".to_owned()
                                });
@@ -7004,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 {
@@ -7052,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) => {
@@ -7720,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) {
@@ -7735,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(),
@@ -8026,6 +8050,7 @@ where
                        per_peer_state: FairRwLock::new(per_peer_state),
 
                        pending_events: Mutex::new(pending_events_read),
+                       pending_events_processor: AtomicBool::new(false),
                        pending_background_events: Mutex::new(pending_background_events),
                        total_consistency_lock: RwLock::new(()),
                        persistence_notifier: Notifier::new(),
@@ -8057,8 +8082,6 @@ mod tests {
        use bitcoin::hashes::Hash;
        use bitcoin::hashes::sha256::Hash as Sha256;
        use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
-       #[cfg(feature = "std")]
-       use core::time::Duration;
        use core::sync::atomic::Ordering;
        use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
        use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
@@ -8482,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))
@@ -9035,7 +9058,7 @@ pub mod bench {
                // calls per node.
                let network = bitcoin::Network::Testnet;
 
-               let tx_broadcaster = test_utils::TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new()), blocks: Arc::new(Mutex::new(Vec::new()))};
+               let tx_broadcaster = test_utils::TestBroadcaster::new(network);
                let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
                let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
                let scorer = Mutex::new(test_utils::TestScorer::new());
index 6d96877625ccb4c74d1b41c0d56ee1ab5c46c1d0..50aed2c5b4355283f77695708235c25f5d36d761 100644 (file)
@@ -30,7 +30,6 @@ use crate::util::config::UserConfig;
 use crate::util::ser::{ReadableArgs, Writeable};
 
 use bitcoin::blockdata::block::{Block, BlockHeader};
-use bitcoin::blockdata::constants::genesis_block;
 use bitcoin::blockdata::transaction::{Transaction, TxOut};
 use bitcoin::network::constants::Network;
 
@@ -151,6 +150,20 @@ impl ConnectStyle {
                }
        }
 
+       pub fn updates_best_block_first(&self) -> bool {
+               match self {
+                       ConnectStyle::BestBlockFirst => true,
+                       ConnectStyle::BestBlockFirstSkippingBlocks => true,
+                       ConnectStyle::BestBlockFirstReorgsOnlyTip => true,
+                       ConnectStyle::TransactionsFirst => false,
+                       ConnectStyle::TransactionsFirstSkippingBlocks => false,
+                       ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks => false,
+                       ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks => false,
+                       ConnectStyle::TransactionsFirstReorgsOnlyTip => false,
+                       ConnectStyle::FullBlockViaListen => false,
+               }
+       }
+
        fn random_style() -> ConnectStyle {
                #[cfg(feature = "std")] {
                        use core::hash::{BuildHasher, Hasher};
@@ -216,6 +229,9 @@ fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, sk
        #[cfg(feature = "std")] {
                eprintln!("Connecting block using Block Connection Style: {:?}", *node.connect_style.borrow());
        }
+       // Update the block internally before handing it over to LDK, to ensure our assertions regarding
+       // transaction broadcast are correct.
+       node.blocks.lock().unwrap().push((block.clone(), height));
        if !skip_intermediaries {
                let txdata: Vec<_> = block.txdata.iter().enumerate().collect();
                match *node.connect_style.borrow() {
@@ -265,7 +281,6 @@ fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, sk
        }
        call_claimable_balances(node);
        node.node.test_process_background_events();
-       node.blocks.lock().unwrap().push((block, height));
 }
 
 pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
@@ -2260,8 +2275,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());
        }
 
@@ -2281,8 +2296,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());
        }
 
@@ -2388,7 +2403,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()
                                },
@@ -2421,10 +2436,7 @@ pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &
 pub fn create_chanmon_cfgs(node_count: usize) -> Vec<TestChanMonCfg> {
        let mut chan_mon_cfgs = Vec::new();
        for i in 0..node_count {
-               let tx_broadcaster = test_utils::TestBroadcaster {
-                       txn_broadcasted: Mutex::new(Vec::new()),
-                       blocks: Arc::new(Mutex::new(vec![(genesis_block(Network::Testnet), 0)])),
-               };
+               let tx_broadcaster = test_utils::TestBroadcaster::new(Network::Testnet);
                let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
                let chain_source = test_utils::TestChainSource::new(Network::Testnet);
                let logger = test_utils::TestLogger::with_id(format!("node {}", i));
index 401667f31f6dc80611fb3ef7e8422ff8f0994fbf..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);
@@ -1284,7 +1284,7 @@ fn test_duplicate_htlc_different_direction_onchain() {
        mine_transaction(&nodes[0], &remote_txn[0]);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
 
        let claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
        assert_eq!(claim_txn.len(), 3);
@@ -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)
@@ -2438,7 +2438,7 @@ fn test_justice_tx_htlc_timeout() {
                test_txn_broadcast(&nodes[1], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::NONE);
 
                mine_transaction(&nodes[0], &revoked_local_txn[0]);
-               connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+               connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
                // Verify broadcast of revoked HTLC-timeout
                let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT);
                check_added_monitors!(nodes[0], 1);
@@ -2765,7 +2765,7 @@ fn test_htlc_on_chain_success() {
        // Verify that B's ChannelManager is able to extract preimage from HTLC Success tx and pass it backward
        let header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[1].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42};
        connect_block(&nodes[1], &Block { header, txdata: vec![commitment_tx[0].clone(), node_txn[0].clone(), node_txn[1].clone()]});
-       connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
        {
                let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
                assert_eq!(added_monitors.len(), 1);
@@ -2894,7 +2894,7 @@ fn test_htlc_on_chain_success() {
        assert_eq!(commitment_spend.input.len(), 2);
        assert_eq!(commitment_spend.input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
        assert_eq!(commitment_spend.input[1].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-       assert_eq!(commitment_spend.lock_time.0, nodes[1].best_block_info().1 + 1);
+       assert_eq!(commitment_spend.lock_time.0, nodes[1].best_block_info().1);
        assert!(commitment_spend.output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
        // We don't bother to check that B can claim the HTLC output on its commitment tx here as
        // we already checked the same situation with A.
@@ -2902,7 +2902,7 @@ fn test_htlc_on_chain_success() {
        // Verify that A's ChannelManager is able to extract preimage from preimage tx and generate PaymentSent
        let mut header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[0].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42};
        connect_block(&nodes[0], &Block { header, txdata: vec![node_a_commitment_tx[0].clone(), commitment_spend.clone()] });
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV + MIN_CLTV_EXPIRY_DELTA as u32 - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV + MIN_CLTV_EXPIRY_DELTA as u32); // Confirm blocks until the HTLC expires
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        let events = nodes[0].node.get_and_clear_pending_events();
@@ -2991,9 +2991,9 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
                if nodes[1].connect_style.borrow().skips_blocks() {
                        assert_eq!(txn.len(), 1);
                } else {
-                       assert_eq!(txn.len(), 2); // Extra rebroadcast of timeout transaction
+                       assert_eq!(txn.len(), 3); // Two extra fee bumps for timeout transaction
                }
-               check_spends!(txn[0], commitment_tx[0]);
+               txn.iter().for_each(|tx| check_spends!(tx, commitment_tx[0]));
                assert_eq!(txn[0].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                txn.remove(0)
        };
@@ -3024,7 +3024,7 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
        check_spends!(commitment_tx[0], chan_1.3);
 
        mine_transaction(&nodes[0], &commitment_tx[0]);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV + MIN_CLTV_EXPIRY_DELTA as u32 - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV + MIN_CLTV_EXPIRY_DELTA as u32); // Confirm blocks until the HTLC expires
 
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
@@ -4446,7 +4446,7 @@ fn test_static_spendable_outputs_timeout_tx() {
                MessageSendEvent::BroadcastChannelUpdate { .. } => {},
                _ => panic!("Unexpected event"),
        }
-       connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
 
        // Check B's monitor was able to send back output descriptor event for timeout tx on A's commitment tx
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
@@ -4524,7 +4524,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
 
        let revoked_htlc_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
        assert_eq!(revoked_htlc_txn.len(), 1);
@@ -4756,7 +4756,7 @@ fn test_onchain_to_onchain_claim() {
        check_spends!(b_txn[0], commitment_tx[0]);
        assert_eq!(b_txn[0].input[0].witness.clone().last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
        assert!(b_txn[0].output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
-       assert_eq!(b_txn[0].lock_time.0, nodes[1].best_block_info().1 + 1); // Success tx
+       assert_eq!(b_txn[0].lock_time.0, nodes[1].best_block_info().1); // Success tx
 
        check_closed_broadcast!(nodes[1], true);
        check_added_monitors!(nodes[1], 1);
@@ -4807,7 +4807,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() {
        check_closed_broadcast!(nodes[1], true);
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[1], TEST_FINAL_CLTV - 40 + MIN_CLTV_EXPIRY_DELTA as u32 - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[1], TEST_FINAL_CLTV - 40 + MIN_CLTV_EXPIRY_DELTA as u32); // Confirm blocks until the HTLC expires
 
        let htlc_timeout_tx;
        { // Extract one of the two HTLC-Timeout transaction
@@ -5287,7 +5287,7 @@ fn test_dynamic_spendable_outputs_local_htlc_timeout_tx() {
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
 
        let htlc_timeout = {
                let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
@@ -5370,7 +5370,7 @@ fn test_key_derivation_params() {
 
        // Timeout HTLC on A's chain and so it can generate a HTLC-Timeout tx
        mine_transaction(&nodes[0], &local_txn_1[0]);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
@@ -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 },
@@ -6927,7 +6927,7 @@ fn do_test_sweep_outbound_htlc_failure_update(revoked: bool, local: bool) {
                check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
                assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
 
-               connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+               connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
                timeout_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().drain(..)
                        .filter(|tx| tx.input[0].previous_output.txid == bs_commitment_tx[0].txid()).collect();
                check_spends!(timeout_tx[0], bs_commitment_tx[0]);
@@ -6938,7 +6938,7 @@ fn do_test_sweep_outbound_htlc_failure_update(revoked: bool, local: bool) {
                if !revoked {
                        assert_eq!(timeout_tx[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                } else {
-                       assert_eq!(timeout_tx[0].lock_time.0, 12);
+                       assert_eq!(timeout_tx[0].lock_time.0, 11);
                }
                // We fail non-dust-HTLC 2 by broadcast of local timeout/revocation-claim tx
                mine_transaction(&nodes[0], &timeout_tx[0]);
@@ -7318,7 +7318,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
        check_closed_broadcast!(nodes[1], true);
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[1], 49); // Confirm blocks until the HTLC expires (note CLTV was explicitly 50 above)
+       connect_blocks(&nodes[1], 50); // Confirm blocks until the HTLC expires (note CLTV was explicitly 50 above)
 
        let revoked_htlc_txn = {
                let txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
@@ -7464,7 +7464,7 @@ fn test_bump_penalty_txn_on_remote_commitment() {
        expect_payment_claimed!(nodes[1], payment_hash, 3_000_000);
        mine_transaction(&nodes[1], &remote_txn[0]);
        check_added_monitors!(nodes[1], 2);
-       connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+       connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
 
        // One or more claim tx should have been broadcast, check it
        let timeout;
@@ -7510,7 +7510,7 @@ fn test_bump_penalty_txn_on_remote_commitment() {
        assert_ne!(feerate_preimage, 0);
 
        // After exhaustion of height timer, new bumped claim txn should have been broadcast, check it
-       connect_blocks(&nodes[1], 15);
+       connect_blocks(&nodes[1], 1);
        {
                let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
                assert_eq!(node_txn.len(), 1);
@@ -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);
 }
@@ -8434,7 +8434,7 @@ fn test_update_err_monitor_lockdown() {
        let block = Block { header, txdata: vec![] };
        // Make the tx_broadcaster aware of enough blocks that it doesn't think we're violating
        // transaction lock time requirements here.
-       chanmon_cfgs[0].tx_broadcaster.blocks.lock().unwrap().resize(200, (block.clone(), 0));
+       chanmon_cfgs[0].tx_broadcaster.blocks.lock().unwrap().resize(200, (block.clone(), 200));
        watchtower.chain_monitor.block_connected(&block, 200);
 
        // Try to update ChannelMonitor
@@ -8486,6 +8486,9 @@ fn test_concurrent_monitor_claim() {
        let chain_source = test_utils::TestChainSource::new(Network::Testnet);
        let logger = test_utils::TestLogger::with_id(format!("node {}", "Alice"));
        let persister = test_utils::TestPersister::new();
+       let alice_broadcaster = test_utils::TestBroadcaster::with_blocks(
+               Arc::new(Mutex::new(nodes[0].blocks.lock().unwrap().clone())),
+       );
        let watchtower_alice = {
                let new_monitor = {
                        let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
@@ -8494,20 +8497,21 @@ fn test_concurrent_monitor_claim() {
                        assert!(new_monitor == *monitor);
                        new_monitor
                };
-               let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &chanmon_cfgs[0].tx_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
+               let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &alice_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
                assert_eq!(watchtower.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
                watchtower
        };
        let header = BlockHeader { version: 0x20000000, prev_blockhash: BlockHash::all_zeros(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
        let block = Block { header, txdata: vec![] };
-       // Make the tx_broadcaster aware of enough blocks that it doesn't think we're violating
-       // transaction lock time requirements here.
-       chanmon_cfgs[0].tx_broadcaster.blocks.lock().unwrap().resize((CHAN_CONFIRM_DEPTH + 1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS) as usize, (block.clone(), 0));
-       watchtower_alice.chain_monitor.block_connected(&block, CHAN_CONFIRM_DEPTH + 1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS);
+       // Make Alice aware of enough blocks that it doesn't think we're violating transaction lock time
+       // requirements here.
+       const HTLC_TIMEOUT_BROADCAST: u32 = CHAN_CONFIRM_DEPTH + 1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS;
+       alice_broadcaster.blocks.lock().unwrap().resize((HTLC_TIMEOUT_BROADCAST) as usize, (block.clone(), HTLC_TIMEOUT_BROADCAST));
+       watchtower_alice.chain_monitor.block_connected(&block, HTLC_TIMEOUT_BROADCAST);
 
        // Watchtower Alice should have broadcast a commitment/HTLC-timeout
        let alice_state = {
-               let mut txn = chanmon_cfgs[0].tx_broadcaster.txn_broadcast();
+               let mut txn = alice_broadcaster.txn_broadcast();
                assert_eq!(txn.len(), 2);
                txn.remove(0)
        };
@@ -8516,6 +8520,7 @@ fn test_concurrent_monitor_claim() {
        let chain_source = test_utils::TestChainSource::new(Network::Testnet);
        let logger = test_utils::TestLogger::with_id(format!("node {}", "Bob"));
        let persister = test_utils::TestPersister::new();
+       let bob_broadcaster = test_utils::TestBroadcaster::with_blocks(Arc::clone(&alice_broadcaster.blocks));
        let watchtower_bob = {
                let new_monitor = {
                        let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
@@ -8524,12 +8529,12 @@ fn test_concurrent_monitor_claim() {
                        assert!(new_monitor == *monitor);
                        new_monitor
                };
-               let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &chanmon_cfgs[0].tx_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
+               let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &bob_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
                assert_eq!(watchtower.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
                watchtower
        };
        let header = BlockHeader { version: 0x20000000, prev_blockhash: BlockHash::all_zeros(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
-       watchtower_bob.chain_monitor.block_connected(&Block { header, txdata: vec![] }, CHAN_CONFIRM_DEPTH + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS);
+       watchtower_bob.chain_monitor.block_connected(&Block { header, txdata: vec![] }, HTLC_TIMEOUT_BROADCAST - 1);
 
        // Route another payment to generate another update with still previous HTLC pending
        let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], 3000000);
@@ -8556,21 +8561,26 @@ fn test_concurrent_monitor_claim() {
 
        //// Provide one more block to watchtower Bob, expect broadcast of commitment and HTLC-Timeout
        let header = BlockHeader { version: 0x20000000, prev_blockhash: BlockHash::all_zeros(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
-       watchtower_bob.chain_monitor.block_connected(&Block { header, txdata: vec![] }, CHAN_CONFIRM_DEPTH + 1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS);
+       watchtower_bob.chain_monitor.block_connected(&Block { header, txdata: vec![] }, HTLC_TIMEOUT_BROADCAST);
 
        // Watchtower Bob should have broadcast a commitment/HTLC-timeout
        let bob_state_y;
        {
-               let mut txn = chanmon_cfgs[0].tx_broadcaster.txn_broadcast();
+               let mut txn = bob_broadcaster.txn_broadcast();
                assert_eq!(txn.len(), 2);
                bob_state_y = txn.remove(0);
        };
 
        // We confirm Bob's state Y on Alice, she should broadcast a HTLC-timeout
        let header = BlockHeader { version: 0x20000000, prev_blockhash: BlockHash::all_zeros(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
-       watchtower_alice.chain_monitor.block_connected(&Block { header, txdata: vec![bob_state_y.clone()] }, CHAN_CONFIRM_DEPTH + 2 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS);
+       let height = HTLC_TIMEOUT_BROADCAST + 1;
+       connect_blocks(&nodes[0], height - nodes[0].best_block_info().1);
+       check_closed_broadcast(&nodes[0], 1, true);
+       check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
+       watchtower_alice.chain_monitor.block_connected(&Block { header, txdata: vec![bob_state_y.clone()] }, height);
+       check_added_monitors(&nodes[0], 1);
        {
-               let htlc_txn = chanmon_cfgs[0].tx_broadcaster.txn_broadcast();
+               let htlc_txn = alice_broadcaster.txn_broadcast();
                assert_eq!(htlc_txn.len(), 2);
                check_spends!(htlc_txn[0], bob_state_y);
                // Alice doesn't clean up the old HTLC claim since it hasn't seen a conflicting spend for
@@ -8650,7 +8660,7 @@ fn test_htlc_no_detection() {
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV);
 
        let htlc_timeout = {
                let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
@@ -9404,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 }
        });
 
@@ -9580,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 }
        });
 
@@ -9826,8 +9836,8 @@ fn test_non_final_funding_tx() {
        assert_eq!(events.len(), 1);
        let mut tx = match events[0] {
                Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => {
-                       // Timelock the transaction _beyond_ the best client height + 2.
-                       Transaction { version: chan_id as i32, lock_time: PackedLockTime(best_height + 3), input: vec![input], output: vec![TxOut {
+                       // Timelock the transaction _beyond_ the best client height + 1.
+                       Transaction { version: chan_id as i32, lock_time: PackedLockTime(best_height + 2), input: vec![input], output: vec![TxOut {
                                value: *channel_value_satoshis, script_pubkey: output_script.clone(),
                        }]}
                },
@@ -9841,7 +9851,7 @@ fn test_non_final_funding_tx() {
                _ => panic!()
        }
 
-       // However, transaction should be accepted if it's in a +2 headroom from best block.
+       // However, transaction should be accepted if it's in a +1 headroom from best block.
        tx.lock_time = PackedLockTime(tx.lock_time.0 - 1);
        assert!(nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
        get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
index d09f4229c8cd292a149e670071ecc6e701c1c6b6..b20a2e57526b6f3a50596477a5097496172f57a9 100644 (file)
@@ -497,7 +497,7 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
                nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
 
        // When the HTLC timeout output is spendable in the next block, A should broadcast it
-       connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1 - 1);
+       connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1);
        let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
        assert_eq!(a_broadcast_txn.len(), 2);
        assert_eq!(a_broadcast_txn[0].input.len(), 1);
@@ -887,7 +887,7 @@ fn test_no_preimage_inbound_htlc_balances() {
        // HTLC has been spent, even after the HTLC expires. We'll also fail the inbound HTLC, but it
        // won't do anything as the channel is already closed.
 
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV);
        let as_htlc_timeout_claim = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
        assert_eq!(as_htlc_timeout_claim.len(), 1);
        check_spends!(as_htlc_timeout_claim[0], as_txn[0]);
@@ -908,7 +908,7 @@ fn test_no_preimage_inbound_htlc_balances() {
 
        // The next few blocks for B look the same as for A, though for the opposite HTLC
        nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
-       connect_blocks(&nodes[1], TEST_FINAL_CLTV - (ANTI_REORG_DELAY - 1) - 1);
+       connect_blocks(&nodes[1], TEST_FINAL_CLTV - (ANTI_REORG_DELAY - 1));
        expect_pending_htlcs_forwardable_conditions!(nodes[1],
                [HTLCDestination::FailedPayment { payment_hash: to_b_failed_payment_hash }]);
        let bs_htlc_timeout_claim = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
@@ -1734,7 +1734,7 @@ fn do_test_restored_packages_retry(check_old_monitor_retries_after_upgrade: bool
        mine_transaction(&nodes[0], &commitment_tx);
 
        // Connect blocks until the HTLC's expiration is met, expecting a transaction broadcast.
-       connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
+       connect_blocks(&nodes[0], TEST_FINAL_CLTV);
        let htlc_timeout_tx = {
                let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
                assert_eq!(txn.len(), 1);
@@ -1778,6 +1778,157 @@ fn test_restored_packages_retry() {
        do_test_restored_packages_retry(true);
 }
 
+fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
+       // Test that we will retry broadcasting pending claims for a force-closed channel on every
+       // `ChainMonitor::rebroadcast_pending_claims` call.
+       if anchors {
+               assert!(cfg!(anchors));
+       }
+       let mut chanmon_cfgs = create_chanmon_cfgs(2);
+       let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+       let mut config = test_default_channel_config();
+       if anchors {
+               #[cfg(anchors)] {
+                       config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+               }
+       }
+       let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
+       let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+       let (_, _, _, chan_id, funding_tx) = create_chan_between_nodes_with_value(
+               &nodes[0], &nodes[1], 1_000_000, 500_000_000
+       );
+       const HTLC_AMT_MSAT: u64 = 1_000_000;
+       const HTLC_AMT_SAT: u64 = HTLC_AMT_MSAT / 1000;
+       route_payment(&nodes[0], &[&nodes[1]], HTLC_AMT_MSAT);
+
+       let htlc_expiry = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1;
+
+       let commitment_txn = get_local_commitment_txn!(&nodes[0], &chan_id);
+       assert_eq!(commitment_txn.len(), if anchors { 1 /* commitment tx only */} else { 2 /* commitment and htlc timeout tx */ });
+       check_spends!(&commitment_txn[0], &funding_tx);
+       mine_transaction(&nodes[0], &commitment_txn[0]);
+       check_closed_broadcast!(&nodes[0], true);
+       check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
+       check_added_monitors(&nodes[0], 1);
+
+       // Set up a helper closure we'll use throughout our test. We should only expect retries without
+       // bumps if fees have not increased after a block has been connected (assuming the height timer
+       // re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called.
+       let mut prev_htlc_tx_feerate = None;
+       let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> {
+               let (htlc_tx, htlc_tx_feerate) = if anchors {
+                       assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
+                       let mut events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+                       assert_eq!(events.len(), if should_retry { 1 } else { 0 });
+                       if !should_retry {
+                               return None;
+                       }
+                       #[allow(unused_assignments)]
+                       let mut tx = Transaction {
+                               version: 2,
+                               lock_time: bitcoin::PackedLockTime::ZERO,
+                               input: vec![],
+                               output: vec![],
+                       };
+                       #[allow(unused_assignments)]
+                       let mut feerate = 0;
+                       #[cfg(anchors)] {
+                               feerate = if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution {
+                                       target_feerate_sat_per_1000_weight, mut htlc_descriptors, tx_lock_time,
+                               }) = events.pop().unwrap() {
+                                       let secp = Secp256k1::new();
+                                       assert_eq!(htlc_descriptors.len(), 1);
+                                       let descriptor = htlc_descriptors.pop().unwrap();
+                                       assert_eq!(descriptor.commitment_txid, commitment_txn[0].txid());
+                                       let htlc_output_idx = descriptor.htlc.transaction_output_index.unwrap() as usize;
+                                       assert!(htlc_output_idx < commitment_txn[0].output.len());
+                                       tx.lock_time = tx_lock_time;
+                                       // Note that we don't care about actually making the HTLC transaction meet the
+                                       // feerate for the test, we just want to make sure the feerates we receive from
+                                       // the events never decrease.
+                                       tx.input.push(descriptor.unsigned_tx_input());
+                                       let signer = nodes[0].keys_manager.derive_channel_keys(
+                                               descriptor.channel_value_satoshis, &descriptor.channel_keys_id,
+                                       );
+                                       let per_commitment_point = signer.get_per_commitment_point(
+                                               descriptor.per_commitment_number, &secp
+                                       );
+                                       tx.output.push(descriptor.tx_output(&per_commitment_point, &secp));
+                                       let our_sig = signer.sign_holder_htlc_transaction(&mut tx, 0, &descriptor, &secp).unwrap();
+                                       let witness_script = descriptor.witness_script(&per_commitment_point, &secp);
+                                       tx.input[0].witness = descriptor.tx_input_witness(&our_sig, &witness_script);
+                                       target_feerate_sat_per_1000_weight as u64
+                               } else { panic!("unexpected event"); };
+                       }
+                       (tx, feerate)
+               } else {
+                       assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
+                       let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
+                       assert_eq!(txn.len(), if should_retry { 1 } else { 0 });
+                       if !should_retry {
+                               return None;
+                       }
+                       let htlc_tx = txn.pop().unwrap();
+                       check_spends!(htlc_tx, commitment_txn[0]);
+                       let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value;
+                       let htlc_tx_feerate = htlc_tx_fee * 1000 / htlc_tx.weight() as u64;
+                       (htlc_tx, htlc_tx_feerate)
+               };
+               if should_bump {
+                       assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap());
+               } else if let Some(prev_feerate) = prev_htlc_tx_feerate.take() {
+                       assert_eq!(htlc_tx_feerate, prev_feerate);
+               }
+               prev_htlc_tx_feerate = Some(htlc_tx_feerate);
+               Some(htlc_tx)
+       };
+
+       // Connect blocks up to one before the HTLC expires. This should not result in a claim/retry.
+       connect_blocks(&nodes[0], htlc_expiry - nodes[0].best_block_info().1 - 1);
+       check_htlc_retry(false, false);
+
+       // Connect one more block, producing our first claim.
+       connect_blocks(&nodes[0], 1);
+       check_htlc_retry(true, false);
+
+       // Connect one more block, expecting a retry with a fee bump. Unfortunately, we cannot bump HTLC
+       // transactions pre-anchors.
+       connect_blocks(&nodes[0], 1);
+       check_htlc_retry(true, anchors);
+
+       // Trigger a call and we should have another retry, but without a bump.
+       nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
+       check_htlc_retry(true, false);
+
+       // Double the feerate and trigger a call, expecting a fee-bumped retry.
+       *nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
+       nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
+       check_htlc_retry(true, anchors);
+
+       // Connect one more block, expecting a retry with a fee bump. Unfortunately, we cannot bump HTLC
+       // transactions pre-anchors.
+       connect_blocks(&nodes[0], 1);
+       let htlc_tx = check_htlc_retry(true, anchors).unwrap();
+
+       // Mine the HTLC transaction to ensure we don't retry claims while they're confirmed.
+       mine_transaction(&nodes[0], &htlc_tx);
+       // If we have a `ConnectStyle` that advertises the new block first without the transasctions,
+       // we'll receive an extra bumped claim.
+       if nodes[0].connect_style.borrow().updates_best_block_first() {
+               check_htlc_retry(true, anchors);
+       }
+       nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
+       check_htlc_retry(false, false);
+}
+
+#[test]
+fn test_monitor_timer_based_claim() {
+       do_test_monitor_rebroadcast_pending_claims(false);
+       #[cfg(anchors)]
+       do_test_monitor_rebroadcast_pending_claims(true);
+}
+
 #[cfg(anchors)]
 #[test]
 fn test_yield_anchors_events() {
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..ebcf83bd90dc2d0db68c5300e50cf2f6b06dd718 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());
@@ -493,21 +493,28 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
                                                                        } else {
                                                                                log_trace!(logger, "Failure provided features a channel update without type prefix. Deprecated, but allowing for now.");
                                                                        }
-                                                                       if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice)) {
+                                                                       let update_opt = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice));
+                                                                       if update_opt.is_ok() || update_slice.is_empty() {
                                                                                // if channel_update should NOT have caused the failure:
                                                                                // MAY treat the channel_update as invalid.
                                                                                let is_chan_update_invalid = match error_code & 0xff {
                                                                                        7 => false,
-                                                                                       11 => amt_to_forward > chan_update.contents.htlc_minimum_msat,
-                                                                                       12 => amt_to_forward
-                                                                                               .checked_mul(chan_update.contents.fee_proportional_millionths as u64)
+                                                                                       11 => update_opt.is_ok() &&
+                                                                                               amt_to_forward >
+                                                                                                       update_opt.as_ref().unwrap().contents.htlc_minimum_msat,
+                                                                                       12 => update_opt.is_ok() && amt_to_forward
+                                                                                               .checked_mul(update_opt.as_ref().unwrap()
+                                                                                                       .contents.fee_proportional_millionths as u64)
                                                                                                .map(|prop_fee| prop_fee / 1_000_000)
-                                                                                               .and_then(|prop_fee| prop_fee.checked_add(chan_update.contents.fee_base_msat as u64))
+                                                                                               .and_then(|prop_fee| prop_fee.checked_add(
+                                                                                                       update_opt.as_ref().unwrap().contents.fee_base_msat as u64))
                                                                                                .map(|fee_msats| route_hop.fee_msat >= fee_msats)
                                                                                                .unwrap_or(false),
-                                                                                       13 => route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta,
+                                                                                       13 => update_opt.is_ok() &&
+                                                                                               route_hop.cltv_expiry_delta as u16 >=
+                                                                                                       update_opt.as_ref().unwrap().contents.cltv_expiry_delta,
                                                                                        14 => false, // expiry_too_soon; always valid?
-                                                                                       20 => chan_update.contents.flags & 2 == 0,
+                                                                                       20 => update_opt.as_ref().unwrap().contents.flags & 2 == 0,
                                                                                        _ => false, // unknown error code; take channel_update as valid
                                                                                };
                                                                                if is_chan_update_invalid {
@@ -518,17 +525,31 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
                                                                                                is_permanent: true,
                                                                                        });
                                                                                } else {
-                                                                                       // Make sure the ChannelUpdate contains the expected
-                                                                                       // short channel id.
-                                                                                       if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id {
-                                                                                               short_channel_id = Some(failing_route_hop.short_channel_id);
+                                                                                       if let Ok(chan_update) = update_opt {
+                                                                                               // Make sure the ChannelUpdate contains the expected
+                                                                                               // short channel id.
+                                                                                               if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id {
+                                                                                                       short_channel_id = Some(failing_route_hop.short_channel_id);
+                                                                                               } else {
+                                                                                                       log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring.");
+                                                                                               }
+                                                                                               network_update = Some(NetworkUpdate::ChannelUpdateMessage {
+                                                                                                       msg: chan_update,
+                                                                                               })
                                                                                        } else {
-                                                                                               log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring.");
+                                                                                               network_update = Some(NetworkUpdate::ChannelFailure {
+                                                                                                       short_channel_id: route_hop.short_channel_id,
+                                                                                                       is_permanent: false,
+                                                                                               });
                                                                                        }
-                                                                                       network_update = Some(NetworkUpdate::ChannelUpdateMessage {
-                                                                                               msg: chan_update,
-                                                                                       })
                                                                                };
+                                                                       } else {
+                                                                               // If the channel_update had a non-zero length (i.e. was
+                                                                               // present) but we couldn't read it, treat it as a total
+                                                                               // node failure.
+                                                                               log_info!(logger,
+                                                                                       "Failed to read a channel_update of len {} in an onion",
+                                                                                       update_slice.len());
                                                                        }
                                                                }
                                                        }
@@ -726,7 +747,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 +904,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 +924,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 +950,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 51811ec632943efb373031bc87eacab3feecfb69..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]
@@ -562,8 +562,8 @@ fn do_test_completed_payment_not_retryable_on_reload(use_dust: bool) {
        mine_transaction(&nodes[0], &bs_commitment_tx[0]);
        mine_transaction(&nodes[1], &bs_commitment_tx[0]);
        if !use_dust {
-               connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1 + (MIN_CLTV_EXPIRY_DELTA as u32));
-               connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1 + (MIN_CLTV_EXPIRY_DELTA as u32));
+               connect_blocks(&nodes[0], TEST_FINAL_CLTV + (MIN_CLTV_EXPIRY_DELTA as u32));
+               connect_blocks(&nodes[1], TEST_FINAL_CLTV + (MIN_CLTV_EXPIRY_DELTA as u32));
                let as_htlc_timeout = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
                check_spends!(as_htlc_timeout[0], bs_commitment_tx[0]);
                assert_eq!(as_htlc_timeout.len(), 1);
@@ -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 f7342671afea7e801632ee22bcf74e5da3ce8263..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;
@@ -948,7 +948,7 @@ fn do_forwarded_payment_no_manager_persistence(use_cs_commitment: bool, claim_ht
                if claim_htlc {
                        confirm_transaction(&nodes[1], &cs_commitment_tx[1]);
                } else {
-                       connect_blocks(&nodes[1], htlc_expiry - nodes[1].best_block_info().1);
+                       connect_blocks(&nodes[1], htlc_expiry - nodes[1].best_block_info().1 + 1);
                        let bs_htlc_timeout_tx = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
                        assert_eq!(bs_htlc_timeout_tx.len(), 1);
                        confirm_transaction(&nodes[1], &bs_htlc_timeout_tx[0]);
index 14b06457fc3f66b497ba86c09ca8409fb00dcd0e..e8f0c1259437142a31f3f3481e788fe031786ba9 100644 (file)
@@ -103,7 +103,7 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) {
 
                // Give node 1 node 2's commitment transaction and get its response (timing the HTLC out)
                mine_transaction(&nodes[1], &node_2_commitment_txn[0]);
-               connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
+               connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
                let node_1_commitment_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
                assert_eq!(node_1_commitment_txn.len(), 1); // ChannelMonitor: 1 offered HTLC-Timeout
                check_spends!(node_1_commitment_txn[0], node_2_commitment_txn[0]);
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 59268b840cd4c411b7a1545905bf17a4b6a24be0..e5f5e63c93893b114137c888b941519e28e5488f 100644 (file)
@@ -212,7 +212,7 @@ pub enum NetworkUpdate {
                msg: ChannelUpdate,
        },
        /// An error indicating that a channel failed to route a payment, which should be applied via
-       /// [`NetworkGraph::channel_failed`].
+       /// [`NetworkGraph::channel_failed_permanent`] if permanent.
        ChannelFailure {
                /// The short channel id of the closed channel.
                short_channel_id: u64,
@@ -352,9 +352,10 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
                                let _ = self.update_channel(msg);
                        },
                        NetworkUpdate::ChannelFailure { short_channel_id, is_permanent } => {
-                               let action = if is_permanent { "Removing" } else { "Disabling" };
-                               log_debug!(self.logger, "{} channel graph entry for {} due to a payment failure.", action, short_channel_id);
-                               self.channel_failed(short_channel_id, is_permanent);
+                               if is_permanent {
+                                       log_debug!(self.logger, "Removing channel graph entry for {} due to a payment failure.", short_channel_id);
+                                       self.channel_failed_permanent(short_channel_id);
+                               }
                        },
                        NetworkUpdate::NodeFailure { ref node_id, is_permanent } => {
                                if is_permanent {
@@ -1632,40 +1633,27 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
                Ok(())
        }
 
-       /// Marks a channel in the graph as failed if a corresponding HTLC fail was sent.
-       /// If permanent, removes a channel from the local storage.
-       /// May cause the removal of nodes too, if this was their last channel.
-       /// If not permanent, makes channels unavailable for routing.
-       pub fn channel_failed(&self, short_channel_id: u64, is_permanent: bool) {
+       /// Marks a channel in the graph as failed permanently.
+       ///
+       /// The channel and any node for which this was their last channel are removed from the graph.
+       pub fn channel_failed_permanent(&self, short_channel_id: u64) {
                #[cfg(feature = "std")]
                let current_time_unix = Some(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs());
                #[cfg(not(feature = "std"))]
                let current_time_unix = None;
 
-               self.channel_failed_with_time(short_channel_id, is_permanent, current_time_unix)
+               self.channel_failed_permanent_with_time(short_channel_id, current_time_unix)
        }
 
-       /// Marks a channel in the graph as failed if a corresponding HTLC fail was sent.
-       /// If permanent, removes a channel from the local storage.
-       /// May cause the removal of nodes too, if this was their last channel.
-       /// If not permanent, makes channels unavailable for routing.
-       fn channel_failed_with_time(&self, short_channel_id: u64, is_permanent: bool, current_time_unix: Option<u64>) {
+       /// Marks a channel in the graph as failed permanently.
+       ///
+       /// The channel and any node for which this was their last channel are removed from the graph.
+       fn channel_failed_permanent_with_time(&self, short_channel_id: u64, current_time_unix: Option<u64>) {
                let mut channels = self.channels.write().unwrap();
-               if is_permanent {
-                       if let Some(chan) = channels.remove(&short_channel_id) {
-                               let mut nodes = self.nodes.write().unwrap();
-                               self.removed_channels.lock().unwrap().insert(short_channel_id, current_time_unix);
-                               Self::remove_channel_in_nodes(&mut nodes, &chan, short_channel_id);
-                       }
-               } else {
-                       if let Some(chan) = channels.get_mut(&short_channel_id) {
-                               if let Some(one_to_two) = chan.one_to_two.as_mut() {
-                                       one_to_two.enabled = false;
-                               }
-                               if let Some(two_to_one) = chan.two_to_one.as_mut() {
-                                       two_to_one.enabled = false;
-                               }
-                       }
+               if let Some(chan) = channels.remove(&short_channel_id) {
+                       let mut nodes = self.nodes.write().unwrap();
+                       self.removed_channels.lock().unwrap().insert(short_channel_id, current_time_unix);
+                       Self::remove_channel_in_nodes(&mut nodes, &chan, short_channel_id);
                }
        }
 
@@ -2450,7 +2438,7 @@ pub(crate) mod tests {
                        assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some());
                }
 
-               // Non-permanent closing just disables a channel
+               // Non-permanent failure doesn't touch the channel at all
                {
                        match network_graph.read_only().channels().get(&short_channel_id) {
                                None => panic!(),
@@ -2467,7 +2455,7 @@ pub(crate) mod tests {
                        match network_graph.read_only().channels().get(&short_channel_id) {
                                None => panic!(),
                                Some(channel_info) => {
-                                       assert!(!channel_info.one_to_two.as_ref().unwrap().enabled);
+                                       assert!(channel_info.one_to_two.as_ref().unwrap().enabled);
                                }
                        };
                }
@@ -2601,7 +2589,7 @@ pub(crate) mod tests {
 
                        // Mark the channel as permanently failed. This will also remove the two nodes
                        // and all of the entries will be tracked as removed.
-                       network_graph.channel_failed_with_time(short_channel_id, true, Some(tracking_time));
+                       network_graph.channel_failed_permanent_with_time(short_channel_id, Some(tracking_time));
 
                        // Should not remove from tracking if insufficient time has passed
                        network_graph.remove_stale_channels_and_tracking_with_time(
@@ -2634,7 +2622,7 @@ pub(crate) mod tests {
 
                        // Mark the channel as permanently failed. This will also remove the two nodes
                        // and all of the entries will be tracked as removed.
-                       network_graph.channel_failed(short_channel_id, true);
+                       network_graph.channel_failed_permanent(short_channel_id);
 
                        // The first time we call the following, the channel will have a removal time assigned.
                        network_graph.remove_stale_channels_and_tracking_with_time(removal_time);
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 679652b2da49402a2c76f2f0720407c0928e043b..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);
                                                }
                                        }
                                }
@@ -308,8 +317,15 @@ pub struct TestBroadcaster {
 }
 
 impl TestBroadcaster {
-       pub fn new(blocks: Arc<Mutex<Vec<(Block, u32)>>>) -> TestBroadcaster {
-               TestBroadcaster { txn_broadcasted: Mutex::new(Vec::new()), blocks }
+       pub fn new(network: Network) -> Self {
+               Self {
+                       txn_broadcasted: Mutex::new(Vec::new()),
+                       blocks: Arc::new(Mutex::new(vec![(genesis_block(network), 0)])),
+               }
+       }
+
+       pub fn with_blocks(blocks: Arc<Mutex<Vec<(Block, u32)>>>) -> Self {
+               Self { txn_broadcasted: Mutex::new(Vec::new()), blocks }
        }
 
        pub fn txn_broadcast(&self) -> Vec<Transaction> {
@@ -328,7 +344,7 @@ impl chaininterface::BroadcasterInterface for TestBroadcaster {
        fn broadcast_transaction(&self, tx: &Transaction) {
                let lock_time = tx.lock_time.0;
                assert!(lock_time < 1_500_000_000);
-               if lock_time > self.blocks.lock().unwrap().len() as u32 + 1 && lock_time < 500_000_000 {
+               if bitcoin::LockTime::from(tx.lock_time).is_block_height() && lock_time > self.blocks.lock().unwrap().last().unwrap().1 {
                        for inp in tx.input.iter() {
                                if inp.sequence != Sequence::MAX {
                                        panic!("We should never broadcast a transaction before its locktime ({})!", tx.lock_time);
@@ -967,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