Limit maximum total CLTV expiry delta during routing.
authorElias Rohrer <elias.rohrer@tu-berlin.de>
Thu, 20 Jan 2022 15:00:00 +0000 (16:00 +0100)
committerElias Rohrer <elias.rohrer@tu-berlin.de>
Thu, 20 Jan 2022 15:00:00 +0000 (16:00 +0100)
lightning/src/routing/router.rs

index baa75ef16c7d3cb63ffc964d15cb1d089eafe3bd..1f6638dae3005cc00388c66f8c743d66624bdeaa 100644 (file)
@@ -173,6 +173,9 @@ impl_writeable_tlv_based!(RouteParameters, {
        (4, final_cltv_expiry_delta, required),
 });
 
+/// Maximum total CTLV difference we allow for a full payment path.
+pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
+
 /// The recipient of a payment.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct Payee {
@@ -192,10 +195,14 @@ pub struct Payee {
 
        /// Expiration of a payment to the payee, in seconds relative to the UNIX epoch.
        pub expiry_time: Option<u64>,
+
+       /// The maximum total CLTV delta we accept for the route.
+       pub max_total_cltv_expiry_delta: u32,
 }
 
 impl_writeable_tlv_based!(Payee, {
        (0, pubkey, required),
+       (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
        (2, features, option),
        (4, route_hints, vec_type),
        (6, expiry_time, option),
@@ -209,6 +216,7 @@ impl Payee {
                        features: None,
                        route_hints: vec![],
                        expiry_time: None,
+                       max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
                }
        }
 
@@ -237,6 +245,13 @@ impl Payee {
        pub fn with_expiry_time(self, expiry_time: u64) -> Self {
                Self { expiry_time: Some(expiry_time), ..self }
        }
+
+       /// Includes a limit for the total CLTV expiry delta which is considered during routing
+       ///
+       /// (C-not exported) since bindings don't support move semantics
+       pub fn with_max_total_cltv_expiry_delta(self, max_total_cltv_expiry_delta: u32) -> Self {
+               Self { max_total_cltv_expiry_delta, ..self }
+       }
 }
 
 /// A list of hops along a payment path terminating with a channel to the recipient.
@@ -296,6 +311,7 @@ struct RouteGraphNode {
        node_id: NodeId,
        lowest_fee_to_peer_through_node: u64,
        lowest_fee_to_node: u64,
+       total_cltv_delta: u32,
        // The maximum value a yet-to-be-constructed payment path might flow through this node.
        // This value is upper-bounded by us by:
        // - how much is needed for a path being constructed
@@ -722,7 +738,7 @@ where L::Target: Logger {
                // since that value has to be transferred over this channel.
                // Returns whether this channel caused an update to `targets`.
                ( $chan_id: expr, $src_node_id: expr, $dest_node_id: expr, $directional_info: expr, $capacity_sats: expr, $chan_features: expr, $next_hops_fee_msat: expr,
-                  $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr ) => { {
+                  $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr ) => { {
                        // We "return" whether we updated the path at the end, via this:
                        let mut did_add_update_path_to_src_node = false;
                        // Channels to self should not be used. This is more of belt-and-suspenders, because in
@@ -784,6 +800,14 @@ where L::Target: Logger {
                                        // Verify the liquidity offered by this channel complies to the minimal contribution.
                                        let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
 
+
+                                       // Do not consider candidates that exceed the maximum total cltv expiry limit.
+                                       let max_total_cltv_expiry_delta = payee.max_total_cltv_expiry_delta;
+                                       let hop_total_cltv_delta = ($next_hops_cltv_delta as u32)
+                                               .checked_add($directional_info.cltv_expiry_delta as u32)
+                                               .unwrap_or(u32::max_value());
+                                       let doesnt_exceed_cltv_delta_limit = hop_total_cltv_delta <= max_total_cltv_expiry_delta;
+
                                        let value_contribution_msat = cmp::min(available_value_contribution_msat, $next_hops_value_contribution);
                                        // Includes paying fees for the use of the following channels.
                                        let amount_to_transfer_over_msat: u64 = match value_contribution_msat.checked_add($next_hops_fee_msat) {
@@ -800,9 +824,9 @@ where L::Target: Logger {
                                        // Since we're choosing amount_to_transfer_over_msat as maximum possible, it can
                                        // be only reduced later (not increased), so this channel should just be skipped
                                        // as not sufficient.
-                                       if !over_path_minimum_msat {
+                                       if !over_path_minimum_msat && doesnt_exceed_cltv_delta_limit {
                                                hit_minimum_limit = true;
-                                       } else if contributes_sufficient_value {
+                                       } else if contributes_sufficient_value && doesnt_exceed_cltv_delta_limit {
                                                // Note that low contribution here (limited by available_liquidity_msat)
                                                // might violate htlc_minimum_msat on the hops which are next along the
                                                // payment path (upstream to the payee). To avoid that, we recompute
@@ -899,6 +923,7 @@ where L::Target: Logger {
                                                                node_id: $src_node_id,
                                                                lowest_fee_to_peer_through_node: total_fee_msat,
                                                                lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat,
+                                                               total_cltv_delta: hop_total_cltv_delta,
                                                                value_contribution_msat: value_contribution_msat,
                                                                path_htlc_minimum_msat,
                                                                path_penalty_msat,
@@ -990,7 +1015,7 @@ where L::Target: Logger {
        // meaning how much will be paid in fees after this node (to the best of our knowledge).
        // This data can later be helpful to optimize routing (pay lower fees).
        macro_rules! add_entries_to_cheapest_to_target_node {
-               ( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr ) => {
+               ( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr ) => {
                        let skip_node = if let Some(elem) = dist.get_mut(&$node_id) {
                                let was_processed = elem.was_processed;
                                elem.was_processed = true;
@@ -1006,7 +1031,7 @@ where L::Target: Logger {
                        if !skip_node {
                                if let Some(first_channels) = first_hop_targets.get(&$node_id) {
                                        for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
-                                               add_entry!(first_hop, our_node_id, $node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat);
+                                               add_entry!(first_hop, our_node_id, $node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat, $next_hops_cltv_delta);
                                        }
                                }
 
@@ -1025,7 +1050,7 @@ where L::Target: Logger {
                                                                if first_hops.is_none() || chan.node_two != our_node_id {
                                                                        if let Some(two_to_one) = chan.two_to_one.as_ref() {
                                                                                if two_to_one.enabled {
-                                                                                       add_entry!(chan_id, chan.node_two, chan.node_one, two_to_one, chan.capacity_sats, &chan.features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat);
+                                                                                       add_entry!(chan_id, chan.node_two, chan.node_one, two_to_one, chan.capacity_sats, &chan.features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat, $next_hops_cltv_delta);
                                                                                }
                                                                        }
                                                                }
@@ -1033,7 +1058,7 @@ where L::Target: Logger {
                                                                if first_hops.is_none() || chan.node_one != our_node_id{
                                                                        if let Some(one_to_two) = chan.one_to_two.as_ref() {
                                                                                if one_to_two.enabled {
-                                                                                       add_entry!(chan_id, chan.node_one, chan.node_two, one_to_two, chan.capacity_sats, &chan.features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat);
+                                                                                       add_entry!(chan_id, chan.node_one, chan.node_two, one_to_two, chan.capacity_sats, &chan.features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat, $next_hops_cltv_delta);
                                                                                }
                                                                        }
                                                                }
@@ -1060,7 +1085,7 @@ where L::Target: Logger {
                // place where it could be added.
                if let Some(first_channels) = first_hop_targets.get(&payee_node_id) {
                        for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
-                               let added = add_entry!(first_hop, our_node_id, payee_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0, 0u64);
+                               let added = add_entry!(first_hop, our_node_id, payee_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0, 0u64, 0);
                                log_trace!(logger, "{} direct route to payee via SCID {}", if added { "Added" } else { "Skipped" }, first_hop);
                        }
                }
@@ -1074,7 +1099,7 @@ where L::Target: Logger {
                        // If not, targets.pop() will not even let us enter the loop in step 2.
                        None => {},
                        Some(node) => {
-                               add_entries_to_cheapest_to_target_node!(node, payee_node_id, 0, path_value_msat, 0, 0u64);
+                               add_entries_to_cheapest_to_target_node!(node, payee_node_id, 0, path_value_msat, 0, 0u64, 0);
                        },
                }
 
@@ -1100,6 +1125,7 @@ where L::Target: Logger {
                                let mut aggregate_next_hops_fee_msat: u64 = 0;
                                let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0;
                                let mut aggregate_next_hops_path_penalty_msat: u64 = 0;
+                               let mut aggregate_next_hops_cltv_delta: u32 = 0;
 
                                for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
                                        // BOLT 11 doesn't allow inclusion of features for the last hop hints, which
@@ -1130,11 +1156,15 @@ where L::Target: Logger {
                                                .checked_add(scorer.channel_penalty_msat(hop.short_channel_id, final_value_msat, None, &src_node_id, &dest_node_id))
                                                .unwrap_or_else(|| u64::max_value());
 
+                                       aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
+                                               .checked_add(hop.cltv_expiry_delta as u32)
+                                               .unwrap_or_else(|| u32::max_value());
+
                                        // We assume that the recipient only included route hints for routes which had
                                        // sufficient value to route `final_value_msat`. Note that in the case of "0-value"
                                        // invoices where the invoice does not specify value this may not be the case, but
                                        // better to include the hints than not.
-                                       if !add_entry!(hop.short_channel_id, src_node_id, dest_node_id, directional_info, channel_cap_sat, &empty_channel_features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat) {
+                                       if !add_entry!(hop.short_channel_id, src_node_id, dest_node_id, directional_info, channel_cap_sat, &empty_channel_features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta) {
                                                // If this hop was not used then there is no use checking the preceding hops
                                                // in the RouteHint. We can break by just searching for a direct channel between
                                                // last checked hop and first_hop_targets
@@ -1144,7 +1174,7 @@ where L::Target: Logger {
                                        // Searching for a direct channel between last checked hop and first_hop_targets
                                        if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&prev_hop_id)) {
                                                for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
-                                                       add_entry!(first_hop, our_node_id , NodeId::from_pubkey(&prev_hop_id), dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat);
+                                                       add_entry!(first_hop, our_node_id , NodeId::from_pubkey(&prev_hop_id), dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta);
                                                }
                                        }
 
@@ -1178,7 +1208,7 @@ where L::Target: Logger {
                                                // path.
                                                if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) {
                                                        for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
-                                                               add_entry!(first_hop, our_node_id , NodeId::from_pubkey(&hop.src_node_id), dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat);
+                                                               add_entry!(first_hop, our_node_id , NodeId::from_pubkey(&hop.src_node_id), dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta);
                                                        }
                                                }
                                        }
@@ -1201,7 +1231,7 @@ where L::Target: Logger {
                // Both these cases (and other cases except reaching recommended_value_msat) mean that
                // paths_collection will be stopped because found_new_path==false.
                // This is not necessarily a routing failure.
-               'path_construction: while let Some(RouteGraphNode { node_id, lowest_fee_to_node, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, .. }) = targets.pop() {
+               'path_construction: while let Some(RouteGraphNode { node_id, lowest_fee_to_node, total_cltv_delta, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, .. }) = targets.pop() {
 
                        // Since we're going payee-to-payer, hitting our node as a target means we should stop
                        // traversing the graph and arrange the path out of what we found.
@@ -1327,7 +1357,7 @@ where L::Target: Logger {
                        match network_nodes.get(&node_id) {
                                None => {},
                                Some(node) => {
-                                       add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat);
+                                       add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, total_cltv_delta);
                                },
                        }
                }
@@ -1731,7 +1761,7 @@ mod tests {
                        short_channel_id: 2,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: u16::max_value(),
+                       cltv_expiry_delta: (5 << 4) | 3,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: u32::max_value(),
@@ -1759,7 +1789,7 @@ mod tests {
                        short_channel_id: 12,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: u16::max_value(),
+                       cltv_expiry_delta: (5 << 4) | 3,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: u32::max_value(),
@@ -1787,7 +1817,7 @@ mod tests {
                        short_channel_id: 3,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (3 << 8) | 1,
+                       cltv_expiry_delta: (3 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1799,7 +1829,7 @@ mod tests {
                        short_channel_id: 3,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (3 << 8) | 2,
+                       cltv_expiry_delta: (3 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 100,
@@ -1813,7 +1843,7 @@ mod tests {
                        short_channel_id: 4,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (4 << 8) | 1,
+                       cltv_expiry_delta: (4 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1825,7 +1855,7 @@ mod tests {
                        short_channel_id: 4,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (4 << 8) | 2,
+                       cltv_expiry_delta: (4 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1839,7 +1869,7 @@ mod tests {
                        short_channel_id: 13,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (13 << 8) | 1,
+                       cltv_expiry_delta: (13 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1851,7 +1881,7 @@ mod tests {
                        short_channel_id: 13,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (13 << 8) | 2,
+                       cltv_expiry_delta: (13 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1867,7 +1897,7 @@ mod tests {
                        short_channel_id: 6,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (6 << 8) | 1,
+                       cltv_expiry_delta: (6 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1879,7 +1909,7 @@ mod tests {
                        short_channel_id: 6,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (6 << 8) | 2,
+                       cltv_expiry_delta: (6 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1893,7 +1923,7 @@ mod tests {
                        short_channel_id: 11,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (11 << 8) | 1,
+                       cltv_expiry_delta: (11 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1905,7 +1935,7 @@ mod tests {
                        short_channel_id: 11,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (11 << 8) | 2,
+                       cltv_expiry_delta: (11 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1923,7 +1953,7 @@ mod tests {
                        short_channel_id: 7,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (7 << 8) | 1,
+                       cltv_expiry_delta: (7 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1935,7 +1965,7 @@ mod tests {
                        short_channel_id: 7,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (7 << 8) | 2,
+                       cltv_expiry_delta: (7 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -1967,7 +1997,7 @@ mod tests {
                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 << 8) | 1);
+               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));
 
@@ -2305,7 +2335,7 @@ mod tests {
                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 << 8) | 1);
+               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
 
@@ -2343,7 +2373,7 @@ mod tests {
                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 << 8) | 1);
+               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
 
@@ -2373,14 +2403,14 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 2);
+               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));
 
@@ -2400,7 +2430,7 @@ mod tests {
                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 << 8) | 1);
+               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
 
@@ -2421,7 +2451,7 @@ mod tests {
                        src_node_id: nodes[3],
                        short_channel_id: 8,
                        fees: zero_fees,
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }
@@ -2432,14 +2462,14 @@ mod tests {
                                base_msat: 1001,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (9 << 8) | 1,
+                       cltv_expiry_delta: (9 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
                        src_node_id: nodes[5],
                        short_channel_id: 10,
                        fees: zero_fees,
-                       cltv_expiry_delta: (10 << 8) | 1,
+                       cltv_expiry_delta: (10 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }])]
@@ -2457,14 +2487,14 @@ mod tests {
                                base_msat: 100,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (5 << 8) | 1,
+                       cltv_expiry_delta: (5 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }, RouteHintHop {
                        src_node_id: nodes[3],
                        short_channel_id: 8,
                        fees: zero_fees,
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }
@@ -2475,14 +2505,14 @@ mod tests {
                                base_msat: 1001,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (9 << 8) | 1,
+                       cltv_expiry_delta: (9 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
                        src_node_id: nodes[5],
                        short_channel_id: 10,
                        fees: zero_fees,
-                       cltv_expiry_delta: (10 << 8) | 1,
+                       cltv_expiry_delta: (10 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }])]
@@ -2506,7 +2536,7 @@ mod tests {
                                base_msat: 1000,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]);
@@ -2527,28 +2557,28 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               assert_eq!(route.paths[0][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));
@@ -2571,7 +2601,7 @@ mod tests {
                        src_node_id: nodes[3],
                        short_channel_id: 8,
                        fees: zero_fees,
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![
@@ -2580,7 +2610,7 @@ mod tests {
                        src_node_id: nodes[5],
                        short_channel_id: 10,
                        fees: zero_fees,
-                       cltv_expiry_delta: (10 << 8) | 1,
+                       cltv_expiry_delta: (10 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }])]
@@ -2601,28 +2631,28 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               assert_eq!(route.paths[0][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));
@@ -2648,21 +2678,21 @@ mod tests {
                                base_msat: 100,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (5 << 8) | 1,
+                       cltv_expiry_delta: (5 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }, RouteHintHop {
                        src_node_id: nodes[3],
                        short_channel_id: 8,
                        fees: zero_fees,
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
                        src_node_id: nodes[5],
                        short_channel_id: 10,
                        fees: zero_fees,
-                       cltv_expiry_delta: (10 << 8) | 1,
+                       cltv_expiry_delta: (10 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }])]
@@ -2709,21 +2739,21 @@ mod tests {
                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, 1025);
+               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, 1281);
+               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, 5);
                assert_eq!(route.paths[0][2].fee_msat, 0);
-               assert_eq!(route.paths[0][2].cltv_expiry_delta, 2049);
+               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());
 
@@ -2744,14 +2774,14 @@ mod tests {
                        src_node_id: nodes[4],
                        short_channel_id: 11,
                        fees: zero_fees,
-                       cltv_expiry_delta: (11 << 8) | 1,
+                       cltv_expiry_delta: (11 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }, RouteHintHop {
                        src_node_id: nodes[3],
                        short_channel_id: 8,
                        fees: zero_fees,
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
@@ -2761,14 +2791,14 @@ mod tests {
                                base_msat: 1001,
                                proportional_millionths: 0,
                        },
-                       cltv_expiry_delta: (9 << 8) | 1,
+                       cltv_expiry_delta: (9 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
                        src_node_id: nodes[5],
                        short_channel_id: 10,
                        fees: zero_fees,
-                       cltv_expiry_delta: (10 << 8) | 1,
+                       cltv_expiry_delta: (10 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
                }])]
@@ -2789,28 +2819,28 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               assert_eq!(route.paths[0][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));
@@ -2840,7 +2870,7 @@ mod tests {
                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 << 8) | 1);
+               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
 
@@ -2861,21 +2891,21 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               assert_eq!(route.paths[0][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));
@@ -2895,28 +2925,28 @@ mod tests {
                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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               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 << 8) | 1);
+               assert_eq!(route.paths[0][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));
@@ -2943,7 +2973,7 @@ mod tests {
                                base_msat: 1000,
                                proportional_millionths: last_hop_fee_prop,
                        },
-                       cltv_expiry_delta: (8 << 8) | 1,
+                       cltv_expiry_delta: (8 << 4) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: last_hop_htlc_max,
                }]);
@@ -2967,7 +2997,7 @@ mod tests {
                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 << 8) | 1);
+               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
 
@@ -3198,7 +3228,7 @@ mod tests {
                        short_channel_id: 333,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (3 << 8) | 1,
+                       cltv_expiry_delta: (3 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -3210,7 +3240,7 @@ mod tests {
                        short_channel_id: 333,
                        timestamp: 1,
                        flags: 1,
-                       cltv_expiry_delta: (3 << 8) | 2,
+                       cltv_expiry_delta: (3 << 4) | 2,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 100,
@@ -4104,7 +4134,7 @@ mod tests {
                        short_channel_id: 1,
                        timestamp: 2,
                        flags: 0,
-                       cltv_expiry_delta: u16::max_value(),
+                       cltv_expiry_delta: (5 << 4) | 5,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Present(99_000),
                        fee_base_msat: u32::max_value(),
@@ -4116,7 +4146,7 @@ mod tests {
                        short_channel_id: 2,
                        timestamp: 2,
                        flags: 0,
-                       cltv_expiry_delta: u16::max_value(),
+                       cltv_expiry_delta: (5 << 4) | 3,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Present(99_000),
                        fee_base_msat: u32::max_value(),
@@ -4128,7 +4158,7 @@ mod tests {
                        short_channel_id: 4,
                        timestamp: 2,
                        flags: 0,
-                       cltv_expiry_delta: (4 << 8) | 1,
+                       cltv_expiry_delta: (4 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 1,
@@ -4140,7 +4170,7 @@ mod tests {
                        short_channel_id: 13,
                        timestamp: 2,
                        flags: 0|2, // Channel disabled
-                       cltv_expiry_delta: (13 << 8) | 1,
+                       cltv_expiry_delta: (13 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4337,7 +4367,7 @@ mod tests {
                        short_channel_id: 6,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (6 << 8) | 0,
+                       cltv_expiry_delta: (6 << 4) | 0,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4352,7 +4382,7 @@ mod tests {
                        short_channel_id: 5,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (5 << 8) | 0,
+                       cltv_expiry_delta: (5 << 4) | 0,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 100,
@@ -4367,7 +4397,7 @@ mod tests {
                        short_channel_id: 4,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (4 << 8) | 0,
+                       cltv_expiry_delta: (4 << 4) | 0,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4382,7 +4412,7 @@ mod tests {
                        short_channel_id: 3,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (3 << 8) | 0,
+                       cltv_expiry_delta: (3 << 4) | 0,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4397,7 +4427,7 @@ mod tests {
                        short_channel_id: 2,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (2 << 8) | 0,
+                       cltv_expiry_delta: (2 << 4) | 0,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4411,7 +4441,7 @@ mod tests {
                        short_channel_id: 1,
                        timestamp: 1,
                        flags: 0,
-                       cltv_expiry_delta: (1 << 8) | 0,
+                       cltv_expiry_delta: (1 << 4) | 0,
                        htlc_minimum_msat: 100,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4429,14 +4459,14 @@ mod tests {
                        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 << 8) | 0);
+                       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 << 8) | 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));
 
@@ -4480,7 +4510,7 @@ mod tests {
                        short_channel_id: 12,
                        timestamp: 2,
                        flags: 0,
-                       cltv_expiry_delta: (4 << 8) | 1,
+                       cltv_expiry_delta: (4 << 4) | 1,
                        htlc_minimum_msat: 0,
                        htlc_maximum_msat: OptionalField::Present(270_000),
                        fee_base_msat: 0,
@@ -4498,7 +4528,7 @@ mod tests {
                        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 << 8) | 1);
+                       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));
 
@@ -4542,7 +4572,7 @@ mod tests {
                        short_channel_id: 4,
                        timestamp: 2,
                        flags: 0,
-                       cltv_expiry_delta: (4 << 8) | 1,
+                       cltv_expiry_delta: (4 << 4) | 1,
                        htlc_minimum_msat: 90_000,
                        htlc_maximum_msat: OptionalField::Absent,
                        fee_base_msat: 0,
@@ -4561,7 +4591,7 @@ mod tests {
                        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 << 8) | 1);
+                       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));
 
@@ -4801,6 +4831,34 @@ mod tests {
                assert_eq!(route.get_total_amount(), 0);
        }
 
+       #[test]
+       fn limits_total_cltv_delta() {
+               let (secp_ctx, network_graph, _, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+
+               let scorer = test_utils::TestScorer::with_fixed_penalty(0);
+
+               // Make sure that generally there is at least one route available
+               let feasible_max_total_cltv_delta = 1008;
+               let feasible_payee = Payee::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes))
+                       .with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta);
+               let route = get_route(&our_id, &feasible_payee, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+               let path = route.paths[0].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
+               let fail_max_total_cltv_delta = 23;
+               let fail_payee = Payee::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes))
+                       .with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta);
+               match get_route(&our_id, &fail_payee, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer)
+               {
+                       Err(LightningError { err, .. } ) => {
+                               assert_eq!(err, "Failed to find a path to the given destination");
+                       },
+                       Ok(_) => panic!("Expected error"),
+               }
+       }
+
        #[cfg(not(feature = "no-std"))]
        pub(super) fn random_init_seed() -> u64 {
                // Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.