Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ impl BlindedPaymentPath {
/// Errors if:
/// * [`BlindedPayInfo`] calculation results in an integer overflow
/// * any unknown features are required in the provided [`ForwardTlvs`]
// TODO: make all payloads the same size with padding + add dummy hops
pub fn new<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64,
Expand All @@ -136,10 +135,7 @@ impl BlindedPaymentPath {
///
/// This improves privacy by making path-length analysis based on fee and CLTV delta
/// values less reliable.
///
/// TODO: Add end-to-end tests validating fee aggregation, CLTV deltas, and
/// HTLC bounds when dummy hops are present, before exposing this API publicly.
pub(crate) fn new_with_dummy_hops<
pub fn new_with_dummy_hops<
ES: EntropySource,
T: secp256k1::Signing + secp256k1::Verification,
>(
Expand Down Expand Up @@ -392,15 +388,57 @@ pub struct DummyTlvs {
pub payment_constraints: PaymentConstraints,
}

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

/// Adds a realistic but stable CLTV cost per dummy hop.
///
/// The router folds this into the blinded path's advertised CLTV delta, so it must
/// be non-trivial enough to model hidden relay latency while remaining predictable
/// for tests and callers reasoning about timeout budgets.
pub(crate) const DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA: u16 = 80;

/// Keeps dummy-hop fee aggregation linear and deterministic.
///
/// A non-zero proportional fee would compound across dummy hops and introduce rounding
/// effects into blinded payinfo. The base fee still makes dummy hops look like plausible relays.
pub(crate) const DEFAULT_DUMMY_HOP_FEE_PROPORTIONAL_MILLIONTHS: u32 = 0;

/// Matches the default relay base fee used by the standard test channel configuration.
///
/// This keeps dummy hops aligned with typical forwarding hops in tests rather than
/// making them appear unrealistically cheap or expensive.
pub(crate) const DEFAULT_DUMMY_HOP_FEE_BASE_MSAT: u32 = 1000;

let payment_constraints =
PaymentConstraints { max_cltv_expiry: u32::MAX, htlc_minimum_msat: 0 };
/// Leaves the dummy hop's absolute CLTV ceiling effectively unbounded by default.
///
/// `PaymentConstraints::max_cltv_expiry` is interpreted as an absolute block height, so using a
/// fixed low value here would cause dummy hops to reject otherwise-valid payments on live chains.
pub(crate) const DEFAULT_DUMMY_HOP_MAX_CLTV_EXPIRY: u32 = u32::MAX;

Self { payment_relay, payment_constraints }
/// Matches the default test channel HTLC minimum.
///
/// The router takes the max of the introduction node's inbound HTLC minimum and this value,
/// so keeping them aligned prevents dummy hops from unexpectedly tightening or loosening
/// admission.
pub(crate) const DEFAULT_DUMMY_HOP_HTLC_MINIMUM_MSAT: u64 = 1000;

impl Default for DummyTlvs {
/// Returns the documented default relay requirements and constraints for synthetic hops.
fn default() -> Self {
Self {
payment_relay: PaymentRelay {
cltv_expiry_delta: DEFAULT_DUMMY_HOP_CLTV_EXPIRY_DELTA,
fee_proportional_millionths: DEFAULT_DUMMY_HOP_FEE_PROPORTIONAL_MILLIONTHS,
fee_base_msat: DEFAULT_DUMMY_HOP_FEE_BASE_MSAT,
},
payment_constraints: PaymentConstraints {
max_cltv_expiry: DEFAULT_DUMMY_HOP_MAX_CLTV_EXPIRY,
htlc_minimum_msat: DEFAULT_DUMMY_HOP_HTLC_MINIMUM_MSAT,
},
}
}
}

Expand Down
32 changes: 32 additions & 0 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,12 @@ pub enum Event {
///
/// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
amount_msat: u64,
/// The additional skimmed fee, in thousandths of a satoshi, that the receiver earns from
/// dummy hops preceding the final receipt, in addition to the `amount_msat`.
///
/// For backwards compatibility with older LDK versions where this TLV was not serialized,
/// this defaults to 0 when absent.
dummy_hops_skimmed_fee_msat: u64,
/// The value, in thousands of a satoshi, that was skimmed off of this payment as an extra fee
/// taken by our channel counterparty.
///
Expand Down Expand Up @@ -965,6 +971,12 @@ pub enum Event {
/// The value, in thousandths of a satoshi, that this payment is for. May be greater than the
/// invoice amount.
amount_msat: u64,
/// The additional skimmed fee, in thousandths of a satoshi, that the receiver earns from
/// dummy hops preceding the final receipt, in addition to the `amount_msat`.
///
/// For backwards compatibility with older LDK versions where this TLV was not serialized,
/// this defaults to 0 when absent.
dummy_hops_skimmed_fee_msat: u64,
/// The purpose of the claimed payment, i.e. whether the payment was for an invoice or a
/// spontaneous payment.
purpose: PaymentPurpose,
Expand Down Expand Up @@ -1891,6 +1903,7 @@ impl Writeable for Event {
&Event::PaymentClaimable {
ref payment_hash,
ref amount_msat,
dummy_hops_skimmed_fee_msat,
counterparty_skimmed_fee_msat,
ref purpose,
ref receiver_node_id,
Expand Down Expand Up @@ -1938,6 +1951,11 @@ impl Writeable for Event {
} else {
Some(counterparty_skimmed_fee_msat)
};
let dummy_skimmed_fee_opt = if dummy_hops_skimmed_fee_msat == 0 {
None
} else {
Some(dummy_hops_skimmed_fee_msat)
};

let (receiving_channel_id_legacy, receiving_user_channel_id_legacy) =
match receiving_channel_ids.last() {
Expand All @@ -1964,6 +1982,7 @@ impl Writeable for Event {
(11, payment_context, option),
(13, payment_id, option),
(15, *receiving_channel_ids, optional_vec),
(17, dummy_skimmed_fee_opt, option),
});
},
&Event::PaymentSent {
Expand Down Expand Up @@ -2189,6 +2208,7 @@ impl Writeable for Event {
&Event::PaymentClaimed {
ref payment_hash,
ref amount_msat,
dummy_hops_skimmed_fee_msat,
ref purpose,
ref receiver_node_id,
ref htlcs,
Expand All @@ -2197,6 +2217,11 @@ impl Writeable for Event {
ref payment_id,
} => {
19u8.write(writer)?;
let dummy_skimmed_fee_opt = if dummy_hops_skimmed_fee_msat == 0 {
None
} else {
Some(dummy_hops_skimmed_fee_msat)
};
write_tlv_fields!(writer, {
(0, payment_hash, required),
(1, receiver_node_id, option),
Expand All @@ -2206,6 +2231,7 @@ impl Writeable for Event {
(7, sender_intended_total_msat, option),
(9, onion_fields, option),
(11, payment_id, option),
(13, dummy_skimmed_fee_opt, option),
});
},
&Event::ProbeSuccessful { ref payment_id, ref payment_hash, ref path } => {
Expand Down Expand Up @@ -2407,6 +2433,7 @@ impl MaybeReadable for Event {
let mut payment_preimage = None;
let mut payment_secret = None;
let mut amount_msat = 0;
let mut dummy_skimmed_fee_msat_opt = None;
let mut counterparty_skimmed_fee_msat_opt = None;
let mut receiver_node_id = None;
let mut _user_payment_id = None::<u64>; // Used in 0.0.103 and earlier, no longer written in 0.0.116+.
Expand All @@ -2432,6 +2459,7 @@ impl MaybeReadable for Event {
(11, payment_context, option),
(13, payment_id, option),
(15, receiving_channel_ids_opt, optional_vec),
(17, dummy_skimmed_fee_msat_opt, option),
});
let purpose = match payment_secret {
Some(secret) => {
Expand All @@ -2455,6 +2483,7 @@ impl MaybeReadable for Event {
receiver_node_id,
payment_hash,
amount_msat,
dummy_hops_skimmed_fee_msat: dummy_skimmed_fee_msat_opt.unwrap_or(0),
counterparty_skimmed_fee_msat: counterparty_skimmed_fee_msat_opt
.unwrap_or(0),
purpose,
Expand Down Expand Up @@ -2760,6 +2789,7 @@ impl MaybeReadable for Event {
let mut payment_hash = PaymentHash([0; 32]);
let mut purpose = UpgradableRequired(None);
let mut amount_msat = 0;
let mut dummy_hops_skimmed_fee_msat_opt = None;
let mut receiver_node_id = None;
let mut htlcs: Option<Vec<ClaimedHTLC>> = Some(vec![]);
let mut sender_intended_total_msat: Option<u64> = None;
Expand All @@ -2775,12 +2805,14 @@ impl MaybeReadable for Event {
(9, onion_fields, (option: ReadableArgs,
sender_intended_total_msat.unwrap_or(amount_msat))),
(11, payment_id, option),
(13, dummy_hops_skimmed_fee_msat_opt, option),
});
Ok(Some(Event::PaymentClaimed {
receiver_node_id,
payment_hash,
purpose: _init_tlv_based_struct_field!(purpose, upgradable_required),
amount_msat,
dummy_hops_skimmed_fee_msat: dummy_hops_skimmed_fee_msat_opt.unwrap_or(0),
htlcs: htlcs.unwrap_or_default(),
sender_intended_total_msat,
onion_fields,
Expand Down
Loading
Loading