Clarify CLTV expiry delta for trampolines further
What changed, and why it matters
This commit only updates documentation comments and tightens an internal consistency check for a special routing feature (Trampoline payments). It does not change any security-sensitive behavior or fix a vulnerability. The change makes the code's internal assertion stricter (requiring exact equality rather than 'not greater than') when trampoline hops are present, but this is a correctness/clarity improvement, not a security fix.
No security action required. Treat as a normal documentation/internal-correctness commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch clarifies in a doc comment how RouteHop::cltv_expiry_delta is computed for blinded payment paths, distinguishing cases with and without trampoline hops. It also changes a debug_assert-guarded validation in Route::verify from trampoline_cltv_sum > last_hop_cltv_delta to !tail.trampoline_hops.is_empty() && trampoline_cltv_sum != last_hop_cltv_delta, so the check only fires when trampoline hops exist and requires exact equality. This is a documentation and internal-consistency refinement, not a vulnerability remediation.
Changed components
lightning/src/routing/router.rsRouteHop documentationRoute::verify validationInspect captured patch +10 / −4
diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs
index f3b1f4e..2a00d44 100644
--- a/lightning/src/routing/router.rs
+++ b/lightning/src/routing/router.rs
@@ -520,8 +520,12 @@ pub struct RouteHop {
pub fee_msat: u64,
/// The CLTV delta added for this hop.
/// If this is the last hop in [`Path::hops`]:
- /// * if we're sending to a [`BlindedPaymentPath`], this is the CLTV delta for the entire blinded
- /// path (including any Trampoline hops)
+ /// * if we're sending to a [`BlindedPaymentPath`] *with* trampoline hops, this is the CLTV
+ /// delta for the entire blinded path including the trampoline hops, and is thus equal to the
+ /// sum of [`TrampolineHop::cltv_expiry_delta`] for all the [`BlindedTail::trampoline_hops`].
+ /// * if we're sending to a [`BlindedPaymentPath`], *without* trampoline hops, this is the CLTV
+ /// delta for the entire blinded path (including
+ /// [`BlindedTail::excess_final_cltv_expiry_delta`]).
/// * otherwise, this is the CLTV delta expected at the destination
pub cltv_expiry_delta: u32,
/// Indicates whether this hop is possibly announced in the public network graph.
@@ -753,9 +757,11 @@ impl Route {
let trampoline_cltv_sum: u32 =
tail.trampoline_hops.iter().map(|hop| hop.cltv_expiry_delta).sum();
let last_hop_cltv_delta = path.hops.last().unwrap().cltv_expiry_delta;
- if trampoline_cltv_sum > last_hop_cltv_delta {
+ if !tail.trampoline_hops.is_empty()
+ && trampoline_cltv_sum != last_hop_cltv_delta
+ {
let err = format!(
- "Path had a total trampoline CLTV of {trampoline_cltv_sum}, which is less than the total last-hop CLTV delta of {last_hop_cltv_delta}"
+ "Path had a total trampoline CLTV of {trampoline_cltv_sum}, which is not equal to the total last-hop CLTV delta of {last_hop_cltv_delta}"
);
debug_assert!(false, "{}", err);
log_error!(logger, "{}", err);
Why this scored 11/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.