Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7856696
ln/refactor: add previous_hop_data helper for HTLCSource
carlaKC Mar 2, 2026
5ee0ea7
ln/refactor: rename shared secret and populate in HTLCPreviousHopData
carlaKC Mar 12, 2026
6014afb
ln/refactor: move mpp timeout check into helper function
carlaKC Feb 25, 2026
a9e5d03
ln/refactor: move on chain timeout check into claimable htlc
carlaKC Jan 22, 2026
8383aac
ln/refactor: move checks on incoming mpp accumulation into method
carlaKC Feb 25, 2026
f1eb379
ln: move receive-specific failures into fail_htlc macro
carlaKC Feb 12, 2026
6907dee
blinded_path/refactor: make construction generic over forwarding type
carlaKC Mar 17, 2026
3229acf
ln/refactor: add constructor for ClaimableHTLC
carlaKC Mar 18, 2026
f15271e
ln/refactor: pass minimum delta into check_incoming_htlc_cltv
carlaKC Feb 12, 2026
a5fd65f
ln: remove incoming trampoline secret from HTLCSource
carlaKC Mar 12, 2026
64cc3e0
ln: store incoming mpp data in PendingHTLCRouting
carlaKC Jan 27, 2026
77f1ba0
ln: use total_msat to calculate the amount for our next trampoline
carlaKC Feb 25, 2026
a23c304
ln: use outer onion cltv values in PendingHTLCInfo for trampoline
carlaKC Feb 25, 2026
9076030
ln: store next trampoline amount and cltv in PendingHTLCRouting
carlaKC Feb 25, 2026
5c912a7
ln: use outer onion values for trampoline NextPacketDetails
carlaKC Feb 12, 2026
83448ef
ln: add Trampoline variant to OnionPayload
carlaKC Jan 23, 2026
f8ce4ae
ln: add awaiting_trampoline_forwards to accumulate inbound MPP
carlaKC Mar 17, 2026
88dca77
ln: add trampoline mpp accumulation with rejection on completion
carlaKC Feb 24, 2026
fba24ce
ln: double encrypt errors received from downstream failures
carlaKC Mar 12, 2026
1812491
ln: handle DecodedOnionFailure for local trampoline failures
carlaKC Mar 12, 2026
626b468
blinded_path: add constructor for trampoline blinded path
carlaKC Mar 17, 2026
3e5dad7
ln: process added trampoline htlcs with CLTV validation
carlaKC Feb 25, 2026
5578828
ln/test: add multi-purpose trampoline test helper
carlaKC Mar 17, 2026
7ca0b3c
ln/test: add test coverage for MPP trampoline
carlaKC Mar 17, 2026
b36e5ab
ln/test: add tests for mpp accumulation of trampoline forwards
carlaKC Mar 18, 2026
f4db5bb
ln: add trampoline forward info to PendingOutboundPayment::Retryable
carlaKC Jan 16, 2026
185ebda
ln: thread trampoline routing information through payment methods
carlaKC Feb 10, 2026
c76cf8c
ln: add blinding point to new_trampoline_entry
carlaKC Feb 10, 2026
6781978
ln function to build trampoline forwarding onions
carlaKC Jan 28, 2026
f6106df
ln: support trampoline in send_payment_along_path
carlaKC Feb 11, 2026
27b608f
ln: add send trampoline payment functionality
carlaKC Jan 16, 2026
211482f
ln: surface trampoline error packet it could not decrypt
carlaKC Mar 17, 2026
01695c2
[wip] ln: add trampoline htlc failure logic to outbound payments
carlaKC Mar 17, 2026
cab35a4
ln: add claim_trampoline_forward to mark trampoline complete
carlaKC Feb 18, 2026
9eb2a12
ln: handle trampoline payments in finalize_claims
carlaKC Feb 18, 2026
631e10c
ln: only fail trampoline payments backwards when payment state ready
carlaKC Mar 12, 2026
977ffb0
ln: claim trampoline payment on completion
carlaKC Feb 18, 2026
17e367a
ln: use correct blinding point for trampoline payload decodes
carlaKC Feb 2, 2026
b8b3b27
ln: allow reading HTLCSource::TrampolineForward
carlaKC Feb 24, 2026
e61ed94
ln: add trampoline payment dispatch after inbound accumulation
carlaKC Mar 12, 2026
8025d30
ln/test: only use replacement onion in trampoline tests when needed
carlaKC Feb 10, 2026
44b155d
[deleteme]: remove assertion that fails on unblinded test
carlaKC Feb 3, 2026
6964d7e
[wip]ln: pass trampoline secret to construct_pending_htlc_fail_msg
carlaKC Mar 17, 2026
4f84baa
[wip]: forwarding tests with messy replacement onion code
carlaKC Mar 17, 2026
d11e0da
[wip]: track already_forwarded_htlcs by full HTLCSource
carlaKC Mar 4, 2026
8a81927
[wip]: support muti-out sources in inbound_forwarded_htlcs
carlaKC Mar 4, 2026
d91f413
[wip]: pass full HTLCSource through in committed_outbound_htlc_sources
carlaKC Mar 4, 2026
df515a5
[wip] dedup trampoline forwards with failed_htlcs
carlaKC Mar 4, 2026
345f9f7
[wip] persist trampoline information in InboundUpdateAdd
carlaKC Mar 4, 2026
2d6605e
[wip] return trampoline forwards in inbound_forwarded_htlcs
carlaKC Mar 4, 2026
a10a8c2
[wip]: return trampoline forwards from outbound_htlc_forwards
carlaKC Mar 4, 2026
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
117 changes: 96 additions & 21 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,35 @@ impl BlindedPaymentPath {
)
}

fn new_inner<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
/// Create a blinded path for a trampoline payment, to be forwarded along `intermediate_nodes`.
#[cfg(any(test, feature = "_test_utils"))]
pub(crate) fn new_for_trampoline<
ES: EntropySource,
T: secp256k1::Signing + secp256k1::Verification,
>(
intermediate_nodes: &[ForwardNode<TrampolineForwardTlvs>], payee_node_id: PublicKey,
local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64,
min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1<T>,
) -> Result<Self, ()> {
Self::new_inner(
intermediate_nodes,
payee_node_id,
local_node_receive_key,
&[],
payee_tlvs,
htlc_maximum_msat,
min_final_cltv_expiry_delta,
entropy_source,
secp_ctx,
)
}

fn new_inner<
F: ForwardTlvsInfo,
ES: EntropySource,
T: secp256k1::Signing + secp256k1::Verification,
>(
intermediate_nodes: &[ForwardNode<F>], payee_node_id: PublicKey,
local_node_receive_key: ReceiveAuthKey, dummy_tlvs: &[DummyTlvs], payee_tlvs: ReceiveTlvs,
htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: ES,
secp_ctx: &Secp256k1<T>,
Expand Down Expand Up @@ -323,18 +350,36 @@ impl BlindedPaymentPath {
}
}

/// An intermediate node, its outbound channel, and relay parameters.
/// Common interface for forward TLV types used in blinded payment paths.
///
/// Both [`ForwardTlvs`] (channel-based forwarding) and [`TrampolineForwardTlvs`] (trampoline
/// node-based forwarding) implement this trait, allowing blinded path construction to be generic
/// over the forwarding mechanism.
pub trait ForwardTlvsInfo: Writeable + Clone {
/// The payment relay parameters for this hop.
fn payment_relay(&self) -> &PaymentRelay;
/// The payment constraints for this hop.
fn payment_constraints(&self) -> &PaymentConstraints;
/// The features for this hop.
fn features(&self) -> &BlindedHopFeatures;
}

/// An intermediate node, its forwarding parameters, and its [`ForwardTlvsInfo`] for use in a
/// [`BlindedPaymentPath`].
#[derive(Clone, Debug)]
pub struct PaymentForwardNode {
pub struct ForwardNode<F: ForwardTlvsInfo> {
/// The TLVs for this node's [`BlindedHop`], where the fee parameters contained within are also
/// used for [`BlindedPayInfo`] construction.
pub tlvs: ForwardTlvs,
pub tlvs: F,
/// This node's pubkey.
pub node_id: PublicKey,
/// The maximum value, in msat, that may be accepted by this node.
pub htlc_maximum_msat: u64,
}

/// An intermediate node for a regular (non-trampoline) [`BlindedPaymentPath`].
pub type PaymentForwardNode = ForwardNode<ForwardTlvs>;

/// Data to construct a [`BlindedHop`] for forwarding a payment.
#[derive(Clone, Debug)]
pub struct ForwardTlvs {
Expand All @@ -354,6 +399,18 @@ pub struct ForwardTlvs {
pub next_blinding_override: Option<PublicKey>,
}

impl ForwardTlvsInfo for ForwardTlvs {
fn payment_relay(&self) -> &PaymentRelay {
&self.payment_relay
}
fn payment_constraints(&self) -> &PaymentConstraints {
&self.payment_constraints
}
fn features(&self) -> &BlindedHopFeatures {
&self.features
}
}

/// Data to construct a [`BlindedHop`] for forwarding a Trampoline payment.
#[derive(Clone, Debug)]
pub struct TrampolineForwardTlvs {
Expand All @@ -373,6 +430,18 @@ pub struct TrampolineForwardTlvs {
pub next_blinding_override: Option<PublicKey>,
}

impl ForwardTlvsInfo for TrampolineForwardTlvs {
fn payment_relay(&self) -> &PaymentRelay {
&self.payment_relay
}
fn payment_constraints(&self) -> &PaymentConstraints {
&self.payment_constraints
}
fn features(&self) -> &BlindedHopFeatures {
&self.features
}
}

/// TLVs carried by a dummy hop within a blinded payment path.
///
/// Dummy hops do not correspond to real forwarding decisions, but are processed
Expand Down Expand Up @@ -440,8 +509,8 @@ pub(crate) enum BlindedTrampolineTlvs {

// Used to include forward and receive TLVs in the same iterator for encoding.
#[derive(Clone)]
enum BlindedPaymentTlvsRef<'a> {
Forward(&'a ForwardTlvs),
enum BlindedPaymentTlvsRef<'a, F: ForwardTlvsInfo = ForwardTlvs> {
Forward(&'a F),
Dummy(&'a DummyTlvs),
Receive(&'a ReceiveTlvs),
}
Expand Down Expand Up @@ -619,7 +688,7 @@ impl Writeable for ReceiveTlvs {
}
}

impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
impl<'a, F: ForwardTlvsInfo> Writeable for BlindedPaymentTlvsRef<'a, F> {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Self::Forward(tlvs) => tlvs.write(w)?,
Expand Down Expand Up @@ -723,8 +792,8 @@ impl Readable for BlindedTrampolineTlvs {
pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30;

/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey,
pub(super) fn blinded_hops<F: ForwardTlvsInfo, T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[ForwardNode<F>], payee_node_id: PublicKey,
dummy_tlvs: &[DummyTlvs], payee_tlvs: ReceiveTlvs, session_priv: &SecretKey,
local_node_receive_key: ReceiveAuthKey,
) -> Vec<BlindedHop> {
Expand Down Expand Up @@ -823,15 +892,15 @@ where
Ok((curr_base_fee, curr_prop_mil))
}

pub(super) fn compute_payinfo(
intermediate_nodes: &[PaymentForwardNode], dummy_tlvs: &[DummyTlvs], payee_tlvs: &ReceiveTlvs,
pub(super) fn compute_payinfo<F: ForwardTlvsInfo>(
intermediate_nodes: &[ForwardNode<F>], dummy_tlvs: &[DummyTlvs], payee_tlvs: &ReceiveTlvs,
payee_htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
) -> Result<BlindedPayInfo, ()> {
let routing_fees = intermediate_nodes
.iter()
.map(|node| RoutingFees {
base_msat: node.tlvs.payment_relay.fee_base_msat,
proportional_millionths: node.tlvs.payment_relay.fee_proportional_millionths,
base_msat: node.tlvs.payment_relay().fee_base_msat,
proportional_millionths: node.tlvs.payment_relay().fee_proportional_millionths,
})
.chain(dummy_tlvs.iter().map(|tlvs| RoutingFees {
base_msat: tlvs.payment_relay.fee_base_msat,
Expand All @@ -847,24 +916,24 @@ pub(super) fn compute_payinfo(
for node in intermediate_nodes.iter() {
// In the future, we'll want to take the intersection of all supported features for the
// `BlindedPayInfo`, but there are no features in that context right now.
if node.tlvs.features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) {
if node.tlvs.features().requires_unknown_bits_from(&BlindedHopFeatures::empty()) {
return Err(());
}

cltv_expiry_delta =
cltv_expiry_delta.checked_add(node.tlvs.payment_relay.cltv_expiry_delta).ok_or(())?;
cltv_expiry_delta.checked_add(node.tlvs.payment_relay().cltv_expiry_delta).ok_or(())?;

// The min htlc for an intermediate node is that node's min minus the fees charged by all of the
// following hops for forwarding that min, since that fee amount will automatically be included
// in the amount that this node receives and contribute towards reaching its min.
htlc_minimum_msat = amt_to_forward_msat(
core::cmp::max(node.tlvs.payment_constraints.htlc_minimum_msat, htlc_minimum_msat),
&node.tlvs.payment_relay,
core::cmp::max(node.tlvs.payment_constraints().htlc_minimum_msat, htlc_minimum_msat),
node.tlvs.payment_relay(),
)
.unwrap_or(1); // If underflow occurs, we definitely reached this node's min
htlc_maximum_msat = amt_to_forward_msat(
core::cmp::min(node.htlc_maximum_msat, htlc_maximum_msat),
&node.tlvs.payment_relay,
node.tlvs.payment_relay(),
)
.ok_or(())?; // If underflow occurs, we cannot send to this hop without exceeding their max
}
Expand Down Expand Up @@ -1038,8 +1107,14 @@ mod tests {
payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 },
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
};
let blinded_payinfo =
super::compute_payinfo(&[], &[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
let blinded_payinfo = super::compute_payinfo::<ForwardTlvs>(
&[],
&[],
&recv_tlvs,
4242,
TEST_FINAL_CLTV as u16,
)
.unwrap();
assert_eq!(blinded_payinfo.fee_base_msat, 0);
assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
assert_eq!(blinded_payinfo.cltv_expiry_delta, TEST_FINAL_CLTV as u16);
Expand Down
13 changes: 12 additions & 1 deletion lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ pub enum PaymentPurpose {
/// Because this is a spontaneous payment, the payer generated their own preimage rather than us
/// (the payee) providing a preimage.
SpontaneousPayment(PaymentPreimage),
/// HTLCs terminating at our node are intended for forwarding onwards as a trampoline
/// forward.
Trampoline {},
}

impl PaymentPurpose {
Expand All @@ -184,6 +187,7 @@ impl PaymentPurpose {
PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. } => *payment_preimage,
PaymentPurpose::Bolt12RefundPayment { payment_preimage, .. } => *payment_preimage,
PaymentPurpose::SpontaneousPayment(preimage) => Some(*preimage),
PaymentPurpose::Trampoline {} => None,
}
}

Expand All @@ -193,6 +197,7 @@ impl PaymentPurpose {
PaymentPurpose::Bolt12OfferPayment { .. } => false,
PaymentPurpose::Bolt12RefundPayment { .. } => false,
PaymentPurpose::SpontaneousPayment(..) => true,
PaymentPurpose::Trampoline {} => false,
}
}

Expand Down Expand Up @@ -240,8 +245,9 @@ impl_writeable_tlv_based_enum_legacy!(PaymentPurpose,
(2, payment_secret, required),
(4, payment_context, required),
},
(3, Trampoline) => {},
;
(2, SpontaneousPayment)
(2, SpontaneousPayment),
);

/// Information about an HTLC that is part of a payment that can be claimed.
Expand Down Expand Up @@ -1932,6 +1938,11 @@ impl Writeable for Event {
PaymentPurpose::SpontaneousPayment(preimage) => {
payment_preimage = Some(*preimage);
},
PaymentPurpose::Trampoline {} => {
payment_secret = None;
payment_preimage = None;
payment_context = None;
},
}
let skimmed_fee_opt = if counterparty_skimmed_fee_msat == 0 {
None
Expand Down
Loading
Loading