Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
37e75e7
Use HTLC CLTV instead of onion CLTV values for payment claim timer
TheBlueMatt Feb 8, 2026
4867c30
Fix trampoline onion encoding to match doc-declared CLTV rules
TheBlueMatt Feb 8, 2026
ec8580b
Clarify CLTV value selection in the first blinded hop marginally
TheBlueMatt Feb 9, 2026
5ce6e42
Add a `Path::total_cltv_expiry_delta` accessor
TheBlueMatt Feb 9, 2026
54be6ef
Validate CLTV somewhat in `Route::debug_assert_route_meets_params`
TheBlueMatt Feb 9, 2026
e39437d
Rename `starting_htlc_offset` `cur_block_height` in onion building
TheBlueMatt Feb 10, 2026
cb7968c
ln/events: multiple htlcs in/out for trampoline PaymentForwarded
carlaKC Dec 16, 2025
2401102
f note 0.3 and above downgrade information loss in event reporting
carlaKC Feb 24, 2026
f799e96
f downgrade note should be inclusive of 0.0.123
carlaKC Feb 24, 2026
c664f37
[upstream] f Test failure belongs on upstream rebase
carlaKC Feb 25, 2026
83de6d3
ln: make event optional in EmitEventAndFreeOtherChannel
carlaKC Dec 16, 2025
2877df7
ln/refactor: rename EmitEventAndFreeOtherChannel to note optional event
carlaKC Jan 7, 2026
aed4a1b
ln+events: allow multiple prev_channel_id in HTLCHandlingFailed
carlaKC Jan 7, 2026
b29a898
events: add TrampolineForward variant to HTLCHandlingFailureType
carlaKC Jan 6, 2026
e241616
ln: add TrampolineForward SendHTLCId variant
carlaKC Dec 2, 2025
719d7bf
ln: add TrampolineForward variant to HTLCSource enum
a-mpch Aug 22, 2025
cfe821b
f do not allow read of HTLCSource::TrampolineForward
carlaKC Feb 24, 2026
44274ae
f note that trampoline SendHTLCId will be distinct for TrampolineForward
carlaKC Feb 24, 2026
ea0d871
f note that TrampolineDispatch is just a single MPP part, not payment
carlaKC Feb 24, 2026
8dd8c89
ln: add failure_type helper to HTLCSource for HTLCHandlingFailureType
carlaKC Feb 11, 2026
f1d77ba
ln/refactor: add claim funds for htlc forward helper
carlaKC Dec 16, 2025
f1eebdb
ln/refactor: pass closure to create PaymentForwarded event
carlaKC Jan 6, 2026
0d49d7a
ln: add trampoline routing payment claiming
carlaKC Jan 6, 2026
526b0ae
ln/refactor: add blinded forwarding failure helper function
carlaKC Nov 20, 2025
b3d5b5c
ln: add trampoline routing failure handling
carlaKC Dec 1, 2025
75b5249
ln/refactor: extract channelmonitor recovery to external helper
carlaKC Feb 25, 2026
a6f87c4
ln: add channel monitor recovery for trampoline forwards
carlaKC Feb 9, 2026
91b02b4
ln/refactor: move outgoing payment replay code into helper function
carlaKC Feb 25, 2026
a29bbce
ln: handle trampoline claims on restart
carlaKC Jan 16, 2026
8065402
ln: store incoming mpp data in PendingHTLCRouting
carlaKC Jan 27, 2026
163ba78
ln: use total_msat to calculate the amount for our next trampoline
carlaKC Feb 25, 2026
26c26c3
ln: use outer onion cltv values in PendingHTLCInfo for trampoline
carlaKC Feb 25, 2026
754d1ed
ln: store next trampoline amount and cltv in PendingHTLCRouting
carlaKC Feb 25, 2026
f94b4fb
ln: use outer onion values for trampoline NextPacketDetails
carlaKC Feb 12, 2026
41ac168
ln/refactor: move mpp timeout check into helper function
carlaKC Feb 25, 2026
18ad632
ln/refactor: move on chain timeout check into claimable htlc
carlaKC Jan 22, 2026
b48a814
[wip]: add Trampoline variant to OnionPayload
carlaKC Jan 23, 2026
0eee30b
[wip]: add awaiting_trampoline_forwards to accumulate inbound MPP
carlaKC Jan 22, 2026
17782a7
ln/refactor: move checks on incoming mpp accumulation into method
carlaKC Feb 25, 2026
d49780e
ln: handle claimable htlcs for payments in dedicated method
carlaKC Feb 25, 2026
9f5a30b
ln: move receive-specific failures into fail_htlc macro
carlaKC Feb 12, 2026
15abfd4
ln: add trampoline mpp accumulation and with rejection of forwards
carlaKC Feb 24, 2026
59a5cf9
ln/refactor: pass minimum delta into check_incoming_htlc_cltv
carlaKC Feb 12, 2026
7f2710e
ln: process added trampoline htlcs with CLTV validation
carlaKC Feb 25, 2026
5244069
ln: add trampoline forward info to PendingOutboundPayment::Retryable
carlaKC Jan 16, 2026
59d558e
ln: thread trampoline routing information through payment methods
carlaKC Feb 10, 2026
ab33ac9
ln: add blinding point to new_trampoline_entry
carlaKC Feb 10, 2026
33c2fe8
ln function to build trampoline forwarding onions
carlaKC Jan 28, 2026
5e25f2a
ln: support trampoline in send_payment_along_path
carlaKC Feb 11, 2026
0687ab5
ln: add send trampoline payment functionality
carlaKC Jan 16, 2026
852bf57
ln/refactor: surface error data in DecodedOnionData for Trampolines
carlaKC Feb 12, 2026
6dc517c
[wip] ln: add trampoline htlc failure logic to outbound payments
carlaKC Feb 9, 2026
49bc0a0
ln: add claim_trampoline_forward to mark trampoline complete
carlaKC Feb 18, 2026
e2dde61
ln: handle trampoline payments in finalize_claims
carlaKC Feb 18, 2026
961623e
ln: block on inbound claim for trampoline update_fulfill_htlc
carlaKC Feb 25, 2026
acb508c
ln: de-duplicate trampoline forwards with failed_htlcs
carlaKC Feb 25, 2026
154db1a
ln: only fail trampoline payments backwards when payment state ready
carlaKC Feb 12, 2026
9248646
ln: claim trampoline payment on completion
carlaKC Feb 18, 2026
4b4ee6a
ln: use correct blinding point for trampoline payload decodes
carlaKC Feb 2, 2026
2b1e212
ln: allow reading HTLCSource::TrampolineForward
carlaKC Feb 24, 2026
61830e5
ln: add trampoline payment dispatch after inbound accumulation
carlaKC Feb 24, 2026
8f8fd8f
ln/test: only use replacement onion in trampoline tests when needed
carlaKC Feb 10, 2026
7aefe2e
[deleteme]: remove assertion that fails on unblinded test
carlaKC Feb 3, 2026
8002678
ln/test: add coverage for blinded and unblinded trampoline forwarding
carlaKC Feb 25, 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
8 changes: 4 additions & 4 deletions lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1331,14 +1331,14 @@ fn client_trusts_lsp_end_to_end_test() {

let total_fee_msat = match service_events[0].clone() {
Event::PaymentForwarded {
prev_node_id,
next_node_id,
ref prev_htlcs,
ref next_htlcs,
skimmed_fee_msat,
total_fee_earned_msat,
..
} => {
assert_eq!(prev_node_id, Some(payer_node_id));
assert_eq!(next_node_id, Some(client_node_id));
assert_eq!(prev_htlcs[0].node_id, Some(payer_node_id));
assert_eq!(next_htlcs[0].node_id, Some(client_node_id));
service_handler.payment_forwarded(channel_id, skimmed_fee_msat.unwrap_or(0)).unwrap();
Some(total_fee_earned_msat.unwrap() - skimmed_fee_msat.unwrap())
},
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2795,6 +2795,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
let outbound_payment = match source {
None => panic!("Outbound HTLCs should have a source"),
Some(&HTLCSource::PreviousHopData(_)) => false,
Some(&HTLCSource::TrampolineForward { .. }) => false,
Some(&HTLCSource::OutboundRoute { .. }) => true,
};
return Some(Balance::MaybeTimeoutClaimableHTLC {
Expand Down Expand Up @@ -3007,6 +3008,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
let outbound_payment = match source {
None => panic!("Outbound HTLCs should have a source"),
Some(HTLCSource::PreviousHopData(_)) => false,
Some(HTLCSource::TrampolineForward { .. }) => false,
Some(HTLCSource::OutboundRoute { .. }) => true,
};
if outbound_payment {
Expand Down
186 changes: 119 additions & 67 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::util::ser::{
UpgradableRequired, WithoutLength, Writeable, Writer,
};

use crate::io;
use crate::io::{self, ErrorKind::InvalidData as IOInvalidData};
use crate::sync::Arc;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
Expand Down Expand Up @@ -163,6 +163,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 @@ -173,6 +176,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 @@ -182,6 +186,7 @@ impl PaymentPurpose {
PaymentPurpose::Bolt12OfferPayment { .. } => false,
PaymentPurpose::Bolt12RefundPayment { .. } => false,
PaymentPurpose::SpontaneousPayment(..) => true,
PaymentPurpose::Trampoline {} => false,
}
}

Expand Down Expand Up @@ -229,8 +234,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 @@ -573,6 +579,10 @@ pub enum HTLCHandlingFailureType {
/// The payment hash of the payment we attempted to process.
payment_hash: PaymentHash,
},
/// We were responsible for pathfinding and forwarding of a trampoline payment, but failed to
/// do so. An example of such an instance is when we can't find a route to the specified
/// trampoline destination.
TrampolineForward {},
}

impl_writeable_tlv_based_enum_upgradable!(HTLCHandlingFailureType,
Expand All @@ -590,6 +600,7 @@ impl_writeable_tlv_based_enum_upgradable!(HTLCHandlingFailureType,
(4, Receive) => {
(0, payment_hash, required),
},
(5, TrampolineForward) => {},
);

/// The reason for HTLC failures in [`Event::HTLCHandlingFailed`].
Expand Down Expand Up @@ -727,6 +738,25 @@ pub enum InboundChannelFunds {
DualFunded,
}

/// Identifies the channel and peer committed to a HTLC, used for both incoming and outgoing HTLCs.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HTLCLocator {
/// The channel that the HTLC was sent or received on.
pub channel_id: ChannelId,

/// The `user_channel_id` for `channel_id`.
pub user_channel_id: Option<u128>,

/// The public key identity of the node that the HTLC was sent to or received from.
pub node_id: Option<PublicKey>,
}

impl_writeable_tlv_based!(HTLCLocator, {
(1, channel_id, required),
(3, user_channel_id, option),
(5, node_id, option),
});

/// An Event which you should probably take some action in response to.
///
/// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use
Expand Down Expand Up @@ -1320,38 +1350,22 @@ pub enum Event {
/// This event is generated when a payment has been successfully forwarded through us and a
/// forwarding fee earned.
///
/// Note that downgrading from 0.3 and above with pending trampoline forwards that use multipart
/// payments will produce an event that only provides information about the first htlc that was
/// received/dispatched.
///
/// # Failure Behavior and Persistence
/// This event will eventually be replayed after failures-to-handle (i.e., the event handler
/// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
PaymentForwarded {
/// The channel id of the incoming channel between the previous node and us.
///
/// This is only `None` for events generated or serialized by versions prior to 0.0.107.
prev_channel_id: Option<ChannelId>,
/// The channel id of the outgoing channel between the next node and us.
///
/// This is only `None` for events generated or serialized by versions prior to 0.0.107.
next_channel_id: Option<ChannelId>,
/// The `user_channel_id` of the incoming channel between the previous node and us.
///
/// This is only `None` for events generated or serialized by versions prior to 0.0.122.
prev_user_channel_id: Option<u128>,
/// The `user_channel_id` of the outgoing channel between the next node and us.
///
/// This will be `None` if the payment was settled via an on-chain transaction. See the
/// caveat described for the `total_fee_earned_msat` field. Moreover it will be `None` for
/// events generated or serialized by versions prior to 0.0.122.
next_user_channel_id: Option<u128>,
/// The node id of the previous node.
///
/// This is only `None` for HTLCs received prior to 0.1 or for events serialized by
/// versions prior to 0.1
prev_node_id: Option<PublicKey>,
/// The node id of the next node.
///
/// This is only `None` for HTLCs received prior to 0.1 or for events serialized by
/// versions prior to 0.1
next_node_id: Option<PublicKey>,
/// The set of HTLCs forwarded to our node that will be claimed by this forward. Contains a
/// single HTLC for source-routed payments, and may contain multiple HTLCs when we acted as
/// a trampoline router, responsible for pathfinding within the route.
prev_htlcs: Vec<HTLCLocator>,
/// The set of HTLCs forwarded by our node that have been claimed by this forward. Contains
/// a single HTLC for regular source-routed payments, and may contain multiple HTLCs when
/// we acted as a trampoline router, responsible for pathfinding within the route.
next_htlcs: Vec<HTLCLocator>,
/// The total fee, in milli-satoshis, which was earned as a result of the payment.
///
/// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC
Expand Down Expand Up @@ -1649,12 +1663,17 @@ pub enum Event {
/// Indicates that the HTLC was accepted, but could not be processed when or after attempting to
/// forward it.
///
/// Note that downgrading from 0.3 with pending trampoline forwards that have incoming multipart
/// payments will produce an event that only provides information about the first htlc that was
/// received/dispatched.
///
/// # Failure Behavior and Persistence
/// This event will eventually be replayed after failures-to-handle (i.e., the event handler
/// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
HTLCHandlingFailed {
/// The channel over which the HTLC was received.
prev_channel_id: ChannelId,
/// The channel(s) over which the HTLC(s) was received. May contain multiple entries for
/// trampoline forwards.
prev_channel_ids: Vec<ChannelId>,
/// The type of HTLC handling that failed.
failure_type: HTLCHandlingFailureType,
/// The reason that the HTLC failed.
Expand Down Expand Up @@ -1906,6 +1925,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 Expand Up @@ -2019,29 +2043,33 @@ impl Writeable for Event {
});
},
&Event::PaymentForwarded {
prev_channel_id,
next_channel_id,
prev_user_channel_id,
next_user_channel_id,
prev_node_id,
next_node_id,
ref prev_htlcs,
ref next_htlcs,
total_fee_earned_msat,
skimmed_fee_msat,
claim_from_onchain_tx,
outbound_amount_forwarded_msat,
} => {
7u8.write(writer)?;
// Fields 1, 3, 9, 11, 13 and 15 are written for backwards compatibility.
let legacy_prev = prev_htlcs.first().ok_or(io::Error::from(IOInvalidData))?;
let legacy_next = next_htlcs.first().ok_or(io::Error::from(IOInvalidData))?;
write_tlv_fields!(writer, {
(0, total_fee_earned_msat, option),
(1, prev_channel_id, option),
(1, Some(legacy_prev.channel_id), option),
(2, claim_from_onchain_tx, required),
(3, next_channel_id, option),
(3, Some(legacy_next.channel_id), option),
(5, outbound_amount_forwarded_msat, option),
(7, skimmed_fee_msat, option),
(9, prev_user_channel_id, option),
(11, next_user_channel_id, option),
(13, prev_node_id, option),
(15, next_node_id, option),
(9, legacy_prev.user_channel_id, option),
(11, legacy_next.user_channel_id, option),
(13, legacy_prev.node_id, option),
(15, legacy_next.node_id, option),
// HTLCs are written as required, rather than required_vec, so that they can be
// deserialized using default_value to fill in legacy fields which expects
// LengthReadable (required_vec is WithoutLength).
(17, *prev_htlcs, required),
(19, *next_htlcs, required),
});
},
&Event::ChannelClosed {
Expand Down Expand Up @@ -2189,15 +2217,19 @@ impl Writeable for Event {
})
},
&Event::HTLCHandlingFailed {
ref prev_channel_id,
ref prev_channel_ids,
ref failure_type,
ref failure_reason,
} => {
25u8.write(writer)?;
let legacy_chan_id =
prev_channel_ids.first().ok_or(io::Error::from(IOInvalidData))?;
write_tlv_fields!(writer, {
(0, prev_channel_id, required),
// Write legacy field to remain backwards compatible.
(0, legacy_chan_id, required),
(1, failure_reason, option),
(2, failure_type, required),
(3, *prev_channel_ids, required),
})
},
&Event::BumpTransaction(ref event) => {
Expand Down Expand Up @@ -2545,35 +2577,51 @@ impl MaybeReadable for Event {
},
7u8 => {
let mut f = || {
let mut prev_channel_id = None;
let mut next_channel_id = None;
let mut prev_user_channel_id = None;
let mut next_user_channel_id = None;
let mut prev_node_id = None;
let mut next_node_id = None;
// Legacy values that have been replaced by prev_htlcs and next_htlcs.
let mut prev_channel_id_legacy = None;
let mut next_channel_id_legacy = None;
let mut prev_user_channel_id_legacy = None;
let mut next_user_channel_id_legacy = None;
let mut prev_node_id_legacy = None;
let mut next_node_id_legacy = None;

let mut total_fee_earned_msat = None;
let mut skimmed_fee_msat = None;
let mut claim_from_onchain_tx = false;
let mut outbound_amount_forwarded_msat = None;
let mut prev_htlcs = vec![];
let mut next_htlcs = vec![];
read_tlv_fields!(reader, {
(0, total_fee_earned_msat, option),
(1, prev_channel_id, option),
(1, prev_channel_id_legacy, option),
(2, claim_from_onchain_tx, required),
(3, next_channel_id, option),
(3, next_channel_id_legacy, option),
(5, outbound_amount_forwarded_msat, option),
(7, skimmed_fee_msat, option),
(9, prev_user_channel_id, option),
(11, next_user_channel_id, option),
(13, prev_node_id, option),
(15, next_node_id, option),
(9, prev_user_channel_id_legacy, option),
(11, next_user_channel_id_legacy, option),
(13, prev_node_id_legacy, option),
(15, next_node_id_legacy, option),
// We can unwrap in the eagerly-evaluated default_value code because we
// always write legacy fields to be backwards compatible, and expect
// this field to be set because the legacy field was only None for versions
// before 0.0.107 and we do not allow upgrades with pending forwards to 0.1
// for any version 0.0.123 or earlier.
(17, prev_htlcs, (default_value, vec![HTLCLocator{
channel_id: prev_channel_id_legacy.unwrap(),
user_channel_id: prev_user_channel_id_legacy,
node_id: prev_node_id_legacy,
}])),
(19, next_htlcs, (default_value, vec![HTLCLocator{
channel_id: next_channel_id_legacy.unwrap(),
user_channel_id: next_user_channel_id_legacy,
node_id: next_node_id_legacy,
}])),
});

Ok(Some(Event::PaymentForwarded {
prev_channel_id,
next_channel_id,
prev_user_channel_id,
next_user_channel_id,
prev_node_id,
next_node_id,
prev_htlcs,
next_htlcs,
total_fee_earned_msat,
skimmed_fee_msat,
claim_from_onchain_tx,
Expand Down Expand Up @@ -2763,13 +2811,17 @@ impl MaybeReadable for Event {
},
25u8 => {
let mut f = || {
let mut prev_channel_id = ChannelId::new_zero();
let mut prev_channel_id_legacy = ChannelId::new_zero();
let mut failure_reason = None;
let mut failure_type_opt = UpgradableRequired(None);
let mut prev_channel_ids = vec![];
read_tlv_fields!(reader, {
(0, prev_channel_id, required),
(0, prev_channel_id_legacy, required),
(1, failure_reason, option),
(2, failure_type_opt, upgradable_required),
(3, prev_channel_ids, (default_value, vec![
prev_channel_id_legacy,
])),
});

// If a legacy HTLCHandlingFailureType::UnknownNextHop was written, upgrade
Expand All @@ -2784,7 +2836,7 @@ impl MaybeReadable for Event {
failure_reason = Some(LocalHTLCFailureReason::UnknownNextPeer.into());
}
Ok(Some(Event::HTLCHandlingFailed {
prev_channel_id,
prev_channel_ids,
failure_type: _init_tlv_based_struct_field!(
failure_type_opt,
upgradable_required
Expand Down
Loading
Loading