Skip to content

Commit 617be01

Browse files
committed
Use realistic relay defaults for DummyTlvs
Replace zeroed dummy-hop relay parameters with non-trivial default fee, CLTV, and HTLC minimum values, and propagate these defaults into blinded payinfo construction and routing tests. Dummy hops only preserve their privacy value if they behave like plausible forwarding hops. Realistic defaults ensure hidden hops affect fees and CLTV like real relays, instead of standing out as zero-cost padding.
1 parent 08bce82 commit 617be01

3 files changed

Lines changed: 67 additions & 12 deletions

File tree

lightning/src/blinded_path/payment.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -392,15 +392,57 @@ pub struct DummyTlvs {
392392
pub payment_constraints: PaymentConstraints,
393393
}
394394

395-
impl Default for DummyTlvs {
396-
fn default() -> Self {
397-
let payment_relay =
398-
PaymentRelay { cltv_expiry_delta: 0, fee_proportional_millionths: 0, fee_base_msat: 0 };
395+
// Default parameters used for dummy hops in blinded paths.
396+
//
397+
// These values are chosen to resemble typical forwarding hops while remaining
398+
// stable and predictable for tests.
399399

400-
let payment_constraints =
401-
PaymentConstraints { max_cltv_expiry: u32::MAX, htlc_minimum_msat: 0 };
400+
/// Adds a realistic but stable CLTV cost per dummy hop.
401+
///
402+
/// The router folds this into the blinded path's advertised CLTV delta, so it must
403+
/// be non-trivial enough to model hidden relay latency while remaining predictable
404+
/// for tests and callers reasoning about timeout budgets.
405+
pub(crate) const DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA: u16 = 80;
402406

403-
Self { payment_relay, payment_constraints }
407+
/// Keeps dummy-hop fee aggregation linear and deterministic.
408+
///
409+
/// A non-zero proportional fee would compound across dummy hops and introduce rounding
410+
/// effects into blinded payinfo. The base fee still makes dummy hops look like plausible relays.
411+
pub(crate) const DEFAULT_DUMMY_HOP_FEE_PROPORTIONAL_MILLIONTHS: u32 = 0;
412+
413+
/// Matches the default relay base fee used by the standard test channel configuration.
414+
///
415+
/// This keeps dummy hops aligned with typical forwarding hops in tests rather than
416+
/// making them appear unrealistically cheap or expensive.
417+
pub(crate) const DEFAULT_DUMMY_HOP_FEE_BASE_MSAT: u32 = 1000;
418+
419+
/// Leaves the dummy hop's absolute CLTV ceiling effectively unbounded by default.
420+
///
421+
/// `PaymentConstraints::max_cltv_expiry` is interpreted as an absolute block height, so using a
422+
/// fixed low value here would cause dummy hops to reject otherwise-valid payments on live chains.
423+
pub(crate) const DEFAULT_DUMMY_HOP_MAX_CLTV_EXPIRY: u32 = u32::MAX;
424+
425+
/// Matches the default test channel HTLC minimum.
426+
///
427+
/// The router takes the max of the introduction node's inbound HTLC minimum and this value,
428+
/// so keeping them aligned prevents dummy hops from unexpectedly tightening or loosening
429+
/// admission.
430+
pub(crate) const DEFAULT_DUMMY_HOP_HTLC_MINIMUM_MSAT: u64 = 1000;
431+
432+
impl Default for DummyTlvs {
433+
/// Returns the documented default relay requirements and constraints for synthetic hops.
434+
fn default() -> Self {
435+
Self {
436+
payment_relay: PaymentRelay {
437+
cltv_expiry_delta: DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA,
438+
fee_proportional_millionths: DEFAULT_DUMMY_HOP_FEE_PROPORTIONAL_MILLIONTHS,
439+
fee_base_msat: DEFAULT_DUMMY_HOP_FEE_BASE_MSAT,
440+
},
441+
payment_constraints: PaymentConstraints {
442+
max_cltv_expiry: DEFAULT_DUMMY_HOP_MAX_CLTV_EXPIRY,
443+
htlc_minimum_msat: DEFAULT_DUMMY_HOP_HTLC_MINIMUM_MSAT,
444+
},
445+
}
404446
}
405447
}
406448

lightning/src/ln/async_payments_tests.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use crate::blinded_path::message::{
1111
BlindedMessagePath, MessageContext, NextMessageHop, OffersContext,
1212
};
1313
use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentTlvs};
14-
use crate::blinded_path::payment::{DummyTlvs, PaymentContext};
14+
use crate::blinded_path::payment::{
15+
DummyTlvs, PaymentContext, DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA,
16+
};
1517
use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
1618
use crate::events::{
1719
Event, EventsProvider, HTLCHandlingFailureReason, HTLCHandlingFailureType, PaidBolt12Invoice,
@@ -3034,11 +3036,16 @@ fn held_htlc_timeout() {
30343036
// Extract the release_htlc_om, but don't deliver it to the sender's LSP.
30353037
let _ = extract_release_htlc_oms(recipient, &[sender, sender_lsp, invoice_server]);
30363038

3039+
// Dummy hops add to the blinded path's total advertised CLTV delta.
3040+
let additional_cltv_expiry =
3041+
DEFAULT_PAYMENT_DUMMY_HOPS as u32 * DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA as u32;
3042+
30373043
// Connect blocks to the sender's LSP until they timeout the HTLC.
30383044
connect_blocks(
30393045
sender_lsp,
30403046
MIN_CLTV_EXPIRY_DELTA as u32
30413047
+ TEST_FINAL_CLTV
3048+
+ additional_cltv_expiry
30423049
+ HTLC_FAIL_BACK_BUFFER
30433050
+ LATENCY_GRACE_PERIOD_BLOCKS,
30443051
);

lightning/src/routing/router.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ where
150150
let network_graph = self.network_graph.deref().read_only();
151151
let is_recipient_announced =
152152
network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient));
153+
let dummy_tlvs = DummyTlvs::default();
153154

154155
let paths = first_hops.into_iter()
155156
.filter(|details| details.counterparty.features.supports_route_blinding())
@@ -176,12 +177,17 @@ where
176177
None => return None,
177178
};
178179

179-
let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32;
180+
let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32
181+
+ dummy_tlvs.payment_relay.cltv_expiry_delta as u32 * DEFAULT_PAYMENT_DUMMY_HOPS as u32;
182+
let htlc_minimum_msat = cmp::max(
183+
details.inbound_htlc_minimum_msat.unwrap_or(0),
184+
dummy_tlvs.payment_constraints.htlc_minimum_msat,
185+
);
180186
let payment_constraints = PaymentConstraints {
181187
max_cltv_expiry: tlvs.payment_constraints
182188
.max_cltv_expiry
183189
.saturating_add(cltv_expiry_delta),
184-
htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0),
190+
htlc_minimum_msat,
185191
};
186192
Some(PaymentForwardNode {
187193
tlvs: ForwardTlvs {
@@ -197,7 +203,7 @@ where
197203
})
198204
.map(|forward_node| {
199205
BlindedPaymentPath::new_with_dummy_hops(
200-
&[forward_node], recipient, &[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS],
206+
&[forward_node], recipient, &[dummy_tlvs; DEFAULT_PAYMENT_DUMMY_HOPS],
201207
local_node_receive_key, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &self.entropy_source, secp_ctx
202208
)
203209
})
@@ -209,7 +215,7 @@ where
209215
_ => {
210216
if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
211217
BlindedPaymentPath::new_with_dummy_hops(
212-
&[], recipient, &[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS],
218+
&[], recipient, &[dummy_tlvs; DEFAULT_PAYMENT_DUMMY_HOPS],
213219
local_node_receive_key, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &self.entropy_source, secp_ctx
214220
).map(|path| vec![path])
215221
} else {

0 commit comments

Comments
 (0)